mirror of
https://github.com/graphql-python/graphene.git
synced 2025-02-02 12:44:15 +03:00
First working version class types
This commit is contained in:
parent
e78936c424
commit
3363f588fe
0
graphene/core/classtypes/__init__.py
Normal file
0
graphene/core/classtypes/__init__.py
Normal file
109
graphene/core/classtypes/base.py
Normal file
109
graphene/core/classtypes/base.py
Normal file
|
@ -0,0 +1,109 @@
|
||||||
|
from collections import OrderedDict
|
||||||
|
import inspect
|
||||||
|
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)
|
||||||
|
return cls
|
||||||
|
|
||||||
|
|
||||||
|
class ClassType(six.with_metaclass(ClassTypeMeta)):
|
||||||
|
@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
|
||||||
|
|
||||||
|
|
||||||
|
class FieldsClassType(six.with_metaclass(FieldsClassTypeMeta, ClassType)):
|
||||||
|
@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)
|
||||||
|
|
||||||
|
# class NamedClassType(ClassType):
|
||||||
|
# pass
|
||||||
|
|
||||||
|
|
||||||
|
# class UnionType(NamedClassType):
|
||||||
|
# class Meta:
|
||||||
|
# abstract = True
|
||||||
|
|
||||||
|
|
||||||
|
# class ObjectType(NamedClassType):
|
||||||
|
# class Meta:
|
||||||
|
# abstract = True
|
||||||
|
|
||||||
|
|
||||||
|
# class InputObjectType(NamedClassType):
|
||||||
|
# class Meta:
|
||||||
|
# abstract = True
|
||||||
|
|
||||||
|
|
||||||
|
# class Mutation(ObjectType):
|
||||||
|
# class Meta:
|
||||||
|
# abstract = True
|
24
graphene/core/classtypes/inputobjecttype.py
Normal file
24
graphene/core/classtypes/inputobjecttype.py
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
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),
|
||||||
|
)
|
54
graphene/core/classtypes/interface.py
Normal file
54
graphene/core/classtypes/interface.py
Normal file
|
@ -0,0 +1,54 @@
|
||||||
|
import six
|
||||||
|
from functools import partial
|
||||||
|
|
||||||
|
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 cls._meta.abstract:
|
||||||
|
raise Exception("Abstract Interfaces don't have a specific type.")
|
||||||
|
|
||||||
|
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)
|
||||||
|
)
|
30
graphene/core/classtypes/mutation.py
Normal file
30
graphene/core/classtypes/mutation.py
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
import six
|
||||||
|
|
||||||
|
from ..types.argument import ArgumentsGroup
|
||||||
|
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):
|
||||||
|
return ArgumentsGroup(**items)
|
||||||
|
|
||||||
|
|
||||||
|
class Mutation(six.with_metaclass(MutationMeta, ObjectType)):
|
||||||
|
class Meta:
|
||||||
|
abstract = True
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_arguments(cls):
|
||||||
|
return cls.arguments
|
99
graphene/core/classtypes/objecttype.py
Normal file
99
graphene/core/classtypes/objecttype.py
Normal file
|
@ -0,0 +1,99 @@
|
||||||
|
import six
|
||||||
|
from functools import partial
|
||||||
|
|
||||||
|
from graphql.core.type import GraphQLObjectType
|
||||||
|
|
||||||
|
from graphene import signals
|
||||||
|
from .base import FieldsOptions, FieldsClassType, FieldsClassTypeMeta
|
||||||
|
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=[schema.T(i) for i in 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)
|
47
graphene/core/classtypes/options.py
Normal file
47
graphene/core/classtypes/options.py
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
class Options(object):
|
||||||
|
def __init__(self, meta=None, **defaults):
|
||||||
|
self.meta = meta
|
||||||
|
self.abstract = False
|
||||||
|
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
|
||||||
|
self.parent = cls
|
||||||
|
# First, construct the default values for these options.
|
||||||
|
self.object_name = cls.__name__
|
||||||
|
self.type_name = self.object_name
|
||||||
|
|
||||||
|
self.description = cls.__doc__
|
||||||
|
# Store the original user-defined values for each option,
|
||||||
|
# for use when serializing the model definition
|
||||||
|
self.original_attrs = {}
|
||||||
|
|
||||||
|
# Next, apply any overridden values from 'class Meta'.
|
||||||
|
if self.meta:
|
||||||
|
meta_attrs = self.meta.__dict__.copy()
|
||||||
|
for name in self.meta.__dict__:
|
||||||
|
# Ignore any private attributes that Django doesn't care about.
|
||||||
|
# NOTE: We can't modify a dictionary's contents while looping
|
||||||
|
# over it, so we loop over the *original* dictionary instead.
|
||||||
|
if name.startswith('_'):
|
||||||
|
del meta_attrs[name]
|
||||||
|
for attr_name in self.valid_attrs:
|
||||||
|
if attr_name in meta_attrs:
|
||||||
|
setattr(self, attr_name, meta_attrs.pop(attr_name))
|
||||||
|
self.original_attrs[attr_name] = getattr(self, attr_name)
|
||||||
|
elif hasattr(self.meta, attr_name):
|
||||||
|
setattr(self, attr_name, getattr(self.meta, attr_name))
|
||||||
|
self.original_attrs[attr_name] = getattr(self, attr_name)
|
||||||
|
|
||||||
|
del self.valid_attrs
|
||||||
|
|
||||||
|
# Any leftover attributes must be invalid.
|
||||||
|
if meta_attrs != {}:
|
||||||
|
raise TypeError(
|
||||||
|
"'class Meta' got invalid attribute(s): %s" %
|
||||||
|
','.join(
|
||||||
|
meta_attrs.keys()))
|
||||||
|
|
||||||
|
del self.meta
|
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 .base import ClassType
|
||||||
|
from ..types.base import MountedType
|
||||||
|
|
||||||
|
|
||||||
|
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
|
||||||
|
)
|
0
graphene/core/classtypes/tests/__init__.py
Normal file
0
graphene/core/classtypes/tests/__init__.py
Normal file
40
graphene/core/classtypes/tests/test_base.py
Normal file
40
graphene/core/classtypes/tests/test_base.py
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
from ..base import ClassType, FieldsClassType
|
||||||
|
from ...types import Field, String
|
||||||
|
from ...schema import Schema
|
||||||
|
|
||||||
|
|
||||||
|
def test_classtype_basic():
|
||||||
|
class Character(ClassType):
|
||||||
|
'''Character description'''
|
||||||
|
pass
|
||||||
|
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_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
|
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 py.test import raises
|
||||||
|
|
||||||
|
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']
|
85
graphene/core/classtypes/tests/test_interface.py
Normal file
85
graphene/core/classtypes/tests/test_interface.py
Normal file
|
@ -0,0 +1,85 @@
|
||||||
|
from py.test import raises
|
||||||
|
|
||||||
|
from graphql.core.type import GraphQLInterfaceType, GraphQLObjectType
|
||||||
|
|
||||||
|
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 ComonClass(object):
|
||||||
|
common_attr = True
|
||||||
|
|
||||||
|
class Character(ComonClass, 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 py.test import raises
|
||||||
|
|
||||||
|
from graphql.core.type import GraphQLObjectType
|
||||||
|
|
||||||
|
from graphene.core.schema import Schema
|
||||||
|
from graphene.core.types import String
|
||||||
|
from ..mutation import Mutation
|
||||||
|
from ...types.argument import ArgumentsGroup
|
||||||
|
|
||||||
|
|
||||||
|
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 py.test import raises
|
||||||
|
|
||||||
|
from graphql.core.type import GraphQLObjectType
|
||||||
|
|
||||||
|
from graphene.core.schema import Schema
|
||||||
|
from graphene.core.types import Int, 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'
|
27
graphene/core/classtypes/tests/test_uniontype.py
Normal file
27
graphene/core/classtypes/tests/test_uniontype.py
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
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)]
|
39
graphene/core/classtypes/uniontype.py
Normal file
39
graphene/core/classtypes/uniontype.py
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
import six
|
||||||
|
|
||||||
|
from graphql.core.type import GraphQLUnionType
|
||||||
|
|
||||||
|
from .base import FieldsOptions, FieldsClassType, FieldsClassTypeMeta
|
||||||
|
|
||||||
|
|
||||||
|
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=map(schema.T, cls._meta.types),
|
||||||
|
resolve_type=cls._resolve_type,
|
||||||
|
description=cls._meta.description,
|
||||||
|
)
|
|
@ -12,6 +12,7 @@ from graphene import signals
|
||||||
|
|
||||||
from .types.base import BaseType
|
from .types.base import BaseType
|
||||||
from .types.objecttype import BaseObjectType
|
from .types.objecttype import BaseObjectType
|
||||||
|
from .classtypes.base import ClassType
|
||||||
|
|
||||||
|
|
||||||
class GraphQLSchema(_GraphQLSchema):
|
class GraphQLSchema(_GraphQLSchema):
|
||||||
|
@ -42,7 +43,7 @@ class Schema(object):
|
||||||
if not object_type:
|
if not object_type:
|
||||||
return
|
return
|
||||||
if inspect.isclass(object_type) and issubclass(
|
if inspect.isclass(object_type) and issubclass(
|
||||||
object_type, BaseType) or isinstance(
|
object_type, (BaseType, ClassType)) or isinstance(
|
||||||
object_type, BaseType):
|
object_type, BaseType):
|
||||||
if object_type not in self._types:
|
if object_type not in self._types:
|
||||||
internal_type = object_type.internal_type(self)
|
internal_type = object_type.internal_type(self)
|
||||||
|
|
|
@ -2,6 +2,9 @@ from functools import total_ordering
|
||||||
|
|
||||||
import six
|
import six
|
||||||
|
|
||||||
|
from ..classtypes.base import FieldsClassType
|
||||||
|
from ..classtypes.inputobjecttype import InputObjectType as NewInputObjectType
|
||||||
|
|
||||||
|
|
||||||
class BaseType(object):
|
class BaseType(object):
|
||||||
|
|
||||||
|
@ -105,10 +108,10 @@ class FieldType(MirroredType):
|
||||||
|
|
||||||
def contribute_to_class(self, cls, name):
|
def contribute_to_class(self, cls, name):
|
||||||
from ..types import BaseObjectType, InputObjectType
|
from ..types import BaseObjectType, InputObjectType
|
||||||
if issubclass(cls, InputObjectType):
|
if issubclass(cls, (InputObjectType, NewInputObjectType)):
|
||||||
inputfield = self.as_inputfield()
|
inputfield = self.as_inputfield()
|
||||||
return inputfield.contribute_to_class(cls, name)
|
return inputfield.contribute_to_class(cls, name)
|
||||||
elif issubclass(cls, BaseObjectType):
|
elif issubclass(cls, (BaseObjectType, FieldsClassType)):
|
||||||
field = self.as_field()
|
field = self.as_field()
|
||||||
return field.contribute_to_class(cls, name)
|
return field.contribute_to_class(cls, name)
|
||||||
|
|
||||||
|
|
|
@ -5,6 +5,8 @@ import six
|
||||||
from graphql.core.type import GraphQLField, GraphQLInputObjectField
|
from graphql.core.type import GraphQLField, GraphQLInputObjectField
|
||||||
|
|
||||||
from ...utils import to_camel_case
|
from ...utils import to_camel_case
|
||||||
|
from ..classtypes.base import FieldsClassType
|
||||||
|
from ..classtypes.inputobjecttype import InputObjectType as NewInputObjectType
|
||||||
from ..types import BaseObjectType, InputObjectType
|
from ..types import BaseObjectType, InputObjectType
|
||||||
from .argument import ArgumentsGroup, snake_case_args
|
from .argument import ArgumentsGroup, snake_case_args
|
||||||
from .base import LazyType, MountType, OrderedType
|
from .base import LazyType, MountType, OrderedType
|
||||||
|
@ -32,7 +34,7 @@ class Field(OrderedType):
|
||||||
|
|
||||||
def contribute_to_class(self, cls, attname):
|
def contribute_to_class(self, cls, attname):
|
||||||
assert issubclass(
|
assert issubclass(
|
||||||
cls, BaseObjectType), 'Field {} cannot be mounted in {}'.format(
|
cls, (BaseObjectType, FieldsClassType)), 'Field {} cannot be mounted in {}'.format(
|
||||||
self, cls)
|
self, cls)
|
||||||
if not self.name:
|
if not self.name:
|
||||||
self.name = to_camel_case(attname)
|
self.name = to_camel_case(attname)
|
||||||
|
@ -126,7 +128,7 @@ class InputField(OrderedType):
|
||||||
|
|
||||||
def contribute_to_class(self, cls, attname):
|
def contribute_to_class(self, cls, attname):
|
||||||
assert issubclass(
|
assert issubclass(
|
||||||
cls, InputObjectType), 'InputField {} cannot be mounted in {}'.format(
|
cls, (InputObjectType, NewInputObjectType)), 'InputField {} cannot be mounted in {}'.format(
|
||||||
self, cls)
|
self, cls)
|
||||||
if not self.name:
|
if not self.name:
|
||||||
self.name = to_camel_case(attname)
|
self.name = to_camel_case(attname)
|
||||||
|
|
Loading…
Reference in New Issue
Block a user