Moved Interface to ObjectType. Improved integration with latest GraphQL-core

This commit is contained in:
Syrus Akbary 2016-06-20 17:47:26 -07:00
parent 7a29502790
commit 9a9d7f8873
10 changed files with 97 additions and 114 deletions

View File

@ -2,7 +2,7 @@ import inspect
from collections import OrderedDict from collections import OrderedDict
from itertools import chain from itertools import chain
from graphql import GraphQLArgument from graphql.type.definition import GraphQLArgument, GraphQLArgumentDefinition
from graphql.utils.assert_valid_name import assert_valid_name from graphql.utils.assert_valid_name import assert_valid_name
from ..utils.orderedtype import OrderedType from ..utils.orderedtype import OrderedType
@ -40,11 +40,15 @@ class Argument(GraphQLArgument, OrderedType):
@classmethod @classmethod
def copy_from(cls, argument): def copy_from(cls, argument):
if isinstance (argument, (GraphQLArgumentDefinition, Argument)):
name = argument.name
else:
name = None
return cls( return cls(
type=argument.type, type=argument.type,
default_value=argument.default_value, default_value=argument.default_value,
description=argument.description, description=argument.description,
name=argument.name, name=name,
_creation_counter=argument.creation_counter if isinstance(argument, Argument) else None, _creation_counter=argument.creation_counter if isinstance(argument, Argument) else None,
) )

View File

