mirror of
https://github.com/graphql-python/graphene.git
synced 2025-02-02 04:34:13 +03:00
Merge pull request #61 from graphql-python/features/classtypes
Refactor ObjectTypes in ClassTypes. Fixed #30
This commit is contained in:
commit
699aebec33
|
@ -1,5 +1,5 @@
|
|||
#!/bin/bash
|
||||
|
||||
autoflake ./ -r --remove-unused-variables --remove-all-unused-imports --in-place
|
||||
autopep8 ./ -r --in-place --experimental --aggressive --max-line-length 120
|
||||
isort -rc .
|
||||
autoflake ./examples/ ./graphene/ -r --remove-unused-variables --remove-all-unused-imports --in-place
|
||||
autopep8 ./examples/ ./graphene/ -r --in-place --experimental --aggressive --max-line-length 120
|
||||
isort -rc ./examples/ ./graphene/
|
||||
|
|
|
@ -8,11 +8,15 @@ from graphene.core.schema import (
|
|||
Schema
|
||||
)
|
||||
|
||||
from graphene.core.types import (
|
||||
from graphene.core.classtypes import (
|
||||
ObjectType,
|
||||
InputObjectType,
|
||||
Interface,
|
||||
Mutation,
|
||||
Scalar
|
||||
)
|
||||
|
||||
from graphene.core.types import (
|
||||
BaseType,
|
||||
LazyType,
|
||||
Argument,
|
||||
|
@ -59,6 +63,7 @@ __all__ = [
|
|||
'InputObjectType',
|
||||
'Interface',
|
||||
'Mutation',
|
||||
'Scalar',
|
||||
'Field',
|
||||
'InputField',
|
||||
'StringField',
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
from graphene.contrib.django.types import (
|
||||
DjangoConnection,
|
||||
DjangoObjectType,
|
||||
DjangoInterface,
|
||||
DjangoNode
|
||||
)
|
||||
from graphene.contrib.django.fields import (
|
||||
|
@ -9,5 +8,5 @@ from graphene.contrib.django.fields import (
|
|||
DjangoModelField
|
||||
)
|
||||
|
||||
__all__ = ['DjangoObjectType', 'DjangoInterface', 'DjangoNode',
|
||||
'DjangoConnection', 'DjangoConnectionField', 'DjangoModelField']
|
||||
__all__ = ['DjangoObjectType', 'DjangoNode', 'DjangoConnection',
|
||||
'DjangoConnectionField', 'DjangoModelField']
|
||||
|
|
|
@ -1,24 +1,15 @@
|
|||
import inspect
|
||||
|
||||
from django.db import models
|
||||
|
||||
from ...core.options import Options
|
||||
from ...core.classtypes.objecttype import ObjectTypeOptions
|
||||
from ...relay.types import Node
|
||||
from ...relay.utils import is_node
|
||||
|
||||
VALID_ATTRS = ('model', 'only_fields', 'exclude_fields')
|
||||
|
||||
|
||||
def is_base(cls):
|
||||
from graphene.contrib.django.types import DjangoObjectType
|
||||
return DjangoObjectType in cls.__bases__
|
||||
|
||||
|
||||
class DjangoOptions(Options):
|
||||
class DjangoOptions(ObjectTypeOptions):
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.model = None
|
||||
super(DjangoOptions, self).__init__(*args, **kwargs)
|
||||
self.model = None
|
||||
self.valid_attrs += VALID_ATTRS
|
||||
self.only_fields = None
|
||||
self.exclude_fields = []
|
||||
|
@ -28,11 +19,3 @@ class DjangoOptions(Options):
|
|||
if is_node(cls):
|
||||
self.exclude_fields = list(self.exclude_fields) + ['id']
|
||||
self.interfaces.append(Node)
|
||||
if not is_node(cls) and not is_base(cls):
|
||||
return
|
||||
if not self.model:
|
||||
raise Exception(
|
||||
'Django ObjectType %s must have a model in the Meta class attr' %
|
||||
cls)
|
||||
elif not inspect.isclass(self.model) or not issubclass(self.model, models.Model):
|
||||
raise Exception('Provided model in %s is not a Django model' % cls)
|
||||
|
|
|
@ -1,9 +1,8 @@
|
|||
from graphql.core.type import GraphQLInterfaceType, GraphQLObjectType
|
||||
from graphql.core.type import GraphQLObjectType
|
||||
from mock import patch
|
||||
from pytest import raises
|
||||
|
||||
from graphene import Schema
|
||||
from graphene.contrib.django.types import DjangoInterface, DjangoNode
|
||||
from graphene.contrib.django.types import DjangoNode, DjangoObjectType
|
||||
from graphene.core.fields import Field
|
||||
from graphene.core.types.scalars import Int
|
||||
from graphene.relay.fields import GlobalIDField
|
||||
|
@ -14,7 +13,8 @@ from .models import Article, Reporter
|
|||
schema = Schema()
|
||||
|
||||
|
||||
class Character(DjangoInterface):
|
||||
@schema.register
|
||||
class Character(DjangoObjectType):
|
||||
'''Character description'''
|
||||
class Meta:
|
||||
model = Reporter
|
||||
|
@ -31,7 +31,7 @@ class Human(DjangoNode):
|
|||
|
||||
|
||||
def test_django_interface():
|
||||
assert DjangoNode._meta.is_interface is True
|
||||
assert DjangoNode._meta.interface is True
|
||||
|
||||
|
||||
@patch('graphene.contrib.django.tests.models.Article.objects.get', return_value=Article(id=1))
|
||||
|
@ -41,17 +41,6 @@ def test_django_get_node(get):
|
|||
assert human.id == 1
|
||||
|
||||
|
||||
def test_pseudo_interface_registered():
|
||||
object_type = schema.T(Character)
|
||||
assert Character._meta.is_interface is True
|
||||
assert isinstance(object_type, GraphQLInterfaceType)
|
||||
assert Character._meta.model == Reporter
|
||||
assert_equal_lists(
|
||||
object_type.get_fields().keys(),
|
||||
['articles', 'firstName', 'lastName', 'email', 'pets', 'id']
|
||||
)
|
||||
|
||||
|
||||
def test_djangonode_idfield():
|
||||
idfield = DjangoNode._meta.fields_map['id']
|
||||
assert isinstance(idfield, GlobalIDField)
|
||||
|
@ -68,32 +57,21 @@ def test_node_replacedfield():
|
|||
assert schema.T(idfield).type == schema.T(Int())
|
||||
|
||||
|
||||
def test_interface_resolve_type():
|
||||
resolve_type = Character._resolve_type(schema, Human())
|
||||
assert isinstance(resolve_type, GraphQLObjectType)
|
||||
|
||||
|
||||
def test_interface_objecttype_init_none():
|
||||
def test_objecttype_init_none():
|
||||
h = Human()
|
||||
assert h._root is None
|
||||
|
||||
|
||||
def test_interface_objecttype_init_good():
|
||||
def test_objecttype_init_good():
|
||||
instance = Article()
|
||||
h = Human(instance)
|
||||
assert h._root == instance
|
||||
|
||||
|
||||
def test_interface_objecttype_init_unexpected():
|
||||
with raises(AssertionError) as excinfo:
|
||||
Human(object())
|
||||
assert str(excinfo.value) == "Human received a non-compatible instance (object) when expecting Article"
|
||||
|
||||
|
||||
def test_object_type():
|
||||
object_type = schema.T(Human)
|
||||
Human._meta.fields_map
|
||||
assert Human._meta.is_interface is False
|
||||
assert Human._meta.interface is False
|
||||
assert isinstance(object_type, GraphQLObjectType)
|
||||
assert_equal_lists(
|
||||
object_type.get_fields().keys(),
|
||||
|
@ -103,5 +81,5 @@ def test_object_type():
|
|||
|
||||
|
||||
def test_node_notinterface():
|
||||
assert Human._meta.is_interface is False
|
||||
assert Human._meta.interface is False
|
||||
assert DjangoNode in Human._meta.interfaces
|
||||
|
|
|
@ -1,22 +1,19 @@
|
|||
import six
|
||||
import inspect
|
||||
|
||||
from ...core.types import BaseObjectType, ObjectTypeMeta
|
||||
from ...relay.fields import GlobalIDField
|
||||
from ...relay.types import BaseNode, Connection
|
||||
import six
|
||||
from django.db import models
|
||||
|
||||
from ...core.classtypes.objecttype import ObjectType, ObjectTypeMeta
|
||||
from ...relay.types import Connection, Node, NodeMeta
|
||||
from .converter import convert_django_field
|
||||
from .options import DjangoOptions
|
||||
from .utils import get_reverse_fields, maybe_queryset
|
||||
|
||||
|
||||
class DjangoObjectTypeMeta(ObjectTypeMeta):
|
||||
options_cls = DjangoOptions
|
||||
options_class = DjangoOptions
|
||||
|
||||
def is_interface(cls, parents):
|
||||
return DjangoInterface in parents
|
||||
|
||||
def add_extra_fields(cls):
|
||||
if not cls._meta.model:
|
||||
return
|
||||
def construct_fields(cls):
|
||||
only_fields = cls._meta.only_fields
|
||||
reverse_fields = get_reverse_fields(cls._meta.model)
|
||||
all_fields = sorted(list(cls._meta.model._meta.fields) +
|
||||
|
@ -35,8 +32,24 @@ class DjangoObjectTypeMeta(ObjectTypeMeta):
|
|||
converted_field = convert_django_field(field)
|
||||
cls.add_to_class(field.name, converted_field)
|
||||
|
||||
def construct(cls, *args, **kwargs):
|
||||
cls = super(DjangoObjectTypeMeta, cls).construct(*args, **kwargs)
|
||||
if not cls._meta.abstract:
|
||||
if not cls._meta.model:
|
||||
raise Exception(
|
||||
'Django ObjectType %s must have a model in the Meta class attr' %
|
||||
cls)
|
||||
elif not inspect.isclass(cls._meta.model) or not issubclass(cls._meta.model, models.Model):
|
||||
raise Exception('Provided model in %s is not a Django model' % cls)
|
||||
|
||||
class InstanceObjectType(BaseObjectType):
|
||||
cls.construct_fields()
|
||||
return cls
|
||||
|
||||
|
||||
class InstanceObjectType(ObjectType):
|
||||
|
||||
class Meta:
|
||||
abstract = True
|
||||
|
||||
def __init__(self, _root=None):
|
||||
if _root:
|
||||
|
@ -63,12 +76,9 @@ class InstanceObjectType(BaseObjectType):
|
|||
|
||||
class DjangoObjectType(six.with_metaclass(
|
||||
DjangoObjectTypeMeta, InstanceObjectType)):
|
||||
pass
|
||||
|
||||
|
||||
class DjangoInterface(six.with_metaclass(
|
||||
DjangoObjectTypeMeta, InstanceObjectType)):
|
||||
pass
|
||||
class Meta:
|
||||
abstract = True
|
||||
|
||||
|
||||
class DjangoConnection(Connection):
|
||||
|
@ -79,8 +89,21 @@ class DjangoConnection(Connection):
|
|||
return super(DjangoConnection, cls).from_list(iterable, *args, **kwargs)
|
||||
|
||||
|
||||
class DjangoNode(BaseNode, DjangoInterface):
|
||||
id = GlobalIDField()
|
||||
class DjangoNodeMeta(DjangoObjectTypeMeta, NodeMeta):
|
||||
pass
|
||||
|
||||
|
||||
class NodeInstance(Node, InstanceObjectType):
|
||||
|
||||
class Meta:
|
||||
abstract = True
|
||||
|
||||
|
||||
class DjangoNode(six.with_metaclass(
|
||||
DjangoNodeMeta, NodeInstance)):
|
||||
|
||||
class Meta:
|
||||
abstract = True
|
||||
|
||||
@classmethod
|
||||
def get_node(cls, id, info=None):
|
||||
|
|
16
graphene/core/classtypes/__init__.py
Normal file
16
graphene/core/classtypes/__init__.py
Normal file
|
@ -0,0 +1,16 @@
|
|||
from .inputobjecttype import InputObjectType
|
||||
from .interface import Interface
|
||||
from .mutation import Mutation
|
||||
from .objecttype import ObjectType
|
||||
from .options import Options
|
||||
from .scalar import Scalar
|
||||
from .uniontype import UnionType
|
||||
|
||||
__all__ = [
|
||||
'InputObjectType',
|
||||
'Interface',
|
||||
'Mutation',
|
||||
'ObjectType',
|
||||
'Options',
|
||||
'Scalar',
|
||||
'UnionType']
|
133
graphene/core/classtypes/base.py
Normal file
133
graphene/core/classtypes/base.py
Normal file
|
@ -0,0 +1,133 @@
|
|||
import copy
|
||||
import inspect
|
||||
from collections import OrderedDict
|
||||
|
||||
import six
|
||||
|
||||
from ..exceptions import SkipField
|
||||
from .options import Options
|
||||
|
||||
|
||||
class ClassTypeMeta(type):
|
||||
options_class = Options
|
||||
|
||||
def __new__(mcs, name, bases, attrs):
|
||||
super_new = super(ClassTypeMeta, mcs).__new__
|
||||
|
||||
module = attrs.pop('__module__', None)
|
||||
doc = attrs.pop('__doc__', None)
|
||||
new_class = super_new(mcs, name, bases, {
|
||||
'__module__': module,
|
||||
'__doc__': doc
|
||||
})
|
||||
attr_meta = attrs.pop('Meta', None)
|
||||
if not attr_meta:
|
||||
meta = getattr(new_class, 'Meta', None)
|
||||
else:
|
||||
meta = attr_meta
|
||||
|
||||
new_class.add_to_class('_meta', new_class.get_options(meta))
|
||||
|
||||
return mcs.construct(new_class, bases, attrs)
|
||||
|
||||
def get_options(cls, meta):
|
||||
return cls.options_class(meta)
|
||||
|
||||
def add_to_class(cls, name, value):
|
||||
# We should call the contribute_to_class method only if it's bound
|
||||
if not inspect.isclass(value) and hasattr(
|
||||
value, 'contribute_to_class'):
|
||||
value.contribute_to_class(cls, name)
|
||||
else:
|
||||
setattr(cls, name, value)
|
||||
|
||||
def construct(cls, bases, attrs):
|
||||
# Add all attributes to the class.
|
||||
for obj_name, obj in attrs.items():
|
||||
cls.add_to_class(obj_name, obj)
|
||||
|
||||
if not cls._meta.abstract:
|
||||
from ..types import List, NonNull
|
||||
setattr(cls, 'NonNull', NonNull(cls))
|
||||
setattr(cls, 'List', List(cls))
|
||||
|
||||
return cls
|
||||
|
||||
|
||||
class ClassType(six.with_metaclass(ClassTypeMeta)):
|
||||
|
||||
class Meta:
|
||||
abstract = True
|
||||
|
||||
@classmethod
|
||||
def internal_type(cls, schema):
|
||||
raise NotImplementedError("Function internal_type not implemented in type {}".format(cls))
|
||||
|
||||
|
||||
class FieldsOptions(Options):
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(FieldsOptions, self).__init__(*args, **kwargs)
|
||||
self.local_fields = []
|
||||
|
||||
def add_field(self, field):
|
||||
self.local_fields.append(field)
|
||||
|
||||
@property
|
||||
def fields(self):
|
||||
return sorted(self.local_fields)
|
||||
|
||||
@property
|
||||
def fields_map(self):
|
||||
return OrderedDict([(f.attname, f) for f in self.fields])
|
||||
|
||||
|
||||
class FieldsClassTypeMeta(ClassTypeMeta):
|
||||
options_class = FieldsOptions
|
||||
|
||||
def extend_fields(cls, bases):
|
||||
new_fields = cls._meta.local_fields
|
||||
field_names = {f.name: f for f in new_fields}
|
||||
|
||||
for base in bases:
|
||||
if not isinstance(base, FieldsClassTypeMeta):
|
||||
continue
|
||||
|
||||
parent_fields = base._meta.local_fields
|
||||
for field in parent_fields:
|
||||
if field.name in field_names and field.type.__class__ != field_names[
|
||||
field.name].type.__class__:
|
||||
raise Exception(
|
||||
'Local field %r in class %r (%r) clashes '
|
||||
'with field with similar name from '
|
||||
'Interface %s (%r)' % (
|
||||
field.name,
|
||||
cls.__name__,
|
||||
field.__class__,
|
||||
base.__name__,
|
||||
field_names[field.name].__class__)
|
||||
)
|
||||
new_field = copy.copy(field)
|
||||
cls.add_to_class(field.attname, new_field)
|
||||
|
||||
def construct(cls, bases, attrs):
|
||||
cls = super(FieldsClassTypeMeta, cls).construct(bases, attrs)
|
||||
cls.extend_fields(bases)
|
||||
return cls
|
||||
|
||||
|
||||
class FieldsClassType(six.with_metaclass(FieldsClassTypeMeta, ClassType)):
|
||||
|
||||
class Meta:
|
||||
abstract = True
|
||||
|
||||
@classmethod
|
||||
def fields_internal_types(cls, schema):
|
||||
fields = []
|
||||
for field in cls._meta.fields:
|
||||
try:
|
||||
fields.append((field.name, schema.T(field)))
|
||||
except SkipField:
|
||||
continue
|
||||
|
||||
return OrderedDict(fields)
|
25
graphene/core/classtypes/inputobjecttype.py
Normal file
25
graphene/core/classtypes/inputobjecttype.py
Normal file
|
@ -0,0 +1,25 @@
|
|||
from functools import partial
|
||||
|
||||
from graphql.core.type import GraphQLInputObjectType
|
||||
|
||||
from .base import FieldsClassType
|
||||
|
||||
|
||||
class InputObjectType(FieldsClassType):
|
||||
|
||||
class Meta:
|
||||
abstract = True
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
raise Exception("An InputObjectType cannot be initialized")
|
||||
|
||||
@classmethod
|
||||
def internal_type(cls, schema):
|
||||
if cls._meta.abstract:
|
||||
raise Exception("Abstract InputObjectTypes don't have a specific type.")
|
||||
|
||||
return GraphQLInputObjectType(
|
||||
cls._meta.type_name,
|
||||
description=cls._meta.description,
|
||||
fields=partial(cls.fields_internal_types, schema),
|
||||
)
|
53
graphene/core/classtypes/interface.py
Normal file
53
graphene/core/classtypes/interface.py
Normal file
|
@ -0,0 +1,53 @@
|
|||
from functools import partial
|
||||
|
||||
import six
|
||||
from graphql.core.type import GraphQLInterfaceType
|
||||
|
||||
from .base import FieldsClassTypeMeta
|
||||
from .objecttype import ObjectType, ObjectTypeMeta
|
||||
|
||||
|
||||
class InterfaceMeta(ObjectTypeMeta):
|
||||
|
||||
def construct(cls, bases, attrs):
|
||||
if cls._meta.abstract or Interface in bases:
|
||||
# Return Interface type
|
||||
cls = FieldsClassTypeMeta.construct(cls, bases, attrs)
|
||||
setattr(cls._meta, 'interface', True)
|
||||
return cls
|
||||
else:
|
||||
# Return ObjectType class with all the inherited interfaces
|
||||
cls = super(InterfaceMeta, cls).construct(bases, attrs)
|
||||
for interface in bases:
|
||||
is_interface = issubclass(interface, Interface) and getattr(interface._meta, 'interface', False)
|
||||
if not is_interface:
|
||||
continue
|
||||
cls._meta.interfaces.append(interface)
|
||||
return cls
|
||||
|
||||
|
||||
class Interface(six.with_metaclass(InterfaceMeta, ObjectType)):
|
||||
|
||||
class Meta:
|
||||
abstract = True
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
if self._meta.interface:
|
||||
raise Exception("An interface cannot be initialized")
|
||||
return super(Interface, self).__init__(*args, **kwargs)
|
||||
|
||||
@classmethod
|
||||
def _resolve_type(cls, schema, instance, *args):
|
||||
return schema.T(instance.__class__)
|
||||
|
||||
@classmethod
|
||||
def internal_type(cls, schema):
|
||||
if not cls._meta.interface:
|
||||
return super(Interface, cls).internal_type(schema)
|
||||
|
||||
return GraphQLInterfaceType(
|
||||
cls._meta.type_name,
|
||||
description=cls._meta.description,
|
||||
resolve_type=partial(cls._resolve_type, schema),
|
||||
fields=partial(cls.fields_internal_types, schema)
|
||||
)
|
32
graphene/core/classtypes/mutation.py
Normal file
32
graphene/core/classtypes/mutation.py
Normal file
|
@ -0,0 +1,32 @@
|
|||
import six
|
||||
|
||||
from .objecttype import ObjectType, ObjectTypeMeta
|
||||
|
||||
|
||||
class MutationMeta(ObjectTypeMeta):
|
||||
|
||||
def construct(cls, bases, attrs):
|
||||
input_class = attrs.pop('Input', None)
|
||||
if input_class:
|
||||
items = dict(vars(input_class))
|
||||
items.pop('__dict__', None)
|
||||
items.pop('__doc__', None)
|
||||
items.pop('__module__', None)
|
||||
items.pop('__weakref__', None)
|
||||
cls.add_to_class('arguments', cls.construct_arguments(items))
|
||||
cls = super(MutationMeta, cls).construct(bases, attrs)
|
||||
return cls
|
||||
|
||||
def construct_arguments(cls, items):
|
||||
from ..types.argument import ArgumentsGroup
|
||||
return ArgumentsGroup(**items)
|
||||
|
||||
|
||||
class Mutation(six.with_metaclass(MutationMeta, ObjectType)):
|
||||
|
||||
class Meta:
|
||||
abstract = True
|
||||
|
||||
@classmethod
|
||||
def get_arguments(cls):
|
||||
return cls.arguments
|
103
graphene/core/classtypes/objecttype.py
Normal file
103
graphene/core/classtypes/objecttype.py
Normal file
|
@ -0,0 +1,103 @@
|
|||
from functools import partial
|
||||
|
||||
import six
|
||||
from graphql.core.type import GraphQLObjectType
|
||||
|
||||
from graphene import signals
|
||||
|
||||
from .base import FieldsClassType, FieldsClassTypeMeta, FieldsOptions
|
||||
from .uniontype import UnionType
|
||||
|
||||
|
||||
def is_objecttype(cls):
|
||||
if not issubclass(cls, ObjectType):
|
||||
return False
|
||||
return not cls._meta.interface
|
||||
|
||||
|
||||
class ObjectTypeOptions(FieldsOptions):
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(ObjectTypeOptions, self).__init__(*args, **kwargs)
|
||||
self.interface = False
|
||||
self.interfaces = []
|
||||
|
||||
|
||||
class ObjectTypeMeta(FieldsClassTypeMeta):
|
||||
|
||||
def construct(cls, bases, attrs):
|
||||
cls = super(ObjectTypeMeta, cls).construct(bases, attrs)
|
||||
if not cls._meta.abstract:
|
||||
union_types = list(filter(is_objecttype, bases))
|
||||
if len(union_types) > 1:
|
||||
meta_attrs = dict(cls._meta.original_attrs, types=union_types)
|
||||
Meta = type('Meta', (object, ), meta_attrs)
|
||||
attrs['Meta'] = Meta
|
||||
attrs['__module__'] = cls.__module__
|
||||
attrs['__doc__'] = cls.__doc__
|
||||
return type(cls.__name__, (UnionType, ), attrs)
|
||||
return cls
|
||||
|
||||
options_class = ObjectTypeOptions
|
||||
|
||||
|
||||
class ObjectType(six.with_metaclass(ObjectTypeMeta, FieldsClassType)):
|
||||
|
||||
class Meta:
|
||||
abstract = True
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
signals.pre_init.send(self.__class__, args=args, kwargs=kwargs)
|
||||
self._root = kwargs.pop('_root', None)
|
||||
args_len = len(args)
|
||||
fields = self._meta.fields
|
||||
if args_len > len(fields):
|
||||
# Daft, but matches old exception sans the err msg.
|
||||
raise IndexError("Number of args exceeds number of fields")
|
||||
fields_iter = iter(fields)
|
||||
|
||||
if not kwargs:
|
||||
for val, field in zip(args, fields_iter):
|
||||
setattr(self, field.attname, val)
|
||||
else:
|
||||
for val, field in zip(args, fields_iter):
|
||||
setattr(self, field.attname, val)
|
||||
kwargs.pop(field.attname, None)
|
||||
|
||||
for field in fields_iter:
|
||||
try:
|
||||
val = kwargs.pop(field.attname)
|
||||
setattr(self, field.attname, val)
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
if kwargs:
|
||||
for prop in list(kwargs):
|
||||
try:
|
||||
if isinstance(getattr(self.__class__, prop), property):
|
||||
setattr(self, prop, kwargs.pop(prop))
|
||||
except AttributeError:
|
||||
pass
|
||||
if kwargs:
|
||||
raise TypeError(
|
||||
"'%s' is an invalid keyword argument for this function" %
|
||||
list(kwargs)[0])
|
||||
|
||||
signals.post_init.send(self.__class__, instance=self)
|
||||
|
||||
@classmethod
|
||||
def internal_type(cls, schema):
|
||||
if cls._meta.abstract:
|
||||
raise Exception("Abstract ObjectTypes don't have a specific type.")
|
||||
|
||||
return GraphQLObjectType(
|
||||
cls._meta.type_name,
|
||||
description=cls._meta.description,
|
||||
interfaces=list(map(schema.T, cls._meta.interfaces)),
|
||||
fields=partial(cls.fields_internal_types, schema),
|
||||
is_type_of=getattr(cls, 'is_type_of', None)
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def wrap(cls, instance, args, info):
|
||||
return cls(_root=instance)
|
|
@ -1,24 +1,11 @@
|
|||
from collections import OrderedDict
|
||||
|
||||
from ..utils import cached_property
|
||||
|
||||
DEFAULT_NAMES = ('description', 'name', 'is_interface', 'is_mutation',
|
||||
'type_name', 'interfaces', 'abstract')
|
||||
|
||||
|
||||
class Options(object):
|
||||
|
||||
def __init__(self, meta=None):
|
||||
def __init__(self, meta=None, **defaults):
|
||||
self.meta = meta
|
||||
self.local_fields = []
|
||||
self.is_interface = False
|
||||
self.is_mutation = False
|
||||
self.is_union = False
|
||||
self.abstract = False
|
||||
self.interfaces = []
|
||||
self.parents = []
|
||||
self.types = []
|
||||
self.valid_attrs = DEFAULT_NAMES
|
||||
for name, value in defaults.items():
|
||||
setattr(self, name, value)
|
||||
self.valid_attrs = list(defaults.keys()) + ['type_name', 'description', 'abstract']
|
||||
|
||||
def contribute_to_class(self, cls, name):
|
||||
cls._meta = self
|
||||
|
@ -59,14 +46,3 @@ class Options(object):
|
|||
meta_attrs.keys()))
|
||||
|
||||
del self.meta
|
||||
|
||||
def add_field(self, field):
|
||||
self.local_fields.append(field)
|
||||
|
||||
@cached_property
|
||||
def fields(self):
|
||||
return sorted(self.local_fields)
|
||||
|
||||
@cached_property
|
||||
def fields_map(self):
|
||||
return OrderedDict([(f.attname, f) for f in self.fields])
|
21
graphene/core/classtypes/scalar.py
Normal file
21
graphene/core/classtypes/scalar.py
Normal file
|
@ -0,0 +1,21 @@
|
|||
from graphql.core.type import GraphQLScalarType
|
||||
|
||||
from ..types.base import MountedType
|
||||
from .base import ClassType
|
||||
|
||||
|
||||
class Scalar(ClassType, MountedType):
|
||||
|
||||
@classmethod
|
||||
def internal_type(cls, schema):
|
||||
serialize = getattr(cls, 'serialize')
|
||||
parse_literal = getattr(cls, 'parse_literal')
|
||||
parse_value = getattr(cls, 'parse_value')
|
||||
|
||||
return GraphQLScalarType(
|
||||
name=cls._meta.type_name,
|
||||
description=cls._meta.description,
|
||||
serialize=serialize,
|
||||
parse_value=parse_value,
|
||||
parse_literal=parse_literal
|
||||
)
|
|
@ -1,11 +1,9 @@
|
|||
from py.test import raises
|
||||
|
||||
from graphene.core.fields import Field
|
||||
from graphene.core.options import Options
|
||||
from graphene.core.classtypes import Options
|
||||
|
||||
|
||||
class Meta:
|
||||
is_interface = True
|
||||
type_name = 'Character'
|
||||
|
||||
|
||||
|
@ -13,19 +11,6 @@ class InvalidMeta:
|
|||
other_value = True
|
||||
|
||||
|
||||
def test_field_added_in_meta():
|
||||
opt = Options(Meta)
|
||||
|
||||
class ObjectType(object):
|
||||
pass
|
||||
|
||||
opt.contribute_to_class(ObjectType, '_meta')
|
||||
f = Field(None)
|
||||
f.attname = 'string_field'
|
||||
opt.add_field(f)
|
||||
assert f in opt.fields
|
||||
|
||||
|
||||
def test_options_contribute():
|
||||
opt = Options(Meta)
|
||||
|
0
graphene/core/classtypes/tests/__init__.py
Normal file
0
graphene/core/classtypes/tests/__init__.py
Normal file
67
graphene/core/classtypes/tests/test_base.py
Normal file
67
graphene/core/classtypes/tests/test_base.py
Normal file
|
@ -0,0 +1,67 @@
|
|||
from ...schema import Schema
|
||||
from ...types import Field, List, NonNull, String
|
||||
from ..base import ClassType, FieldsClassType
|
||||
|
||||
|
||||
def test_classtype_basic():
|
||||
class Character(ClassType):
|
||||
'''Character description'''
|
||||
assert Character._meta.type_name == 'Character'
|
||||
assert Character._meta.description == 'Character description'
|
||||
|
||||
|
||||
def test_classtype_advanced():
|
||||
class Character(ClassType):
|
||||
|
||||
class Meta:
|
||||
type_name = 'OtherCharacter'
|
||||
description = 'OtherCharacter description'
|
||||
assert Character._meta.type_name == 'OtherCharacter'
|
||||
assert Character._meta.description == 'OtherCharacter description'
|
||||
|
||||
|
||||
def test_classtype_definition_list():
|
||||
class Character(ClassType):
|
||||
'''Character description'''
|
||||
assert isinstance(Character.List, List)
|
||||
assert Character.List.of_type == Character
|
||||
|
||||
|
||||
def test_classtype_definition_nonnull():
|
||||
class Character(ClassType):
|
||||
'''Character description'''
|
||||
assert isinstance(Character.NonNull, NonNull)
|
||||
assert Character.NonNull.of_type == Character
|
||||
|
||||
|
||||
def test_fieldsclasstype():
|
||||
f = Field(String())
|
||||
|
||||
class Character(FieldsClassType):
|
||||
field_name = f
|
||||
|
||||
assert Character._meta.fields == [f]
|
||||
|
||||
|
||||
def test_fieldsclasstype_fieldtype():
|
||||
f = Field(String())
|
||||
|
||||
class Character(FieldsClassType):
|
||||
field_name = f
|
||||
|
||||
schema = Schema(query=Character)
|
||||
assert Character.fields_internal_types(schema)['fieldName'] == schema.T(f)
|
||||
assert Character._meta.fields_map['field_name'] == f
|
||||
|
||||
|
||||
def test_fieldsclasstype_inheritfields():
|
||||
name_field = Field(String())
|
||||
last_name_field = Field(String())
|
||||
|
||||
class Fields1(FieldsClassType):
|
||||
name = name_field
|
||||
|
||||
class Fields2(Fields1):
|
||||
last_name = last_name_field
|
||||
|
||||
assert list(Fields2._meta.fields_map.keys()) == ['name', 'last_name']
|
21
graphene/core/classtypes/tests/test_inputobjecttype.py
Normal file
21
graphene/core/classtypes/tests/test_inputobjecttype.py
Normal file
|
@ -0,0 +1,21 @@
|
|||
|
||||
from graphql.core.type import GraphQLInputObjectType
|
||||
|
||||
from graphene.core.schema import Schema
|
||||
from graphene.core.types import String
|
||||
|
||||
from ..inputobjecttype import InputObjectType
|
||||
|
||||
|
||||
def test_inputobjecttype():
|
||||
class InputCharacter(InputObjectType):
|
||||
'''InputCharacter description'''
|
||||
name = String()
|
||||
|
||||
schema = Schema()
|
||||
|
||||
object_type = schema.T(InputCharacter)
|
||||
assert isinstance(object_type, GraphQLInputObjectType)
|
||||
assert InputCharacter._meta.type_name == 'InputCharacter'
|
||||
assert object_type.description == 'InputCharacter description'
|
||||
assert list(object_type.get_fields().keys()) == ['name']
|
86
graphene/core/classtypes/tests/test_interface.py
Normal file
86
graphene/core/classtypes/tests/test_interface.py
Normal file
|
@ -0,0 +1,86 @@
|
|||
from graphql.core.type import GraphQLInterfaceType, GraphQLObjectType
|
||||
from py.test import raises
|
||||
|
||||
from graphene.core.schema import Schema
|
||||
from graphene.core.types import String
|
||||
|
||||
from ..interface import Interface
|
||||
from ..objecttype import ObjectType
|
||||
|
||||
|
||||
def test_interface():
|
||||
class Character(Interface):
|
||||
'''Character description'''
|
||||
name = String()
|
||||
|
||||
schema = Schema()
|
||||
|
||||
object_type = schema.T(Character)
|
||||
assert issubclass(Character, Interface)
|
||||
assert isinstance(object_type, GraphQLInterfaceType)
|
||||
assert Character._meta.interface
|
||||
assert Character._meta.type_name == 'Character'
|
||||
assert object_type.description == 'Character description'
|
||||
assert list(object_type.get_fields().keys()) == ['name']
|
||||
|
||||
|
||||
def test_interface_cannot_initialize():
|
||||
class Character(Interface):
|
||||
pass
|
||||
|
||||
with raises(Exception) as excinfo:
|
||||
Character()
|
||||
assert 'An interface cannot be initialized' == str(excinfo.value)
|
||||
|
||||
|
||||
def test_interface_inheritance_abstract():
|
||||
class Character(Interface):
|
||||
pass
|
||||
|
||||
class ShouldBeInterface(Character):
|
||||
|
||||
class Meta:
|
||||
abstract = True
|
||||
|
||||
class ShouldBeObjectType(ShouldBeInterface):
|
||||
pass
|
||||
|
||||
assert ShouldBeInterface._meta.interface
|
||||
assert not ShouldBeObjectType._meta.interface
|
||||
assert issubclass(ShouldBeObjectType, ObjectType)
|
||||
|
||||
|
||||
def test_interface_inheritance():
|
||||
class Character(Interface):
|
||||
pass
|
||||
|
||||
class GeneralInterface(Interface):
|
||||
pass
|
||||
|
||||
class ShouldBeObjectType(GeneralInterface, Character):
|
||||
pass
|
||||
|
||||
schema = Schema()
|
||||
|
||||
assert Character._meta.interface
|
||||
assert not ShouldBeObjectType._meta.interface
|
||||
assert issubclass(ShouldBeObjectType, ObjectType)
|
||||
assert Character in ShouldBeObjectType._meta.interfaces
|
||||
assert GeneralInterface in ShouldBeObjectType._meta.interfaces
|
||||
assert isinstance(schema.T(Character), GraphQLInterfaceType)
|
||||
assert isinstance(schema.T(ShouldBeObjectType), GraphQLObjectType)
|
||||
|
||||
|
||||
def test_interface_inheritance_non_objects():
|
||||
class CommonClass(object):
|
||||
common_attr = True
|
||||
|
||||
class Character(CommonClass, Interface):
|
||||
pass
|
||||
|
||||
class ShouldBeObjectType(Character):
|
||||
pass
|
||||
|
||||
assert Character._meta.interface
|
||||
assert Character.common_attr
|
||||
assert ShouldBeObjectType.common_attr
|
27
graphene/core/classtypes/tests/test_mutation.py
Normal file
27
graphene/core/classtypes/tests/test_mutation.py
Normal file
|
@ -0,0 +1,27 @@
|
|||
|
||||
from graphql.core.type import GraphQLObjectType
|
||||
|
||||
from graphene.core.schema import Schema
|
||||
from graphene.core.types import String
|
||||
|
||||
from ...types.argument import ArgumentsGroup
|
||||
from ..mutation import Mutation
|
||||
|
||||
|
||||
def test_mutation():
|
||||
class MyMutation(Mutation):
|
||||
'''MyMutation description'''
|
||||
class Input:
|
||||
arg_name = String()
|
||||
name = String()
|
||||
|
||||
schema = Schema()
|
||||
|
||||
object_type = schema.T(MyMutation)
|
||||
assert MyMutation._meta.type_name == 'MyMutation'
|
||||
assert isinstance(object_type, GraphQLObjectType)
|
||||
assert object_type.description == 'MyMutation description'
|
||||
assert list(object_type.get_fields().keys()) == ['name']
|
||||
assert MyMutation._meta.fields_map['name'].object_type == MyMutation
|
||||
assert isinstance(MyMutation.arguments, ArgumentsGroup)
|
||||
assert 'argName' in MyMutation.arguments
|
89
graphene/core/classtypes/tests/test_objecttype.py
Normal file
89
graphene/core/classtypes/tests/test_objecttype.py
Normal file
|
@ -0,0 +1,89 @@
|
|||
from graphql.core.type import GraphQLObjectType
|
||||
from py.test import raises
|
||||
|
||||
from graphene.core.schema import Schema
|
||||
from graphene.core.types import String
|
||||
|
||||
from ..objecttype import ObjectType
|
||||
from ..uniontype import UnionType
|
||||
|
||||
|
||||
def test_object_type():
|
||||
class Human(ObjectType):
|
||||
'''Human description'''
|
||||
name = String()
|
||||
friends = String()
|
||||
|
||||
schema = Schema()
|
||||
|
||||
object_type = schema.T(Human)
|
||||
assert Human._meta.type_name == 'Human'
|
||||
assert isinstance(object_type, GraphQLObjectType)
|
||||
assert object_type.description == 'Human description'
|
||||
assert list(object_type.get_fields().keys()) == ['name', 'friends']
|
||||
assert Human._meta.fields_map['name'].object_type == Human
|
||||
|
||||
|
||||
def test_object_type_container():
|
||||
class Human(ObjectType):
|
||||
name = String()
|
||||
friends = String()
|
||||
|
||||
h = Human(name='My name')
|
||||
assert h.name == 'My name'
|
||||
|
||||
|
||||
def test_object_type_set_properties():
|
||||
class Human(ObjectType):
|
||||
name = String()
|
||||
friends = String()
|
||||
|
||||
@property
|
||||
def readonly_prop(self):
|
||||
return 'readonly'
|
||||
|
||||
@property
|
||||
def write_prop(self):
|
||||
return self._write_prop
|
||||
|
||||
@write_prop.setter
|
||||
def write_prop(self, value):
|
||||
self._write_prop = value
|
||||
|
||||
h = Human(readonly_prop='custom', write_prop='custom')
|
||||
assert h.readonly_prop == 'readonly'
|
||||
assert h.write_prop == 'custom'
|
||||
|
||||
|
||||
def test_object_type_container_invalid_kwarg():
|
||||
class Human(ObjectType):
|
||||
name = String()
|
||||
|
||||
with raises(TypeError):
|
||||
Human(invalid='My name')
|
||||
|
||||
|
||||
def test_object_type_container_too_many_args():
|
||||
class Human(ObjectType):
|
||||
name = String()
|
||||
|
||||
with raises(IndexError):
|
||||
Human('Peter', 'No friends :(', None)
|
||||
|
||||
|
||||
def test_object_type_union():
|
||||
class Human(ObjectType):
|
||||
name = String()
|
||||
|
||||
class Pet(ObjectType):
|
||||
name = String()
|
||||
|
||||
class Thing(Human, Pet):
|
||||
'''Thing union description'''
|
||||
my_attr = True
|
||||
|
||||
assert issubclass(Thing, UnionType)
|
||||
assert Thing._meta.types == [Human, Pet]
|
||||
assert Thing._meta.type_name == 'Thing'
|
||||
assert Thing._meta.description == 'Thing union description'
|
||||
assert Thing.my_attr
|
32
graphene/core/classtypes/tests/test_scalar.py
Normal file
32
graphene/core/classtypes/tests/test_scalar.py
Normal file
|
@ -0,0 +1,32 @@
|
|||
from graphql.core.type import GraphQLScalarType
|
||||
|
||||
from ...schema import Schema
|
||||
from ..scalar import Scalar
|
||||
|
||||
|
||||
def test_custom_scalar():
|
||||
import datetime
|
||||
from graphql.core.language import ast
|
||||
|
||||
class DateTimeScalar(Scalar):
|
||||
'''DateTimeScalar Documentation'''
|
||||
@staticmethod
|
||||
def serialize(dt):
|
||||
return dt.isoformat()
|
||||
|
||||
@staticmethod
|
||||
def parse_literal(node):
|
||||
if isinstance(node, ast.StringValue):
|
||||
return datetime.datetime.strptime(
|
||||
node.value, "%Y-%m-%dT%H:%M:%S.%f")
|
||||
|
||||
@staticmethod
|
||||
def parse_value(value):
|
||||
return datetime.datetime.strptime(value, "%Y-%m-%dT%H:%M:%S.%f")
|
||||
|
||||
schema = Schema()
|
||||
|
||||
scalar_type = schema.T(DateTimeScalar)
|
||||
assert isinstance(scalar_type, GraphQLScalarType)
|
||||
assert scalar_type.name == 'DateTimeScalar'
|
||||
assert scalar_type.description == 'DateTimeScalar Documentation'
|
28
graphene/core/classtypes/tests/test_uniontype.py
Normal file
28
graphene/core/classtypes/tests/test_uniontype.py
Normal file
|
@ -0,0 +1,28 @@
|
|||
from graphql.core.type import GraphQLUnionType
|
||||
|
||||
from graphene.core.schema import Schema
|
||||
from graphene.core.types import String
|
||||
|
||||
from ..objecttype import ObjectType
|
||||
from ..uniontype import UnionType
|
||||
|
||||
|
||||
def test_uniontype():
|
||||
class Human(ObjectType):
|
||||
name = String()
|
||||
|
||||
class Pet(ObjectType):
|
||||
name = String()
|
||||
|
||||
class Thing(UnionType):
|
||||
'''Thing union description'''
|
||||
class Meta:
|
||||
types = [Human, Pet]
|
||||
|
||||
schema = Schema()
|
||||
|
||||
object_type = schema.T(Thing)
|
||||
assert isinstance(object_type, GraphQLUnionType)
|
||||
assert Thing._meta.type_name == 'Thing'
|
||||
assert object_type.description == 'Thing union description'
|
||||
assert object_type.get_possible_types() == [schema.T(Human), schema.T(Pet)]
|
40
graphene/core/classtypes/uniontype.py
Normal file
40
graphene/core/classtypes/uniontype.py
Normal file
|
@ -0,0 +1,40 @@
|
|||
import six
|
||||
from graphql.core.type import GraphQLUnionType
|
||||
|
||||
from .base import FieldsClassType, FieldsClassTypeMeta, FieldsOptions
|
||||
|
||||
|
||||
class UnionTypeOptions(FieldsOptions):
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(UnionTypeOptions, self).__init__(*args, **kwargs)
|
||||
self.types = []
|
||||
|
||||
|
||||
class UnionTypeMeta(FieldsClassTypeMeta):
|
||||
options_class = UnionTypeOptions
|
||||
|
||||
def get_options(cls, meta):
|
||||
return cls.options_class(meta, types=[])
|
||||
|
||||
|
||||
class UnionType(six.with_metaclass(UnionTypeMeta, FieldsClassType)):
|
||||
|
||||
class Meta:
|
||||
abstract = True
|
||||
|
||||
@classmethod
|
||||
def _resolve_type(cls, schema, instance, *args):
|
||||
return schema.T(instance.__class__)
|
||||
|
||||
@classmethod
|
||||
def internal_type(cls, schema):
|
||||
if cls._meta.abstract:
|
||||
raise Exception("Abstract ObjectTypes don't have a specific type.")
|
||||
|
||||
return GraphQLUnionType(
|
||||
cls._meta.type_name,
|
||||
types=list(map(schema.T, cls._meta.types)),
|
||||
resolve_type=cls._resolve_type,
|
||||
description=cls._meta.description,
|
||||
)
|
|
@ -10,8 +10,8 @@ from graphql.core.utils.schema_printer import print_schema
|
|||
|
||||
from graphene import signals
|
||||
|
||||
from .classtypes.base import ClassType
|
||||
from .types.base import BaseType
|
||||
from .types.objecttype import BaseObjectType
|
||||
|
||||
|
||||
class GraphQLSchema(_GraphQLSchema):
|
||||
|
@ -42,13 +42,13 @@ class Schema(object):
|
|||
if not object_type:
|
||||
return
|
||||
if inspect.isclass(object_type) and issubclass(
|
||||
object_type, BaseType) or isinstance(
|
||||
object_type, (BaseType, ClassType)) or isinstance(
|
||||
object_type, BaseType):
|
||||
if object_type not in self._types:
|
||||
internal_type = object_type.internal_type(self)
|
||||
self._types[object_type] = internal_type
|
||||
is_objecttype = inspect.isclass(
|
||||
object_type) and issubclass(object_type, BaseObjectType)
|
||||
object_type) and issubclass(object_type, ClassType)
|
||||
if is_objecttype:
|
||||
self.register(object_type)
|
||||
return self._types[object_type]
|
||||
|
@ -90,7 +90,7 @@ class Schema(object):
|
|||
if name:
|
||||
objecttype = self._types_names.get(name, None)
|
||||
if objecttype and inspect.isclass(
|
||||
objecttype) and issubclass(objecttype, BaseObjectType):
|
||||
objecttype) and issubclass(objecttype, ClassType):
|
||||
return objecttype
|
||||
|
||||
def __str__(self):
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
from .base import BaseType, LazyType, OrderedType
|
||||
from .argument import Argument, ArgumentsGroup, to_arguments
|
||||
from .definitions import List, NonNull
|
||||
from .objecttype import ObjectTypeMeta, BaseObjectType, Interface, ObjectType, Mutation, InputObjectType
|
||||
# Compatibility import
|
||||
from .objecttype import Interface, ObjectType, Mutation, InputObjectType
|
||||
|
||||
from .scalars import String, ID, Boolean, Int, Float, Scalar
|
||||
from .field import Field, InputField
|
||||
|
||||
|
@ -17,8 +19,6 @@ __all__ = [
|
|||
'Field',
|
||||
'InputField',
|
||||
'Interface',
|
||||
'BaseObjectType',
|
||||
'ObjectTypeMeta',
|
||||
'ObjectType',
|
||||
'Mutation',
|
||||
'InputObjectType',
|
||||
|
|
|
@ -104,11 +104,12 @@ class ArgumentType(MirroredType):
|
|||
class FieldType(MirroredType):
|
||||
|
||||
def contribute_to_class(self, cls, name):
|
||||
from ..types import BaseObjectType, InputObjectType
|
||||
if issubclass(cls, InputObjectType):
|
||||
from ..classtypes.base import FieldsClassType
|
||||
from ..classtypes.inputobjecttype import InputObjectType
|
||||
if issubclass(cls, (InputObjectType)):
|
||||
inputfield = self.as_inputfield()
|
||||
return inputfield.contribute_to_class(cls, name)
|
||||
elif issubclass(cls, BaseObjectType):
|
||||
elif issubclass(cls, (FieldsClassType)):
|
||||
field = self.as_field()
|
||||
return field.contribute_to_class(cls, name)
|
||||
|
||||
|
|
|
@ -5,7 +5,9 @@ import six
|
|||
from graphql.core.type import GraphQLField, GraphQLInputObjectField
|
||||
|
||||
from ...utils import to_camel_case
|
||||
from ..types import BaseObjectType, InputObjectType
|
||||
from ..classtypes.base import FieldsClassType
|
||||
from ..classtypes.inputobjecttype import InputObjectType
|
||||
from ..classtypes.mutation import Mutation
|
||||
from .argument import ArgumentsGroup, snake_case_args
|
||||
from .base import LazyType, MountType, OrderedType
|
||||
from .definitions import NonNull
|
||||
|
@ -32,7 +34,7 @@ class Field(OrderedType):
|
|||
|
||||
def contribute_to_class(self, cls, attname):
|
||||
assert issubclass(
|
||||
cls, BaseObjectType), 'Field {} cannot be mounted in {}'.format(
|
||||
cls, (FieldsClassType)), 'Field {} cannot be mounted in {}'.format(
|
||||
self, cls)
|
||||
if not self.name:
|
||||
self.name = to_camel_case(attname)
|
||||
|
@ -69,7 +71,7 @@ class Field(OrderedType):
|
|||
description = resolver.__doc__
|
||||
type = schema.T(self.get_type(schema))
|
||||
type_objecttype = schema.objecttype(type)
|
||||
if type_objecttype and type_objecttype._meta.is_mutation:
|
||||
if type_objecttype and issubclass(type_objecttype, Mutation):
|
||||
assert len(arguments) == 0
|
||||
arguments = type_objecttype.get_arguments()
|
||||
resolver = getattr(type_objecttype, 'mutate')
|
||||
|
@ -126,7 +128,7 @@ class InputField(OrderedType):
|
|||
|
||||
def contribute_to_class(self, cls, attname):
|
||||
assert issubclass(
|
||||
cls, InputObjectType), 'InputField {} cannot be mounted in {}'.format(
|
||||
cls, (InputObjectType)), 'InputField {} cannot be mounted in {}'.format(
|
||||
self, cls)
|
||||
if not self.name:
|
||||
self.name = to_camel_case(attname)
|
||||
|
|
|
@ -1,282 +1,3 @@
|
|||
import copy
|
||||
import inspect
|
||||
from collections import OrderedDict
|
||||
from functools import partial
|
||||
from ..classtypes import InputObjectType, Interface, Mutation, ObjectType
|
||||
|
||||
import six
|
||||
from graphql.core.type import (GraphQLInputObjectType, GraphQLInterfaceType,
|
||||
GraphQLObjectType, GraphQLUnionType)
|
||||
|
||||
from graphene import signals
|
||||
|
||||
from ..exceptions import SkipField
|
||||
from ..options import Options
|
||||
from .argument import ArgumentsGroup
|
||||
from .base import BaseType
|
||||
from .definitions import List, NonNull
|
||||
|
||||
|
||||
def is_objecttype(cls):
|
||||
if not issubclass(cls, BaseObjectType):
|
||||
return False
|
||||
_meta = getattr(cls, '_meta', None)
|
||||
return not(_meta and (_meta.abstract or _meta.is_interface))
|
||||
|
||||
|
||||
class ObjectTypeMeta(type):
|
||||
options_cls = Options
|
||||
|
||||
def is_interface(cls, parents):
|
||||
return Interface in parents
|
||||
|
||||
def is_mutation(cls, parents):
|
||||
return issubclass(cls, Mutation)
|
||||
|
||||
def __new__(cls, name, bases, attrs):
|
||||
super_new = super(ObjectTypeMeta, cls).__new__
|
||||
parents = [b for b in bases if isinstance(b, cls)]
|
||||
if not parents:
|
||||
# If this isn't a subclass of Model, don't do anything special.
|
||||
return super_new(cls, name, bases, attrs)
|
||||
|
||||
module = attrs.pop('__module__', None)
|
||||
doc = attrs.pop('__doc__', None)
|
||||
new_class = super_new(cls, name, bases, {
|
||||
'__module__': module,
|
||||
'__doc__': doc
|
||||
})
|
||||
attr_meta = attrs.pop('Meta', None)
|
||||
abstract = getattr(attr_meta, 'abstract', False)
|
||||
if not attr_meta:
|
||||
meta = getattr(new_class, 'Meta', None)
|
||||
else:
|
||||
meta = attr_meta
|
||||
|
||||
base_meta = getattr(new_class, '_meta', None)
|
||||
|
||||
new_class.add_to_class('_meta', new_class.options_cls(meta))
|
||||
|
||||
new_class._meta.is_interface = new_class.is_interface(parents)
|
||||
new_class._meta.is_mutation = new_class.is_mutation(parents) or (base_meta and base_meta.is_mutation)
|
||||
union_types = list(filter(is_objecttype, parents))
|
||||
|
||||
new_class._meta.is_union = len(union_types) > 1
|
||||
new_class._meta.types = union_types
|
||||
|
||||
assert not (
|
||||
new_class._meta.is_interface and new_class._meta.is_mutation)
|
||||
|
||||
assert not (
|
||||
new_class._meta.is_interface and new_class._meta.is_union)
|
||||
|
||||
# Add all attributes to the class.
|
||||
for obj_name, obj in attrs.items():
|
||||
new_class.add_to_class(obj_name, obj)
|
||||
|
||||
if abstract:
|
||||
new_class._prepare()
|
||||
return new_class
|
||||
|
||||
if new_class._meta.is_mutation:
|
||||
assert hasattr(
|
||||
new_class, 'mutate'), "All mutations must implement mutate method"
|
||||
|
||||
new_class.add_extra_fields()
|
||||
|
||||
new_fields = new_class._meta.local_fields
|
||||
assert not(new_class._meta.is_union and new_fields), 'An union cannot have extra fields'
|
||||
|
||||
field_names = {f.name: f for f in new_fields}
|
||||
|
||||
for base in parents:
|
||||
if not hasattr(base, '_meta'):
|
||||
# Things without _meta aren't functional models, so they're
|
||||
# uninteresting parents.
|
||||
continue
|
||||
# if base._meta.schema != new_class._meta.schema:
|
||||
# raise Exception('The parent schema is not the same')
|
||||
|
||||
parent_fields = base._meta.local_fields
|
||||
# Check for clashes between locally declared fields and those
|
||||
# on the base classes (we cannot handle shadowed fields at the
|
||||
# moment).
|
||||
for field in parent_fields:
|
||||
if field.name in field_names and field.type.__class__ != field_names[
|
||||
field.name].type.__class__:
|
||||
raise Exception(
|
||||
'Local field %r in class %r (%r) clashes '
|
||||
'with field with similar name from '
|
||||
'Interface %s (%r)' % (
|
||||
field.name,
|
||||
new_class.__name__,
|
||||
field.__class__,
|
||||
base.__name__,
|
||||
field_names[field.name].__class__)
|
||||
)
|
||||
new_field = copy.copy(field)
|
||||
new_class.add_to_class(field.attname, new_field)
|
||||
|
||||
new_class._meta.parents.append(base)
|
||||
if base._meta.is_interface:
|
||||
new_class._meta.interfaces.append(base)
|
||||
# new_class._meta.parents.extend(base._meta.parents)
|
||||
|
||||
setattr(new_class, 'NonNull', NonNull(new_class))
|
||||
setattr(new_class, 'List', List(new_class))
|
||||
|
||||
new_class._prepare()
|
||||
return new_class
|
||||
|
||||
def add_extra_fields(cls):
|
||||
pass
|
||||
|
||||
def _prepare(cls):
|
||||
if hasattr(cls, '_prepare_class'):
|
||||
cls._prepare_class()
|
||||
signals.class_prepared.send(cls)
|
||||
|
||||
def add_to_class(cls, name, value):
|
||||
# We should call the contribute_to_class method only if it's bound
|
||||
if not inspect.isclass(value) and hasattr(
|
||||
value, 'contribute_to_class'):
|
||||
value.contribute_to_class(cls, name)
|
||||
else:
|
||||
setattr(cls, name, value)
|
||||
|
||||
|
||||
class BaseObjectType(BaseType):
|
||||
|
||||
def __new__(cls, *args, **kwargs):
|
||||
if cls._meta.is_interface:
|
||||
raise Exception("An interface cannot be initialized")
|
||||
elif cls._meta.is_union:
|
||||
raise Exception("An union cannot be initialized")
|
||||
elif cls._meta.abstract:
|
||||
raise Exception("An abstract ObjectType cannot be initialized")
|
||||
return super(BaseObjectType, cls).__new__(cls)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
signals.pre_init.send(self.__class__, args=args, kwargs=kwargs)
|
||||
self._root = kwargs.pop('_root', None)
|
||||
args_len = len(args)
|
||||
fields = self._meta.fields
|
||||
if args_len > len(fields):
|
||||
# Daft, but matches old exception sans the err msg.
|
||||
raise IndexError("Number of args exceeds number of fields")
|
||||
fields_iter = iter(fields)
|
||||
|
||||
if not kwargs:
|
||||
for val, field in zip(args, fields_iter):
|
||||
setattr(self, field.attname, val)
|
||||
else:
|
||||
for val, field in zip(args, fields_iter):
|
||||
setattr(self, field.attname, val)
|
||||
kwargs.pop(field.attname, None)
|
||||
|
||||
for field in fields_iter:
|
||||
try:
|
||||
val = kwargs.pop(field.attname)
|
||||
setattr(self, field.attname, val)
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
if kwargs:
|
||||
for prop in list(kwargs):
|
||||
try:
|
||||
if isinstance(getattr(self.__class__, prop), property):
|
||||
setattr(self, prop, kwargs.pop(prop))
|
||||
except AttributeError:
|
||||
pass
|
||||
if kwargs:
|
||||
raise TypeError(
|
||||
"'%s' is an invalid keyword argument for this function" %
|
||||
list(kwargs)[0])
|
||||
|
||||
signals.post_init.send(self.__class__, instance=self)
|
||||
|
||||
@classmethod
|
||||
def _resolve_type(cls, schema, instance, *args):
|
||||
return schema.T(instance.__class__)
|
||||
|
||||
@classmethod
|
||||
def internal_type(cls, schema):
|
||||
if cls._meta.abstract:
|
||||
raise Exception("Abstract ObjectTypes don't have a specific type.")
|
||||
|
||||
if cls._meta.is_interface:
|
||||
return GraphQLInterfaceType(
|
||||
cls._meta.type_name,
|
||||
description=cls._meta.description,
|
||||
resolve_type=partial(cls._resolve_type, schema),
|
||||
fields=partial(cls.get_fields, schema)
|
||||
)
|
||||
elif cls._meta.is_union:
|
||||
return GraphQLUnionType(
|
||||
cls._meta.type_name,
|
||||
types=cls._meta.types,
|
||||
description=cls._meta.description,
|
||||
)
|
||||
return GraphQLObjectType(
|
||||
cls._meta.type_name,
|
||||
description=cls._meta.description,
|
||||
interfaces=[schema.T(i) for i in cls._meta.interfaces],
|
||||
fields=partial(cls.get_fields, schema),
|
||||
is_type_of=getattr(cls, 'is_type_of', None)
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def get_fields(cls, schema):
|
||||
fields = []
|
||||
for field in cls._meta.fields:
|
||||
try:
|
||||
fields.append((field.name, schema.T(field)))
|
||||
except SkipField:
|
||||
continue
|
||||
|
||||
return OrderedDict(fields)
|
||||
|
||||
@classmethod
|
||||
def wrap(cls, instance, args, info):
|
||||
return cls(_root=instance)
|
||||
|
||||
|
||||
class Interface(six.with_metaclass(ObjectTypeMeta, BaseObjectType)):
|
||||
pass
|
||||
|
||||
|
||||
class ObjectType(six.with_metaclass(ObjectTypeMeta, BaseObjectType)):
|
||||
pass
|
||||
|
||||
|
||||
class Mutation(six.with_metaclass(ObjectTypeMeta, BaseObjectType)):
|
||||
|
||||
@classmethod
|
||||
def _construct_arguments(cls, items):
|
||||
return ArgumentsGroup(**items)
|
||||
|
||||
@classmethod
|
||||
def _prepare_class(cls):
|
||||
input_class = getattr(cls, 'Input', None)
|
||||
if input_class:
|
||||
items = dict(vars(input_class))
|
||||
items.pop('__dict__', None)
|
||||
items.pop('__doc__', None)
|
||||
items.pop('__module__', None)
|
||||
items.pop('__weakref__', None)
|
||||
cls.add_to_class('arguments', cls._construct_arguments(items))
|
||||
delattr(cls, 'Input')
|
||||
|
||||
@classmethod
|
||||
def get_arguments(cls):
|
||||
return cls.arguments
|
||||
|
||||
|
||||
class InputObjectType(ObjectType):
|
||||
|
||||
@classmethod
|
||||
def internal_type(cls, schema):
|
||||
return GraphQLInputObjectType(
|
||||
cls._meta.type_name,
|
||||
description=cls._meta.description,
|
||||
fields=partial(cls.get_fields, schema),
|
||||
)
|
||||
__all__ = ['ObjectType', 'Interface', 'Mutation', 'InputObjectType']
|
||||
|
|
|
@ -1,194 +0,0 @@
|
|||
from graphql.core.execution.middlewares.utils import (resolver_has_tag,
|
||||
tag_resolver)
|
||||
from graphql.core.type import (GraphQLInterfaceType, GraphQLObjectType,
|
||||
GraphQLUnionType)
|
||||
from py.test import raises
|
||||
|
||||
from graphene.core.schema import Schema
|
||||
from graphene.core.types import Int, Interface, ObjectType, String
|
||||
|
||||
|
||||
class Character(Interface):
|
||||
'''Character description'''
|
||||
name = String()
|
||||
|
||||
class Meta:
|
||||
type_name = 'core_Character'
|
||||
|
||||
|
||||
class Human(Character):
|
||||
'''Human description'''
|
||||
friends = String()
|
||||
|
||||
class Meta:
|
||||
type_name = 'core_Human'
|
||||
|
||||
@property
|
||||
def readonly_prop(self):
|
||||
return 'readonly'
|
||||
|
||||
@property
|
||||
def write_prop(self):
|
||||
return self._write_prop
|
||||
|
||||
@write_prop.setter
|
||||
def write_prop(self, value):
|
||||
self._write_prop = value
|
||||
|
||||
|
||||
class Droid(Character):
|
||||
'''Droid description'''
|
||||
|
||||
|
||||
class CharacterType(Droid, Human):
|
||||
'''Union Type'''
|
||||
|
||||
schema = Schema()
|
||||
|
||||
|
||||
def test_interface():
|
||||
object_type = schema.T(Character)
|
||||
assert Character._meta.is_interface is True
|
||||
assert isinstance(object_type, GraphQLInterfaceType)
|
||||
assert Character._meta.type_name == 'core_Character'
|
||||
assert object_type.description == 'Character description'
|
||||
assert list(object_type.get_fields().keys()) == ['name']
|
||||
|
||||
|
||||
def test_interface_cannot_initialize():
|
||||
with raises(Exception) as excinfo:
|
||||
Character()
|
||||
assert 'An interface cannot be initialized' == str(excinfo.value)
|
||||
|
||||
|
||||
def test_union():
|
||||
object_type = schema.T(CharacterType)
|
||||
assert CharacterType._meta.is_union is True
|
||||
assert isinstance(object_type, GraphQLUnionType)
|
||||
assert object_type.description == 'Union Type'
|
||||
|
||||
|
||||
def test_union_cannot_initialize():
|
||||
with raises(Exception) as excinfo:
|
||||
CharacterType()
|
||||
assert 'An union cannot be initialized' == str(excinfo.value)
|
||||
|
||||
|
||||
def test_interface_resolve_type():
|
||||
resolve_type = Character._resolve_type(schema, Human(object()))
|
||||
assert isinstance(resolve_type, GraphQLObjectType)
|
||||
|
||||
|
||||
def test_object_type():
|
||||
object_type = schema.T(Human)
|
||||
assert Human._meta.is_interface is False
|
||||
assert Human._meta.type_name == 'core_Human'
|
||||
assert isinstance(object_type, GraphQLObjectType)
|
||||
assert object_type.description == 'Human description'
|
||||
assert list(object_type.get_fields().keys()) == ['name', 'friends']
|
||||
assert object_type.get_interfaces() == [schema.T(Character)]
|
||||
assert Human._meta.fields_map['name'].object_type == Human
|
||||
|
||||
|
||||
def test_object_type_container():
|
||||
h = Human(name='My name')
|
||||
assert h.name == 'My name'
|
||||
|
||||
|
||||
def test_object_type_set_properties():
|
||||
h = Human(readonly_prop='custom', write_prop='custom')
|
||||
assert h.readonly_prop == 'readonly'
|
||||
assert h.write_prop == 'custom'
|
||||
|
||||
|
||||
def test_object_type_container_invalid_kwarg():
|
||||
with raises(TypeError):
|
||||
Human(invalid='My name')
|
||||
|
||||
|
||||
def test_object_type_container_too_many_args():
|
||||
with raises(IndexError):
|
||||
Human('Peter', 'No friends :(', None)
|
||||
|
||||
|
||||
def test_field_clashes():
|
||||
with raises(Exception) as excinfo:
|
||||
class Droid(Character):
|
||||
name = Int()
|
||||
|
||||
assert 'clashes' in str(excinfo.value)
|
||||
|
||||
|
||||
def test_fields_inherited_should_be_different():
|
||||
assert Character._meta.fields_map['name'] != Human._meta.fields_map['name']
|
||||
|
||||
|
||||
def test_field_mantain_resolver_tags():
|
||||
class Droid(Character):
|
||||
name = String()
|
||||
|
||||
def resolve_name(self, *args):
|
||||
return 'My Droid'
|
||||
|
||||
tag_resolver(resolve_name, 'test')
|
||||
|
||||
field = schema.T(Droid._meta.fields_map['name'])
|
||||
assert resolver_has_tag(field.resolver, 'test')
|
||||
|
||||
|
||||
def test_type_has_nonnull():
|
||||
class Droid(Character):
|
||||
name = String()
|
||||
|
||||
assert Droid.NonNull.of_type == Droid
|
||||
|
||||
|
||||
def test_type_has_list():
|
||||
class Droid(Character):
|
||||
name = String()
|
||||
|
||||
assert Droid.List.of_type == Droid
|
||||
|
||||
|
||||
def test_abstracttype():
|
||||
class MyObject1(ObjectType):
|
||||
class Meta:
|
||||
abstract = True
|
||||
name1 = String()
|
||||
|
||||
class MyObject2(ObjectType):
|
||||
class Meta:
|
||||
abstract = True
|
||||
name2 = String()
|
||||
|
||||
class MyObject(MyObject1, MyObject2):
|
||||
pass
|
||||
|
||||
object_type = schema.T(MyObject)
|
||||
|
||||
assert list(MyObject._meta.fields_map.keys()) == ['name1', 'name2']
|
||||
assert MyObject._meta.fields_map['name1'].object_type == MyObject
|
||||
assert MyObject._meta.fields_map['name2'].object_type == MyObject
|
||||
assert isinstance(object_type, GraphQLObjectType)
|
||||
|
||||
|
||||
def test_abstracttype_initialize():
|
||||
class MyAbstractObjectType(ObjectType):
|
||||
class Meta:
|
||||
abstract = True
|
||||
|
||||
with raises(Exception) as excinfo:
|
||||
MyAbstractObjectType()
|
||||
|
||||
assert 'An abstract ObjectType cannot be initialized' == str(excinfo.value)
|
||||
|
||||
|
||||
def test_abstracttype_type():
|
||||
class MyAbstractObjectType(ObjectType):
|
||||
class Meta:
|
||||
abstract = True
|
||||
|
||||
with raises(Exception) as excinfo:
|
||||
schema.T(MyAbstractObjectType)
|
||||
|
||||
assert 'Abstract ObjectTypes don\'t have a specific type.' == str(excinfo.value)
|
|
@ -90,11 +90,5 @@ class GlobalIDField(Field):
|
|||
def __init__(self, *args, **kwargs):
|
||||
super(GlobalIDField, self).__init__(NonNull(ID()), *args, **kwargs)
|
||||
|
||||
def contribute_to_class(self, cls, name):
|
||||
from graphene.relay.utils import is_node, is_node_type
|
||||
in_node = is_node(cls) or is_node_type(cls)
|
||||
assert in_node, 'GlobalIDField could only be inside a Node, but got %r' % cls
|
||||
super(GlobalIDField, self).contribute_to_class(cls, name)
|
||||
|
||||
def resolver(self, instance, args, info):
|
||||
return instance.to_global_id()
|
||||
|
|
|
@ -3,11 +3,14 @@ import warnings
|
|||
from collections import Iterable
|
||||
from functools import wraps
|
||||
|
||||
import six
|
||||
from graphql_relay.connection.arrayconnection import connection_from_list
|
||||
from graphql_relay.node.node import to_global_id
|
||||
|
||||
from ..core.types import (Boolean, Field, InputObjectType, Interface, List,
|
||||
Mutation, ObjectType, String)
|
||||
from ..core.classtypes import InputObjectType, Interface, Mutation, ObjectType
|
||||
from ..core.classtypes.interface import InterfaceMeta
|
||||
from ..core.classtypes.mutation import MutationMeta
|
||||
from ..core.types import Boolean, Field, List, String
|
||||
from ..core.types.argument import ArgumentsGroup
|
||||
from ..core.types.definitions import NonNull
|
||||
from ..utils import memoize
|
||||
|
@ -83,33 +86,44 @@ class Connection(ObjectType):
|
|||
return self._connection_data
|
||||
|
||||
|
||||
class BaseNode(object):
|
||||
class NodeMeta(InterfaceMeta):
|
||||
|
||||
@classmethod
|
||||
def _prepare_class(cls):
|
||||
from graphene.relay.utils import is_node
|
||||
if is_node(cls):
|
||||
get_node = getattr(cls, 'get_node')
|
||||
assert get_node, 'get_node classmethod not found in %s Node' % cls
|
||||
assert callable(get_node), 'get_node have to be callable'
|
||||
args = 3
|
||||
if isinstance(get_node, staticmethod):
|
||||
args -= 1
|
||||
def construct_get_node(cls):
|
||||
get_node = getattr(cls, 'get_node', None)
|
||||
assert get_node, 'get_node classmethod not found in %s Node' % cls
|
||||
assert callable(get_node), 'get_node have to be callable'
|
||||
args = 3
|
||||
if isinstance(get_node, staticmethod):
|
||||
args -= 1
|
||||
|
||||
get_node_num_args = len(inspect.getargspec(get_node).args)
|
||||
if get_node_num_args < args:
|
||||
warnings.warn("get_node will receive also the info arg"
|
||||
" in future versions of graphene".format(cls.__name__),
|
||||
FutureWarning)
|
||||
get_node_num_args = len(inspect.getargspec(get_node).args)
|
||||
if get_node_num_args < args:
|
||||
warnings.warn("get_node will receive also the info arg"
|
||||
" in future versions of graphene".format(cls.__name__),
|
||||
FutureWarning)
|
||||
|
||||
@staticmethod
|
||||
@wraps(get_node)
|
||||
def wrapped_node(*node_args):
|
||||
if len(node_args) < args:
|
||||
node_args += (None, )
|
||||
return get_node(*node_args[:-1])
|
||||
@staticmethod
|
||||
@wraps(get_node)
|
||||
def wrapped_node(*node_args):
|
||||
if len(node_args) < args:
|
||||
node_args += (None, )
|
||||
return get_node(*node_args[:-1])
|
||||
|
||||
setattr(cls, 'get_node', wrapped_node)
|
||||
setattr(cls, 'get_node', wrapped_node)
|
||||
|
||||
def construct(cls, *args, **kwargs):
|
||||
cls = super(NodeMeta, cls).construct(*args, **kwargs)
|
||||
if not cls._meta.abstract:
|
||||
cls.construct_get_node()
|
||||
return cls
|
||||
|
||||
|
||||
class Node(six.with_metaclass(NodeMeta, Interface)):
|
||||
'''An object with an ID'''
|
||||
id = GlobalIDField()
|
||||
|
||||
class Meta:
|
||||
abstract = True
|
||||
|
||||
def to_global_id(self):
|
||||
type_name = self._meta.type_name
|
||||
|
@ -127,27 +141,32 @@ class BaseNode(object):
|
|||
return cls.edge_type
|
||||
|
||||
|
||||
class Node(BaseNode, Interface):
|
||||
'''An object with an ID'''
|
||||
id = GlobalIDField()
|
||||
|
||||
|
||||
class MutationInputType(InputObjectType):
|
||||
client_mutation_id = String(required=True)
|
||||
|
||||
|
||||
class ClientIDMutation(Mutation):
|
||||
client_mutation_id = String(required=True)
|
||||
class RelayMutationMeta(MutationMeta):
|
||||
|
||||
@classmethod
|
||||
def _construct_arguments(cls, items):
|
||||
assert hasattr(
|
||||
cls, 'mutate_and_get_payload'), 'You have to implement mutate_and_get_payload'
|
||||
def construct(cls, *args, **kwargs):
|
||||
cls = super(RelayMutationMeta, cls).construct(*args, **kwargs)
|
||||
if not cls._meta.abstract:
|
||||
assert hasattr(
|
||||
cls, 'mutate_and_get_payload'), 'You have to implement mutate_and_get_payload'
|
||||
return cls
|
||||
|
||||
def construct_arguments(cls, items):
|
||||
new_input_type = type('{}Input'.format(
|
||||
cls._meta.type_name), (MutationInputType, ), items)
|
||||
cls.add_to_class('input_type', new_input_type)
|
||||
return ArgumentsGroup(input=NonNull(new_input_type))
|
||||
|
||||
|
||||
class ClientIDMutation(six.with_metaclass(RelayMutationMeta, Mutation)):
|
||||
client_mutation_id = String(required=True)
|
||||
|
||||
class Meta:
|
||||
abstract = True
|
||||
|
||||
@classmethod
|
||||
def mutate(cls, instance, args, info):
|
||||
input = args.get('input')
|
||||
|
|
|
@ -1,10 +1,11 @@
|
|||
from .types import BaseNode
|
||||
from .types import Node
|
||||
|
||||
|
||||
def is_node(object_type):
|
||||
return object_type and issubclass(
|
||||
object_type, BaseNode) and not is_node_type(object_type)
|
||||
object_type, Node) and not object_type._meta.abstract
|
||||
|
||||
|
||||
def is_node_type(object_type):
|
||||
return BaseNode in object_type.__bases__
|
||||
return object_type and issubclass(
|
||||
object_type, Node) and object_type._meta.abstract
|
||||
|
|
|
@ -2,6 +2,7 @@ try:
|
|||
from blinker import Signal
|
||||
except ImportError:
|
||||
class Signal(object):
|
||||
|
||||
def send(self, *args, **kwargs):
|
||||
pass
|
||||
|
||||
|
|
Loading…
Reference in New Issue
Block a user