Improved mutations, tests and overall integration.

This commit is contained in:
Syrus Akbary 2015-11-10 21:29:11 -08:00
parent 6f87720fc1
commit 28d89a44f1
16 changed files with 82 additions and 410 deletions

View File

@ -9,18 +9,11 @@ from .types.definitions import List, NonNull
class DeprecatedField(FieldType): class DeprecatedField(FieldType):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
cls = self.__class__ cls = self.__class__
warnings.warn("Using {} is not longer supported".format(cls.__name__) warnings.warn("Using {} is not longer supported".format(cls.__name__), FutureWarning)
, FutureWarning) if 'resolve' in kwargs:
kwargs['resolver'] = kwargs.pop('resolve', None) kwargs['resolver'] = kwargs.pop('resolve')
self.required = kwargs.pop('required', False)
return super(DeprecatedField, self).__init__(*args, **kwargs) return super(DeprecatedField, self).__init__(*args, **kwargs)
def as_field(self):
t = self
if self.required:
t = NonNull(t)
return Field(t, _creation_counter=self.creation_counter, *self.args, **self.kwargs)
class StringField(DeprecatedField, String): class StringField(DeprecatedField, String):
pass pass

View File

@ -8,6 +8,7 @@ from graphql.core.execution.middlewares.sync import \
from graphql.core.type import GraphQLSchema as _GraphQLSchema from graphql.core.type import GraphQLSchema as _GraphQLSchema
from graphql.core.utils.introspection_query import introspection_query from graphql.core.utils.introspection_query import introspection_query
from graphene.core.types.base import BaseType from graphene.core.types.base import BaseType
from graphene.core.types.objecttype import BaseObjectType
class GraphQLSchema(_GraphQLSchema): class GraphQLSchema(_GraphQLSchema):
@ -18,7 +19,6 @@ class GraphQLSchema(_GraphQLSchema):
class Schema(object): class Schema(object):
_query = None
_executor = None _executor = None
def __init__(self, query=None, mutation=None, name='Schema', executor=None): def __init__(self, query=None, mutation=None, name='Schema', executor=None):
@ -47,26 +47,10 @@ class Schema(object):
else: else:
return object_type return object_type
@property
def query(self):
return self._query
@query.setter
def query(self, query):
self._query = query
@property
def mutation(self):
return self._mutation
@mutation.setter
def mutation(self, mutation):
self._mutation = mutation
@property @property
def executor(self): def executor(self):
if not self._executor: if not self._executor:
self.executor = Executor( self._executor = Executor(
[SynchronousExecutionMiddleware()], map_type=OrderedDict) [SynchronousExecutionMiddleware()], map_type=OrderedDict)
return self._executor return self._executor
@ -76,18 +60,29 @@ class Schema(object):
@property @property
def schema(self): def schema(self):
if not self._query: if not self.query:
raise Exception('You have to define a base query type') raise Exception('You have to define a base query type')
return GraphQLSchema(self, query=self.T(self._query), mutation=self.T(self._mutation)) return GraphQLSchema(self, query=self.T(self.query), mutation=self.T(self.mutation))
def register(self, object_type): def register(self, object_type):
self._types_names[object_type._meta.type_name] = object_type self._types_names[object_type._meta.type_name] = object_type
return object_type return object_type
def objecttype(self, type):
name = getattr(type, 'name', None)
if name:
objecttype = self._types_names.get(name, None)
if objecttype and inspect.isclass(objecttype) and issubclass(objecttype, BaseObjectType):
return objecttype
def setup(self):
assert self.query, 'The base query type is not set'
self.T(self.query)
def get_type(self, type_name): def get_type(self, type_name):
# self.schema._build_type_map() self.setup()
if type_name not in self._types_names: if type_name not in self._types_names:
raise Exception('Type %s not found in %r' % (type_name, self)) raise KeyError('Type %r not found in %r' % (type_name, self))
return self._types_names[type_name] return self._types_names[type_name]
@property @property

View File

