From 28d89a44f14d951b6ed6a3e393907f73f8cce35e Mon Sep 17 00:00:00 2001 From: Syrus Akbary Date: Tue, 10 Nov 2015 21:29:11 -0800 Subject: [PATCH] Improved mutations, tests and overall integration. --- graphene/core/fields.py | 13 +- graphene/core/schema.py | 39 ++--- .../core/tests}/test_mutations.py | 5 +- .../core/tests}/test_options.py | 0 .../core/tests}/test_query.py | 0 graphene/core/tests/test_schema.py | 3 +- graphene/core/types/argument.py | 24 ++- graphene/core/types/base.py | 3 + graphene/core/types/field.py | 53 +++--- graphene/core/types/objecttype.py | 16 +- graphene/core/types/tests/test_definitions.py | 2 +- graphene/core/types/tests/test_field.py | 7 +- .../core/types/tests/test_objecttype.py | 6 +- tests/core/test_fields.py | 162 ------------------ tests/core/test_scalars.py | 16 -- tests/core/test_schema.py | 143 ---------------- 16 files changed, 82 insertions(+), 410 deletions(-) rename {tests/core => graphene/core/tests}/test_mutations.py (86%) rename {tests/core => graphene/core/tests}/test_options.py (100%) rename {tests/core => graphene/core/tests}/test_query.py (100%) rename tests/core/test_types.py => graphene/core/types/tests/test_objecttype.py (91%) delete mode 100644 tests/core/test_fields.py delete mode 100644 tests/core/test_scalars.py delete mode 100644 tests/core/test_schema.py diff --git a/graphene/core/fields.py b/graphene/core/fields.py index 99782efc..f799ec9f 100644 --- a/graphene/core/fields.py +++ b/graphene/core/fields.py @@ -9,18 +9,11 @@ from .types.definitions import List, NonNull class DeprecatedField(FieldType): def __init__(self, *args, **kwargs): cls = self.__class__ - warnings.warn("Using {} is not longer supported".format(cls.__name__) - , FutureWarning) - kwargs['resolver'] = kwargs.pop('resolve', None) - self.required = kwargs.pop('required', False) + warnings.warn("Using {} is not longer supported".format(cls.__name__), FutureWarning) + if 'resolve' in kwargs: + kwargs['resolver'] = kwargs.pop('resolve') 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): pass diff --git a/graphene/core/schema.py b/graphene/core/schema.py index ec9637fd..171b0171 100644 --- a/graphene/core/schema.py +++ b/graphene/core/schema.py @@ -8,6 +8,7 @@ from graphql.core.execution.middlewares.sync import \ from graphql.core.type import GraphQLSchema as _GraphQLSchema from graphql.core.utils.introspection_query import introspection_query from graphene.core.types.base import BaseType +from graphene.core.types.objecttype import BaseObjectType class GraphQLSchema(_GraphQLSchema): @@ -18,7 +19,6 @@ class GraphQLSchema(_GraphQLSchema): class Schema(object): - _query = None _executor = None def __init__(self, query=None, mutation=None, name='Schema', executor=None): @@ -47,26 +47,10 @@ class Schema(object): else: 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 def executor(self): if not self._executor: - self.executor = Executor( + self._executor = Executor( [SynchronousExecutionMiddleware()], map_type=OrderedDict) return self._executor @@ -76,18 +60,29 @@ class Schema(object): @property def schema(self): - if not self._query: + if not self.query: 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): self._types_names[object_type._meta.type_name] = 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): - # self.schema._build_type_map() + self.setup() 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] @property diff --git a/tests/core/test_mutations.py b/graphene/core/tests/test_mutations.py similarity index 86% rename from tests/core/test_mutations.py rename to graphene/core/tests/test_mutations.py index 859e0444..260fe6b8 100644 --- a/tests/core/test_mutations.py +++ b/graphene/core/tests/test_mutations.py @@ -1,4 +1,3 @@ - import graphene from graphene.core.schema import Schema @@ -31,9 +30,7 @@ schema = Schema(query=Query, mutation=MyResultMutation) def test_mutation_input(): - assert ChangeNumber.input_type - assert ChangeNumber.input_type._meta.type_name == 'ChangeNumberInput' - assert list(ChangeNumber.input_type._meta.fields_map.keys()) == ['to'] + assert schema.T(ChangeNumber.arguments).keys() == ['to'] def test_execute_mutations(): diff --git a/tests/core/test_options.py b/graphene/core/tests/test_options.py similarity index 100% rename from tests/core/test_options.py rename to graphene/core/tests/test_options.py diff --git a/tests/core/test_query.py b/graphene/core/tests/test_query.py similarity index 100% rename from tests/core/test_query.py rename to graphene/core/tests/test_query.py diff --git a/graphene/core/tests/test_schema.py b/graphene/core/tests/test_schema.py index 082ed080..2b4dc397 100644 --- a/graphene/core/tests/test_schema.py +++ b/graphene/core/tests/test_schema.py @@ -120,7 +120,7 @@ def test_schema_register(): assert schema.get_type('MyType') == MyType -def test_schema_register(): +def test_schema_register_no_query_type(): schema = Schema(name='My own schema') @schema.register @@ -149,6 +149,7 @@ def test_lazytype(): t = LazyType('MyType') + @schema.register class MyType(ObjectType): type = StringField(resolve=lambda *_: 'Dog') diff --git a/graphene/core/types/argument.py b/graphene/core/types/argument.py index 2e5ebaf6..1c762345 100644 --- a/graphene/core/types/argument.py +++ b/graphene/core/types/argument.py @@ -1,8 +1,9 @@ +from collections import OrderedDict from itertools import chain from graphql.core.type import GraphQLArgument -from .base import OrderedType, ArgumentType +from .base import BaseType, OrderedType, ArgumentType from ...utils import to_camel_case @@ -21,6 +22,27 @@ class Argument(OrderedType): 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): arguments = {} iter_arguments = chain(kwargs.items(), [(None, a) for a in args]) diff --git a/graphene/core/types/base.py b/graphene/core/types/base.py index cc8d4e20..f3c04ad6 100644 --- a/graphene/core/types/base.py +++ b/graphene/core/types/base.py @@ -11,6 +11,9 @@ class LazyType(BaseType): def __init__(self, type_str): self.type_str = type_str + def is_self(self): + return self.type_str == 'self' + def internal_type(self, schema): type = schema.get_type(self.type_str) return schema.T(type) diff --git a/graphene/core/types/field.py b/graphene/core/types/field.py index 599fe83e..56f172f5 100644 --- a/graphene/core/types/field.py +++ b/graphene/core/types/field.py @@ -1,32 +1,35 @@ import six from collections import OrderedDict -from functools import wraps from graphql.core.type import GraphQLField, GraphQLInputObjectField 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 ..types import BaseObjectType, InputObjectType class Empty(object): pass - + 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) super(Field, self).__init__(_creation_counter=_creation_counter) self.name = name - if isinstance(type, six.string_types) and type != 'self': + if isinstance(type, six.string_types): type = LazyType(type) + if required: + type = NonNull(type) self.type = type self.description = description args = OrderedDict(args or {}, **kwargs) - self.arguments = to_arguments(*args_list, **args) + self.arguments = ArgumentsGroup(*args_list, **args) self.object_type = None self.resolver = resolver + self.default = default def contribute_to_class(self, cls, attname): 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.attname = attname self.object_type = cls - if self.type == 'self': + if isinstance(self.type, LazyType) and self.type.is_self(): self.type = cls cls._meta.add_field(self) @@ -49,41 +52,29 @@ class Field(OrderedType): def get_resolver_fn(self): resolve_fn_name = 'resolve_%s' % self.attname 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): - return getattr(instance, self.attname, None) - + return getattr(instance, self.attname, self.default) return default_getter def internal_type(self, schema): resolver = self.resolver description = self.description + arguments = self.arguments if not description and resolver: description = resolver.__doc__ 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) - return GraphQLField(type, args=self.get_arguments(schema), resolver=resolver, + return GraphQLField(type, args=schema.T(arguments), resolver=resolver, 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): """ Displays the module, class and name of the field. @@ -103,9 +94,11 @@ class Field(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) self.name = name + if required: + type = NonNull(type) self.type = type self.description = description self.default = default diff --git a/graphene/core/types/objecttype.py b/graphene/core/types/objecttype.py index 89a77ec3..a6c73602 100644 --- a/graphene/core/types/objecttype.py +++ b/graphene/core/types/objecttype.py @@ -8,6 +8,7 @@ import six from graphene import signals from graphene.core.options import Options from graphene.core.types.base import BaseType +from graphene.core.types.argument import ArgumentsGroup from graphql.core.type import (GraphQLArgument, GraphQLInputObjectType, GraphQLInterfaceType, GraphQLObjectType) @@ -66,9 +67,10 @@ class ObjectTypeMeta(type): if input_class: items = dict(input_class.__dict__) items.pop('__dict__', None) - input_type = type('{}Input'.format( - new_class._meta.type_name), (ObjectType, ), items) - new_class.add_to_class('input_type', input_type) + items.pop('__doc__', None) + items.pop('__module__', None) + arguments = ArgumentsGroup(**items) + new_class.add_to_class('arguments', arguments) new_class.add_extra_fields() @@ -88,7 +90,7 @@ class ObjectTypeMeta(type): # 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.__class__ != field_names[field.name].__class__: + 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 ' @@ -213,14 +215,10 @@ class ObjectType(six.with_metaclass(ObjectTypeMeta, BaseObjectType)): class Mutation(six.with_metaclass(ObjectTypeMeta, BaseObjectType)): - - @classmethod - def get_input_type(cls): - return getattr(cls, 'input_type', None) + pass class InputObjectType(ObjectType): - @classmethod def internal_type(cls, schema): fields = lambda: OrderedDict([(f.name, schema.T(f)) diff --git a/graphene/core/types/tests/test_definitions.py b/graphene/core/types/tests/test_definitions.py index 652a5cd8..9b175a89 100644 --- a/graphene/core/types/tests/test_definitions.py +++ b/graphene/core/types/tests/test_definitions.py @@ -19,7 +19,7 @@ def test_nonnull_scalar(): assert type.of_type == GraphQLString -def test_mixed_scalar(): +def test_nested_scalars(): type = schema.T(NonNull(List(String()))) assert isinstance(type, GraphQLNonNull) assert isinstance(type.of_type, GraphQLList) diff --git a/graphene/core/types/tests/test_field.py b/graphene/core/types/tests/test_field.py index 5f8042d5..db5444a3 100644 --- a/graphene/core/types/tests/test_field.py +++ b/graphene/core/types/tests/test_field.py @@ -75,12 +75,7 @@ def test_field_string_reference(): def test_field_custom_arguments(): field = Field(None, name='my_customName', p=String()) - class MyObjectType(ObjectType): - my_field = field - - schema = Schema(query=MyObjectType) - - args = field.get_arguments(schema) + args = field.arguments assert 'p' in args diff --git a/tests/core/test_types.py b/graphene/core/types/tests/test_objecttype.py similarity index 91% rename from tests/core/test_types.py rename to graphene/core/types/tests/test_objecttype.py index f7c29883..125df407 100644 --- a/tests/core/test_types.py +++ b/graphene/core/types/tests/test_objecttype.py @@ -1,5 +1,4 @@ from py.test import raises -from pytest import raises from graphene.core.fields import IntField, StringField from graphene.core.schema import Schema @@ -66,9 +65,6 @@ def test_object_type(): assert isinstance(object_type, GraphQLObjectType) assert object_type.description == 'Human description' 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 Human._meta.fields_map['name'].object_type == Human @@ -115,5 +111,5 @@ def test_field_mantain_resolver_tags(): 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') diff --git a/tests/core/test_fields.py b/tests/core/test_fields.py deleted file mode 100644 index bb3c0c27..00000000 --- a/tests/core/test_fields.py +++ /dev/null @@ -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) == "" - - -def test_field_repr_contributed(): - f = StringField() - f.contribute_to_class(ot, 'field_name') - assert repr(f) == "" - - -def test_field_resolve_objecttype_cos(): - f = StringField() - f.contribute_to_class(ot, 'customdoc') - field = f.internal_field(schema) - assert field.description == 'Resolver documentation' diff --git a/tests/core/test_scalars.py b/tests/core/test_scalars.py deleted file mode 100644 index 1252741d..00000000 --- a/tests/core/test_scalars.py +++ /dev/null @@ -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 diff --git a/tests/core/test_schema.py b/tests/core/test_schema.py deleted file mode 100644 index accc2f46..00000000 --- a/tests/core/test_schema.py +++ /dev/null @@ -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