Added InputField, InputObjectType. Improved Field implementation

This commit is contained in:
Syrus Akbary 2016-06-04 15:22:10 -07:00
parent 9cf4da5fcb
commit ab72393e66
12 changed files with 238 additions and 45 deletions

View File

@ -13,7 +13,7 @@ class Address(graphene.ObjectType):
class Query(graphene.ObjectType): class Query(graphene.ObjectType):
address = graphene.Field(Address, geo=graphene.Argument(GeoInput)) address = graphene.Field(Address, geo=graphene.Argument(GeoInput))
def resolve_address(self, args, info): def resolve_address(self, args, context, info):
geo = args.get('geo') geo = args.get('geo')
return Address(latlng="({},{})".format(geo.get('lat'), geo.get('lng'))) return Address(latlng="({},{})".format(geo.get('lat'), geo.get('lng')))
@ -27,5 +27,17 @@ query = '''
} }
''' '''
result = schema.execute(query)
print(result.data['address']['latlng']) def test_query():
result = schema.execute(query)
assert not result.errors
assert result.data == {
'address': {
'latlng': "(32.2,12.0)",
}
}
if __name__ == '__main__':
result = schema.execute(query)
print(result.data['address']['latlng'])

View File

@ -1,8 +1,10 @@
from .types import ( from .types import (
ObjectType, ObjectType,
InputObjectType,
Interface, Interface,
implements, implements,
Field, Field,
InputField,
Schema, Schema,
Scalar, Scalar,
String, ID, Int, Float, Boolean, String, ID, Int, Float, Boolean,
@ -12,4 +14,4 @@ from .types import (
) )
from .utils.resolve_only_args import resolve_only_args from .utils.resolve_only_args import resolve_only_args
__all__ = ['ObjectType', 'Interface', 'implements', 'Field', 'Schema', 'Scalar', 'String', 'ID', 'Int', 'Float', 'Enum', 'Boolean', 'List','NonNull', 'Argument','resolve_only_args'] __all__ = ['ObjectType', 'InputObjectType', 'Interface', 'implements', 'Field', 'InputField', 'Schema', 'Scalar', 'String', 'ID', 'Int', 'Float', 'Enum', 'Boolean', 'List','NonNull', 'Argument','resolve_only_args']

View File

@ -1,10 +1,11 @@
from .objecttype import ObjectType, implements from .objecttype import ObjectType, implements
from .inputobjecttype import InputObjectType
from .interface import Interface from .interface import Interface
from .scalars import Scalar, String, ID, Int, Float, Boolean from .scalars import Scalar, String, ID, Int, Float, Boolean
from .schema import Schema from .schema import Schema
from .structures import List, NonNull from .structures import List, NonNull
from .enum import Enum from .enum import Enum
from .field import Field from .field import Field, InputField
from .argument import Argument from .argument import Argument
__all__ = ['ObjectType', 'Interface', 'implements', 'Enum', 'Field', 'Schema', 'Scalar', 'String', 'ID', 'Int', 'Float', 'Boolean', 'List', 'NonNull', 'Argument'] __all__ = ['ObjectType', 'InputObjectType', 'Interface', 'implements', 'Enum', 'Field', 'InputField', 'Schema', 'Scalar', 'String', 'ID', 'Int', 'Float', 'Boolean', 'List', 'NonNull', 'Argument']

View File

@ -1,44 +1,17 @@
import inspect import inspect
from graphql import GraphQLField from graphql.type import GraphQLField, GraphQLInputObjectField
from graphql.utils.assert_valid_name import assert_valid_name from graphql.utils.assert_valid_name import assert_valid_name
from .objecttype import ObjectType from .objecttype import ObjectType
from .inputobjecttype import InputObjectType
from .interface import Interface from .interface import Interface
from ..utils.orderedtype import OrderedType from ..utils.orderedtype import OrderedType
from ..utils.str_converters import to_camel_case from ..utils.str_converters import to_camel_case
from .argument import to_arguments from .argument import to_arguments
class Field(GraphQLField, OrderedType): class AbstractField(object):
__slots__ = ('_name', '_type', '_args', '_resolver', 'deprecation_reason', 'description', 'source', 'attname', 'parent', 'creation_counter')
def __init__(self, type, args=None, resolver=None, source=None, deprecation_reason=None, name=None, description=None, _creation_counter=None, **extra_args):
self.name = name
self.attname = None
self.parent = None
self.type = type
self.args = to_arguments(args, extra_args)
assert not (source and resolver), ('You cannot have a source '
'and a resolver at the same time')
self.resolver = resolver
self.source = source
self.deprecation_reason = deprecation_reason
self.description = description
OrderedType.__init__(self, _creation_counter=_creation_counter)
def contribute_to_class(self, cls, attname):
assert issubclass(cls, (ObjectType, Interface)), 'Field {} cannot be mounted in {}'.format(
self,
cls
)
self.attname = attname
self.parent = cls
add_field = getattr(cls._meta.graphql_type, "add_field", None)
assert add_field, "Field {} cannot be mounted in {}".format(self, cls)
add_field(self)
@property @property
def name(self): def name(self):
return self._name or to_camel_case(self.attname) return self._name or to_camel_case(self.attname)
@ -52,14 +25,54 @@ class Field(GraphQLField, OrderedType):
@property @property
def type(self): def type(self):
from ..utils.get_graphql_type import get_graphql_type from ..utils.get_graphql_type import get_graphql_type
from .structures import NonNull
if inspect.isfunction(self._type): if inspect.isfunction(self._type):
return get_graphql_type(self._type()) _type = self._type()
return get_graphql_type(self._type) else:
_type = self._type
if self.required:
return NonNull(_type)
return get_graphql_type(_type)
@type.setter @type.setter
def type(self, type): def type(self, type):
self._type = type self._type = type
class Field(AbstractField, GraphQLField, OrderedType):
__slots__ = ('_name', '_type', '_args', '_resolver', 'deprecation_reason', 'description', 'source', 'attname', 'parent', 'creation_counter', 'required')
def __init__(self, type, args=None, resolver=None, source=None, deprecation_reason=None, name=None, description=None, required=False, _creation_counter=None, **extra_args):
self.name = name
self.attname = None
self.parent = None
self.type = type
self.args = to_arguments(args, extra_args)
assert not (source and resolver), ('You cannot have a source '
'and a resolver at the same time')
self.resolver = resolver
self.source = source
self.required = required
self.deprecation_reason = deprecation_reason
self.description = description
OrderedType.__init__(self, _creation_counter=_creation_counter)
def mount_error_message(self, where):
return 'Field "{}" can only be mounted in ObjectType or Interface, received {}.'.format(
self,
where.__name__
)
def contribute_to_class(self, cls, attname):
assert issubclass(cls, (ObjectType, Interface)), self.mount_error_message(cls)
self.attname = attname
self.parent = cls
add_field = getattr(cls._meta.graphql_type, "add_field", None)
assert add_field, self.mount_error_message(cls)
add_field(self)
@property @property
def resolver(self): def resolver(self):
def default_resolver(root, args, context, info): def default_resolver(root, args, context, info):
@ -86,6 +99,7 @@ class Field(GraphQLField, OrderedType):
source=self.source, source=self.source,
deprecation_reason=self.deprecation_reason, deprecation_reason=self.deprecation_reason,
name=self._name, name=self._name,
required=self.required,
description=self.description, description=self.description,
_creation_counter=self.creation_counter, _creation_counter=self.creation_counter,
) )
@ -97,3 +111,38 @@ class Field(GraphQLField, OrderedType):
if not self.parent: if not self.parent:
return 'Not bounded field' return 'Not bounded field'
return "{}.{}".format(self.parent._meta.graphql_type, self.attname) return "{}.{}".format(self.parent._meta.graphql_type, self.attname)
class InputField(AbstractField, GraphQLInputObjectField, OrderedType):
__slots__ = ('_name', '_type', 'default_value', 'description', 'required')
def __init__(self, type, default_value=None, description=None, name=None, required=False, _creation_counter=None):
self.name = name
self.type = type
self.default_value = default_value
self.description = description
self.required = required
OrderedType.__init__(self, _creation_counter=_creation_counter)
def mount_error_message(self, where):
return 'InputField {} can only be mounted in InputObjectType classes, received {}.'.format(
self,
where.__name__
)
def contribute_to_class(self, cls, attname):
assert issubclass(cls, (InputObjectType)), self.mount_error_message(cls)
self.attname = attname
self.parent = cls
add_field = getattr(cls._meta.graphql_type, "add_field", None)
assert add_field, self.mount_error_message(cls)
add_field(self)
def __copy__(self):
return InputField(
type=self._type,
name=self._name,
required=self.required,
default_value=self.default_value,
description=self.description,
)

View File

@ -0,0 +1,43 @@
import six
from graphql import GraphQLInputObjectType
from .definitions import ClassTypeMeta, GrapheneFieldsType, FieldMap
class GrapheneInputObjectType(GrapheneFieldsType, GraphQLInputObjectType):
__slots__ = ('graphene_type', '_name', '_description', '_fields', '_field_map')
class InputObjectTypeMeta(ClassTypeMeta):
def get_options(cls, meta):
options = cls.options_class(
meta,
name=None,
description=None,
graphql_type=None,
)
options.valid_attrs = ['graphql_type', 'name', 'description', 'abstract']
return options
def construct_graphql_type(cls, bases):
if not cls._meta.graphql_type and not cls._meta.abstract:
from ..utils.get_graphql_type import get_graphql_type
from ..utils.is_graphene_type import is_graphene_type
inherited_types = [
base._meta.graphql_type for base in bases if is_graphene_type(base)
]
cls._meta.graphql_type = GrapheneInputObjectType(
graphene_type=cls,
name=cls._meta.name or cls.__name__,
description=cls._meta.description,
fields=FieldMap(cls, bases=filter(None, inherited_types)),
)
class InputObjectType(six.with_metaclass(InputObjectTypeMeta)):
class Meta:
abstract = True

View File

@ -39,7 +39,6 @@ class GrapheneObjectType(GrapheneFieldsType, GraphQLObjectType):
self._provided_interfaces.append(graphql_type) self._provided_interfaces.append(graphql_type)
class ObjectTypeMeta(ClassTypeMeta): class ObjectTypeMeta(ClassTypeMeta):
def get_options(cls, meta): def get_options(cls, meta):

View File

@ -1,6 +1,11 @@
from .field import Field from .field import Field, InputField
from .argument import Argument from .argument import Argument
from .objecttype import ObjectType
from .interface import Interface
from .inputobjecttype import InputObjectType
from ..utils.orderedtype import OrderedType from ..utils.orderedtype import OrderedType
@ -21,6 +26,14 @@ class TypeProxy(OrderedType):
**self.kwargs **self.kwargs
) )
def as_inputfield(self):
return InputField(
self.get_type(),
*self.args,
_creation_counter=self.creation_counter,
**self.kwargs
)
def as_argument(self): def as_argument(self):
return Argument( return Argument(
self.get_type(), self.get_type(),
@ -30,5 +43,11 @@ class TypeProxy(OrderedType):
) )
def contribute_to_class(self, cls, attname): def contribute_to_class(self, cls, attname):
field = self.as_field() if issubclass(cls, (ObjectType, Interface)):
return field.contribute_to_class(cls, attname) inner = self.as_field()
elif issubclass(cls, (InputObjectType)):
inner = self.as_inputfield()
else:
raise Exception('TypedProxy "{}" cannot be mounted in {}'.format(self.get_type(), cls))
return inner.contribute_to_class(cls, attname)

View File

@ -1,7 +1,7 @@
import pytest import pytest
import copy import copy
from graphql import GraphQLString, GraphQLField, GraphQLInt from graphql import GraphQLString, GraphQLField, GraphQLInt, GraphQLNonNull
from ..field import Field from ..field import Field
from ..argument import Argument from ..argument import Argument
@ -14,6 +14,14 @@ def test_field():
assert isinstance(field, GraphQLField) assert isinstance(field, GraphQLField)
assert field.name == "name" assert field.name == "name"
assert field.description == "description" assert field.description == "description"
assert field.type == GraphQLString
def test_field_required():
field = Field(GraphQLString, required=True)
assert isinstance(field, GraphQLField)
assert isinstance(field.type, GraphQLNonNull)
assert field.type.of_type == GraphQLString
def test_field_wrong_name(): def test_field_wrong_name():

View File

@ -0,0 +1,58 @@
import pytest
from graphql import GraphQLObjectType, GraphQLField, GraphQLString, GraphQLInputObjectType
from ..objecttype import ObjectType
from ..inputobjecttype import InputObjectType
from ..field import Field, InputField
from ..scalars import String
def test_generate_inputobjecttype():
class MyObjectType(InputObjectType):
'''Documentation'''
pass
graphql_type = MyObjectType._meta.graphql_type
assert isinstance(graphql_type, GraphQLInputObjectType)
assert graphql_type.name == "MyObjectType"
assert graphql_type.description == "Documentation"
def test_generate_inputobjecttype_with_meta():
class MyObjectType(InputObjectType):
class Meta:
name = 'MyOtherObjectType'
description = 'Documentation'
graphql_type = MyObjectType._meta.graphql_type
assert isinstance(graphql_type, GraphQLInputObjectType)
assert graphql_type.name == "MyOtherObjectType"
assert graphql_type.description == "Documentation"
def test_empty_inputobjecttype_has_meta():
class MyObjectType(InputObjectType):
pass
assert MyObjectType._meta
def test_generate_objecttype_with_fields():
class MyObjectType(InputObjectType):
field = InputField(GraphQLString)
graphql_type = MyObjectType._meta.graphql_type
fields = graphql_type.get_fields()
assert 'field' in fields
assert isinstance(fields['field'], InputField)
def test_generate_objecttype_with_graphene_fields():
class MyObjectType(InputObjectType):
field = String()
graphql_type = MyObjectType._meta.graphql_type
fields = graphql_type.get_fields()
assert 'field' in fields
assert isinstance(fields['field'], InputField)

View File

@ -81,4 +81,4 @@ def test_interface_add_fields_in_reused_graphql_type():
class Meta: class Meta:
graphql_type = MyGraphQLType graphql_type = MyGraphQLType
assert "cannot be mounted in" in str(excinfo.value) assert """Field "MyGraphQLType.field" can only be mounted in ObjectType or Interface, received GrapheneInterface.""" == str(excinfo.value)

View File

@ -129,7 +129,7 @@ def test_objecttype_add_fields_in_reused_graphql_type():
class Meta: class Meta:
graphql_type = MyGraphQLType graphql_type = MyGraphQLType
assert "cannot be mounted in" in str(excinfo.value) assert """Field "MyGraphQLType.field" can only be mounted in ObjectType or Interface, received GrapheneObjectType.""" == str(excinfo.value)
def test_objecttype_graphql_interface(): def test_objecttype_graphql_interface():

View File

@ -1,5 +1,6 @@
import inspect import inspect
from ..types.objecttype import ObjectType from ..types.objecttype import ObjectType
from ..types.inputobjecttype import InputObjectType
from ..types.interface import Interface from ..types.interface import Interface
from ..types.scalars import Scalar from ..types.scalars import Scalar
from ..types.enum import Enum from ..types.enum import Enum
@ -10,6 +11,7 @@ def is_graphene_type(_type):
return issubclass(_type, ( return issubclass(_type, (
Interface, Interface,
ObjectType, ObjectType,
InputObjectType,
Scalar, Scalar,
Enum Enum
)) ))