@ -1,4 +1,3 @@
import graphene import graphene
from graphene.core.schema import Schema from graphene.core.schema import Schema
@ -31,9 +30,7 @@ schema = Schema(query=Query, mutation=MyResultMutation)
def test_mutation_input(): def test_mutation_input():
assert ChangeNumber.input_type assert schema.T(ChangeNumber.arguments).keys() == ['to']
assert ChangeNumber.input_type._meta.type_name == 'ChangeNumberInput'
assert list(ChangeNumber.input_type._meta.fields_map.keys()) == ['to']
def test_execute_mutations(): def test_execute_mutations():

View File

@ -120,7 +120,7 @@ def test_schema_register():
assert schema.get_type('MyType') == MyType assert schema.get_type('MyType') == MyType
def test_schema_register(): def test_schema_register_no_query_type():
schema = Schema(name='My own schema') schema = Schema(name='My own schema')
@schema.register @schema.register
@ -149,6 +149,7 @@ def test_lazytype():
t = LazyType('MyType') t = LazyType('MyType')
@schema.register
class MyType(ObjectType): class MyType(ObjectType):
type = StringField(resolve=lambda *_: 'Dog') type = StringField(resolve=lambda *_: 'Dog')

View File

@ -1,8 +1,9 @@
from collections import OrderedDict
from itertools import chain from itertools import chain
from graphql.core.type import GraphQLArgument from graphql.core.type import GraphQLArgument
from .base import OrderedType, ArgumentType from .base import BaseType, OrderedType, ArgumentType
from ...utils import to_camel_case from ...utils import to_camel_case
@ -21,6 +22,27 @@ class Argument(OrderedType):
return self.name return self.name
class ArgumentsGroup(BaseType):
def __init__(self, *args, **kwargs):
arguments = to_arguments(*args, **kwargs)
self.arguments = OrderedDict([(arg.name, arg) for arg in arguments])
def internal_type(self, schema):
return OrderedDict([(arg.name, schema.T(arg)) for arg in self.arguments.values()])
def __len__(self):
return len(self.arguments)
def __iter__(self):
return iter(self.arguments)
def __contains__(self, *args):
return self.arguments.__contains__(*args)
def __getitem__(self, *args):
return self.arguments.__getitem__(*args)
def to_arguments(*args, **kwargs): def to_arguments(*args, **kwargs):
arguments = {} arguments = {}
iter_arguments = chain(kwargs.items(), [(None, a) for a in args]) iter_arguments = chain(kwargs.items(), [(None, a) for a in args])

View File

@ -11,6 +11,9 @@ class LazyType(BaseType):
def __init__(self, type_str): def __init__(self, type_str):
self.type_str = type_str self.type_str = type_str
def is_self(self):
return self.type_str == 'self'
def internal_type(self, schema): def internal_type(self, schema):
type = schema.get_type(self.type_str) type = schema.get_type(self.type_str)
return schema.T(type) return schema.T(type)

View File