@ -1,6 +1,7 @@
from collections import OrderedDict
import inspect import inspect
from graphql.type import GraphQLField, GraphQLInputObjectField from graphql.type import GraphQLField, GraphQLInputObjectField, GraphQLFieldDefinition
from graphql.utils.assert_valid_name import assert_valid_name from graphql.utils.assert_valid_name import assert_valid_name
from ..utils.orderedtype import OrderedType from ..utils.orderedtype import OrderedType
@ -126,18 +127,23 @@ class Field(AbstractField, GraphQLField, OrderedType):
_creation_counter = field.creation_counter if _creation_counter is False else None _creation_counter = field.creation_counter if _creation_counter is False else None
attname = attname or field.attname attname = attname or field.attname
parent = parent or field.parent parent = parent or field.parent
args = to_arguments(args, field.args)
else: else:
# If is a GraphQLField # If is a GraphQLField
type = type or field.type type = type or field.type
resolver = resolver or field.resolver resolver = resolver or field.resolver
name = field.name field_args = field.args
if isinstance(field, GraphQLFieldDefinition):
name = name or field.name
field_args = OrderedDict((a.name, a) for a in field_args)
args = to_arguments(args, field_args)
_creation_counter = None _creation_counter = None
attname = attname or name attname = attname or name
parent = parent parent = parent
new_field = cls( new_field = cls(
type=type, type=type,
args=to_arguments(args, field.args), args=args,
resolver=resolver, resolver=resolver,
source=source, source=source,
deprecation_reason=field.deprecation_reason, deprecation_reason=field.deprecation_reason,

View File

@ -7,7 +7,7 @@ from ..utils.get_fields import get_fields
from ..utils.is_base_type import is_base_type from ..utils.is_base_type import is_base_type
from .definitions import GrapheneGraphQLType from .definitions import GrapheneGraphQLType
from .field import InputField from .field import InputField
from .interface import attrs_without_fields from .objecttype import attrs_without_fields
from .options import Options from .options import Options
from .unmountedtype import UnmountedType from .unmountedtype import UnmountedType

View File

@ -1,81 +1 @@
import six from .objecttype import Interface, GrapheneInterfaceType
from graphql import GraphQLInterfaceType
from ..utils.copy_fields import copy_fields
from ..utils.get_fields import get_fields
from ..utils.is_base_type import is_base_type
from .definitions import GrapheneGraphQLType
from .field import Field
from .options import Options
class GrapheneInterfaceType(GrapheneGraphQLType, GraphQLInterfaceType):
pass
class InterfaceTypeMeta(type):
@staticmethod
def _get_interface_options(meta):
return Options(
meta,
name=None,
description=None,
graphql_type=None,
abstract=False
)
def __new__(cls, name, bases, attrs):
super_new = super(InterfaceTypeMeta, cls).__new__
# Also ensure initialization is only performed for subclasses of Model
# (excluding Model class itself).
if not is_base_type(bases, InterfaceTypeMeta):
return super_new(cls, name, bases, attrs)
options = cls._get_interface_options(attrs.pop('Meta', None))
fields = get_fields(Interface, attrs, bases)
attrs = attrs_without_fields(attrs, fields)
cls = super_new(cls, name, bases, dict(attrs, _meta=options))
if not options.graphql_type:
fields = copy_fields(Field, fields, parent=cls)
options.graphql_type = GrapheneInterfaceType(
graphene_type=cls,
name=options.name or cls.__name__,
resolve_type=cls.resolve_type,
description=options.description or cls.__doc__,
fields=fields,
)
else:
assert not fields, "Can't mount Fields in an Interface with a defined graphql_type"
fields = copy_fields(options.graphql_type.get_fields(), parent=cls)
for name, field in fields.items():
setattr(cls, field.attname or name, field)
return cls
def attrs_without_fields(attrs, fields):
return {k: v for k, v in attrs.items() if k not in fields}
class Interface(six.with_metaclass(InterfaceTypeMeta)):
resolve_type = None
def __init__(self, *args, **kwargs):
from .objecttype import ObjectType
if not isinstance(self, ObjectType):
raise Exception("An interface cannot be intitialized")
super(Interface, self).__init__(*args, **kwargs)
@classmethod
def implements(cls, object_type):
'''
We use this function for customizing when a ObjectType have this class as Interface
For example, if we want to check that the ObjectType have some required things
in it like Node.get_node
'''

View File

@ -1,18 +1,20 @@
import six import six
from graphql import GraphQLObjectType from graphql import GraphQLObjectType, GraphQLInterfaceType
from ..utils.copy_fields import copy_fields from ..utils.copy_fields import copy_fields
from ..utils.get_fields import get_fields from ..utils.get_fields import get_fields
from ..utils.is_base_type import is_base_type from ..utils.is_base_type import is_base_type
from .definitions import GrapheneGraphQLType from .definitions import GrapheneGraphQLType
from .field import Field from .field import Field
from .interface import (GrapheneInterfaceType, Interface, InterfaceTypeMeta,
attrs_without_fields)
from .options import Options from .options import Options
class GrapheneInterfaceType(GrapheneGraphQLType, GraphQLInterfaceType):
pass
class GrapheneObjectType(GrapheneGraphQLType, GraphQLObjectType): class GrapheneObjectType(GrapheneGraphQLType, GraphQLObjectType):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
@ -42,10 +44,13 @@ def is_objecttype(bases):
return False return False
def attrs_without_fields(attrs, fields):
return {k: v for k, v in attrs.items() if k not in fields}
# We inherit from InterfaceTypeMeta instead of type for being able # We inherit from InterfaceTypeMeta instead of type for being able
# to have ObjectTypes extending Interfaces using Python syntax, like: # to have ObjectTypes extending Interfaces using Python syntax, like:
# class MyObjectType(ObjectType, MyInterface) # class MyObjectType(ObjectType, MyInterface)
class ObjectTypeMeta(InterfaceTypeMeta): class ObjectTypeMeta(type):
def __new__(cls, name, bases, attrs): def __new__(cls, name, bases, attrs):
super_new = type.__new__ super_new = type.__new__
@ -57,7 +62,7 @@ class ObjectTypeMeta(InterfaceTypeMeta):
return super_new(cls, name, bases, attrs) return super_new(cls, name, bases, attrs)
if not is_objecttype(bases): if not is_objecttype(bases):
return super(ObjectTypeMeta, cls).__new__(cls, name, bases, attrs) return cls._create_interface(cls, name, bases, attrs)
options = Options( options = Options(
attrs.pop('Meta', None), attrs.pop('Meta', None),
@ -99,6 +104,42 @@ class ObjectTypeMeta(InterfaceTypeMeta):
def is_object_type(cls): def is_object_type(cls):
return issubclass(cls, ObjectType) return issubclass(cls, ObjectType)
@staticmethod
def _get_interface_options(meta):
return Options(
meta,
name=None,
description=None,
graphql_type=None,
abstract=False
)
@staticmethod
def _create_interface(cls, name, bases, attrs):
options = cls._get_interface_options(attrs.pop('Meta', None))
fields = get_fields(Interface, attrs, bases)
attrs = attrs_without_fields(attrs, fields)
cls = type.__new__(cls, name, bases, dict(attrs, _meta=options))
if not options.graphql_type:
fields = copy_fields(Field, fields, parent=cls)
options.graphql_type = GrapheneInterfaceType(
graphene_type=cls,
name=options.name or cls.__name__,
resolve_type=cls.resolve_type,
description=options.description or cls.__doc__,
fields=fields,
)
else:
assert not fields, "Can't mount Fields in an Interface with a defined graphql_type"
fields = copy_fields(options.graphql_type.get_fields(), parent=cls)
for name, field in fields.items():
setattr(cls, field.attname or name, field)
return cls
class ObjectType(six.with_metaclass(ObjectTypeMeta)): class ObjectType(six.with_metaclass(ObjectTypeMeta)):
@ -152,3 +193,21 @@ class ObjectType(six.with_metaclass(ObjectTypeMeta)):
return graphql_type.name == cls._meta.graphql_type.name return graphql_type.name == cls._meta.graphql_type.name
except: except:
return False return False
class Interface(six.with_metaclass(ObjectTypeMeta)):
resolve_type = None
def __init__(self, *args, **kwargs):
from .objecttype import ObjectType
if not isinstance(self, ObjectType):
raise Exception("An interface cannot be intitialized")
super(Interface, self).__init__(*args, **kwargs)
@classmethod
def implements(cls, object_type):
'''
We use this function for customizing when a ObjectType have this class as Interface
For example, if we want to check that the ObjectType have some required things
in it like Node.get_node
'''

View File

@ -1,5 +1,6 @@
from graphql import GraphQLInputObjectType, GraphQLString from graphql import GraphQLInputObjectType, GraphQLString
from graphql.type.definition import GraphQLInputFieldDefinition
from ..field import InputField from ..field import InputField
from ..inputobjecttype import InputObjectType from ..inputobjecttype import InputObjectType
@ -43,7 +44,7 @@ def test_generate_objecttype_with_fields():
graphql_type = MyObjectType._meta.graphql_type graphql_type = MyObjectType._meta.graphql_type
fields = graphql_type.get_fields() fields = graphql_type.get_fields()
assert 'field' in fields assert 'field' in fields
assert isinstance(fields['field'], InputField) assert isinstance(fields['field'], GraphQLInputFieldDefinition)
def test_generate_objecttype_with_graphene_fields(): def test_generate_objecttype_with_graphene_fields():
@ -53,4 +54,4 @@ def test_generate_objecttype_with_graphene_fields():
graphql_type = MyObjectType._meta.graphql_type graphql_type = MyObjectType._meta.graphql_type
fields = graphql_type.get_fields() fields = graphql_type.get_fields()
assert 'field' in fields assert 'field' in fields
assert isinstance(fields['field'], InputField) assert isinstance(fields['field'], GraphQLInputFieldDefinition)

View File

@ -56,7 +56,7 @@ def test_interface_inheritance():
fields = graphql_type.get_fields() fields = graphql_type.get_fields()
assert 'field' in fields assert 'field' in fields
assert 'inherited' in fields assert 'inherited' in fields
assert fields['field'] > fields['inherited'] assert MyInterface.field > MyInheritedInterface.inherited
def test_interface_instance(): def test_interface_instance():

View File

@ -74,11 +74,7 @@ def test_objecttype_inheritance():
graphql_type = MyObjectType._meta.graphql_type graphql_type = MyObjectType._meta.graphql_type
fields = graphql_type.get_fields() fields = graphql_type.get_fields()
assert 'field1' in fields assert fields.keys() == ['inherited', 'field1', 'field2']
assert 'field2' in fields
assert 'inherited' in fields
assert fields['field1'] > fields['inherited']
assert fields['field2'] > fields['field1']
def test_objecttype_as_container_get_fields(): def test_objecttype_as_container_get_fields():
@ -195,11 +191,7 @@ def test_objecttype_graphene_interface():
graphql_type = GrapheneObjectType._meta.graphql_type graphql_type = GrapheneObjectType._meta.graphql_type
assert graphql_type.get_interfaces() == (GrapheneInterface._meta.graphql_type, ) assert graphql_type.get_interfaces() == (GrapheneInterface._meta.graphql_type, )
assert graphql_type.is_type_of(GrapheneObjectType(), None, None) assert graphql_type.is_type_of(GrapheneObjectType(), None, None)
fields = graphql_type.get_fields() fields = graphql_type.get_fields().keys() == ['name', 'extended', 'field']
assert 'field' in fields
assert 'extended' in fields
assert 'name' in fields
assert fields['field'] > fields['extended'] > fields['name']
def test_objecttype_graphene_inherit_interface(): def test_objecttype_graphene_inherit_interface():

View File

@ -3,8 +3,9 @@ import datetime
import pytest import pytest
from graphene.utils.get_graphql_type import get_graphql_type from graphene.utils.get_graphql_type import get_graphql_type
from graphql import (GraphQLBoolean, GraphQLFloat, GraphQLInt, from graphql import graphql
GraphQLScalarType, GraphQLString, graphql) from graphql.type import (GraphQLBoolean, GraphQLFloat, GraphQLInt,
GraphQLScalarType, GraphQLString, GraphQLFieldDefinition)
from graphql.language import ast from graphql.language import ast
from ..field import Field from ..field import Field
@ -94,7 +95,7 @@ def test_scalar_in_objecttype(scalar_class, graphql_type):
graphql_type = get_graphql_type(MyObjectType) graphql_type = get_graphql_type(MyObjectType)
fields = graphql_type.get_fields() fields = graphql_type.get_fields()
assert list(fields.keys()) == ['before', 'field', 'after'] assert list(fields.keys()) == ['before', 'field', 'after']
assert isinstance(fields['field'], Field) assert isinstance(fields['field'], GraphQLFieldDefinition)
def test_custom_scalar_empty(): def test_custom_scalar_empty():

View File

@ -11,6 +11,12 @@ class Character(Interface):
friends = List(lambda: Character) friends = List(lambda: Character)
best_friend = Field(lambda: Character) best_friend = Field(lambda: Character)
def resolve_friends(self, *args):
return [Human(name='Peter')]
def resolve_best_friend(self, *args):
return Human(name='Best')
class Pet(ObjectType): class Pet(ObjectType):
type = String() type = String()
@ -26,12 +32,6 @@ class Human(ObjectType):
def resolve_pet(self, *args): def resolve_pet(self, *args):
return Pet(type='Dog') return Pet(type='Dog')
def resolve_friends(self, *args):
return [Human(name='Peter')]
def resolve_best_friend(self, *args):
return Human(name='Best')
class RootQuery(ObjectType): class RootQuery(ObjectType):
character = Field(Character) character = Field(Character)