@ -1,11 +1,11 @@
import six import six
from collections import OrderedDict from collections import OrderedDict
from functools import wraps
from graphql.core.type import GraphQLField, GraphQLInputObjectField from graphql.core.type import GraphQLField, GraphQLInputObjectField
from .base import LazyType, OrderedType from .base import LazyType, OrderedType
from .argument import to_arguments from .argument import ArgumentsGroup
from .definitions import NonNull
from ...utils import to_camel_case from ...utils import to_camel_case
from ..types import BaseObjectType, InputObjectType from ..types import BaseObjectType, InputObjectType
@ -15,18 +15,21 @@ class Empty(object):
class Field(OrderedType): class Field(OrderedType):
def __init__(self, type, description=None, args=None, name=None, resolver=None, *args_list, **kwargs): def __init__(self, type, description=None, args=None, name=None, resolver=None, required=False, default=None, *args_list, **kwargs):
_creation_counter = kwargs.pop('_creation_counter', None) _creation_counter = kwargs.pop('_creation_counter', None)
super(Field, self).__init__(_creation_counter=_creation_counter) super(Field, self).__init__(_creation_counter=_creation_counter)
self.name = name self.name = name
if isinstance(type, six.string_types) and type != 'self': if isinstance(type, six.string_types):
type = LazyType(type) type = LazyType(type)
if required:
type = NonNull(type)
self.type = type self.type = type
self.description = description self.description = description
args = OrderedDict(args or {}, **kwargs) args = OrderedDict(args or {}, **kwargs)
self.arguments = to_arguments(*args_list, **args) self.arguments = ArgumentsGroup(*args_list, **args)
self.object_type = None self.object_type = None
self.resolver = resolver self.resolver = resolver
self.default = default
def contribute_to_class(self, cls, attname): def contribute_to_class(self, cls, attname):
assert issubclass(cls, BaseObjectType), 'Field {} cannot be mounted in {}'.format(self, cls) assert issubclass(cls, BaseObjectType), 'Field {} cannot be mounted in {}'.format(self, cls)
@ -34,7 +37,7 @@ class Field(OrderedType):
self.name = to_camel_case(attname) self.name = to_camel_case(attname)
self.attname = attname self.attname = attname
self.object_type = cls self.object_type = cls
if self.type == 'self': if isinstance(self.type, LazyType) and self.type.is_self():
self.type = cls self.type = cls
cls._meta.add_field(self) cls._meta.add_field(self)
@ -49,41 +52,29 @@ class Field(OrderedType):
def get_resolver_fn(self): def get_resolver_fn(self):
resolve_fn_name = 'resolve_%s' % self.attname resolve_fn_name = 'resolve_%s' % self.attname
if hasattr(self.object_type, resolve_fn_name): if hasattr(self.object_type, resolve_fn_name):
resolve_fn = getattr(self.object_type, resolve_fn_name) return getattr(self.object_type, resolve_fn_name)
@wraps(resolve_fn)
def custom_resolve_fn(instance, args, info):
return resolve_fn(instance, args, info)
return custom_resolve_fn
def default_getter(instance, args, info): def default_getter(instance, args, info):
return getattr(instance, self.attname, None) return getattr(instance, self.attname, self.default)
return default_getter return default_getter
def internal_type(self, schema): def internal_type(self, schema):
resolver = self.resolver resolver = self.resolver
description = self.description description = self.description
arguments = self.arguments
if not description and resolver: if not description and resolver:
description = resolver.__doc__ description = resolver.__doc__
type = schema.T(self.type) type = schema.T(self.type)
type_objecttype = schema.objecttype(type)
if type_objecttype and type_objecttype._meta.is_mutation:
assert len(arguments) == 0
arguments = type_objecttype.arguments
resolver = getattr(type_objecttype, 'mutate')
assert type, 'Internal type for field %s is None' % str(self) assert type, 'Internal type for field %s is None' % str(self)
return GraphQLField(type, args=self.get_arguments(schema), resolver=resolver, return GraphQLField(type, args=schema.T(arguments), resolver=resolver,
description=description,) description=description,)
def get_arguments(self, schema):
if not self.arguments:
return None
return OrderedDict([(arg.name, schema.T(arg)) for arg in self.arguments])
def __copy__(self):
obj = Empty()
obj.__class__ = self.__class__
obj.__dict__ = self.__dict__.copy()
obj.object_type = None
return obj
def __repr__(self): def __repr__(self):
""" """
Displays the module, class and name of the field. Displays the module, class and name of the field.
@ -103,9 +94,11 @@ class Field(OrderedType):
class InputField(OrderedType): class InputField(OrderedType):
def __init__(self, type, description=None, default=None, name=None, _creation_counter=None): def __init__(self, type, description=None, default=None, name=None, _creation_counter=None, required=False):
super(InputField, self).__init__(_creation_counter=_creation_counter) super(InputField, self).__init__(_creation_counter=_creation_counter)
self.name = name self.name = name
if required:
type = NonNull(type)
self.type = type self.type = type
self.description = description self.description = description
self.default = default self.default = default

View File

@ -8,6 +8,7 @@ import six
from graphene import signals from graphene import signals
from graphene.core.options import Options from graphene.core.options import Options
from graphene.core.types.base import BaseType from graphene.core.types.base import BaseType
from graphene.core.types.argument import ArgumentsGroup
from graphql.core.type import (GraphQLArgument, GraphQLInputObjectType, from graphql.core.type import (GraphQLArgument, GraphQLInputObjectType,
GraphQLInterfaceType, GraphQLObjectType) GraphQLInterfaceType, GraphQLObjectType)
@ -66,9 +67,10 @@ class ObjectTypeMeta(type):
if input_class: if input_class:
items = dict(input_class.__dict__) items = dict(input_class.__dict__)
items.pop('__dict__', None) items.pop('__dict__', None)
input_type = type('{}Input'.format( items.pop('__doc__', None)
new_class._meta.type_name), (ObjectType, ), items) items.pop('__module__', None)
new_class.add_to_class('input_type', input_type) arguments = ArgumentsGroup(**items)
new_class.add_to_class('arguments', arguments)
new_class.add_extra_fields() new_class.add_extra_fields()
@ -88,7 +90,7 @@ class ObjectTypeMeta(type):
# on the base classes (we cannot handle shadowed fields at the # on the base classes (we cannot handle shadowed fields at the
# moment). # moment).
for field in parent_fields: for field in parent_fields:
if field.name in field_names and field.__class__ != field_names[field.name].__class__: if field.name in field_names and field.type.__class__ != field_names[field.name].type.__class__:
raise Exception( raise Exception(
'Local field %r in class %r (%r) clashes ' 'Local field %r in class %r (%r) clashes '
'with field with similar name from ' 'with field with similar name from '
@ -213,14 +215,10 @@ class ObjectType(six.with_metaclass(ObjectTypeMeta, BaseObjectType)):
class Mutation(six.with_metaclass(ObjectTypeMeta, BaseObjectType)): class Mutation(six.with_metaclass(ObjectTypeMeta, BaseObjectType)):
pass
@classmethod
def get_input_type(cls):
return getattr(cls, 'input_type', None)
class InputObjectType(ObjectType): class InputObjectType(ObjectType):
@classmethod @classmethod
def internal_type(cls, schema): def internal_type(cls, schema):
fields = lambda: OrderedDict([(f.name, schema.T(f)) fields = lambda: OrderedDict([(f.name, schema.T(f))

View File

@ -19,7 +19,7 @@ def test_nonnull_scalar():
assert type.of_type == GraphQLString assert type.of_type == GraphQLString
def test_mixed_scalar(): def test_nested_scalars():
type = schema.T(NonNull(List(String()))) type = schema.T(NonNull(List(String())))
assert isinstance(type, GraphQLNonNull) assert isinstance(type, GraphQLNonNull)
assert isinstance(type.of_type, GraphQLList) assert isinstance(type.of_type, GraphQLList)

View File

@ -75,12 +75,7 @@ def test_field_string_reference():
def test_field_custom_arguments(): def test_field_custom_arguments():
field = Field(None, name='my_customName', p=String()) field = Field(None, name='my_customName', p=String())
class MyObjectType(ObjectType): args = field.arguments
my_field = field
schema = Schema(query=MyObjectType)
args = field.get_arguments(schema)
assert 'p' in args assert 'p' in args

View File

@ -1,5 +1,4 @@
from py.test import raises from py.test import raises
from pytest import raises
from graphene.core.fields import IntField, StringField from graphene.core.fields import IntField, StringField
from graphene.core.schema import Schema from graphene.core.schema import Schema
@ -66,9 +65,6 @@ def test_object_type():
assert isinstance(object_type, GraphQLObjectType) assert isinstance(object_type, GraphQLObjectType)
assert object_type.description == 'Human description' assert object_type.description == 'Human description'
assert list(object_type.get_fields().keys()) == ['name', 'friends'] assert list(object_type.get_fields().keys()) == ['name', 'friends']
# assert object_type.get_fields() == {'name': Human._meta.fields_map['name'].internal_field(
# schema), 'friends':
# Human._meta.fields_map['friends'].internal_field(schema)}
assert object_type.get_interfaces() == [schema.T(Character)] assert object_type.get_interfaces() == [schema.T(Character)]
assert Human._meta.fields_map['name'].object_type == Human assert Human._meta.fields_map['name'].object_type == Human
@ -115,5 +111,5 @@ def test_field_mantain_resolver_tags():
tag_resolver(resolve_name, 'test') tag_resolver(resolve_name, 'test')
field = Droid._meta.fields_map['name'].internal_field(schema) field = schema.T(Droid._meta.fields_map['name'])
assert resolver_has_tag(field.resolver, 'test') assert resolver_has_tag(field.resolver, 'test')

View File

@ -1,162 +0,0 @@
from py.test import raises
from pytest import raises
from graphene.core.fields import Field, NonNullField, StringField
from graphene.core.options import Options
from graphene.core.schema import Schema
from graphene.core.types import ObjectType
from graphql.core.type import (GraphQLBoolean, GraphQLField, GraphQLID,
GraphQLInt, GraphQLNonNull, GraphQLString)
class ot(ObjectType):
def resolve_customdoc(self, *args, **kwargs):
'''Resolver documentation'''
return None
def __str__(self):
return "ObjectType"
schema = Schema()
def test_field_no_contributed_raises_error():
f = Field(GraphQLString)
with raises(Exception) as excinfo:
schema.T(f)
def test_field_type():
f = Field(GraphQLString)
f.contribute_to_class(ot, 'field_name')
assert isinstance(schema.T(f), GraphQLField)
assert schema.T(f).type == GraphQLString
def test_field_name_automatic_camelcase():
f = Field(GraphQLString)
f.contribute_to_class(ot, 'field_name')
assert f.name == 'fieldName'
def test_field_name_use_name_if_exists():
f = Field(GraphQLString, name='my_custom_name')
f.contribute_to_class(ot, 'field_name')
assert f.name == 'my_custom_name'
def test_stringfield_type():
f = StringField()
f.contribute_to_class(ot, 'field_name')
assert schema.T(f) == GraphQLString
def test_nonnullfield_type():
f = NonNullField(StringField())
f.contribute_to_class(ot, 'field_name')
assert isinstance(schema.T(f), GraphQLNonNull)
def test_stringfield_type_required():
f = Field(StringField(required=True))
f.contribute_to_class(ot, 'field_name')
assert isinstance(schema.T(f), GraphQLField)
assert isinstance(schema.T(f).type, GraphQLNonNull)
def test_field_resolve():
f = StringField(required=True, resolve=lambda *args: 'RESOLVED')
f.contribute_to_class(ot, 'field_name')
field_type = f.internal_field(schema)
assert 'RESOLVED' == field_type.resolver(ot, None, None)
def test_field_resolve_type_custom():
class MyCustomType(ObjectType):
pass
class OtherType(ObjectType):
pass
s = Schema()
f = Field('MyCustomType')
f.contribute_to_class(OtherType, 'field_name')
field_type = f.get_object_type(s)
assert field_type == MyCustomType
def test_field_resolve_type_custom():
s = Schema()
f = Field('self')
f.contribute_to_class(ot, 'field_name')
field_type = f.get_object_type(s)
assert field_type == ot
def test_field_orders():
f1 = Field(None)
f2 = Field(None)
assert f1 < f2
def test_field_orders_wrong_type():
field = Field(None)
try:
assert not field < 1
except TypeError:
# Fix exception raising in Python3+
pass
def test_field_eq():
f1 = Field(None)
f2 = Field(None)
assert f1 != f2
def test_field_eq_wrong_type():
field = Field(None)
assert field != 1
def test_field_hash():
f1 = Field(None)
f2 = Field(None)
assert hash(f1) != hash(f2)
def test_field_none_type_raises_error():
s = Schema()
f = Field(None)
f.contribute_to_class(ot, 'field_name')
with raises(Exception) as excinfo:
f.internal_field(s)
assert str(
excinfo.value) == "Internal type for field ObjectType.field_name is None"
def test_field_str():
f = StringField()
f.contribute_to_class(ot, 'field_name')
assert str(f) == "ObjectType.field_name"
def test_field_repr():
f = StringField()
assert repr(f) == "<graphene.core.fields.StringField>"
def test_field_repr_contributed():
f = StringField()
f.contribute_to_class(ot, 'field_name')
assert repr(f) == "<graphene.core.fields.StringField: field_name>"
def test_field_resolve_objecttype_cos():
f = StringField()
f.contribute_to_class(ot, 'customdoc')
field = f.internal_field(schema)
assert field.description == 'Resolver documentation'

View File

@ -1,16 +0,0 @@
from graphene.core.scalars import GraphQLSkipField
def test_skipfield_serialize():
f = GraphQLSkipField
assert f.serialize('a') is None
def test_skipfield_parse_value():
f = GraphQLSkipField
assert f.parse_value('a') is None
def test_skipfield_parse_literal():
f = GraphQLSkipField
assert f.parse_literal('a') is None

View File

@ -1,143 +0,0 @@
from py.test import raises
from pytest import raises
from graphene import Interface, ObjectType, Schema
from graphene.core.fields import Field, ListField, StringField
from graphql.core import graphql
from graphql.core.type import (GraphQLInterfaceType, GraphQLObjectType,
GraphQLSchema)
from tests.utils import assert_equal_lists
schema = Schema(name='My own schema')
class Character(Interface):
name = StringField()
class Pet(ObjectType):
type = StringField(resolve=lambda *_: 'Dog')
class Human(Character):
friends = ListField(Character)
pet = Field(Pet)
def resolve_name(self, *args):
return 'Peter'
def resolve_friend(self, *args):
return Human(object())
def resolve_pet(self, *args):
return Pet(object())
schema.query = Human
def test_get_registered_type():
assert schema.get_type('Character') == Character
def test_get_unregistered_type():
with raises(Exception) as excinfo:
schema.get_type('NON_EXISTENT_MODEL')
assert 'not found' in str(excinfo.value)
def test_schema_query():
assert schema.query == Human
def test_query_schema_graphql():
object()
query = '''
{
name
pet {
type
}
}
'''
expected = {
'name': 'Peter',
'pet': {
'type': 'Dog'
}
}
result = graphql(schema.schema, query, root=Human(object()))
assert not result.errors
assert result.data == expected
def test_query_schema_execute():
object()
query = '''
{
name
pet {
type
}
}
'''
expected = {
'name': 'Peter',
'pet': {
'type': 'Dog'
}
}
result = schema.execute(query, root=object())
assert not result.errors
assert result.data == expected
def test_schema_get_type_map():
assert_equal_lists(
schema.schema.get_type_map().keys(),
['__Field', 'String', 'Pet', 'Character', '__InputValue', '__Directive',
'__TypeKind', '__Schema', '__Type', 'Human', '__EnumValue', 'Boolean']
)
def test_schema_no_query():
schema = Schema(name='My own schema')
with raises(Exception) as excinfo:
schema.schema
assert 'define a base query type' in str(excinfo)
def test_schema_register():
schema = Schema(name='My own schema')
@schema.register
class MyType(ObjectType):
type = StringField(resolve=lambda *_: 'Dog')
schema.query = MyType
assert schema.get_type('MyType') == MyType
def test_schema_register():
schema = Schema(name='My own schema')
@schema.register
class MyType(ObjectType):
type = StringField(resolve=lambda *_: 'Dog')
with raises(Exception) as excinfo:
schema.get_type('MyType')
assert 'base query type' in str(excinfo.value)
def test_schema_introspect():
schema = Schema(name='My own schema')
class MyType(ObjectType):
type = StringField(resolve=lambda *_: 'Dog')
schema.query = MyType
introspection = schema.introspect()
assert '__schema' in introspection