From afe861475398992857806653c664352e4fd4ea8b Mon Sep 17 00:00:00 2001 From: Syrus Akbary Date: Fri, 6 Nov 2015 00:07:44 -0800 Subject: [PATCH 01/20] First types implementation --- graphene/core/ntypes/__init__.py | 0 graphene/core/ntypes/argument.py | 42 +++++++++++ graphene/core/ntypes/base.py | 71 +++++++++++++++++++ graphene/core/ntypes/definitions.py | 20 ++++++ graphene/core/ntypes/field.py | 61 ++++++++++++++++ graphene/core/ntypes/scalars.py | 40 +++++++++++ graphene/core/ntypes/tests/__init__.py | 0 graphene/core/ntypes/tests/test_argument.py | 45 ++++++++++++ graphene/core/ntypes/tests/test_base.py | 66 +++++++++++++++++ .../core/ntypes/tests/test_definitions.py | 26 +++++++ graphene/core/ntypes/tests/test_field.py | 62 ++++++++++++++++ graphene/core/ntypes/tests/test_scalars.py | 52 ++++++++++++++ graphene/core/schema.py | 4 +- setup.cfg | 3 + setup.py | 1 + 15 files changed, 492 insertions(+), 1 deletion(-) create mode 100644 graphene/core/ntypes/__init__.py create mode 100644 graphene/core/ntypes/argument.py create mode 100644 graphene/core/ntypes/base.py create mode 100644 graphene/core/ntypes/definitions.py create mode 100644 graphene/core/ntypes/field.py create mode 100644 graphene/core/ntypes/scalars.py create mode 100644 graphene/core/ntypes/tests/__init__.py create mode 100644 graphene/core/ntypes/tests/test_argument.py create mode 100644 graphene/core/ntypes/tests/test_base.py create mode 100644 graphene/core/ntypes/tests/test_definitions.py create mode 100644 graphene/core/ntypes/tests/test_field.py create mode 100644 graphene/core/ntypes/tests/test_scalars.py diff --git a/graphene/core/ntypes/__init__.py b/graphene/core/ntypes/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/graphene/core/ntypes/argument.py b/graphene/core/ntypes/argument.py new file mode 100644 index 00000000..6e8788ce --- /dev/null +++ b/graphene/core/ntypes/argument.py @@ -0,0 +1,42 @@ +from itertools import chain + +from graphql.core.type import GraphQLArgument + +from .base import OrderedType, ArgumentType +from ...utils import to_camel_case + + +class Argument(OrderedType): + def __init__(self, type, description=None, default=None, name=None, _creation_counter=None): + super(Argument, self).__init__(_creation_counter=_creation_counter) + self.name = name + self.type = type + self.description = description + self.default = default + + def internal_type(self, schema): + return GraphQLArgument(schema.T(self.type), self.default, self.description) + + def __repr__(self): + return self.name + + +def to_arguments(*args, **kwargs): + arguments = {} + iter_arguments = chain(kwargs.items(), [(None, a) for a in args]) + + for name, arg in iter_arguments: + if isinstance(arg, Argument): + argument = arg + elif isinstance(arg, ArgumentType): + argument = arg.as_argument() + else: + raise ValueError('Unknown argument value type %r' % arg) + + if name: + argument.name = to_camel_case(name) + assert argument.name, 'Argument in field must have a name' + assert argument.name not in arguments, 'Found more than one Argument with same name {}'.format(argument.name) + arguments[argument.name] = argument + + return sorted(arguments.values()) diff --git a/graphene/core/ntypes/base.py b/graphene/core/ntypes/base.py new file mode 100644 index 00000000..20b1d271 --- /dev/null +++ b/graphene/core/ntypes/base.py @@ -0,0 +1,71 @@ +from functools import total_ordering +from ..types import BaseObjectType, InputObjectType + + +@total_ordering +class OrderedType(object): + creation_counter = 0 + + def __init__(self, _creation_counter=None): + self.creation_counter = _creation_counter or self.gen_counter() + + @staticmethod + def gen_counter(): + counter = OrderedType.creation_counter + OrderedType.creation_counter += 1 + return counter + + def __eq__(self, other): + # Needed for @total_ordering + if type(self) == type(other): + return self.creation_counter == other.creation_counter + return NotImplemented + + def __lt__(self, other): + # This is needed because bisect does not take a comparison function. + if type(self) == type(other): + return self.creation_counter < other.creation_counter + return NotImplemented + + def __hash__(self): + return hash((self.creation_counter)) + + +class MirroredType(OrderedType): + def __init__(self, *args, **kwargs): + _creation_counter = kwargs.pop('_creation_counter', None) + super(MirroredType, self).__init__(_creation_counter=_creation_counter) + self.args = args + self.kwargs = kwargs + + @classmethod + def internal_type(cls, schema): + return getattr(cls, 'T', None) + + +class ArgumentType(MirroredType): + def as_argument(self): + from .argument import Argument + return Argument(self.__class__, _creation_counter=self.creation_counter, *self.args, **self.kwargs) + + +class FieldType(MirroredType): + def contribute_to_class(self, cls, name): + if issubclass(cls, InputObjectType): + inputfield = self.as_inputfield() + return inputfield.contribute_to_class(cls, name) + elif issubclass(cls, BaseObjectType): + field = self.as_field() + return field.contribute_to_class(cls, name) + + def as_field(self): + from .field import Field + return Field(self.__class__, _creation_counter=self.creation_counter, *self.args, **self.kwargs) + + def as_inputfield(self): + from .field import InputField + return InputField(self.__class__, _creation_counter=self.creation_counter, *self.args, **self.kwargs) + + +class MountedType(FieldType, ArgumentType): + pass diff --git a/graphene/core/ntypes/definitions.py b/graphene/core/ntypes/definitions.py new file mode 100644 index 00000000..7eaf9a88 --- /dev/null +++ b/graphene/core/ntypes/definitions.py @@ -0,0 +1,20 @@ +from graphql.core.type import (GraphQLList, GraphQLNonNull) + +from .base import MountedType + + +class OfType(MountedType): + def __init__(self, of_type, *args, **kwargs): + self.of_type = of_type + super(OfType, self).__init__(*args, **kwargs) + + def internal_type(self, schema): + return self.T(schema.T(self.of_type)) + + +class List(OfType): + T = GraphQLList + + +class NonNull(OfType): + T = GraphQLNonNull diff --git a/graphene/core/ntypes/field.py b/graphene/core/ntypes/field.py new file mode 100644 index 00000000..a9eae3d8 --- /dev/null +++ b/graphene/core/ntypes/field.py @@ -0,0 +1,61 @@ +from collections import OrderedDict + +from graphql.core.type import GraphQLField, GraphQLInputObjectField + +from .base import OrderedType +from .argument import to_arguments +from ...utils import to_camel_case +from ..types import BaseObjectType, InputObjectType + + +class Field(OrderedType): + def __init__(self, type, description=None, args=None, name=None, resolver=None, *args_list, **kwargs): + _creation_counter = kwargs.pop('_creation_counter', None) + super(Field, self).__init__(_creation_counter=_creation_counter) + self.name = name + self.type = type + self.description = description + args = OrderedDict(args or {}, **kwargs) + self.arguments = to_arguments(*args_list, **args) + self.resolver = resolver + + def contribute_to_class(self, cls, attname): + assert issubclass(cls, BaseObjectType), 'Field {} cannot be mounted in {}'.format(self, cls) + if not self.name: + self.name = to_camel_case(attname) + self.attname = attname + self.object_type = cls + if self.type == 'self': + self.type = cls + cls._meta.add_field(self) + + def internal_type(self, schema): + return GraphQLField(schema.T(self.type), args=self.get_arguments(schema), resolver=self.resolver, + description=self.description,) + + def get_arguments(self, schema): + if not self.arguments: + return None + + return OrderedDict([(arg.name, schema.T(arg)) for arg in self.arguments]) + + +class InputField(OrderedType): + def __init__(self, type, description=None, default=None, name=None, _creation_counter=None): + super(InputField, self).__init__(_creation_counter=_creation_counter) + self.name = name + self.type = type + self.description = description + self.default = default + + def contribute_to_class(self, cls, attname): + assert issubclass(cls, InputObjectType), 'InputField {} cannot be mounted in {}'.format(self, cls) + if not self.name: + self.name = to_camel_case(attname) + self.attname = attname + self.object_type = cls + cls._meta.add_field(self) + + def internal_type(self, schema): + return GraphQLInputObjectField(schema.T(self.type), default_value=self.default, + description=self.description) diff --git a/graphene/core/ntypes/scalars.py b/graphene/core/ntypes/scalars.py new file mode 100644 index 00000000..2dfc32b7 --- /dev/null +++ b/graphene/core/ntypes/scalars.py @@ -0,0 +1,40 @@ +from graphql.core.type import (GraphQLBoolean, GraphQLFloat, GraphQLID, + GraphQLInt, GraphQLScalarType, GraphQLString) + +from .base import MountedType + + +class String(MountedType): + T = GraphQLString + + +class Int(MountedType): + T = GraphQLInt + + +class Boolean(MountedType): + T = GraphQLBoolean + + +class ID(MountedType): + T = GraphQLID + + +class Float(MountedType): + T = GraphQLFloat + + +class Scalar(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.__name__, + description=cls.__doc__, + serialize=serialize, + parse_value=parse_value, + parse_literal=parse_literal + ) diff --git a/graphene/core/ntypes/tests/__init__.py b/graphene/core/ntypes/tests/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/graphene/core/ntypes/tests/test_argument.py b/graphene/core/ntypes/tests/test_argument.py new file mode 100644 index 00000000..ad7650b1 --- /dev/null +++ b/graphene/core/ntypes/tests/test_argument.py @@ -0,0 +1,45 @@ +from pytest import raises +from graphql.core.type import GraphQLArgument + +from ..argument import Argument, to_arguments +from ..scalars import String +from graphene.core.types import ObjectType +from graphene.core.schema import Schema + + +def test_argument_internal_type(): + class MyObjectType(ObjectType): + pass + schema = Schema(query=MyObjectType) + a = Argument(MyObjectType, description='My argument', default='3') + type = schema.T(a) + assert isinstance(type, GraphQLArgument) + assert type.description == 'My argument' + assert type.default_value == '3' + + +def test_to_arguments(): + arguments = to_arguments( + Argument(String, name='myArg'), + String(name='otherArg'), + my_kwarg=String(), + other_kwarg=String(), + ) + + assert [a.name for a in arguments] == ['myArg', 'otherArg', 'myKwarg', 'otherKwarg'] + + +def test_to_arguments_no_name(): + with raises(AssertionError) as excinfo: + to_arguments( + String(), + ) + assert 'must have a name' in str(excinfo.value) + + +def test_to_arguments_wrong_type(): + with raises(ValueError) as excinfo: + to_arguments( + p=3 + ) + assert 'Unknown argument value type 3' == str(excinfo.value) diff --git a/graphene/core/ntypes/tests/test_base.py b/graphene/core/ntypes/tests/test_base.py new file mode 100644 index 00000000..9537f0ea --- /dev/null +++ b/graphene/core/ntypes/tests/test_base.py @@ -0,0 +1,66 @@ +from mock import patch + +from ..base import OrderedType, MountedType +from ..field import Field, InputField +from ..argument import Argument +from graphene.core.types import ObjectType, InputObjectType + + +def test_orderedtype_equal(): + a = OrderedType() + assert a == a + assert hash(a) == hash(a) + + +def test_orderedtype_different(): + a = OrderedType() + b = OrderedType() + assert a != b + assert hash(a) != hash(b) + assert a < b + assert b > a + + +@patch('graphene.core.ntypes.field.Field') +def test_type_as_field_called(Field): + resolver = lambda x: x + a = MountedType(2, description='A', resolver=resolver) + a.as_field() + Field.assert_called_with(MountedType, 2, _creation_counter=a.creation_counter, description='A', resolver=resolver) + + +@patch('graphene.core.ntypes.argument.Argument') +def test_type_as_argument_called(Argument): + a = MountedType(2, description='A') + a.as_argument() + Argument.assert_called_with(MountedType, 2, _creation_counter=a.creation_counter, description='A') + + +def test_type_as_field(): + resolver = lambda x: x + + class MyObjectType(ObjectType): + t = MountedType(description='A', resolver=resolver) + + fields_map = MyObjectType._meta.fields_map + field = fields_map.get('t') + assert isinstance(field, Field) + assert field.description == 'A' + assert field.object_type == MyObjectType + + +def test_type_as_inputfield(): + class MyObjectType(InputObjectType): + t = MountedType(description='A') + + fields_map = MyObjectType._meta.fields_map + field = fields_map.get('t') + assert isinstance(field, InputField) + assert field.description == 'A' + assert field.object_type == MyObjectType + + +def test_type_as_argument(): + a = MountedType(description='A') + argument = a.as_argument() + assert isinstance(argument, Argument) diff --git a/graphene/core/ntypes/tests/test_definitions.py b/graphene/core/ntypes/tests/test_definitions.py new file mode 100644 index 00000000..652a5cd8 --- /dev/null +++ b/graphene/core/ntypes/tests/test_definitions.py @@ -0,0 +1,26 @@ +from graphql.core.type import (GraphQLList, GraphQLString, GraphQLNonNull) + +from ..definitions import List, NonNull +from ..scalars import String +from graphene.core.schema import Schema + +schema = Schema() + + +def test_list_scalar(): + type = schema.T(List(String())) + assert isinstance(type, GraphQLList) + assert type.of_type == GraphQLString + + +def test_nonnull_scalar(): + type = schema.T(NonNull(String())) + assert isinstance(type, GraphQLNonNull) + assert type.of_type == GraphQLString + + +def test_mixed_scalar(): + type = schema.T(NonNull(List(String()))) + assert isinstance(type, GraphQLNonNull) + assert isinstance(type.of_type, GraphQLList) + assert type.of_type.of_type == GraphQLString diff --git a/graphene/core/ntypes/tests/test_field.py b/graphene/core/ntypes/tests/test_field.py new file mode 100644 index 00000000..e34a1184 --- /dev/null +++ b/graphene/core/ntypes/tests/test_field.py @@ -0,0 +1,62 @@ +from graphql.core.type import GraphQLField, GraphQLInputObjectField, GraphQLString + +from ..field import Field, InputField +from ..scalars import String +from graphene.core.types import ObjectType, InputObjectType +from graphene.core.schema import Schema + + +def test_field_internal_type(): + resolver = lambda *args: args + + field = Field(String, description='My argument', resolver=resolver) + + class Query(ObjectType): + my_field = field + schema = Schema(query=Query) + + type = schema.T(field) + assert field.name == 'myField' + assert isinstance(type, GraphQLField) + assert type.description == 'My argument' + assert type.resolver == resolver + assert type.type == GraphQLString + + +def test_field_custom_name(): + field = Field(None, name='my_customName') + + class MyObjectType(ObjectType): + my_field = field + + assert field.name == 'my_customName' + + +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) + assert 'p' in args + + +def test_inputfield_internal_type(): + field = InputField(String, description='My input field', default='3') + + class MyObjectType(InputObjectType): + my_field = field + + class Query(ObjectType): + input_ot = Field(MyObjectType) + + schema = Schema(query=MyObjectType) + + type = schema.T(field) + assert field.name == 'myField' + assert isinstance(type, GraphQLInputObjectField) + assert type.description == 'My input field' + assert type.default_value == '3' diff --git a/graphene/core/ntypes/tests/test_scalars.py b/graphene/core/ntypes/tests/test_scalars.py new file mode 100644 index 00000000..7312752a --- /dev/null +++ b/graphene/core/ntypes/tests/test_scalars.py @@ -0,0 +1,52 @@ +from graphql.core.type import (GraphQLBoolean, GraphQLFloat, GraphQLID, + GraphQLInt, GraphQLScalarType, GraphQLString) + +from ..scalars import String, Int, Boolean, ID, Float, Scalar +from graphene.core.schema import Schema + +schema = Schema() + + +def test_string_scalar(): + assert schema.T(String()) == GraphQLString + + +def test_int_scalar(): + assert schema.T(Int()) == GraphQLInt + + +def test_boolean_scalar(): + assert schema.T(Boolean()) == GraphQLBoolean + + +def test_id_scalar(): + assert schema.T(ID()) == GraphQLID + + +def test_float_scalar(): + assert schema.T(Float()) == GraphQLFloat + + +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") + + scalar_type = schema.T(DateTimeScalar) + assert isinstance(scalar_type, GraphQLScalarType) + assert scalar_type.name == 'DateTimeScalar' + assert scalar_type.description == 'DateTimeScalar Documentation' diff --git a/graphene/core/schema.py b/graphene/core/schema.py index 54361100..1ad1141d 100644 --- a/graphene/core/schema.py +++ b/graphene/core/schema.py @@ -37,7 +37,9 @@ class Schema(object): if object_type not in self._types: internal_type = object_type.internal_type(self) self._types[object_type] = internal_type - self._types_names[internal_type.name] = object_type + name = getattr(internal_type, 'name', None) + if name: + self._types_names[name] = object_type return self._types[object_type] @property diff --git a/setup.cfg b/setup.cfg index 15bcaf1b..52475711 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,3 +1,6 @@ [flake8] exclude = tests/*,setup.py max-line-length = 160 + +[coverage:run] +omit = core/ntypes/tests/* diff --git a/setup.py b/setup.py index c3ab220b..216fa2d3 100644 --- a/setup.py +++ b/setup.py @@ -62,6 +62,7 @@ setup( tests_require=[ 'pytest>=2.7.2', 'pytest-django', + 'mock', ], extras_require={ 'django': [ From 9bab0d9d6fb05ac2a67a013d821f5f409845e5c5 Mon Sep 17 00:00:00 2001 From: Syrus Akbary Date: Mon, 9 Nov 2015 20:38:12 -0800 Subject: [PATCH 02/20] Improved fields resolver --- graphene/core/ntypes/field.py | 30 ++++++++++++++++++++++-- graphene/core/ntypes/tests/test_field.py | 21 +++++++++++++++++ 2 files changed, 49 insertions(+), 2 deletions(-) diff --git a/graphene/core/ntypes/field.py b/graphene/core/ntypes/field.py index a9eae3d8..13bdcf62 100644 --- a/graphene/core/ntypes/field.py +++ b/graphene/core/ntypes/field.py @@ -1,4 +1,5 @@ from collections import OrderedDict +from functools import wraps from graphql.core.type import GraphQLField, GraphQLInputObjectField @@ -29,9 +30,34 @@ class Field(OrderedType): self.type = cls cls._meta.add_field(self) + @property + def resolver(self): + return self._resolver + + @resolver.setter + def resolver(self, value): + self._resolver = value + + 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) + + @wraps(resolve_fn) + def custom_resolve_fn(instance, args, info): + return resolve_fn(instance, args, info) + return custom_resolve_fn + def internal_type(self, schema): - return GraphQLField(schema.T(self.type), args=self.get_arguments(schema), resolver=self.resolver, - description=self.description,) + resolver = self.resolver + description = self.description + if not resolver: + resolver = self.get_resolver_fn() + if not description and resolver: + description = resolver.__doc__ + + return GraphQLField(schema.T(self.type), args=self.get_arguments(schema), resolver=resolver, + description=description,) def get_arguments(self, schema): if not self.arguments: diff --git a/graphene/core/ntypes/tests/test_field.py b/graphene/core/ntypes/tests/test_field.py index e34a1184..449f52a3 100644 --- a/graphene/core/ntypes/tests/test_field.py +++ b/graphene/core/ntypes/tests/test_field.py @@ -17,12 +17,31 @@ def test_field_internal_type(): type = schema.T(field) assert field.name == 'myField' + assert field.attname == 'my_field' assert isinstance(type, GraphQLField) assert type.description == 'My argument' assert type.resolver == resolver assert type.type == GraphQLString +def test_field_objectype_resolver(): + field = Field(String) + + class Query(ObjectType): + my_field = field + + def resolve_my_field(self, *args, **kwargs): + '''Custom description''' + return 'RESOLVED' + + schema = Schema(query=Query) + + type = schema.T(field) + assert isinstance(type, GraphQLField) + assert type.description == 'Custom description' + assert type.resolver(Query(), {}, None) == 'RESOLVED' + + def test_field_custom_name(): field = Field(None, name='my_customName') @@ -30,6 +49,7 @@ def test_field_custom_name(): my_field = field assert field.name == 'my_customName' + assert field.attname == 'my_field' def test_field_custom_arguments(): @@ -57,6 +77,7 @@ def test_inputfield_internal_type(): type = schema.T(field) assert field.name == 'myField' + assert field.attname == 'my_field' assert isinstance(type, GraphQLInputObjectField) assert type.description == 'My input field' assert type.default_value == '3' From 3c65deb3138709d400ae1ecb178f6d25e6aab870 Mon Sep 17 00:00:00 2001 From: Syrus Akbary Date: Tue, 10 Nov 2015 01:29:38 -0800 Subject: [PATCH 03/20] In work version graphene new types --- graphene/core/fields.py | 266 ++---------------- graphene/core/ntypes/tests/__init__.py | 0 graphene/core/schema.py | 18 +- graphene/core/types/__init__.py | 1 + graphene/core/{ntypes => types}/argument.py | 2 +- graphene/core/{ntypes => types}/base.py | 18 +- .../core/{ntypes => types}/definitions.py | 0 graphene/core/{ntypes => types}/field.py | 29 +- .../core/{types.py => types/objecttype.py} | 7 +- graphene/core/{ntypes => types}/scalars.py | 0 .../core/{ntypes => types/tests}/__init__.py | 0 .../{ntypes => types}/tests/test_argument.py | 0 .../core/{ntypes => types}/tests/test_base.py | 4 +- .../tests/test_definitions.py | 0 .../{ntypes => types}/tests/test_field.py | 0 .../{ntypes => types}/tests/test_scalars.py | 0 tests/core/test_fields.py | 12 +- tests/core/test_query.py | 7 +- 18 files changed, 85 insertions(+), 279 deletions(-) delete mode 100644 graphene/core/ntypes/tests/__init__.py create mode 100644 graphene/core/types/__init__.py rename graphene/core/{ntypes => types}/argument.py (95%) rename graphene/core/{ntypes => types}/base.py (86%) rename graphene/core/{ntypes => types}/definitions.py (100%) rename graphene/core/{ntypes => types}/field.py (80%) rename graphene/core/{types.py => types/objecttype.py} (97%) rename graphene/core/{ntypes => types}/scalars.py (100%) rename graphene/core/{ntypes => types/tests}/__init__.py (100%) rename graphene/core/{ntypes => types}/tests/test_argument.py (100%) rename graphene/core/{ntypes => types}/tests/test_base.py (94%) rename graphene/core/{ntypes => types}/tests/test_definitions.py (100%) rename graphene/core/{ntypes => types}/tests/test_field.py (100%) rename graphene/core/{ntypes => types}/tests/test_scalars.py (100%) diff --git a/graphene/core/fields.py b/graphene/core/fields.py index 7cb821aa..d2edef38 100644 --- a/graphene/core/fields.py +++ b/graphene/core/fields.py @@ -1,258 +1,38 @@ -import inspect -from functools import total_ordering, wraps - -import six - -from graphene.core.scalars import GraphQLSkipField -from graphene.core.types import BaseObjectType, InputObjectType -from graphene.utils import ProxySnakeDict, enum_to_graphql_enum, to_camel_case -from graphql.core.type import (GraphQLArgument, GraphQLBoolean, GraphQLField, - GraphQLFloat, GraphQLID, - GraphQLInputObjectField, GraphQLInt, - GraphQLList, GraphQLNonNull, GraphQLString) - -try: - from enum import Enum -except ImportError: - class Enum(object): - pass +from .types.field import Field +from .types.scalars import String, Int, Boolean, ID, Float +from .types.definitions import List, NonNull -class Empty(object): +class DeprecatedField(object): + def __init__(self, *args, **kwargs): + print("Using {} is not longer supported".format(self.__class__.__name__)) + kwargs['resolver'] = kwargs.pop('resolve', None) + return super(DeprecatedField, self).__init__(*args, **kwargs) + + +class StringField(DeprecatedField, String): pass -@total_ordering -class Field(object): - SKIP = GraphQLSkipField - creation_counter = 0 - required = False - - def __init__(self, field_type, name=None, resolve=None, required=False, args=None, description='', default=None, **extra_args): - self.field_type = field_type - self.resolve_fn = resolve - self.required = self.required or required - self.args = args or {} - self.extra_args = extra_args - self._type = None - self.name = name - self.description = description or self.__doc__ - self.object_type = None - self.default = default - self.creation_counter = Field.creation_counter - Field.creation_counter += 1 - - def get_default(self): - return self.default - - def contribute_to_class(self, cls, name, add=True): - if not self.name: - self.name = to_camel_case(name) - self.attname = name - self.object_type = cls - if isinstance(self.field_type, Field) and not self.field_type.object_type: - self.field_type.contribute_to_class(cls, name, False) - if add: - cls._meta.add_field(self) - - def resolve(self, instance, args, info): - schema = info and getattr(info.schema, 'graphene_schema', None) - resolve_fn = self.get_resolve_fn(schema) - if resolve_fn: - return resolve_fn(instance, ProxySnakeDict(args), info) - else: - return getattr(instance, self.attname, self.get_default()) - - def get_resolve_fn(self, schema): - object_type = self.get_object_type(schema) - if object_type and object_type._meta.is_mutation: - return object_type.mutate - elif self.resolve_fn: - return self.resolve_fn - else: - custom_resolve_fn_name = 'resolve_%s' % self.attname - if hasattr(self.object_type, custom_resolve_fn_name): - resolve_fn = getattr(self.object_type, custom_resolve_fn_name) - - @wraps(resolve_fn) - def custom_resolve_fn(instance, args, info): - return resolve_fn(instance, args, info) - return custom_resolve_fn - - def get_object_type(self, schema): - field_type = self.field_type - if inspect.isfunction(field_type): - field_type = field_type(self) - _is_class = inspect.isclass(field_type) - if isinstance(field_type, Field): - return field_type.get_object_type(schema) - if _is_class and issubclass(field_type, BaseObjectType): - return field_type - elif isinstance(field_type, six.string_types): - if field_type == 'self': - return self.object_type - else: - return schema.get_type(field_type) - - def type_wrapper(self, field_type): - if self.required: - field_type = GraphQLNonNull(field_type) - return field_type - - def internal_type(self, schema): - field_type = self.field_type - _is_class = inspect.isclass(field_type) - if isinstance(field_type, Field): - field_type = self.field_type.internal_type(schema) - elif _is_class and issubclass(field_type, Enum): - field_type = enum_to_graphql_enum(field_type) - else: - object_type = self.get_object_type(schema) - if object_type: - field_type = schema.T(object_type) - - field_type = self.type_wrapper(field_type) - return field_type - - def internal_field(self, schema): - if not self.object_type: - raise Exception( - 'Field could not be constructed in a non graphene.ObjectType or graphene.Interface') - - extra_args = self.extra_args.copy() - for arg_name, arg_value in self.extra_args.items(): - if isinstance(arg_value, GraphQLArgument): - self.args[arg_name] = arg_value - del extra_args[arg_name] - - if extra_args != {}: - raise TypeError("Field %s.%s initiated with invalid args: %s" % ( - self.object_type, - self.attname, - ','.join(extra_args.keys()) - )) - - args = self.args - - object_type = self.get_object_type(schema) - if object_type and object_type._meta.is_mutation: - assert not self.args, 'Arguments provided for mutations are defined in Input class in Mutation' - args = object_type.get_input_type().fields_as_arguments(schema) - - internal_type = self.internal_type(schema) - if not internal_type: - raise Exception("Internal type for field %s is None" % self) - - description = self.description - resolve_fn = self.get_resolve_fn(schema) - if resolve_fn: - description = resolve_fn.__doc__ or description - - @wraps(resolve_fn) - def resolver(*args): - return self.resolve(*args) - else: - resolver = self.resolve - - if issubclass(self.object_type, InputObjectType): - return GraphQLInputObjectField( - internal_type, - description=description, - ) - - return GraphQLField( - internal_type, - description=description, - args=args, - resolver=resolver, - ) - - def __str__(self): - """ Return "object_type.name". """ - return '%s.%s' % (self.object_type.__name__, self.attname) - - def __repr__(self): - """ - Displays the module, class and name of the field. - """ - path = '%s.%s' % (self.__class__.__module__, self.__class__.__name__) - name = getattr(self, 'attname', None) - if name is not None: - return '<%s: %s>' % (path, name) - return '<%s>' % path - - def __eq__(self, other): - # Needed for @total_ordering - if isinstance(other, Field): - return self.creation_counter == other.creation_counter and \ - self.object_type == other.object_type - return NotImplemented - - def __lt__(self, other): - # This is needed because bisect does not take a comparison function. - if isinstance(other, Field): - return self.creation_counter < other.creation_counter - return NotImplemented - - def __hash__(self): - return hash((self.creation_counter, self.object_type)) - - def __copy__(self): - # We need to avoid hitting __reduce__, so define this - # slightly weird copy construct. - obj = Empty() - obj.__class__ = self.__class__ - obj.__dict__ = self.__dict__.copy() - if self.field_type == 'self': - obj.field_type = self.object_type - return obj +class IntField(DeprecatedField, Int): + pass -class LazyField(Field): - - def inner_field(self, schema): - return self.get_field(schema) - - def internal_type(self, schema): - return self.inner_field(schema).internal_type(schema) - - def internal_field(self, schema): - return self.inner_field(schema).internal_field(schema) +class BooleanField(DeprecatedField, Boolean): + pass -class TypeField(Field): - - def __init__(self, *args, **kwargs): - super(TypeField, self).__init__(self.field_type, *args, **kwargs) +class IDField(DeprecatedField, ID): + pass -class StringField(TypeField): - field_type = GraphQLString +class FloatField(DeprecatedField, Float): + pass -class IntField(TypeField): - field_type = GraphQLInt +class ListField(DeprecatedField, List): + pass -class BooleanField(TypeField): - field_type = GraphQLBoolean - - -class IDField(TypeField): - field_type = GraphQLID - - -class FloatField(TypeField): - field_type = GraphQLFloat - - -class ListField(Field): - - def type_wrapper(self, field_type): - return GraphQLList(field_type) - - -class NonNullField(Field): - - def type_wrapper(self, field_type): - return GraphQLNonNull(field_type) +class NonNullField(DeprecatedField, NonNull): + pass diff --git a/graphene/core/ntypes/tests/__init__.py b/graphene/core/ntypes/tests/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/graphene/core/schema.py b/graphene/core/schema.py index 1ad1141d..66184b61 100644 --- a/graphene/core/schema.py +++ b/graphene/core/schema.py @@ -1,3 +1,4 @@ +import inspect from collections import OrderedDict from graphene import signals @@ -6,6 +7,7 @@ from graphql.core.execution.middlewares.sync import \ SynchronousExecutionMiddleware from graphql.core.type import GraphQLSchema as _GraphQLSchema from graphql.core.utils.introspection_query import introspection_query +from graphene.core.types.base import BaseType class GraphQLSchema(_GraphQLSchema): @@ -34,13 +36,15 @@ class Schema(object): def T(self, object_type): if not object_type: return - if object_type not in self._types: - internal_type = object_type.internal_type(self) - self._types[object_type] = internal_type - name = getattr(internal_type, 'name', None) - if name: - self._types_names[name] = object_type - return self._types[object_type] + # if inspect.isclass(object_type) and issubclass(object_type, BaseType): + if True: + if object_type not in self._types: + internal_type = object_type.internal_type(self) + self._types[object_type] = internal_type + name = getattr(internal_type, 'name', None) + if name: + self._types_names[name] = object_type + return self._types[object_type] @property def query(self): diff --git a/graphene/core/types/__init__.py b/graphene/core/types/__init__.py new file mode 100644 index 00000000..17c5283f --- /dev/null +++ b/graphene/core/types/__init__.py @@ -0,0 +1 @@ +from .objecttype import ObjectTypeMeta, BaseObjectType, ObjectType, Interface, Mutation, InputObjectType diff --git a/graphene/core/ntypes/argument.py b/graphene/core/types/argument.py similarity index 95% rename from graphene/core/ntypes/argument.py rename to graphene/core/types/argument.py index 6e8788ce..2e5ebaf6 100644 --- a/graphene/core/ntypes/argument.py +++ b/graphene/core/types/argument.py @@ -31,7 +31,7 @@ def to_arguments(*args, **kwargs): elif isinstance(arg, ArgumentType): argument = arg.as_argument() else: - raise ValueError('Unknown argument value type %r' % arg) + raise ValueError('Unknown argument %s=%r' % (name, arg)) if name: argument.name = to_camel_case(name) diff --git a/graphene/core/ntypes/base.py b/graphene/core/types/base.py similarity index 86% rename from graphene/core/ntypes/base.py rename to graphene/core/types/base.py index 20b1d271..e1ca98ae 100644 --- a/graphene/core/ntypes/base.py +++ b/graphene/core/types/base.py @@ -1,9 +1,14 @@ from functools import total_ordering -from ..types import BaseObjectType, InputObjectType + + +class BaseType(object): + @classmethod + def internal_type(cls, schema): + return getattr(cls, 'T', None) @total_ordering -class OrderedType(object): +class OrderedType(BaseType): creation_counter = 0 def __init__(self, _creation_counter=None): @@ -38,10 +43,6 @@ class MirroredType(OrderedType): self.args = args self.kwargs = kwargs - @classmethod - def internal_type(cls, schema): - return getattr(cls, 'T', None) - class ArgumentType(MirroredType): def as_argument(self): @@ -51,6 +52,7 @@ class ArgumentType(MirroredType): class FieldType(MirroredType): def contribute_to_class(self, cls, name): + from ..types import BaseObjectType, InputObjectType if issubclass(cls, InputObjectType): inputfield = self.as_inputfield() return inputfield.contribute_to_class(cls, name) @@ -60,11 +62,11 @@ class FieldType(MirroredType): def as_field(self): from .field import Field - return Field(self.__class__, _creation_counter=self.creation_counter, *self.args, **self.kwargs) + return Field(self, _creation_counter=self.creation_counter, *self.args, **self.kwargs) def as_inputfield(self): from .field import InputField - return InputField(self.__class__, _creation_counter=self.creation_counter, *self.args, **self.kwargs) + return InputField(self, _creation_counter=self.creation_counter, *self.args, **self.kwargs) class MountedType(FieldType, ArgumentType): diff --git a/graphene/core/ntypes/definitions.py b/graphene/core/types/definitions.py similarity index 100% rename from graphene/core/ntypes/definitions.py rename to graphene/core/types/definitions.py diff --git a/graphene/core/ntypes/field.py b/graphene/core/types/field.py similarity index 80% rename from graphene/core/ntypes/field.py rename to graphene/core/types/field.py index 13bdcf62..5e03e2d0 100644 --- a/graphene/core/ntypes/field.py +++ b/graphene/core/types/field.py @@ -9,6 +9,10 @@ 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): _creation_counter = kwargs.pop('_creation_counter', None) @@ -18,6 +22,7 @@ class Field(OrderedType): self.description = description args = OrderedDict(args or {}, **kwargs) self.arguments = to_arguments(*args_list, **args) + self.object_type = None self.resolver = resolver def contribute_to_class(self, cls, attname): @@ -32,7 +37,7 @@ class Field(OrderedType): @property def resolver(self): - return self._resolver + return self._resolver or self.get_resolver_fn() @resolver.setter def resolver(self, value): @@ -51,8 +56,6 @@ class Field(OrderedType): def internal_type(self, schema): resolver = self.resolver description = self.description - if not resolver: - resolver = self.get_resolver_fn() if not description and resolver: description = resolver.__doc__ @@ -65,6 +68,26 @@ class Field(OrderedType): 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. + """ + path = '%s.%s' % (self.__class__.__module__, self.__class__.__name__) + name = getattr(self, 'attname', None) + if name is not None: + return '<%s: %s>' % (path, name) + return '<%s>' % path + + def __hash__(self): + return hash((self.creation_counter, self.object_type)) + class InputField(OrderedType): def __init__(self, type, description=None, default=None, name=None, _creation_counter=None): diff --git a/graphene/core/types.py b/graphene/core/types/objecttype.py similarity index 97% rename from graphene/core/types.py rename to graphene/core/types/objecttype.py index 2341c0fc..89a77ec3 100644 --- a/graphene/core/types.py +++ b/graphene/core/types/objecttype.py @@ -7,6 +7,7 @@ import six from graphene import signals from graphene.core.options import Options +from graphene.core.types.base import BaseType from graphql.core.type import (GraphQLArgument, GraphQLInputObjectType, GraphQLInterfaceType, GraphQLObjectType) @@ -125,7 +126,7 @@ class ObjectTypeMeta(type): setattr(cls, name, value) -class BaseObjectType(object): +class BaseObjectType(BaseType): def __new__(cls, *args, **kwargs): if cls._meta.is_interface: @@ -185,7 +186,7 @@ class BaseObjectType(object): @classmethod def internal_type(cls, schema): - fields = lambda: OrderedDict([(f.name, f.internal_field(schema)) + fields = lambda: OrderedDict([(f.name, schema.T(f)) for f in cls._meta.fields]) if cls._meta.is_interface: return GraphQLInterfaceType( @@ -222,7 +223,7 @@ class InputObjectType(ObjectType): @classmethod def internal_type(cls, schema): - fields = lambda: OrderedDict([(f.name, f.internal_field(schema)) + fields = lambda: OrderedDict([(f.name, schema.T(f)) for f in cls._meta.fields]) return GraphQLInputObjectType( cls._meta.type_name, diff --git a/graphene/core/ntypes/scalars.py b/graphene/core/types/scalars.py similarity index 100% rename from graphene/core/ntypes/scalars.py rename to graphene/core/types/scalars.py diff --git a/graphene/core/ntypes/__init__.py b/graphene/core/types/tests/__init__.py similarity index 100% rename from graphene/core/ntypes/__init__.py rename to graphene/core/types/tests/__init__.py diff --git a/graphene/core/ntypes/tests/test_argument.py b/graphene/core/types/tests/test_argument.py similarity index 100% rename from graphene/core/ntypes/tests/test_argument.py rename to graphene/core/types/tests/test_argument.py diff --git a/graphene/core/ntypes/tests/test_base.py b/graphene/core/types/tests/test_base.py similarity index 94% rename from graphene/core/ntypes/tests/test_base.py rename to graphene/core/types/tests/test_base.py index 9537f0ea..c512e112 100644 --- a/graphene/core/ntypes/tests/test_base.py +++ b/graphene/core/types/tests/test_base.py @@ -21,7 +21,7 @@ def test_orderedtype_different(): assert b > a -@patch('graphene.core.ntypes.field.Field') +@patch('graphene.core.types.field.Field') def test_type_as_field_called(Field): resolver = lambda x: x a = MountedType(2, description='A', resolver=resolver) @@ -29,7 +29,7 @@ def test_type_as_field_called(Field): Field.assert_called_with(MountedType, 2, _creation_counter=a.creation_counter, description='A', resolver=resolver) -@patch('graphene.core.ntypes.argument.Argument') +@patch('graphene.core.types.argument.Argument') def test_type_as_argument_called(Argument): a = MountedType(2, description='A') a.as_argument() diff --git a/graphene/core/ntypes/tests/test_definitions.py b/graphene/core/types/tests/test_definitions.py similarity index 100% rename from graphene/core/ntypes/tests/test_definitions.py rename to graphene/core/types/tests/test_definitions.py diff --git a/graphene/core/ntypes/tests/test_field.py b/graphene/core/types/tests/test_field.py similarity index 100% rename from graphene/core/ntypes/tests/test_field.py rename to graphene/core/types/tests/test_field.py diff --git a/graphene/core/ntypes/tests/test_scalars.py b/graphene/core/types/tests/test_scalars.py similarity index 100% rename from graphene/core/ntypes/tests/test_scalars.py rename to graphene/core/types/tests/test_scalars.py diff --git a/tests/core/test_fields.py b/tests/core/test_fields.py index be565dd2..d7c4ed12 100644 --- a/tests/core/test_fields.py +++ b/tests/core/test_fields.py @@ -10,9 +10,7 @@ from graphql.core.type import (GraphQLBoolean, GraphQLField, GraphQLID, GraphQLInt, GraphQLNonNull, GraphQLString) -class ObjectType(object): - _meta = Options() - +class ot(ObjectType): def resolve_customdoc(self, *args, **kwargs): '''Resolver documentation''' return None @@ -20,22 +18,20 @@ class ObjectType(object): def __str__(self): return "ObjectType" -ot = ObjectType - schema = Schema() def test_field_no_contributed_raises_error(): f = Field(GraphQLString) with raises(Exception) as excinfo: - f.internal_field(schema) + schema.T(f) def test_field_type(): f = Field(GraphQLString) f.contribute_to_class(ot, 'field_name') - assert isinstance(f.internal_field(schema), GraphQLField) - assert f.internal_type(schema) == GraphQLString + assert isinstance(schema.T(f), GraphQLField) + assert schema.T(f).type == GraphQLString def test_field_name_automatic_camelcase(): diff --git a/tests/core/test_query.py b/tests/core/test_query.py index 4814460f..739bbdc6 100644 --- a/tests/core/test_query.py +++ b/tests/core/test_query.py @@ -4,8 +4,7 @@ from graphene.core.fields import Field, ListField, StringField from graphene.core.schema import Schema from graphene.core.types import Interface, ObjectType from graphql.core import graphql -from graphql.core.type import (GraphQLInterfaceType, GraphQLObjectType, - GraphQLSchema) +from graphql.core.type import GraphQLSchema class Character(Interface): @@ -38,8 +37,8 @@ Human_type = schema.T(Human) def test_type(): - assert Human._meta.fields_map['name'].resolve( - Human(object()), None, None) == 'Peter' + assert Human._meta.fields_map['name'].resolver( + Human(object()), {}, None) == 'Peter' def test_query(): From 6ad668fe38593b53b486b06c55d1ee38bad245ee Mon Sep 17 00:00:00 2001 From: Syrus Akbary Date: Tue, 10 Nov 2015 11:52:29 -0800 Subject: [PATCH 04/20] Fixed tests --- graphene/core/types/tests/test_argument.py | 2 +- graphene/core/types/tests/test_base.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/graphene/core/types/tests/test_argument.py b/graphene/core/types/tests/test_argument.py index ad7650b1..08a7718a 100644 --- a/graphene/core/types/tests/test_argument.py +++ b/graphene/core/types/tests/test_argument.py @@ -42,4 +42,4 @@ def test_to_arguments_wrong_type(): to_arguments( p=3 ) - assert 'Unknown argument value type 3' == str(excinfo.value) + assert 'Unknown argument p=3' == str(excinfo.value) diff --git a/graphene/core/types/tests/test_base.py b/graphene/core/types/tests/test_base.py index c512e112..1ef17d6a 100644 --- a/graphene/core/types/tests/test_base.py +++ b/graphene/core/types/tests/test_base.py @@ -26,7 +26,7 @@ def test_type_as_field_called(Field): resolver = lambda x: x a = MountedType(2, description='A', resolver=resolver) a.as_field() - Field.assert_called_with(MountedType, 2, _creation_counter=a.creation_counter, description='A', resolver=resolver) + Field.assert_called_with(a, 2, _creation_counter=a.creation_counter, description='A', resolver=resolver) @patch('graphene.core.types.argument.Argument') From b474010060cba077af65d0a6dfe50f9a1cd09ad1 Mon Sep 17 00:00:00 2001 From: Syrus Akbary Date: Tue, 10 Nov 2015 11:54:20 -0800 Subject: [PATCH 05/20] Added LazyType --- graphene/core/schema.py | 3 +-- graphene/core/types/base.py | 9 +++++++++ graphene/core/types/field.py | 5 ++++- graphene/core/types/tests/test_field.py | 20 ++++++++++++++++++++ 4 files changed, 34 insertions(+), 3 deletions(-) diff --git a/graphene/core/schema.py b/graphene/core/schema.py index 66184b61..baf46d59 100644 --- a/graphene/core/schema.py +++ b/graphene/core/schema.py @@ -36,8 +36,7 @@ class Schema(object): def T(self, object_type): if not object_type: return - # if inspect.isclass(object_type) and issubclass(object_type, BaseType): - if True: + if inspect.isclass(object_type) and issubclass(object_type, BaseType) or isinstance(object_type, BaseType): if object_type not in self._types: internal_type = object_type.internal_type(self) self._types[object_type] = internal_type diff --git a/graphene/core/types/base.py b/graphene/core/types/base.py index e1ca98ae..cc8d4e20 100644 --- a/graphene/core/types/base.py +++ b/graphene/core/types/base.py @@ -7,6 +7,15 @@ class BaseType(object): return getattr(cls, 'T', None) +class LazyType(BaseType): + def __init__(self, type_str): + self.type_str = type_str + + def internal_type(self, schema): + type = schema.get_type(self.type_str) + return schema.T(type) + + @total_ordering class OrderedType(BaseType): creation_counter = 0 diff --git a/graphene/core/types/field.py b/graphene/core/types/field.py index 5e03e2d0..d6367b5f 100644 --- a/graphene/core/types/field.py +++ b/graphene/core/types/field.py @@ -1,9 +1,10 @@ +import six from collections import OrderedDict from functools import wraps from graphql.core.type import GraphQLField, GraphQLInputObjectField -from .base import OrderedType +from .base import LazyType, OrderedType from .argument import to_arguments from ...utils import to_camel_case from ..types import BaseObjectType, InputObjectType @@ -18,6 +19,8 @@ class Field(OrderedType): _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': + type = LazyType(type) self.type = type self.description = description args = OrderedDict(args or {}, **kwargs) diff --git a/graphene/core/types/tests/test_field.py b/graphene/core/types/tests/test_field.py index 449f52a3..5f8042d5 100644 --- a/graphene/core/types/tests/test_field.py +++ b/graphene/core/types/tests/test_field.py @@ -2,6 +2,7 @@ from graphql.core.type import GraphQLField, GraphQLInputObjectField, GraphQLStri from ..field import Field, InputField from ..scalars import String +from ..base import LazyType from graphene.core.types import ObjectType, InputObjectType from graphene.core.schema import Schema @@ -52,6 +53,25 @@ def test_field_custom_name(): assert field.attname == 'my_field' +def test_field_self(): + field = Field('self', name='my_customName') + + class MyObjectType(ObjectType): + my_field = field + + assert field.type == MyObjectType + + +def test_field_string_reference(): + field = Field('MyObjectType', name='my_customName') + + class MyObjectType(ObjectType): + my_field = field + + assert isinstance(field.type, LazyType) + assert field.type.type_str == 'MyObjectType' + + def test_field_custom_arguments(): field = Field(None, name='my_customName', p=String()) From 6f87720fc17b552f224749cbcfa4a51e7fea742d Mon Sep 17 00:00:00 2001 From: Syrus Akbary Date: Tue, 10 Nov 2015 17:50:11 -0800 Subject: [PATCH 06/20] Improved tests and coverage --- examples/starwars/schema.py | 2 +- graphene/__init__.py | 19 +++- graphene/core/fields.py | 16 ++- graphene/core/schema.py | 4 +- graphene/core/tests/test_fields.py | 154 ++++++++++++++++++++++++++++ graphene/core/tests/test_schema.py | 157 +++++++++++++++++++++++++++++ graphene/core/types/__init__.py | 5 + graphene/core/types/definitions.py | 5 +- graphene/core/types/field.py | 14 ++- tests/core/test_fields.py | 10 +- 10 files changed, 369 insertions(+), 17 deletions(-) create mode 100644 graphene/core/tests/test_fields.py create mode 100644 graphene/core/tests/test_schema.py diff --git a/examples/starwars/schema.py b/examples/starwars/schema.py index 9e4e2a2f..6107a62d 100644 --- a/examples/starwars/schema.py +++ b/examples/starwars/schema.py @@ -14,7 +14,7 @@ Episode = graphene.Enum('Episode', dict( class Character(graphene.Interface): id = graphene.IDField() name = graphene.StringField() - friends = graphene.ListField('self') + friends = graphene.ListField('Character') appears_in = graphene.ListField(Episode) def resolve_friends(self, args, *_): diff --git a/graphene/__init__.py b/graphene/__init__.py index ad3b3b10..c8c1d03d 100644 --- a/graphene/__init__.py +++ b/graphene/__init__.py @@ -1,9 +1,5 @@ from graphql.core.type import ( - GraphQLEnumType as Enum, - GraphQLArgument as Argument, - GraphQLString as String, - GraphQLInt as Int, - GraphQLID as ID + GraphQLEnumType as Enum ) from graphene import signals @@ -16,6 +12,19 @@ from graphene.core.types import ( ObjectType, Interface, Mutation, + BaseType, + LazyType, + OrderedType, + Argument, + Field, + InputField, + String, + Int, + Boolean, + ID, + Float, + List, + NonNull ) from graphene.core.fields import ( diff --git a/graphene/core/fields.py b/graphene/core/fields.py index d2edef38..99782efc 100644 --- a/graphene/core/fields.py +++ b/graphene/core/fields.py @@ -1,14 +1,26 @@ +import warnings + +from .types.base import FieldType from .types.field import Field from .types.scalars import String, Int, Boolean, ID, Float from .types.definitions import List, NonNull -class DeprecatedField(object): +class DeprecatedField(FieldType): def __init__(self, *args, **kwargs): - print("Using {} is not longer supported".format(self.__class__.__name__)) + 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) 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 baf46d59..ec9637fd 100644 --- a/graphene/core/schema.py +++ b/graphene/core/schema.py @@ -44,6 +44,8 @@ class Schema(object): if name: self._types_names[name] = object_type return self._types[object_type] + else: + return object_type @property def query(self): @@ -83,7 +85,7 @@ class Schema(object): return object_type def get_type(self, type_name): - self.schema._build_type_map() + # self.schema._build_type_map() if type_name not in self._types_names: raise Exception('Type %s not found in %r' % (type_name, self)) return self._types_names[type_name] diff --git a/graphene/core/tests/test_fields.py b/graphene/core/tests/test_fields.py new file mode 100644 index 00000000..d919c26f --- /dev/null +++ b/graphene/core/tests/test_fields.py @@ -0,0 +1,154 @@ + +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 = StringField(required=True).as_field() + 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').as_field() + f.contribute_to_class(ot, 'field_name') + field_type = schema.T(f) + assert 'RESOLVED' == field_type.resolver(ot, None, None) + + +def test_field_resolve_type_custom(): + class MyCustomType(ObjectType): + pass + + f = Field('MyCustomType') + + class OtherType(ObjectType): + field_name = f + + s = Schema() + s.query = OtherType + s.register(MyCustomType) + + assert s.T(f).type == s.T(MyCustomType) + + +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: + s.T(f) + assert str( + excinfo.value) == "Internal type for field ot.field_name is None" + + +def test_field_str(): + f = StringField().as_field() + f.contribute_to_class(ot, 'field_name') + assert str(f) == "ot.field_name" + + +def test_field_repr(): + f = StringField().as_field() + assert repr(f) == "" + + +def test_field_repr_contributed(): + f = StringField().as_field() + f.contribute_to_class(ot, 'field_name') + assert repr(f) == "" + + +def test_field_resolve_objecttype_cos(): + f = StringField().as_field() + f.contribute_to_class(ot, 'customdoc') + field = schema.T(f) + assert field.description == 'Resolver documentation' diff --git a/graphene/core/tests/test_schema.py b/graphene/core/tests/test_schema.py new file mode 100644 index 00000000..082ed080 --- /dev/null +++ b/graphene/core/tests/test_schema.py @@ -0,0 +1,157 @@ + +from py.test import raises +from pytest import raises + +from graphene import Interface, ObjectType, Schema +from graphene.core.fields import Field, ListField, StringField +from graphene.core.types.base import LazyType +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 + + +def test_lazytype(): + schema = Schema(name='My own schema') + + t = LazyType('MyType') + + class MyType(ObjectType): + type = StringField(resolve=lambda *_: 'Dog') + + schema.query = MyType + + assert schema.T(t) == schema.T(MyType) diff --git a/graphene/core/types/__init__.py b/graphene/core/types/__init__.py index 17c5283f..adf3c2a2 100644 --- a/graphene/core/types/__init__.py +++ b/graphene/core/types/__init__.py @@ -1 +1,6 @@ from .objecttype import ObjectTypeMeta, BaseObjectType, ObjectType, Interface, Mutation, InputObjectType +from .base import BaseType, LazyType, OrderedType +from .argument import Argument +from .field import Field, InputField +from .scalars import String, Int, Boolean, ID, Float +from .definitions import List, NonNull diff --git a/graphene/core/types/definitions.py b/graphene/core/types/definitions.py index 7eaf9a88..573be0f9 100644 --- a/graphene/core/types/definitions.py +++ b/graphene/core/types/definitions.py @@ -1,10 +1,13 @@ +import six from graphql.core.type import (GraphQLList, GraphQLNonNull) -from .base import MountedType +from .base import MountedType, LazyType class OfType(MountedType): def __init__(self, of_type, *args, **kwargs): + if isinstance(of_type, six.string_types) and of_type != 'self': + of_type = LazyType(of_type) self.of_type = of_type super(OfType, self).__init__(*args, **kwargs) diff --git a/graphene/core/types/field.py b/graphene/core/types/field.py index d6367b5f..599fe83e 100644 --- a/graphene/core/types/field.py +++ b/graphene/core/types/field.py @@ -55,14 +55,20 @@ class Field(OrderedType): 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 default_getter def internal_type(self, schema): resolver = self.resolver description = self.description if not description and resolver: description = resolver.__doc__ - - return GraphQLField(schema.T(self.type), args=self.get_arguments(schema), resolver=resolver, + type = schema.T(self.type) + assert type, 'Internal type for field %s is None' % str(self) + return GraphQLField(type, args=self.get_arguments(schema), resolver=resolver, description=description,) def get_arguments(self, schema): @@ -88,6 +94,10 @@ class Field(OrderedType): return '<%s: %s>' % (path, name) return '<%s>' % path + def __str__(self): + """ Return "object_type.field_name". """ + return '%s.%s' % (self.object_type.__name__, self.attname) + def __hash__(self): return hash((self.creation_counter, self.object_type)) diff --git a/tests/core/test_fields.py b/tests/core/test_fields.py index d7c4ed12..bb3c0c27 100644 --- a/tests/core/test_fields.py +++ b/tests/core/test_fields.py @@ -49,20 +49,20 @@ def test_field_name_use_name_if_exists(): def test_stringfield_type(): f = StringField() f.contribute_to_class(ot, 'field_name') - assert f.internal_type(schema) == GraphQLString + assert schema.T(f) == GraphQLString def test_nonnullfield_type(): f = NonNullField(StringField()) f.contribute_to_class(ot, 'field_name') - assert isinstance(f.internal_type(schema), GraphQLNonNull) + assert isinstance(schema.T(f), GraphQLNonNull) def test_stringfield_type_required(): - f = StringField(required=True) + f = Field(StringField(required=True)) f.contribute_to_class(ot, 'field_name') - assert isinstance(f.internal_field(schema), GraphQLField) - assert isinstance(f.internal_type(schema), GraphQLNonNull) + assert isinstance(schema.T(f), GraphQLField) + assert isinstance(schema.T(f).type, GraphQLNonNull) def test_field_resolve(): From 28d89a44f14d951b6ed6a3e393907f73f8cce35e Mon Sep 17 00:00:00 2001 From: Syrus Akbary Date: Tue, 10 Nov 2015 21:29:11 -0800 Subject: [PATCH 07/20] 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 From 41648b5a946a06c847575f080fe7d9d5c48ea286 Mon Sep 17 00:00:00 2001 From: Syrus Akbary Date: Tue, 10 Nov 2015 22:57:22 -0800 Subject: [PATCH 08/20] Improved relay integration --- graphene/core/types/field.py | 5 +++-- graphene/core/types/objecttype.py | 28 +++++++++++++++------------- graphene/relay/fields.py | 20 ++++++++++---------- graphene/relay/types.py | 26 ++++++++++++++------------ tests/relay/test_relay_mutations.py | 26 +++++++++++++------------- tests/relay/test_relayfields.py | 7 ++++--- 6 files changed, 59 insertions(+), 53 deletions(-) diff --git a/graphene/core/types/field.py b/graphene/core/types/field.py index 56f172f5..6ea48d86 100644 --- a/graphene/core/types/field.py +++ b/graphene/core/types/field.py @@ -21,7 +21,8 @@ class Field(OrderedType): self.name = name if isinstance(type, six.string_types): type = LazyType(type) - if required: + self.required = required + if self.required: type = NonNull(type) self.type = type self.description = description @@ -68,7 +69,7 @@ class Field(OrderedType): type_objecttype = schema.objecttype(type) if type_objecttype and type_objecttype._meta.is_mutation: assert len(arguments) == 0 - arguments = type_objecttype.arguments + arguments = type_objecttype.get_arguments() resolver = getattr(type_objecttype, 'mutate') assert type, 'Internal type for field %s is None' % str(self) diff --git a/graphene/core/types/objecttype.py b/graphene/core/types/objecttype.py index a6c73602..8399c75d 100644 --- a/graphene/core/types/objecttype.py +++ b/graphene/core/types/objecttype.py @@ -52,10 +52,6 @@ class ObjectTypeMeta(type): assert not ( new_class._meta.is_interface and new_class._meta.is_mutation) - input_class = None - if new_class._meta.is_mutation: - input_class = attrs.pop('Input', None) - # Add all attributes to the class. for obj_name, obj in attrs.items(): new_class.add_to_class(obj_name, obj) @@ -64,14 +60,6 @@ class ObjectTypeMeta(type): assert hasattr( new_class, 'mutate'), "All mutations must implement mutate method" - if input_class: - items = dict(input_class.__dict__) - items.pop('__dict__', None) - items.pop('__doc__', None) - items.pop('__module__', None) - arguments = ArgumentsGroup(**items) - new_class.add_to_class('arguments', arguments) - new_class.add_extra_fields() new_fields = new_class._meta.local_fields @@ -215,7 +203,21 @@ class ObjectType(six.with_metaclass(ObjectTypeMeta, BaseObjectType)): class Mutation(six.with_metaclass(ObjectTypeMeta, BaseObjectType)): - pass + @classmethod + def _prepare_class(cls): + input_class = getattr(cls, 'Input', None) + if input_class: + items = dict(input_class.__dict__) + items.pop('__dict__', None) + items.pop('__doc__', None) + items.pop('__module__', None) + arguments = ArgumentsGroup(**items) + cls.add_to_class('arguments', arguments) + delattr(cls, 'Input') + + @classmethod + def get_arguments(cls): + return cls.arguments class InputObjectType(ObjectType): diff --git a/graphene/relay/fields.py b/graphene/relay/fields.py index 7efa30d8..78ef0cc6 100644 --- a/graphene/relay/fields.py +++ b/graphene/relay/fields.py @@ -1,18 +1,21 @@ from collections import Iterable from graphene.core.fields import Field, IDField +from graphene.core.types.scalars import String, ID from graphql.core.type import GraphQLArgument, GraphQLID, GraphQLNonNull from graphql_relay.connection.arrayconnection import connection_from_list -from graphql_relay.connection.connection import connection_args from graphql_relay.node.node import from_global_id class ConnectionField(Field): - def __init__(self, field_type, resolve=None, description='', + def __init__(self, field_type, resolver=None, description='', connection_type=None, edge_type=None, **kwargs): - super(ConnectionField, self).__init__(field_type, resolve=resolve, - args=connection_args, + super(ConnectionField, self).__init__(field_type, resolver=resolver, + before=String(), + after=String(), + first=String(), + last=String(), description=description, **kwargs) self.connection_type = connection_type self.edge_type = edge_type @@ -60,12 +63,9 @@ class NodeField(Field): def __init__(self, object_type=None, *args, **kwargs): from graphene.relay.types import Node + kwargs['id'] = ID(description='The ID of an object') super(NodeField, self).__init__(object_type or Node, *args, **kwargs) self.field_object_type = object_type - self.args['id'] = GraphQLArgument( - GraphQLNonNull(GraphQLID), - description='The ID of an object' - ) def id_fetcher(self, global_id, info): from graphene.relay.utils import is_node @@ -88,11 +88,11 @@ class GlobalIDField(IDField): '''The ID of an object''' required = True - def contribute_to_class(self, cls, name, add=True): + def contribute_to_class(self, cls, name): from graphene.relay.utils import is_node, is_node_type in_node = is_node(cls) or is_node_type(cls) assert in_node, 'GlobalIDField could only be inside a Node, but got %r' % cls - super(GlobalIDField, self).contribute_to_class(cls, name, add) + super(GlobalIDField, self).contribute_to_class(cls, name) def resolve(self, instance, args, info): return self.object_type.to_global_id(instance, args, info) diff --git a/graphene/relay/types.py b/graphene/relay/types.py index 748db640..dfbd51c1 100644 --- a/graphene/relay/types.py +++ b/graphene/relay/types.py @@ -1,6 +1,8 @@ from graphene.core.fields import BooleanField, Field, ListField, StringField from graphene.core.types import (InputObjectType, Interface, Mutation, ObjectType) +from graphene.core.types.argument import ArgumentsGroup +from graphene.core.types.definitions import NonNull from graphene.relay.fields import GlobalIDField from graphene.utils import memoize from graphql_relay.node.node import to_global_id @@ -90,7 +92,7 @@ class BaseNode(object): class Node(BaseNode, Interface): '''An object with an ID''' - id = GlobalIDField() + id = GlobalIDField(required=True) class MutationInputType(InputObjectType): @@ -102,19 +104,19 @@ class ClientIDMutation(Mutation): @classmethod def _prepare_class(cls): - input_type = getattr(cls, 'input_type', None) - if input_type: + Input = getattr(cls, 'Input', None) + if Input: assert hasattr( cls, 'mutate_and_get_payload'), 'You have to implement mutate_and_get_payload' - new_input_inner_type = type('{}InnerInput'.format( - cls._meta.type_name), (MutationInputType, input_type, ), {}) - items = { - 'input': Field(new_input_inner_type) - } - assert issubclass(new_input_inner_type, InputObjectType) - input_type = type('{}Input'.format( - cls._meta.type_name), (ObjectType, ), items) - setattr(cls, 'input_type', input_type) + + items = dict(Input.__dict__) + items.pop('__dict__', None) + new_input_type = type('{}Input'.format( + cls._meta.type_name), (MutationInputType, ), items) + cls.add_to_class('input_type', new_input_type) + arguments = ArgumentsGroup(input=NonNull(new_input_type)) + cls.add_to_class('arguments', arguments) + delattr(cls, 'Input') @classmethod def mutate(cls, instance, args, info): diff --git a/tests/relay/test_relay_mutations.py b/tests/relay/test_relay_mutations.py index de206d70..5f3a91a3 100644 --- a/tests/relay/test_relay_mutations.py +++ b/tests/relay/test_relay_mutations.py @@ -32,19 +32,19 @@ class MyResultMutation(graphene.ObjectType): 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()) == ['input'] - _input = ChangeNumber.input_type._meta.fields_map['input'] - inner_type = _input.get_object_type(schema) - client_mutation_id_field = inner_type._meta.fields_map[ - 'client_mutation_id'] - assert issubclass(inner_type, InputObjectType) - assert isinstance(client_mutation_id_field, graphene.StringField) - assert client_mutation_id_field.object_type == inner_type - assert isinstance(client_mutation_id_field.internal_field( - schema), GraphQLInputObjectField) +def test_mutation_arguments(): + assert ChangeNumber.arguments + assert list(ChangeNumber.arguments) == ['input'] + _input = ChangeNumber.arguments['input'] + + # inner_type = _input.get_object_type(schema) + # client_mutation_id_field = inner_type._meta.fields_map[ + # 'client_mutation_id'] + # assert issubclass(inner_type, InputObjectType) + # assert isinstance(client_mutation_id_field, graphene.StringField) + # assert client_mutation_id_field.object_type == inner_type + # assert isinstance(client_mutation_id_field.internal_field( + # schema), GraphQLInputObjectField) def test_execute_mutations(): diff --git a/tests/relay/test_relayfields.py b/tests/relay/test_relayfields.py index 34e6d305..44313daf 100644 --- a/tests/relay/test_relayfields.py +++ b/tests/relay/test_relayfields.py @@ -21,7 +21,7 @@ class MyNode(relay.Node): class Query(graphene.ObjectType): my_node = relay.NodeField(MyNode) all_my_nodes = relay.ConnectionField( - MyNode, connection_type=MyConnection, customArg=graphene.Argument(graphene.String)) + MyNode, connection_type=MyConnection, customArg=graphene.String()) def resolve_all_my_nodes(self, args, info): custom_arg = args.get('customArg') @@ -73,5 +73,6 @@ def test_nodefield_query(): def test_nodeidfield(): id_field = MyNode._meta.fields_map['id'] - assert isinstance(id_field.internal_field(schema).type, GraphQLNonNull) - assert id_field.internal_field(schema).type.of_type == GraphQLID + id_field_type = schema.T(id_field) + assert isinstance(id_field_type.type, GraphQLNonNull) + assert id_field_type.type.of_type == GraphQLID From 862fa6f3121f75c935ceb85460a6cdec9305f0e1 Mon Sep 17 00:00:00 2001 From: Syrus Akbary Date: Tue, 10 Nov 2015 23:05:11 -0800 Subject: [PATCH 09/20] Fixed argument types --- graphene/core/types/base.py | 2 +- graphene/core/types/field.py | 12 +++++++++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/graphene/core/types/base.py b/graphene/core/types/base.py index f3c04ad6..becf6883 100644 --- a/graphene/core/types/base.py +++ b/graphene/core/types/base.py @@ -59,7 +59,7 @@ class MirroredType(OrderedType): class ArgumentType(MirroredType): def as_argument(self): from .argument import Argument - return Argument(self.__class__, _creation_counter=self.creation_counter, *self.args, **self.kwargs) + return Argument(self, _creation_counter=self.creation_counter, *self.args, **self.kwargs) class FieldType(MirroredType): diff --git a/graphene/core/types/field.py b/graphene/core/types/field.py index 6ea48d86..a51fb967 100644 --- a/graphene/core/types/field.py +++ b/graphene/core/types/field.py @@ -1,15 +1,24 @@ 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 ArgumentsGroup from .definitions import NonNull -from ...utils import to_camel_case +from ...utils import to_camel_case, ProxySnakeDict from ..types import BaseObjectType, InputObjectType +def make_args_snake_case(resolver): + @wraps(resolver) + def wrapped_resolver(instance, args, info): + return resolver(instance, ProxySnakeDict(args), info) + + return wrapped_resolver + + class Empty(object): pass @@ -72,6 +81,7 @@ class Field(OrderedType): arguments = type_objecttype.get_arguments() resolver = getattr(type_objecttype, 'mutate') + resolver = make_args_snake_case(resolver) assert type, 'Internal type for field %s is None' % str(self) return GraphQLField(type, args=schema.T(arguments), resolver=resolver, description=description,) From bdcd533bf9998881c84644b3f3122be0d5a0edf8 Mon Sep 17 00:00:00 2001 From: Syrus Akbary Date: Wed, 11 Nov 2015 00:55:15 -0800 Subject: [PATCH 10/20] Fixed tests --- graphene/core/types/tests/test_base.py | 2 +- graphene/core/types/tests/test_field.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/graphene/core/types/tests/test_base.py b/graphene/core/types/tests/test_base.py index 1ef17d6a..50d14c69 100644 --- a/graphene/core/types/tests/test_base.py +++ b/graphene/core/types/tests/test_base.py @@ -33,7 +33,7 @@ def test_type_as_field_called(Field): def test_type_as_argument_called(Argument): a = MountedType(2, description='A') a.as_argument() - Argument.assert_called_with(MountedType, 2, _creation_counter=a.creation_counter, description='A') + Argument.assert_called_with(a, 2, _creation_counter=a.creation_counter, description='A') def test_type_as_field(): diff --git a/graphene/core/types/tests/test_field.py b/graphene/core/types/tests/test_field.py index db5444a3..c86918a5 100644 --- a/graphene/core/types/tests/test_field.py +++ b/graphene/core/types/tests/test_field.py @@ -8,7 +8,7 @@ from graphene.core.schema import Schema def test_field_internal_type(): - resolver = lambda *args: args + resolver = lambda *args: 'RESOLVED' field = Field(String, description='My argument', resolver=resolver) @@ -21,7 +21,7 @@ def test_field_internal_type(): assert field.attname == 'my_field' assert isinstance(type, GraphQLField) assert type.description == 'My argument' - assert type.resolver == resolver + assert type.resolver(None, {}, None) == 'RESOLVED' assert type.type == GraphQLString From cfba52e6f39b5d2ddfe575d99492cd296947be0d Mon Sep 17 00:00:00 2001 From: Syrus Akbary Date: Wed, 11 Nov 2015 02:12:17 -0800 Subject: [PATCH 11/20] Improved lazy type resolvers --- graphene/core/types/base.py | 35 ++++++++++++++++++++----- graphene/core/types/definitions.py | 7 ++++- graphene/core/types/field.py | 15 ++++++++--- graphene/core/types/tests/test_field.py | 15 ++++++++++- graphene/relay/fields.py | 6 +++-- graphene/relay/types.py | 5 ++-- 6 files changed, 67 insertions(+), 16 deletions(-) diff --git a/graphene/core/types/base.py b/graphene/core/types/base.py index becf6883..b0acf52d 100644 --- a/graphene/core/types/base.py +++ b/graphene/core/types/base.py @@ -1,3 +1,4 @@ +import six from functools import total_ordering @@ -7,20 +8,36 @@ class BaseType(object): return getattr(cls, 'T', None) -class LazyType(BaseType): - def __init__(self, type_str): - self.type_str = type_str +class MountType(BaseType): + parent = None + def mount(self, cls): + self.parent = cls + + +class LazyType(MountType): + def __init__(self, type): + self.type = type + + @property def is_self(self): - return self.type_str == 'self' + return self.type == 'self' def internal_type(self, schema): - type = schema.get_type(self.type_str) + type = None + if callable(self.type): + type = self.type(self.parent) + elif isinstance(self.type, six.string_types): + if self.is_self: + type = self.parent + else: + type = schema.get_type(self.type) + assert type, 'Type in %s %r cannot be none' % (self.type, self.parent) return schema.T(type) @total_ordering -class OrderedType(BaseType): +class OrderedType(MountType): creation_counter = 0 def __init__(self, _creation_counter=None): @@ -44,6 +61,12 @@ class OrderedType(BaseType): return self.creation_counter < other.creation_counter return NotImplemented + def __gt__(self, other): + # This is needed because bisect does not take a comparison function. + if type(self) == type(other): + return self.creation_counter > other.creation_counter + return NotImplemented + def __hash__(self): return hash((self.creation_counter)) diff --git a/graphene/core/types/definitions.py b/graphene/core/types/definitions.py index 573be0f9..15b03cd8 100644 --- a/graphene/core/types/definitions.py +++ b/graphene/core/types/definitions.py @@ -1,7 +1,7 @@ import six from graphql.core.type import (GraphQLList, GraphQLNonNull) -from .base import MountedType, LazyType +from .base import MountType, MountedType, LazyType class OfType(MountedType): @@ -14,6 +14,11 @@ class OfType(MountedType): def internal_type(self, schema): return self.T(schema.T(self.of_type)) + def mount(self, cls): + self.parent = cls + if isinstance(self.of_type, MountType): + self.of_type.mount(cls) + class List(OfType): T = GraphQLList diff --git a/graphene/core/types/field.py b/graphene/core/types/field.py index a51fb967..eae3ae69 100644 --- a/graphene/core/types/field.py +++ b/graphene/core/types/field.py @@ -4,7 +4,7 @@ from functools import wraps from graphql.core.type import GraphQLField, GraphQLInputObjectField -from .base import LazyType, OrderedType +from .base import MountType, LazyType, OrderedType from .argument import ArgumentsGroup from .definitions import NonNull from ...utils import to_camel_case, ProxySnakeDict @@ -47,8 +47,9 @@ class Field(OrderedType): self.name = to_camel_case(attname) self.attname = attname self.object_type = cls - if isinstance(self.type, LazyType) and self.type.is_self(): - self.type = cls + self.mount(cls) + if isinstance(self.type, MountType): + self.type.mount(cls) cls._meta.add_field(self) @property @@ -68,13 +69,16 @@ class Field(OrderedType): return getattr(instance, self.attname, self.default) return default_getter + def get_type(self, schema): + return self.type + 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 = schema.T(self.get_type(schema)) type_objecttype = schema.objecttype(type) if type_objecttype and type_objecttype._meta.is_mutation: assert len(arguments) == 0 @@ -120,6 +124,9 @@ class InputField(OrderedType): self.name = to_camel_case(attname) self.attname = attname self.object_type = cls + self.mount(cls) + if isinstance(self.type, MountType): + self.type.mount(cls) cls._meta.add_field(self) def internal_type(self, schema): diff --git a/graphene/core/types/tests/test_field.py b/graphene/core/types/tests/test_field.py index c86918a5..1a0e90c7 100644 --- a/graphene/core/types/tests/test_field.py +++ b/graphene/core/types/tests/test_field.py @@ -3,6 +3,7 @@ from graphql.core.type import GraphQLField, GraphQLInputObjectField, GraphQLStri from ..field import Field, InputField from ..scalars import String from ..base import LazyType +from ..definitions import List from graphene.core.types import ObjectType, InputObjectType from graphene.core.schema import Schema @@ -59,7 +60,19 @@ def test_field_self(): class MyObjectType(ObjectType): my_field = field - assert field.type == MyObjectType + schema = Schema() + + assert schema.T(field).type == schema.T(MyObjectType) + + +def test_field_mounted(): + field = Field(List('MyObjectType'), name='my_customName') + + class MyObjectType(ObjectType): + my_field = field + + assert field.parent == MyObjectType + assert field.type.parent == MyObjectType def test_field_string_reference(): diff --git a/graphene/relay/fields.py b/graphene/relay/fields.py index 78ef0cc6..13e76559 100644 --- a/graphene/relay/fields.py +++ b/graphene/relay/fields.py @@ -23,6 +23,7 @@ class ConnectionField(Field): def wrap_resolved(self, value, instance, args, info): return value + def resolve(self, instance, args, info): from graphene.relay.types import PageInfo schema = info.schema.graphene_schema @@ -50,9 +51,10 @@ class ConnectionField(Field): def get_edge_type(self, node): return self.edge_type or node.get_edge_type() - def internal_type(self, schema): + def get_type(self, schema): from graphene.relay.utils import is_node - node = self.get_object_type(schema) + type = schema.T(self.type) + node = schema.objecttype(type) assert is_node(node), 'Only nodes have connections.' schema.register(node) connection_type = self.get_connection_type(node) diff --git a/graphene/relay/types.py b/graphene/relay/types.py index dfbd51c1..40f04d20 100644 --- a/graphene/relay/types.py +++ b/graphene/relay/types.py @@ -1,6 +1,7 @@ from graphene.core.fields import BooleanField, Field, ListField, StringField from graphene.core.types import (InputObjectType, Interface, Mutation, ObjectType) +from graphene.core.types.base import LazyType from graphene.core.types.argument import ArgumentsGroup from graphene.core.types.definitions import NonNull from graphene.relay.fields import GlobalIDField @@ -24,7 +25,7 @@ class Edge(ObjectType): class Meta: type_name = 'DefaultEdge' - node = Field(lambda field: field.object_type.node_type, + node = Field(LazyType(lambda object_type: object_type.node_type), description='The item at the end of the edge') cursor = StringField( required=True, description='A cursor for use in pagination') @@ -44,7 +45,7 @@ class Connection(ObjectType): page_info = Field(PageInfo, required=True, description='The Information to aid in pagination') - edges = ListField(lambda field: field.object_type.edge_type, + edges = ListField(LazyType(lambda object_type: object_type.edge_type), description='Information to aid in pagination.') _connection_data = None From 2fed5c7e4a7bf7a8cc6d5fad909e5588ab9c4776 Mon Sep 17 00:00:00 2001 From: Syrus Akbary Date: Wed, 11 Nov 2015 02:21:25 -0800 Subject: [PATCH 12/20] Improved fields --- graphene/relay/fields.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/graphene/relay/fields.py b/graphene/relay/fields.py index 13e76559..855dfbfd 100644 --- a/graphene/relay/fields.py +++ b/graphene/relay/fields.py @@ -65,8 +65,8 @@ class NodeField(Field): def __init__(self, object_type=None, *args, **kwargs): from graphene.relay.types import Node - kwargs['id'] = ID(description='The ID of an object') - super(NodeField, self).__init__(object_type or Node, *args, **kwargs) + id = kwargs.pop('id', None) or ID(description='The ID of an object') + super(NodeField, self).__init__(object_type or Node, id=id, *args, **kwargs) self.field_object_type = object_type def id_fetcher(self, global_id, info): From b0f2b4dd55a38061fd1396df06287014c1a4cea7 Mon Sep 17 00:00:00 2001 From: Syrus Akbary Date: Wed, 11 Nov 2015 17:33:23 -0800 Subject: [PATCH 13/20] Improved relay --- graphene/core/schema.py | 10 +++++--- graphene/core/types/field.py | 10 ++++---- graphene/relay/fields.py | 23 +++++++++++-------- .../relay/tests/test_mutations.py | 0 .../relay/tests/test_query.py | 4 +++- .../relay/tests/test_types.py | 0 6 files changed, 28 insertions(+), 19 deletions(-) rename tests/relay/test_relay_mutations.py => graphene/relay/tests/test_mutations.py (100%) rename tests/relay/test_relayfields.py => graphene/relay/tests/test_query.py (95%) rename tests/relay/test_relay.py => graphene/relay/tests/test_types.py (100%) diff --git a/graphene/core/schema.py b/graphene/core/schema.py index 171b0171..49147e48 100644 --- a/graphene/core/schema.py +++ b/graphene/core/schema.py @@ -40,9 +40,9 @@ class Schema(object): if object_type not in self._types: internal_type = object_type.internal_type(self) self._types[object_type] = internal_type - name = getattr(internal_type, 'name', None) - if name: - self._types_names[name] = object_type + is_objecttype = inspect.isclass(object_type) and issubclass(object_type, BaseObjectType) + if is_objecttype: + self.register(object_type) return self._types[object_type] else: return object_type @@ -65,6 +65,10 @@ class Schema(object): return GraphQLSchema(self, query=self.T(self.query), mutation=self.T(self.mutation)) def register(self, object_type): + type_name = object_type._meta.type_name + registered_object_type = self._types_names.get(type_name, None) + if registered_object_type: + assert registered_object_type == object_type, 'Type {} already registered with other object type'.format(type_name) self._types_names[object_type._meta.type_name] = object_type return object_type diff --git a/graphene/core/types/field.py b/graphene/core/types/field.py index eae3ae69..61098485 100644 --- a/graphene/core/types/field.py +++ b/graphene/core/types/field.py @@ -31,14 +31,12 @@ class Field(OrderedType): if isinstance(type, six.string_types): type = LazyType(type) self.required = required - if self.required: - type = NonNull(type) self.type = type self.description = description args = OrderedDict(args or {}, **kwargs) self.arguments = ArgumentsGroup(*args_list, **args) self.object_type = None - self.resolver = resolver + self.resolver_fn = resolver self.default = default def contribute_to_class(self, cls, attname): @@ -54,11 +52,11 @@ class Field(OrderedType): @property def resolver(self): - return self._resolver or self.get_resolver_fn() + return self.resolver_fn or self.get_resolver_fn() @resolver.setter def resolver(self, value): - self._resolver = value + self.resolver_fn = value def get_resolver_fn(self): resolve_fn_name = 'resolve_%s' % self.attname @@ -70,6 +68,8 @@ class Field(OrderedType): return default_getter def get_type(self, schema): + if self.required: + return NonNull(self.type) return self.type def internal_type(self, schema): diff --git a/graphene/relay/fields.py b/graphene/relay/fields.py index 13e76559..22fd3f73 100644 --- a/graphene/relay/fields.py +++ b/graphene/relay/fields.py @@ -1,7 +1,7 @@ from collections import Iterable from graphene.core.fields import Field, IDField -from graphene.core.types.scalars import String, ID +from graphene.core.types.scalars import String, ID, Int from graphql.core.type import GraphQLArgument, GraphQLID, GraphQLNonNull from graphql_relay.connection.arrayconnection import connection_from_list from graphql_relay.node.node import from_global_id @@ -14,8 +14,8 @@ class ConnectionField(Field): super(ConnectionField, self).__init__(field_type, resolver=resolver, before=String(), after=String(), - first=String(), - last=String(), + first=Int(), + last=Int(), description=description, **kwargs) self.connection_type = connection_type self.edge_type = edge_type @@ -24,17 +24,18 @@ class ConnectionField(Field): return value - def resolve(self, instance, args, info): + def resolver(self, instance, args, info): from graphene.relay.types import PageInfo schema = info.schema.graphene_schema - resolved = super(ConnectionField, self).resolve(instance, args, info) + resolved = super(ConnectionField, self).resolver(instance, args, info) if resolved: resolved = self.wrap_resolved(resolved, instance, args, info) assert isinstance( resolved, Iterable), 'Resolved value from the connection field have to be iterable' - node = self.get_object_type(schema) + type = schema.T(self.type) + node = schema.objecttype(type) connection_type = self.get_connection_type(node) edge_type = self.get_edge_type(node) @@ -81,14 +82,16 @@ class NodeField(Field): return object_type.get_node(_id) - def resolve(self, instance, args, info): + def resolver(self, instance, args, info): global_id = args.get('id') return self.id_fetcher(global_id, info) -class GlobalIDField(IDField): +class GlobalIDField(Field): '''The ID of an object''' - required = True + def __init__(self, *args, **kwargs): + super(GlobalIDField, self).__init__(ID(), *args, **kwargs) + self.required = True def contribute_to_class(self, cls, name): from graphene.relay.utils import is_node, is_node_type @@ -96,5 +99,5 @@ class GlobalIDField(IDField): assert in_node, 'GlobalIDField could only be inside a Node, but got %r' % cls super(GlobalIDField, self).contribute_to_class(cls, name) - def resolve(self, instance, args, info): + def resolver(self, instance, args, info): return self.object_type.to_global_id(instance, args, info) diff --git a/tests/relay/test_relay_mutations.py b/graphene/relay/tests/test_mutations.py similarity index 100% rename from tests/relay/test_relay_mutations.py rename to graphene/relay/tests/test_mutations.py diff --git a/tests/relay/test_relayfields.py b/graphene/relay/tests/test_query.py similarity index 95% rename from tests/relay/test_relayfields.py rename to graphene/relay/tests/test_query.py index 44313daf..92e9d6f7 100644 --- a/tests/relay/test_relayfields.py +++ b/graphene/relay/tests/test_query.py @@ -15,7 +15,7 @@ class MyNode(relay.Node): @classmethod def get_node(cls, id): - return MyNode(name='mo') + return MyNode(id=id, name='mo') class Query(graphene.ObjectType): @@ -35,6 +35,7 @@ def test_nodefield_query(): query = ''' query RebelsShipsQuery { myNode(id:"TXlOb2RlOjE=") { + id name }, allMyNodes (customArg:"1") { @@ -52,6 +53,7 @@ def test_nodefield_query(): ''' expected = { 'myNode': { + 'id': 'TXlOb2RlOjE=', 'name': 'mo' }, 'allMyNodes': { diff --git a/tests/relay/test_relay.py b/graphene/relay/tests/test_types.py similarity index 100% rename from tests/relay/test_relay.py rename to graphene/relay/tests/test_types.py From cf657b365cc43bb1d415b1edcf2cae14a9a15584 Mon Sep 17 00:00:00 2001 From: Syrus Akbary Date: Wed, 11 Nov 2015 17:33:37 -0800 Subject: [PATCH 14/20] Improved django support --- graphene/contrib/django/converter.py | 13 +++---- graphene/contrib/django/fields.py | 40 ++++++++-------------- tests/contrib_django/test_converter.py | 47 +++++++++++++------------- tests/contrib_django/test_types.py | 6 ++-- 4 files changed, 50 insertions(+), 56 deletions(-) diff --git a/graphene/contrib/django/converter.py b/graphene/contrib/django/converter.py index b1ddc791..d5799a76 100644 --- a/graphene/contrib/django/converter.py +++ b/graphene/contrib/django/converter.py @@ -5,6 +5,7 @@ from graphene.contrib.django.fields import (ConnectionOrListField, DjangoModelField) from graphene.core.fields import (BooleanField, FloatField, IDField, IntField, StringField) +from graphene.core.types.scalars import Boolean, Float, ID, Int, String try: UUIDField = models.UUIDField @@ -28,12 +29,12 @@ def convert_django_field(field): @convert_django_field.register(models.URLField) @convert_django_field.register(UUIDField) def convert_field_to_string(field): - return StringField(description=field.help_text) + return String(description=field.help_text) @convert_django_field.register(models.AutoField) def convert_field_to_id(field): - return IDField(description=field.help_text) + return ID(description=field.help_text) @convert_django_field.register(models.PositiveIntegerField) @@ -42,23 +43,23 @@ def convert_field_to_id(field): @convert_django_field.register(models.BigIntegerField) @convert_django_field.register(models.IntegerField) def convert_field_to_int(field): - return IntField(description=field.help_text) + return Int(description=field.help_text) @convert_django_field.register(models.BooleanField) def convert_field_to_boolean(field): - return BooleanField(description=field.help_text, required=True) + return Boolean(description=field.help_text, required=True) @convert_django_field.register(models.NullBooleanField) def convert_field_to_nullboolean(field): - return BooleanField(description=field.help_text) + return Boolean(description=field.help_text) @convert_django_field.register(models.DecimalField) @convert_django_field.register(models.FloatField) def convert_field_to_float(field): - return FloatField(description=field.help_text) + return Float(description=field.help_text) @convert_django_field.register(models.ManyToManyField) diff --git a/graphene/contrib/django/fields.py b/graphene/contrib/django/fields.py index 3abbfa94..b95a6d79 100644 --- a/graphene/contrib/django/fields.py +++ b/graphene/contrib/django/fields.py @@ -1,6 +1,7 @@ from graphene import relay from graphene.contrib.django.utils import get_type_for_model, lazy_map -from graphene.core.fields import Field, LazyField, ListField +from graphene.core.fields import Field, ListField +from graphene.core.types.base import FieldType from graphene.relay.utils import is_node @@ -8,60 +9,49 @@ class DjangoConnectionField(relay.ConnectionField): def wrap_resolved(self, value, instance, args, info): schema = info.schema.graphene_schema - return lazy_map(value, self.get_object_type(schema)) + return lazy_map(value, self.type.get_object_type(schema)) class LazyListField(ListField): - def resolve(self, instance, args, info): + def resolver(self, instance, args, info): schema = info.schema.graphene_schema - resolved = super(LazyListField, self).resolve(instance, args, info) + resolved = super(LazyListField, self).resolver(instance, args, info) return lazy_map(resolved, self.get_object_type(schema)) -class ConnectionOrListField(LazyField): +class ConnectionOrListField(Field): - def get_field(self, schema): - model_field = self.field_type + def internal_type(self, schema): + model_field = self.type field_object_type = model_field.get_object_type(schema) if is_node(field_object_type): field = DjangoConnectionField(model_field) else: field = LazyListField(model_field) field.contribute_to_class(self.object_type, self.name) - return field + return field.internal_type(schema) -class DjangoModelField(Field): +class DjangoModelField(FieldType): def __init__(self, model, *args, **kwargs): - super(DjangoModelField, self).__init__(None, *args, **kwargs) self.model = model - - def resolve(self, instance, args, info): - resolved = super(DjangoModelField, self).resolve(instance, args, info) - schema = info.schema.graphene_schema - _type = self.get_object_type(schema) - assert _type, ("Field %s cannot be retrieved as the " - "ObjectType is not registered by the schema" % ( - self.attname - )) - return _type(resolved) + super(DjangoModelField, self).__init__(*args, **kwargs) def internal_type(self, schema): _type = self.get_object_type(schema) - if not _type and self.object_type._meta.only_fields: + if not _type and self.parent._meta.only_fields: raise Exception( "Model %r is not accessible by the schema. " "You can either register the type manually " "using @schema.register. " - "Or disable the field %s in %s" % ( + "Or disable the field in %s" % ( self.model, - self.attname, - self.object_type + self.parent, ) ) - return schema.T(_type) or Field.SKIP + return schema.T(_type) def get_object_type(self, schema): return get_type_for_model(schema, self.model) diff --git a/tests/contrib_django/test_converter.py b/tests/contrib_django/test_converter.py index 217849ab..699b6ebd 100644 --- a/tests/contrib_django/test_converter.py +++ b/tests/contrib_django/test_converter.py @@ -15,8 +15,9 @@ def assert_conversion(django_field, graphene_field, *args): field = django_field(*args, help_text='Custom Help Text') graphene_type = convert_django_field(field) assert isinstance(graphene_type, graphene_field) - assert graphene_type.description == 'Custom Help Text' - return graphene_type + field = graphene_type.as_field() + assert field.description == 'Custom Help Text' + return field def test_should_unknown_django_field_raise_exception(): @@ -26,86 +27,86 @@ def test_should_unknown_django_field_raise_exception(): def test_should_date_convert_string(): - assert_conversion(models.DateField, graphene.StringField) + assert_conversion(models.DateField, graphene.String) def test_should_char_convert_string(): - assert_conversion(models.CharField, graphene.StringField) + assert_conversion(models.CharField, graphene.String) def test_should_text_convert_string(): - assert_conversion(models.TextField, graphene.StringField) + assert_conversion(models.TextField, graphene.String) def test_should_email_convert_string(): - assert_conversion(models.EmailField, graphene.StringField) + assert_conversion(models.EmailField, graphene.String) def test_should_slug_convert_string(): - assert_conversion(models.SlugField, graphene.StringField) + assert_conversion(models.SlugField, graphene.String) def test_should_url_convert_string(): - assert_conversion(models.URLField, graphene.StringField) + assert_conversion(models.URLField, graphene.String) def test_should_auto_convert_id(): - assert_conversion(models.AutoField, graphene.IDField) + assert_conversion(models.AutoField, graphene.ID) def test_should_positive_integer_convert_int(): - assert_conversion(models.PositiveIntegerField, graphene.IntField) + assert_conversion(models.PositiveIntegerField, graphene.Int) def test_should_positive_small_convert_int(): - assert_conversion(models.PositiveSmallIntegerField, graphene.IntField) + assert_conversion(models.PositiveSmallIntegerField, graphene.Int) def test_should_small_integer_convert_int(): - assert_conversion(models.SmallIntegerField, graphene.IntField) + assert_conversion(models.SmallIntegerField, graphene.Int) def test_should_big_integer_convert_int(): - assert_conversion(models.BigIntegerField, graphene.IntField) + assert_conversion(models.BigIntegerField, graphene.Int) def test_should_integer_convert_int(): - assert_conversion(models.IntegerField, graphene.IntField) + assert_conversion(models.IntegerField, graphene.Int) def test_should_boolean_convert_boolean(): - field = assert_conversion(models.BooleanField, graphene.BooleanField) + field = assert_conversion(models.BooleanField, graphene.Boolean) assert field.required is True def test_should_nullboolean_convert_boolean(): - field = assert_conversion(models.NullBooleanField, graphene.BooleanField) + field = assert_conversion(models.NullBooleanField, graphene.Boolean) assert field.required is False def test_should_float_convert_float(): - assert_conversion(models.FloatField, graphene.FloatField) + assert_conversion(models.FloatField, graphene.Float) def test_should_manytomany_convert_connectionorlist(): graphene_type = convert_django_field(Reporter._meta.local_many_to_many[0]) assert isinstance(graphene_type, ConnectionOrListField) - assert isinstance(graphene_type.field_type, DjangoModelField) - assert graphene_type.field_type.model == Reporter + assert isinstance(graphene_type.type, DjangoModelField) + assert graphene_type.type.model == Reporter def test_should_manytoone_convert_connectionorlist(): graphene_type = convert_django_field(Reporter.articles.related) assert isinstance(graphene_type, ConnectionOrListField) - assert isinstance(graphene_type.field_type, DjangoModelField) - assert graphene_type.field_type.model == Article + assert isinstance(graphene_type.type, DjangoModelField) + assert graphene_type.type.model == Article def test_should_onetoone_convert_model(): field = assert_conversion(models.OneToOneField, DjangoModelField, Article) - assert field.model == Article + assert field.type.model == Article def test_should_foreignkey_convert_model(): field = assert_conversion(models.ForeignKey, DjangoModelField, Article) - assert field.model == Article + assert field.type.model == Article diff --git a/tests/contrib_django/test_types.py b/tests/contrib_django/test_types.py index 94472745..bd864e0a 100644 --- a/tests/contrib_django/test_types.py +++ b/tests/contrib_django/test_types.py @@ -2,7 +2,8 @@ from graphene import Schema from graphene.contrib.django.types import DjangoInterface, DjangoNode -from graphene.core.fields import IntField +from graphene.core.fields import IntField, Field +from graphene.core.types.scalars import String, Int from graphene.relay.fields import GlobalIDField from graphql.core.type import GraphQLInterfaceType, GraphQLObjectType from tests.utils import assert_equal_lists @@ -57,7 +58,8 @@ def test_node_idfield(): def test_node_replacedfield(): idfield = Human._meta.fields_map['pub_date'] - assert isinstance(idfield, IntField) + assert isinstance(idfield, Field) + assert schema.T(idfield).type == schema.T(Int()) def test_interface_resolve_type(): From 707320851736afeeff83aa6de89de77db3fe24ac Mon Sep 17 00:00:00 2001 From: Syrus Akbary Date: Wed, 11 Nov 2015 20:02:57 -0800 Subject: [PATCH 15/20] Django integration finished --- bin/autolinter | 3 +- examples/starwars_django/schema.py | 6 +- graphene/__init__.py | 6 +- graphene/contrib/django/converter.py | 4 +- graphene/contrib/django/fields.py | 11 ++- .../contrib/django/tests}/__init__.py | 0 .../contrib/django/tests}/data.py | 0 .../contrib/django/tests}/models.py | 0 .../contrib/django/tests}/test_converter.py | 1 - .../contrib/django/tests/test_query.py | 90 +++++-------------- graphene/contrib/django/tests/test_schema.py | 45 ++++++++++ .../contrib/django/tests}/test_types.py | 11 +-- .../contrib/django/tests}/test_urls.py | 0 .../contrib/django/tests}/test_views.py | 16 ++-- graphene/core/exceptions.py | 2 + graphene/core/fields.py | 12 ++- graphene/core/schema.py | 10 ++- graphene/core/tests/test_mutations.py | 2 +- .../{test_fields.py => test_old_fields.py} | 68 +++++++++----- graphene/core/tests/test_options.py | 2 - graphene/core/tests/test_schema.py | 4 - graphene/core/types/__init__.py | 11 ++- graphene/core/types/argument.py | 7 +- graphene/core/types/base.py | 12 ++- graphene/core/types/definitions.py | 8 +- graphene/core/types/field.py | 25 ++++-- graphene/core/types/objecttype.py | 25 ++++-- graphene/core/types/scalars.py | 1 + graphene/core/types/tests/test_argument.py | 8 +- graphene/core/types/tests/test_base.py | 13 +-- graphene/core/types/tests/test_definitions.py | 4 +- graphene/core/types/tests/test_field.py | 15 ++-- graphene/core/types/tests/test_scalars.py | 4 +- graphene/relay/fields.py | 14 +-- graphene/relay/tests/__init__.py | 0 graphene/relay/tests/test_mutations.py | 4 +- graphene/relay/types.py | 18 ++-- .../utils/tests}/test_misc.py | 0 .../utils/tests}/test_proxy_snake_dict.py | 0 .../utils/tests}/test_str_converter.py | 0 tests/django_settings.py | 1 - 41 files changed, 274 insertions(+), 189 deletions(-) rename {tests/contrib_django => graphene/contrib/django/tests}/__init__.py (100%) rename {tests/contrib_django => graphene/contrib/django/tests}/data.py (100%) rename {tests/contrib_django => graphene/contrib/django/tests}/models.py (100%) rename {tests/contrib_django => graphene/contrib/django/tests}/test_converter.py (99%) rename tests/contrib_django/test_schema.py => graphene/contrib/django/tests/test_query.py (51%) create mode 100644 graphene/contrib/django/tests/test_schema.py rename {tests/contrib_django => graphene/contrib/django/tests}/test_types.py (94%) rename {tests/contrib_django => graphene/contrib/django/tests}/test_urls.py (100%) rename {tests/contrib_django => graphene/contrib/django/tests}/test_views.py (82%) create mode 100644 graphene/core/exceptions.py rename graphene/core/tests/{test_fields.py => test_old_fields.py} (64%) create mode 100644 graphene/relay/tests/__init__.py rename {tests/utils => graphene/utils/tests}/test_misc.py (100%) rename {tests/utils => graphene/utils/tests}/test_proxy_snake_dict.py (100%) rename {tests/utils => graphene/utils/tests}/test_str_converter.py (100%) diff --git a/bin/autolinter b/bin/autolinter index 47d2201c..77af4388 100755 --- a/bin/autolinter +++ b/bin/autolinter @@ -1,4 +1,5 @@ #!/bin/bash -autoflake ./ -r --remove-unused-variables --remove-all-unused-imports --in-place +autoflake ./ -r --remove-unused-variables --remove-all-unused-imports --in-place +autopep8 ./ -r --in-place isort -rc . diff --git a/examples/starwars_django/schema.py b/examples/starwars_django/schema.py index 21b29a1f..22e30e6a 100644 --- a/examples/starwars_django/schema.py +++ b/examples/starwars_django/schema.py @@ -12,6 +12,7 @@ schema = graphene.Schema(name='Starwars Django Relay Schema') class Ship(DjangoNode): + class Meta: model = ShipModel @@ -21,11 +22,13 @@ class Ship(DjangoNode): class Character(DjangoObjectType): + class Meta: model = CharacterModel class Faction(DjangoNode): + class Meta: model = FactionModel @@ -35,6 +38,7 @@ class Faction(DjangoNode): class IntroduceShip(relay.ClientIDMutation): + class Input: ship_name = graphene.StringField(required=True) faction_id = graphene.StringField(required=True) @@ -48,7 +52,7 @@ class IntroduceShip(relay.ClientIDMutation): faction_id = input.get('faction_id') ship = create_ship(ship_name, faction_id) faction = get_faction(faction_id) - return IntroduceShip(ship=ship, faction=faction) + return IntroduceShip(ship=Ship(ship), faction=Faction(faction)) class Query(graphene.ObjectType): diff --git a/graphene/__init__.py b/graphene/__init__.py index c8c1d03d..71791c4d 100644 --- a/graphene/__init__.py +++ b/graphene/__init__.py @@ -14,7 +14,6 @@ from graphene.core.types import ( Mutation, BaseType, LazyType, - OrderedType, Argument, Field, InputField, @@ -28,7 +27,6 @@ from graphene.core.types import ( ) from graphene.core.fields import ( - Field, StringField, IntField, BooleanField, @@ -42,7 +40,7 @@ from graphene.decorators import ( resolve_only_args ) -__all__ = ['Enum', 'Argument', 'String', 'Int', 'ID', 'signals', 'Schema', - 'ObjectType', 'Interface', 'Mutation', 'Field', 'StringField', +__all__ = ['Enum', 'Argument', 'String', 'Int', 'Boolean', 'Float', 'ID', 'List', 'NonNull', 'signals', 'Schema', + 'BaseType', 'LazyType', 'ObjectType', 'Interface', 'Mutation', 'Field', 'InputField', 'StringField', 'IntField', 'BooleanField', 'IDField', 'ListField', 'NonNullField', 'FloatField', 'resolve_only_args'] diff --git a/graphene/contrib/django/converter.py b/graphene/contrib/django/converter.py index d5799a76..0a11d364 100644 --- a/graphene/contrib/django/converter.py +++ b/graphene/contrib/django/converter.py @@ -3,9 +3,7 @@ from singledispatch import singledispatch from graphene.contrib.django.fields import (ConnectionOrListField, DjangoModelField) -from graphene.core.fields import (BooleanField, FloatField, IDField, IntField, - StringField) -from graphene.core.types.scalars import Boolean, Float, ID, Int, String +from graphene.core.types.scalars import ID, Boolean, Float, Int, String try: UUIDField = models.UUIDField diff --git a/graphene/contrib/django/fields.py b/graphene/contrib/django/fields.py index b95a6d79..d76e984e 100644 --- a/graphene/contrib/django/fields.py +++ b/graphene/contrib/django/fields.py @@ -1,7 +1,9 @@ from graphene import relay from graphene.contrib.django.utils import get_type_for_model, lazy_map -from graphene.core.fields import Field, ListField +from graphene.core.exceptions import SkipField +from graphene.core.fields import Field from graphene.core.types.base import FieldType +from graphene.core.types.definitions import List from graphene.relay.utils import is_node @@ -12,7 +14,10 @@ class DjangoConnectionField(relay.ConnectionField): return lazy_map(value, self.type.get_object_type(schema)) -class LazyListField(ListField): +class LazyListField(Field): + + def get_type(self, schema): + return List(self.type) def resolver(self, instance, args, info): schema = info.schema.graphene_schema @@ -51,6 +56,8 @@ class DjangoModelField(FieldType): self.parent, ) ) + if not _type: + raise SkipField() return schema.T(_type) def get_object_type(self, schema): diff --git a/tests/contrib_django/__init__.py b/graphene/contrib/django/tests/__init__.py similarity index 100% rename from tests/contrib_django/__init__.py rename to graphene/contrib/django/tests/__init__.py diff --git a/tests/contrib_django/data.py b/graphene/contrib/django/tests/data.py similarity index 100% rename from tests/contrib_django/data.py rename to graphene/contrib/django/tests/data.py diff --git a/tests/contrib_django/models.py b/graphene/contrib/django/tests/models.py similarity index 100% rename from tests/contrib_django/models.py rename to graphene/contrib/django/tests/models.py diff --git a/tests/contrib_django/test_converter.py b/graphene/contrib/django/tests/test_converter.py similarity index 99% rename from tests/contrib_django/test_converter.py rename to graphene/contrib/django/tests/test_converter.py index 699b6ebd..3f4c3894 100644 --- a/tests/contrib_django/test_converter.py +++ b/graphene/contrib/django/tests/test_converter.py @@ -1,7 +1,6 @@ from django.db import models from py.test import raises -from pytest import raises import graphene from graphene.contrib.django.converter import convert_django_field diff --git a/tests/contrib_django/test_schema.py b/graphene/contrib/django/tests/test_query.py similarity index 51% rename from tests/contrib_django/test_schema.py rename to graphene/contrib/django/tests/test_query.py index a02cf81c..8d9366cd 100644 --- a/tests/contrib_django/test_schema.py +++ b/graphene/contrib/django/tests/test_query.py @@ -1,40 +1,21 @@ - from py.test import raises -from pytest import raises import graphene from graphene import relay from graphene.contrib.django import DjangoNode, DjangoObjectType -from tests.utils import assert_equal_lists from .models import Article, Reporter -def test_should_raise_if_no_model(): - with raises(Exception) as excinfo: - class Character1(DjangoObjectType): - pass - assert 'model in the Meta' in str(excinfo.value) - - -def test_should_raise_if_model_is_invalid(): - with raises(Exception) as excinfo: - class Character2(DjangoObjectType): - - class Meta: - model = 1 - assert 'not a Django model' in str(excinfo.value) - - -def test_should_raise_if_model_is_invalid(): - with raises(Exception) as excinfo: - class ReporterTypeError(DjangoObjectType): +def test_should_query_only_fields(): + with raises(Exception): + class ReporterType(DjangoObjectType): class Meta: model = Reporter only_fields = ('articles', ) - schema = graphene.Schema(query=ReporterTypeError) + schema = graphene.Schema(query=ReporterType) query = ''' query ReporterQuery { articles @@ -44,24 +25,13 @@ def test_should_raise_if_model_is_invalid(): assert not result.errors -def test_should_map_fields_correctly(): - class ReporterType2(DjangoObjectType): - - class Meta: - model = Reporter - assert_equal_lists( - ReporterType2._meta.fields_map.keys(), - ['articles', 'first_name', 'last_name', 'email', 'pets', 'id'] - ) - - -def test_should_map_fields(): +def test_should_query_well(): class ReporterType(DjangoObjectType): class Meta: model = Reporter - class Query2(graphene.ObjectType): + class Query(graphene.ObjectType): reporter = graphene.Field(ReporterType) def resolve_reporter(self, *args, **kwargs): @@ -83,53 +53,41 @@ def test_should_map_fields(): 'email': '' } } - Schema = graphene.Schema(query=Query2) - result = Schema.execute(query) + schema = graphene.Schema(query=Query) + result = schema.execute(query) assert not result.errors assert result.data == expected -def test_should_map_only_few_fields(): - class Reporter2(DjangoObjectType): - - class Meta: - model = Reporter - only_fields = ('id', 'email') - assert_equal_lists( - Reporter2._meta.fields_map.keys(), - ['id', 'email'] - ) - - def test_should_node(): - class ReporterNodeType(DjangoNode): + class ReporterNode(DjangoNode): class Meta: model = Reporter @classmethod def get_node(cls, id): - return ReporterNodeType(Reporter(id=2, first_name='Cookie Monster')) + return ReporterNode(Reporter(id=2, first_name='Cookie Monster')) def resolve_articles(self, *args, **kwargs): - return [ArticleNodeType(Article(headline='Hi!'))] + return [ArticleNode(Article(headline='Hi!'))] - class ArticleNodeType(DjangoNode): + class ArticleNode(DjangoNode): class Meta: model = Article @classmethod def get_node(cls, id): - return ArticleNodeType(Article(id=1, headline='Article node')) + return ArticleNode(Article(id=1, headline='Article node')) - class Query1(graphene.ObjectType): + class Query(graphene.ObjectType): node = relay.NodeField() - reporter = graphene.Field(ReporterNodeType) - article = graphene.Field(ArticleNodeType) + reporter = graphene.Field(ReporterNode) + article = graphene.Field(ArticleNode) def resolve_reporter(self, *args, **kwargs): - return ReporterNodeType(Reporter(id=1, first_name='ABA', last_name='X')) + return ReporterNode(Reporter(id=1, first_name='ABA', last_name='X')) query = ''' query ReporterQuery { @@ -146,12 +104,12 @@ def test_should_node(): lastName, email } - myArticle: node(id:"QXJ0aWNsZU5vZGVUeXBlOjE=") { + myArticle: node(id:"QXJ0aWNsZU5vZGU6MQ==") { id - ... on ReporterNodeType { + ... on ReporterNode { firstName } - ... on ArticleNodeType { + ... on ArticleNode { headline } } @@ -159,7 +117,7 @@ def test_should_node(): ''' expected = { 'reporter': { - 'id': 'UmVwb3J0ZXJOb2RlVHlwZTox', + 'id': 'UmVwb3J0ZXJOb2RlOjE=', 'firstName': 'ABA', 'lastName': 'X', 'email': '', @@ -172,11 +130,11 @@ def test_should_node(): }, }, 'myArticle': { - 'id': 'QXJ0aWNsZU5vZGVUeXBlOjE=', + 'id': 'QXJ0aWNsZU5vZGU6MQ==', 'headline': 'Article node' } } - Schema = graphene.Schema(query=Query1) - result = Schema.execute(query) + schema = graphene.Schema(query=Query) + result = schema.execute(query) assert not result.errors assert result.data == expected diff --git a/graphene/contrib/django/tests/test_schema.py b/graphene/contrib/django/tests/test_schema.py new file mode 100644 index 00000000..e474121f --- /dev/null +++ b/graphene/contrib/django/tests/test_schema.py @@ -0,0 +1,45 @@ +from py.test import raises + +from graphene.contrib.django import DjangoObjectType +from tests.utils import assert_equal_lists + +from .models import Reporter + + +def test_should_raise_if_no_model(): + with raises(Exception) as excinfo: + class Character1(DjangoObjectType): + pass + assert 'model in the Meta' in str(excinfo.value) + + +def test_should_raise_if_model_is_invalid(): + with raises(Exception) as excinfo: + class Character2(DjangoObjectType): + + class Meta: + model = 1 + assert 'not a Django model' in str(excinfo.value) + + +def test_should_map_fields_correctly(): + class ReporterType2(DjangoObjectType): + + class Meta: + model = Reporter + assert_equal_lists( + ReporterType2._meta.fields_map.keys(), + ['articles', 'first_name', 'last_name', 'email', 'pets', 'id'] + ) + + +def test_should_map_only_few_fields(): + class Reporter2(DjangoObjectType): + + class Meta: + model = Reporter + only_fields = ('id', 'email') + assert_equal_lists( + Reporter2._meta.fields_map.keys(), + ['id', 'email'] + ) diff --git a/tests/contrib_django/test_types.py b/graphene/contrib/django/tests/test_types.py similarity index 94% rename from tests/contrib_django/test_types.py rename to graphene/contrib/django/tests/test_types.py index bd864e0a..f1af176d 100644 --- a/tests/contrib_django/test_types.py +++ b/graphene/contrib/django/tests/test_types.py @@ -2,14 +2,16 @@ from graphene import Schema from graphene.contrib.django.types import DjangoInterface, DjangoNode -from graphene.core.fields import IntField, Field -from graphene.core.types.scalars import String, Int +from graphene.core.fields import Field, IntField +from graphene.core.types.scalars import Int from graphene.relay.fields import GlobalIDField from graphql.core.type import GraphQLInterfaceType, GraphQLObjectType from tests.utils import assert_equal_lists from .models import Article, Reporter +schema = Schema() + class Character(DjangoInterface): '''Character description''' @@ -17,6 +19,7 @@ class Character(DjangoInterface): model = Reporter +@schema.register class Human(DjangoNode): '''Human description''' @@ -28,14 +31,12 @@ class Human(DjangoNode): class Meta: model = Article -schema = Schema() - def test_django_interface(): assert DjangoNode._meta.is_interface is True -def test_pseudo_interface(): +def test_pseudo_interface_registered(): object_type = schema.T(Character) assert Character._meta.is_interface is True assert isinstance(object_type, GraphQLInterfaceType) diff --git a/tests/contrib_django/test_urls.py b/graphene/contrib/django/tests/test_urls.py similarity index 100% rename from tests/contrib_django/test_urls.py rename to graphene/contrib/django/tests/test_urls.py diff --git a/tests/contrib_django/test_views.py b/graphene/contrib/django/tests/test_views.py similarity index 82% rename from tests/contrib_django/test_views.py rename to graphene/contrib/django/tests/test_views.py index c04e8b6a..a3537802 100644 --- a/tests/contrib_django/test_views.py +++ b/graphene/contrib/django/tests/test_views.py @@ -6,7 +6,7 @@ def format_response(response): def test_client_get_no_query(settings, client): - settings.ROOT_URLCONF = 'tests.contrib_django.test_urls' + settings.ROOT_URLCONF = 'graphene.contrib.django.tests.test_urls' response = client.get('/graphql') json_response = format_response(response) assert json_response == {'errors': [ @@ -14,7 +14,7 @@ def test_client_get_no_query(settings, client): def test_client_post_no_query(settings, client): - settings.ROOT_URLCONF = 'tests.contrib_django.test_urls' + settings.ROOT_URLCONF = 'graphene.contrib.django.tests.test_urls' response = client.post('/graphql', {}) json_response = format_response(response) assert json_response == {'errors': [ @@ -22,7 +22,7 @@ def test_client_post_no_query(settings, client): def test_client_post_malformed_json(settings, client): - settings.ROOT_URLCONF = 'tests.contrib_django.test_urls' + settings.ROOT_URLCONF = 'graphene.contrib.django.tests.test_urls' response = client.post('/graphql', 'MALFORMED', 'application/json') json_response = format_response(response) assert json_response == {'errors': [ @@ -30,7 +30,7 @@ def test_client_post_malformed_json(settings, client): def test_client_post_empty_query(settings, client): - settings.ROOT_URLCONF = 'tests.contrib_django.test_urls' + settings.ROOT_URLCONF = 'graphene.contrib.django.tests.test_urls' response = client.post( '/graphql', json.dumps({'query': ''}), 'application/json') json_response = format_response(response) @@ -39,7 +39,7 @@ def test_client_post_empty_query(settings, client): def test_client_post_bad_query(settings, client): - settings.ROOT_URLCONF = 'tests.contrib_django.test_urls' + settings.ROOT_URLCONF = 'graphene.contrib.django.tests.test_urls' response = client.post( '/graphql', json.dumps({'query': '{ MALFORMED'}), 'application/json') json_response = format_response(response) @@ -49,7 +49,7 @@ def test_client_post_bad_query(settings, client): def test_client_get_good_query(settings, client): - settings.ROOT_URLCONF = 'tests.contrib_django.test_urls' + settings.ROOT_URLCONF = 'graphene.contrib.django.tests.test_urls' response = client.get('/graphql', {'query': '{ headline }'}) json_response = format_response(response) expected_json = { @@ -61,7 +61,7 @@ def test_client_get_good_query(settings, client): def test_client_get_good_query_with_raise(settings, client): - settings.ROOT_URLCONF = 'tests.contrib_django.test_urls' + settings.ROOT_URLCONF = 'graphene.contrib.django.tests.test_urls' response = client.get('/graphql', {'query': '{ raises }'}) json_response = format_response(response) assert json_response['errors'][0][ @@ -70,7 +70,7 @@ def test_client_get_good_query_with_raise(settings, client): def test_client_post_good_query(settings, client): - settings.ROOT_URLCONF = 'tests.contrib_django.test_urls' + settings.ROOT_URLCONF = 'graphene.contrib.django.tests.test_urls' response = client.post( '/graphql', json.dumps({'query': '{ headline }'}), 'application/json') json_response = format_response(response) diff --git a/graphene/core/exceptions.py b/graphene/core/exceptions.py new file mode 100644 index 00000000..ce89b521 --- /dev/null +++ b/graphene/core/exceptions.py @@ -0,0 +1,2 @@ +class SkipField(Exception): + pass diff --git a/graphene/core/fields.py b/graphene/core/fields.py index f799ec9f..463e1983 100644 --- a/graphene/core/fields.py +++ b/graphene/core/fields.py @@ -1,15 +1,17 @@ import warnings from .types.base import FieldType -from .types.field import Field -from .types.scalars import String, Int, Boolean, ID, Float from .types.definitions import List, NonNull +from .types.field import Field +from .types.scalars import ID, Boolean, Float, Int, String class DeprecatedField(FieldType): + def __init__(self, *args, **kwargs): cls = self.__class__ - warnings.warn("Using {} is not longer supported".format(cls.__name__), FutureWarning) + 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) @@ -41,3 +43,7 @@ class ListField(DeprecatedField, List): class NonNullField(DeprecatedField, NonNull): pass + + +__all__ = ['Field', 'StringField', 'IntField', 'BooleanField', + 'IDField', 'FloatField', 'ListField', 'NonNullField'] diff --git a/graphene/core/schema.py b/graphene/core/schema.py index 49147e48..c0a4f94a 100644 --- a/graphene/core/schema.py +++ b/graphene/core/schema.py @@ -2,13 +2,13 @@ import inspect from collections import OrderedDict from graphene import signals +from graphene.core.types.base import BaseType +from graphene.core.types.objecttype import BaseObjectType from graphql.core.execution.executor import Executor from graphql.core.execution.middlewares.sync import \ SynchronousExecutionMiddleware 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): @@ -40,7 +40,8 @@ class Schema(object): if object_type not in self._types: internal_type = object_type.internal_type(self) self._types[object_type] = internal_type - is_objecttype = inspect.isclass(object_type) and issubclass(object_type, BaseObjectType) + is_objecttype = inspect.isclass( + object_type) and issubclass(object_type, BaseObjectType) if is_objecttype: self.register(object_type) return self._types[object_type] @@ -68,7 +69,8 @@ class Schema(object): type_name = object_type._meta.type_name registered_object_type = self._types_names.get(type_name, None) if registered_object_type: - assert registered_object_type == object_type, 'Type {} already registered with other object type'.format(type_name) + assert registered_object_type == object_type, 'Type {} already registered with other object type'.format( + type_name) self._types_names[object_type._meta.type_name] = object_type return object_type diff --git a/graphene/core/tests/test_mutations.py b/graphene/core/tests/test_mutations.py index 260fe6b8..d0efac76 100644 --- a/graphene/core/tests/test_mutations.py +++ b/graphene/core/tests/test_mutations.py @@ -30,7 +30,7 @@ schema = Schema(query=Query, mutation=MyResultMutation) def test_mutation_input(): - assert schema.T(ChangeNumber.arguments).keys() == ['to'] + assert list(schema.T(ChangeNumber.arguments).keys()) == ['to'] def test_execute_mutations(): diff --git a/graphene/core/tests/test_fields.py b/graphene/core/tests/test_old_fields.py similarity index 64% rename from graphene/core/tests/test_fields.py rename to graphene/core/tests/test_old_fields.py index d919c26f..527444ae 100644 --- a/graphene/core/tests/test_fields.py +++ b/graphene/core/tests/test_old_fields.py @@ -1,16 +1,16 @@ - 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.fields import (BooleanField, Field, FloatField, IDField, + IntField, NonNullField, StringField) from graphene.core.schema import Schema from graphene.core.types import ObjectType -from graphql.core.type import (GraphQLBoolean, GraphQLField, GraphQLID, - GraphQLInt, GraphQLNonNull, GraphQLString) +from graphql.core.type import (GraphQLBoolean, GraphQLField, GraphQLFloat, + GraphQLID, GraphQLInt, GraphQLNonNull, + GraphQLString) -class ot(ObjectType): +class MyOt(ObjectType): + def resolve_customdoc(self, *args, **kwargs): '''Resolver documentation''' return None @@ -23,53 +23,77 @@ schema = Schema() def test_field_no_contributed_raises_error(): f = Field(GraphQLString) - with raises(Exception) as excinfo: + with raises(Exception): schema.T(f) def test_field_type(): f = Field(GraphQLString) - f.contribute_to_class(ot, 'field_name') + f.contribute_to_class(MyOt, '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') + f.contribute_to_class(MyOt, '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') + f.contribute_to_class(MyOt, 'field_name') assert f.name == 'my_custom_name' def test_stringfield_type(): f = StringField() - f.contribute_to_class(ot, 'field_name') + f.contribute_to_class(MyOt, 'field_name') assert schema.T(f) == GraphQLString +def test_idfield_type(): + f = IDField() + f.contribute_to_class(MyOt, 'field_name') + assert schema.T(f) == GraphQLID + + +def test_booleanfield_type(): + f = BooleanField() + f.contribute_to_class(MyOt, 'field_name') + assert schema.T(f) == GraphQLBoolean + + +def test_intfield_type(): + f = IntField() + f.contribute_to_class(MyOt, 'field_name') + assert schema.T(f) == GraphQLInt + + +def test_floatfield_type(): + f = FloatField() + f.contribute_to_class(MyOt, 'field_name') + assert schema.T(f) == GraphQLFloat + + def test_nonnullfield_type(): f = NonNullField(StringField()) - f.contribute_to_class(ot, 'field_name') + f.contribute_to_class(MyOt, 'field_name') assert isinstance(schema.T(f), GraphQLNonNull) def test_stringfield_type_required(): f = StringField(required=True).as_field() - f.contribute_to_class(ot, 'field_name') + f.contribute_to_class(MyOt, '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').as_field() - f.contribute_to_class(ot, 'field_name') + f.contribute_to_class(MyOt, 'field_name') field_type = schema.T(f) - assert 'RESOLVED' == field_type.resolver(ot, None, None) + assert 'RESOLVED' == field_type.resolver(MyOt, None, None) def test_field_resolve_type_custom(): @@ -123,17 +147,17 @@ def test_field_hash(): def test_field_none_type_raises_error(): s = Schema() f = Field(None) - f.contribute_to_class(ot, 'field_name') + f.contribute_to_class(MyOt, 'field_name') with raises(Exception) as excinfo: s.T(f) assert str( - excinfo.value) == "Internal type for field ot.field_name is None" + excinfo.value) == "Internal type for field MyOt.field_name is None" def test_field_str(): f = StringField().as_field() - f.contribute_to_class(ot, 'field_name') - assert str(f) == "ot.field_name" + f.contribute_to_class(MyOt, 'field_name') + assert str(f) == "MyOt.field_name" def test_field_repr(): @@ -143,12 +167,12 @@ def test_field_repr(): def test_field_repr_contributed(): f = StringField().as_field() - f.contribute_to_class(ot, 'field_name') + f.contribute_to_class(MyOt, 'field_name') assert repr(f) == "" def test_field_resolve_objecttype_cos(): f = StringField().as_field() - f.contribute_to_class(ot, 'customdoc') + f.contribute_to_class(MyOt, 'customdoc') field = schema.T(f) assert field.description == 'Resolver documentation' diff --git a/graphene/core/tests/test_options.py b/graphene/core/tests/test_options.py index 0673c289..009ae25d 100644 --- a/graphene/core/tests/test_options.py +++ b/graphene/core/tests/test_options.py @@ -1,6 +1,4 @@ - from py.test import raises -from pytest import raises from graphene.core.fields import StringField from graphene.core.options import Options diff --git a/graphene/core/tests/test_schema.py b/graphene/core/tests/test_schema.py index 2b4dc397..baa8a0bd 100644 --- a/graphene/core/tests/test_schema.py +++ b/graphene/core/tests/test_schema.py @@ -1,13 +1,9 @@ - from py.test import raises -from pytest import raises from graphene import Interface, ObjectType, Schema from graphene.core.fields import Field, ListField, StringField from graphene.core.types.base import LazyType 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') diff --git a/graphene/core/types/__init__.py b/graphene/core/types/__init__.py index adf3c2a2..44bc2aa5 100644 --- a/graphene/core/types/__init__.py +++ b/graphene/core/types/__init__.py @@ -1,6 +1,9 @@ -from .objecttype import ObjectTypeMeta, BaseObjectType, ObjectType, Interface, Mutation, InputObjectType from .base import BaseType, LazyType, OrderedType -from .argument import Argument -from .field import Field, InputField -from .scalars import String, Int, Boolean, ID, Float +from .argument import Argument, ArgumentsGroup, to_arguments from .definitions import List, NonNull +from .objecttype import ObjectTypeMeta, BaseObjectType, Interface, ObjectType, Mutation, InputObjectType +from .scalars import String, ID, Boolean, Int, Float, Scalar +from .field import Field, InputField + +__all__ = ['BaseType', 'LazyType', 'OrderedType', 'Argument', 'ArgumentsGroup', 'to_arguments', 'List', 'NonNull', 'Field', 'InputField', + 'Interface', 'BaseObjectType', 'ObjectTypeMeta', 'ObjectType', 'Mutation', 'InputObjectType', 'String', 'ID', 'Boolean', 'Int', 'Float', 'Scalar'] diff --git a/graphene/core/types/argument.py b/graphene/core/types/argument.py index 1c762345..db7b58be 100644 --- a/graphene/core/types/argument.py +++ b/graphene/core/types/argument.py @@ -3,11 +3,12 @@ from itertools import chain from graphql.core.type import GraphQLArgument -from .base import BaseType, OrderedType, ArgumentType from ...utils import to_camel_case +from .base import ArgumentType, BaseType, OrderedType class Argument(OrderedType): + def __init__(self, type, description=None, default=None, name=None, _creation_counter=None): super(Argument, self).__init__(_creation_counter=_creation_counter) self.name = name @@ -23,6 +24,7 @@ class Argument(OrderedType): class ArgumentsGroup(BaseType): + def __init__(self, *args, **kwargs): arguments = to_arguments(*args, **kwargs) self.arguments = OrderedDict([(arg.name, arg) for arg in arguments]) @@ -58,7 +60,8 @@ def to_arguments(*args, **kwargs): if name: argument.name = to_camel_case(name) assert argument.name, 'Argument in field must have a name' - assert argument.name not in arguments, 'Found more than one Argument with same name {}'.format(argument.name) + assert argument.name not in arguments, 'Found more than one Argument with same name {}'.format( + argument.name) arguments[argument.name] = argument return sorted(arguments.values()) diff --git a/graphene/core/types/base.py b/graphene/core/types/base.py index b0acf52d..084c929d 100644 --- a/graphene/core/types/base.py +++ b/graphene/core/types/base.py @@ -1,8 +1,10 @@ -import six from functools import total_ordering +import six + class BaseType(object): + @classmethod def internal_type(cls, schema): return getattr(cls, 'T', None) @@ -16,6 +18,7 @@ class MountType(BaseType): class LazyType(MountType): + def __init__(self, type): self.type = type @@ -57,13 +60,13 @@ class OrderedType(MountType): def __lt__(self, other): # This is needed because bisect does not take a comparison function. - if type(self) == type(other): + if isinstance(other, OrderedType): return self.creation_counter < other.creation_counter return NotImplemented def __gt__(self, other): # This is needed because bisect does not take a comparison function. - if type(self) == type(other): + if isinstance(other, OrderedType): return self.creation_counter > other.creation_counter return NotImplemented @@ -72,6 +75,7 @@ class OrderedType(MountType): class MirroredType(OrderedType): + def __init__(self, *args, **kwargs): _creation_counter = kwargs.pop('_creation_counter', None) super(MirroredType, self).__init__(_creation_counter=_creation_counter) @@ -80,12 +84,14 @@ class MirroredType(OrderedType): class ArgumentType(MirroredType): + def as_argument(self): from .argument import Argument return Argument(self, _creation_counter=self.creation_counter, *self.args, **self.kwargs) class FieldType(MirroredType): + def contribute_to_class(self, cls, name): from ..types import BaseObjectType, InputObjectType if issubclass(cls, InputObjectType): diff --git a/graphene/core/types/definitions.py b/graphene/core/types/definitions.py index 15b03cd8..ac48f8d9 100644 --- a/graphene/core/types/definitions.py +++ b/graphene/core/types/definitions.py @@ -1,12 +1,14 @@ import six -from graphql.core.type import (GraphQLList, GraphQLNonNull) -from .base import MountType, MountedType, LazyType +from graphql.core.type import GraphQLList, GraphQLNonNull + +from .base import LazyType, MountedType, MountType class OfType(MountedType): + def __init__(self, of_type, *args, **kwargs): - if isinstance(of_type, six.string_types) and of_type != 'self': + if isinstance(of_type, six.string_types): of_type = LazyType(of_type) self.of_type = of_type super(OfType, self).__init__(*args, **kwargs) diff --git a/graphene/core/types/field.py b/graphene/core/types/field.py index 61098485..ea83aeb9 100644 --- a/graphene/core/types/field.py +++ b/graphene/core/types/field.py @@ -1,14 +1,15 @@ -import six from collections import OrderedDict from functools import wraps +import six + from graphql.core.type import GraphQLField, GraphQLInputObjectField -from .base import MountType, LazyType, OrderedType -from .argument import ArgumentsGroup -from .definitions import NonNull -from ...utils import to_camel_case, ProxySnakeDict +from ...utils import ProxySnakeDict, to_camel_case from ..types import BaseObjectType, InputObjectType +from .argument import ArgumentsGroup +from .base import LazyType, MountType, OrderedType +from .definitions import NonNull def make_args_snake_case(resolver): @@ -24,6 +25,7 @@ class Empty(object): class Field(OrderedType): + 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) @@ -40,7 +42,8 @@ class Field(OrderedType): self.default = default 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) if not self.name: self.name = to_camel_case(attname) self.attname = attname @@ -104,11 +107,18 @@ class Field(OrderedType): """ Return "object_type.field_name". """ return '%s.%s' % (self.object_type.__name__, self.attname) + def __eq__(self, other): + eq = super(Field, self).__eq__(other) + if type(self) == type(other): + return eq and self.object_type == other.object_type + return NotImplemented + def __hash__(self): return hash((self.creation_counter, self.object_type)) class InputField(OrderedType): + 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 @@ -119,7 +129,8 @@ class InputField(OrderedType): self.default = default def contribute_to_class(self, cls, attname): - assert issubclass(cls, InputObjectType), 'InputField {} cannot be mounted in {}'.format(self, cls) + assert issubclass( + cls, InputObjectType), 'InputField {} cannot be mounted in {}'.format(self, cls) if not self.name: self.name = to_camel_case(attname) self.attname = attname diff --git a/graphene/core/types/objecttype.py b/graphene/core/types/objecttype.py index 8399c75d..aebf8e17 100644 --- a/graphene/core/types/objecttype.py +++ b/graphene/core/types/objecttype.py @@ -6,9 +6,10 @@ from functools import partial import six from graphene import signals +from graphene.core.exceptions import SkipField from graphene.core.options import Options -from graphene.core.types.base import BaseType from graphene.core.types.argument import ArgumentsGroup +from graphene.core.types.base import BaseType from graphql.core.type import (GraphQLArgument, GraphQLInputObjectType, GraphQLInterfaceType, GraphQLObjectType) @@ -176,23 +177,32 @@ class BaseObjectType(BaseType): @classmethod def internal_type(cls, schema): - fields = lambda: OrderedDict([(f.name, schema.T(f)) - for f in cls._meta.fields]) if cls._meta.is_interface: return GraphQLInterfaceType( cls._meta.type_name, description=cls._meta.description, resolve_type=partial(cls.resolve_type, schema), - fields=fields + fields=partial(cls.get_fields, schema) ) return GraphQLObjectType( cls._meta.type_name, description=cls._meta.description, interfaces=[schema.T(i) for i in cls._meta.interfaces], - fields=fields, + fields=partial(cls.get_fields, schema), is_type_of=getattr(cls, 'is_type_of', None) ) + @classmethod + def get_fields(cls, schema): + fields = [] + for field in cls._meta.fields: + try: + fields.append((field.name, schema.T(field))) + except SkipField: + continue + + return OrderedDict(fields) + class Interface(six.with_metaclass(ObjectTypeMeta, BaseObjectType)): pass @@ -203,14 +213,16 @@ class ObjectType(six.with_metaclass(ObjectTypeMeta, BaseObjectType)): class Mutation(six.with_metaclass(ObjectTypeMeta, BaseObjectType)): + @classmethod def _prepare_class(cls): input_class = getattr(cls, 'Input', None) if input_class: - items = dict(input_class.__dict__) + items = dict(vars(input_class)) items.pop('__dict__', None) items.pop('__doc__', None) items.pop('__module__', None) + items.pop('__weakref__', None) arguments = ArgumentsGroup(**items) cls.add_to_class('arguments', arguments) delattr(cls, 'Input') @@ -221,6 +233,7 @@ class Mutation(six.with_metaclass(ObjectTypeMeta, BaseObjectType)): class InputObjectType(ObjectType): + @classmethod def internal_type(cls, schema): fields = lambda: OrderedDict([(f.name, schema.T(f)) diff --git a/graphene/core/types/scalars.py b/graphene/core/types/scalars.py index 2dfc32b7..75cd70a3 100644 --- a/graphene/core/types/scalars.py +++ b/graphene/core/types/scalars.py @@ -25,6 +25,7 @@ class Float(MountedType): class Scalar(MountedType): + @classmethod def internal_type(cls, schema): serialize = getattr(cls, 'serialize') diff --git a/graphene/core/types/tests/test_argument.py b/graphene/core/types/tests/test_argument.py index 08a7718a..fed2f29c 100644 --- a/graphene/core/types/tests/test_argument.py +++ b/graphene/core/types/tests/test_argument.py @@ -1,10 +1,11 @@ from pytest import raises + +from graphene.core.schema import Schema +from graphene.core.types import ObjectType from graphql.core.type import GraphQLArgument from ..argument import Argument, to_arguments from ..scalars import String -from graphene.core.types import ObjectType -from graphene.core.schema import Schema def test_argument_internal_type(): @@ -26,7 +27,8 @@ def test_to_arguments(): other_kwarg=String(), ) - assert [a.name for a in arguments] == ['myArg', 'otherArg', 'myKwarg', 'otherKwarg'] + assert [a.name for a in arguments] == [ + 'myArg', 'otherArg', 'myKwarg', 'otherKwarg'] def test_to_arguments_no_name(): diff --git a/graphene/core/types/tests/test_base.py b/graphene/core/types/tests/test_base.py index 50d14c69..80be2d40 100644 --- a/graphene/core/types/tests/test_base.py +++ b/graphene/core/types/tests/test_base.py @@ -1,9 +1,10 @@ from mock import patch -from ..base import OrderedType, MountedType -from ..field import Field, InputField +from graphene.core.types import InputObjectType, ObjectType + from ..argument import Argument -from graphene.core.types import ObjectType, InputObjectType +from ..base import MountedType, OrderedType +from ..field import Field, InputField def test_orderedtype_equal(): @@ -26,14 +27,16 @@ def test_type_as_field_called(Field): resolver = lambda x: x a = MountedType(2, description='A', resolver=resolver) a.as_field() - Field.assert_called_with(a, 2, _creation_counter=a.creation_counter, description='A', resolver=resolver) + Field.assert_called_with( + a, 2, _creation_counter=a.creation_counter, description='A', resolver=resolver) @patch('graphene.core.types.argument.Argument') def test_type_as_argument_called(Argument): a = MountedType(2, description='A') a.as_argument() - Argument.assert_called_with(a, 2, _creation_counter=a.creation_counter, description='A') + Argument.assert_called_with( + a, 2, _creation_counter=a.creation_counter, description='A') def test_type_as_field(): diff --git a/graphene/core/types/tests/test_definitions.py b/graphene/core/types/tests/test_definitions.py index 9b175a89..8382dbd0 100644 --- a/graphene/core/types/tests/test_definitions.py +++ b/graphene/core/types/tests/test_definitions.py @@ -1,8 +1,8 @@ -from graphql.core.type import (GraphQLList, GraphQLString, GraphQLNonNull) +from graphene.core.schema import Schema +from graphql.core.type import GraphQLList, GraphQLNonNull, GraphQLString from ..definitions import List, NonNull from ..scalars import String -from graphene.core.schema import Schema schema = Schema() diff --git a/graphene/core/types/tests/test_field.py b/graphene/core/types/tests/test_field.py index 1a0e90c7..c9c21586 100644 --- a/graphene/core/types/tests/test_field.py +++ b/graphene/core/types/tests/test_field.py @@ -1,11 +1,12 @@ -from graphql.core.type import GraphQLField, GraphQLInputObjectField, GraphQLString +from graphene.core.schema import Schema +from graphene.core.types import InputObjectType, ObjectType +from graphql.core.type import (GraphQLField, GraphQLInputObjectField, + GraphQLString) -from ..field import Field, InputField -from ..scalars import String from ..base import LazyType from ..definitions import List -from graphene.core.types import ObjectType, InputObjectType -from graphene.core.schema import Schema +from ..field import Field, InputField +from ..scalars import String def test_field_internal_type(): @@ -81,8 +82,10 @@ def test_field_string_reference(): class MyObjectType(ObjectType): my_field = field + schema = Schema(query=MyObjectType) + assert isinstance(field.type, LazyType) - assert field.type.type_str == 'MyObjectType' + assert schema.T(field.type) == schema.T(MyObjectType) def test_field_custom_arguments(): diff --git a/graphene/core/types/tests/test_scalars.py b/graphene/core/types/tests/test_scalars.py index 7312752a..d1fcc0ac 100644 --- a/graphene/core/types/tests/test_scalars.py +++ b/graphene/core/types/tests/test_scalars.py @@ -1,8 +1,8 @@ +from graphene.core.schema import Schema from graphql.core.type import (GraphQLBoolean, GraphQLFloat, GraphQLID, GraphQLInt, GraphQLScalarType, GraphQLString) -from ..scalars import String, Int, Boolean, ID, Float, Scalar -from graphene.core.schema import Schema +from ..scalars import ID, Boolean, Float, Int, Scalar, String schema = Schema() diff --git a/graphene/relay/fields.py b/graphene/relay/fields.py index 047ac1d5..ca33a01d 100644 --- a/graphene/relay/fields.py +++ b/graphene/relay/fields.py @@ -1,8 +1,7 @@ from collections import Iterable -from graphene.core.fields import Field, IDField -from graphene.core.types.scalars import String, ID, Int -from graphql.core.type import GraphQLArgument, GraphQLID, GraphQLNonNull +from graphene.core.fields import Field +from graphene.core.types.scalars import ID, Int, String from graphql_relay.connection.arrayconnection import connection_from_list from graphql_relay.node.node import from_global_id @@ -23,7 +22,6 @@ class ConnectionField(Field): def wrap_resolved(self, value, instance, args, info): return value - def resolver(self, instance, args, info): from graphene.relay.types import PageInfo schema = info.schema.graphene_schema @@ -59,7 +57,7 @@ class ConnectionField(Field): assert is_node(node), 'Only nodes have connections.' schema.register(node) connection_type = self.get_connection_type(node) - return schema.T(connection_type) + return connection_type class NodeField(Field): @@ -67,7 +65,8 @@ class NodeField(Field): def __init__(self, object_type=None, *args, **kwargs): from graphene.relay.types import Node id = kwargs.pop('id', None) or ID(description='The ID of an object') - super(NodeField, self).__init__(object_type or Node, id=id, *args, **kwargs) + super(NodeField, self).__init__( + object_type or Node, id=id, *args, **kwargs) self.field_object_type = object_type def id_fetcher(self, global_id, info): @@ -89,6 +88,7 @@ class NodeField(Field): class GlobalIDField(Field): '''The ID of an object''' + def __init__(self, *args, **kwargs): super(GlobalIDField, self).__init__(ID(), *args, **kwargs) self.required = True @@ -100,4 +100,4 @@ class GlobalIDField(Field): super(GlobalIDField, self).contribute_to_class(cls, name) def resolver(self, instance, args, info): - return self.object_type.to_global_id(instance, args, info) + return instance.to_global_id() diff --git a/graphene/relay/tests/__init__.py b/graphene/relay/tests/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/graphene/relay/tests/test_mutations.py b/graphene/relay/tests/test_mutations.py index 5f3a91a3..313a2c1d 100644 --- a/graphene/relay/tests/test_mutations.py +++ b/graphene/relay/tests/test_mutations.py @@ -1,8 +1,6 @@ import graphene from graphene import relay from graphene.core.schema import Schema -from graphene.core.types import InputObjectType -from graphql.core.type import GraphQLInputObjectField my_id = 0 @@ -35,7 +33,7 @@ schema = Schema(query=Query, mutation=MyResultMutation) def test_mutation_arguments(): assert ChangeNumber.arguments assert list(ChangeNumber.arguments) == ['input'] - _input = ChangeNumber.arguments['input'] + ChangeNumber.arguments['input'] # inner_type = _input.get_object_type(schema) # client_mutation_id_field = inner_type._meta.fields_map[ diff --git a/graphene/relay/types.py b/graphene/relay/types.py index 40f04d20..36820a75 100644 --- a/graphene/relay/types.py +++ b/graphene/relay/types.py @@ -1,8 +1,8 @@ from graphene.core.fields import BooleanField, Field, ListField, StringField from graphene.core.types import (InputObjectType, Interface, Mutation, ObjectType) -from graphene.core.types.base import LazyType from graphene.core.types.argument import ArgumentsGroup +from graphene.core.types.base import LazyType from graphene.core.types.definitions import NonNull from graphene.relay.fields import GlobalIDField from graphene.utils import memoize @@ -74,10 +74,9 @@ class BaseNode(object): assert hasattr( cls, 'get_node'), 'get_node classmethod not found in %s Node' % cls - @classmethod - def to_global_id(cls, instance, args, info): - type_name = cls._meta.type_name - return to_global_id(type_name, instance.id) + def to_global_id(self): + type_name = self._meta.type_name + return to_global_id(type_name, self.id) connection_type = Connection edge_type = Edge @@ -105,13 +104,16 @@ class ClientIDMutation(Mutation): @classmethod def _prepare_class(cls): - Input = getattr(cls, 'Input', None) - if Input: + input_class = getattr(cls, 'Input', None) + if input_class: assert hasattr( cls, 'mutate_and_get_payload'), 'You have to implement mutate_and_get_payload' - items = dict(Input.__dict__) + items = dict(vars(input_class)) items.pop('__dict__', None) + items.pop('__doc__', None) + items.pop('__module__', None) + items.pop('__weakref__', None) new_input_type = type('{}Input'.format( cls._meta.type_name), (MutationInputType, ), items) cls.add_to_class('input_type', new_input_type) diff --git a/tests/utils/test_misc.py b/graphene/utils/tests/test_misc.py similarity index 100% rename from tests/utils/test_misc.py rename to graphene/utils/tests/test_misc.py diff --git a/tests/utils/test_proxy_snake_dict.py b/graphene/utils/tests/test_proxy_snake_dict.py similarity index 100% rename from tests/utils/test_proxy_snake_dict.py rename to graphene/utils/tests/test_proxy_snake_dict.py diff --git a/tests/utils/test_str_converter.py b/graphene/utils/tests/test_str_converter.py similarity index 100% rename from tests/utils/test_str_converter.py rename to graphene/utils/tests/test_str_converter.py diff --git a/tests/django_settings.py b/tests/django_settings.py index d8f90141..2af62bb3 100644 --- a/tests/django_settings.py +++ b/tests/django_settings.py @@ -2,7 +2,6 @@ SECRET_KEY = 1 INSTALLED_APPS = [ 'examples.starwars_django', - 'tests.contrib_django', ] DATABASES = { From a2ab008ead0f97e24fbb0434db98e771a7171ca8 Mon Sep 17 00:00:00 2001 From: Syrus Akbary Date: Wed, 11 Nov 2015 21:54:45 -0800 Subject: [PATCH 16/20] Fixed lint errors --- bin/autolinter | 2 +- graphene/__init__.py | 31 ++++++++++++++++--- graphene/contrib/django/converter.py | 3 +- graphene/contrib/django/options.py | 3 +- graphene/contrib/django/tests/test_query.py | 3 +- graphene/contrib/django/types.py | 6 ++-- graphene/contrib/django/views.py | 10 ++++-- graphene/core/options.py | 4 ++- graphene/core/schema.py | 17 +++++++--- graphene/core/tests/test_schema.py | 6 ++-- graphene/core/types/__init__.py | 25 +++++++++++++-- graphene/core/types/argument.py | 10 ++++-- graphene/core/types/base.py | 11 ++++--- graphene/core/types/field.py | 20 +++++++----- graphene/core/types/objecttype.py | 14 ++++++--- graphene/core/types/tests/test_base.py | 6 +++- graphene/core/types/tests/test_scalars.py | 3 +- graphene/relay/fields.py | 22 ++++++++----- graphene/relay/types.py | 16 +++++++--- graphene/relay/utils.py | 3 +- graphene/utils/tests/test_proxy_snake_dict.py | 13 +++++--- setup.cfg | 4 +-- 22 files changed, 167 insertions(+), 65 deletions(-) diff --git a/bin/autolinter b/bin/autolinter index 77af4388..164618ae 100755 --- a/bin/autolinter +++ b/bin/autolinter @@ -1,5 +1,5 @@ #!/bin/bash autoflake ./ -r --remove-unused-variables --remove-all-unused-imports --in-place -autopep8 ./ -r --in-place +autopep8 ./ -r --in-place --experimental --aggressive --max-line-length 120 isort -rc . diff --git a/graphene/__init__.py b/graphene/__init__.py index 71791c4d..7f58b988 100644 --- a/graphene/__init__.py +++ b/graphene/__init__.py @@ -40,7 +40,30 @@ from graphene.decorators import ( resolve_only_args ) -__all__ = ['Enum', 'Argument', 'String', 'Int', 'Boolean', 'Float', 'ID', 'List', 'NonNull', 'signals', 'Schema', - 'BaseType', 'LazyType', 'ObjectType', 'Interface', 'Mutation', 'Field', 'InputField', 'StringField', - 'IntField', 'BooleanField', 'IDField', 'ListField', 'NonNullField', - 'FloatField', 'resolve_only_args'] +__all__ = [ + 'Enum', + 'Argument', + 'String', + 'Int', + 'Boolean', + 'Float', + 'ID', + 'List', + 'NonNull', + 'signals', + 'Schema', + 'BaseType', + 'LazyType', + 'ObjectType', + 'Interface', + 'Mutation', + 'Field', + 'InputField', + 'StringField', + 'IntField', + 'BooleanField', + 'IDField', + 'ListField', + 'NonNullField', + 'FloatField', + 'resolve_only_args'] diff --git a/graphene/contrib/django/converter.py b/graphene/contrib/django/converter.py index 0a11d364..29b52dec 100644 --- a/graphene/contrib/django/converter.py +++ b/graphene/contrib/django/converter.py @@ -16,7 +16,8 @@ except AttributeError: @singledispatch def convert_django_field(field): raise Exception( - "Don't know how to convert the Django field %s (%s)" % (field, field.__class__)) + "Don't know how to convert the Django field %s (%s)" % + (field, field.__class__)) @convert_django_field.register(models.DateField) diff --git a/graphene/contrib/django/options.py b/graphene/contrib/django/options.py index 9293af6e..a65ce52d 100644 --- a/graphene/contrib/django/options.py +++ b/graphene/contrib/django/options.py @@ -32,6 +32,7 @@ class DjangoOptions(Options): return if not self.model: raise Exception( - 'Django ObjectType %s must have a model in the Meta class attr' % cls) + 'Django ObjectType %s must have a model in the Meta class attr' % + cls) elif not inspect.isclass(self.model) or not issubclass(self.model, models.Model): raise Exception('Provided model in %s is not a Django model' % cls) diff --git a/graphene/contrib/django/tests/test_query.py b/graphene/contrib/django/tests/test_query.py index 8d9366cd..a8e8229b 100644 --- a/graphene/contrib/django/tests/test_query.py +++ b/graphene/contrib/django/tests/test_query.py @@ -87,7 +87,8 @@ def test_should_node(): article = graphene.Field(ArticleNode) def resolve_reporter(self, *args, **kwargs): - return ReporterNode(Reporter(id=1, first_name='ABA', last_name='X')) + return ReporterNode( + Reporter(id=1, first_name='ABA', last_name='X')) query = ''' query ReporterQuery { diff --git a/graphene/contrib/django/types.py b/graphene/contrib/django/types.py index f9282f14..2780d33e 100644 --- a/graphene/contrib/django/types.py +++ b/graphene/contrib/django/types.py @@ -46,11 +46,13 @@ class InstanceObjectType(BaseObjectType): return getattr(self.instance, attr) -class DjangoObjectType(six.with_metaclass(DjangoObjectTypeMeta, InstanceObjectType)): +class DjangoObjectType(six.with_metaclass( + DjangoObjectTypeMeta, InstanceObjectType)): pass -class DjangoInterface(six.with_metaclass(DjangoObjectTypeMeta, InstanceObjectType)): +class DjangoInterface(six.with_metaclass( + DjangoObjectTypeMeta, InstanceObjectType)): pass diff --git a/graphene/contrib/django/views.py b/graphene/contrib/django/views.py index 22a809a2..95274cdc 100644 --- a/graphene/contrib/django/views.py +++ b/graphene/contrib/django/views.py @@ -28,11 +28,14 @@ class GraphQLView(View): errors = [{ "message": str(e) } for e in errors] - return HttpResponse(json.dumps({'errors': errors}), content_type='application/json') + return HttpResponse( + json.dumps({'errors': errors}), + content_type='application/json') def execute_query(self, request, query, *args, **kwargs): if not query: - return self.response_errors(Exception("Must provide query string.")) + return self.response_errors( + Exception("Must provide query string.")) else: try: result = self.schema.execute(query, *args, **kwargs) @@ -59,7 +62,8 @@ class GraphQLView(View): received_json_data = json.loads(request.body.decode()) query = received_json_data.get('query') except ValueError: - return self.response_errors(ValueError("Malformed json body in the post data")) + return self.response_errors(ValueError( + "Malformed json body in the post data")) else: query = request.POST.get('query') or request.GET.get('query') return self.execute_query(request, query or '') diff --git a/graphene/core/options.py b/graphene/core/options.py index 8e12abb4..fac5081a 100644 --- a/graphene/core/options.py +++ b/graphene/core/options.py @@ -52,7 +52,9 @@ class Options(object): # Any leftover attributes must be invalid. if meta_attrs != {}: raise TypeError( - "'class Meta' got invalid attribute(s): %s" % ','.join(meta_attrs.keys())) + "'class Meta' got invalid attribute(s): %s" % + ','.join( + meta_attrs.keys())) else: self.proxy = False diff --git a/graphene/core/schema.py b/graphene/core/schema.py index c0a4f94a..a15a10c8 100644 --- a/graphene/core/schema.py +++ b/graphene/core/schema.py @@ -21,7 +21,8 @@ class GraphQLSchema(_GraphQLSchema): class Schema(object): _executor = None - def __init__(self, query=None, mutation=None, name='Schema', executor=None): + def __init__(self, query=None, mutation=None, + name='Schema', executor=None): self._types_names = {} self._types = {} self.mutation = mutation @@ -36,7 +37,9 @@ class Schema(object): def T(self, object_type): if not object_type: return - if inspect.isclass(object_type) and issubclass(object_type, BaseType) or isinstance(object_type, BaseType): + if inspect.isclass(object_type) and issubclass( + object_type, BaseType) or isinstance( + object_type, BaseType): if object_type not in self._types: internal_type = object_type.internal_type(self) self._types[object_type] = internal_type @@ -63,7 +66,9 @@ class Schema(object): def schema(self): 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): type_name = object_type._meta.type_name @@ -78,7 +83,8 @@ class Schema(object): name = getattr(type, 'name', None) if name: objecttype = self._types_names.get(name, None) - if objecttype and inspect.isclass(objecttype) and issubclass(objecttype, BaseObjectType): + if objecttype and inspect.isclass( + objecttype) and issubclass(objecttype, BaseObjectType): return objecttype def setup(self): @@ -95,7 +101,8 @@ class Schema(object): def types(self): return self._types_names - def execute(self, request='', root=None, vars=None, operation_name=None, **kwargs): + def execute(self, request='', root=None, vars=None, + operation_name=None, **kwargs): root = root or object() return self.executor.execute( self.schema, diff --git a/graphene/core/tests/test_schema.py b/graphene/core/tests/test_schema.py index baa8a0bd..03f7732a 100644 --- a/graphene/core/tests/test_schema.py +++ b/graphene/core/tests/test_schema.py @@ -92,9 +92,9 @@ def test_query_schema_execute(): 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'] - ) + ['__Field', 'String', 'Pet', 'Character', '__InputValue', + '__Directive', '__TypeKind', '__Schema', '__Type', 'Human', + '__EnumValue', 'Boolean']) def test_schema_no_query(): diff --git a/graphene/core/types/__init__.py b/graphene/core/types/__init__.py index 44bc2aa5..5bd49f2f 100644 --- a/graphene/core/types/__init__.py +++ b/graphene/core/types/__init__.py @@ -5,5 +5,26 @@ from .objecttype import ObjectTypeMeta, BaseObjectType, Interface, ObjectType, M from .scalars import String, ID, Boolean, Int, Float, Scalar from .field import Field, InputField -__all__ = ['BaseType', 'LazyType', 'OrderedType', 'Argument', 'ArgumentsGroup', 'to_arguments', 'List', 'NonNull', 'Field', 'InputField', - 'Interface', 'BaseObjectType', 'ObjectTypeMeta', 'ObjectType', 'Mutation', 'InputObjectType', 'String', 'ID', 'Boolean', 'Int', 'Float', 'Scalar'] +__all__ = [ + 'BaseType', + 'LazyType', + 'OrderedType', + 'Argument', + 'ArgumentsGroup', + 'to_arguments', + 'List', + 'NonNull', + 'Field', + 'InputField', + 'Interface', + 'BaseObjectType', + 'ObjectTypeMeta', + 'ObjectType', + 'Mutation', + 'InputObjectType', + 'String', + 'ID', + 'Boolean', + 'Int', + 'Float', + 'Scalar'] diff --git a/graphene/core/types/argument.py b/graphene/core/types/argument.py index db7b58be..6f2de8f5 100644 --- a/graphene/core/types/argument.py +++ b/graphene/core/types/argument.py @@ -9,7 +9,8 @@ from .base import ArgumentType, BaseType, OrderedType class Argument(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): super(Argument, self).__init__(_creation_counter=_creation_counter) self.name = name self.type = type @@ -17,7 +18,9 @@ class Argument(OrderedType): self.default = default def internal_type(self, schema): - return GraphQLArgument(schema.T(self.type), self.default, self.description) + return GraphQLArgument( + schema.T(self.type), + self.default, self.description) def __repr__(self): return self.name @@ -30,7 +33,8 @@ class ArgumentsGroup(BaseType): 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()]) + return OrderedDict([(arg.name, schema.T(arg)) + for arg in self.arguments.values()]) def __len__(self): return len(self.arguments) diff --git a/graphene/core/types/base.py b/graphene/core/types/base.py index 084c929d..cec83117 100644 --- a/graphene/core/types/base.py +++ b/graphene/core/types/base.py @@ -54,7 +54,7 @@ class OrderedType(MountType): def __eq__(self, other): # Needed for @total_ordering - if type(self) == type(other): + if isinstance(self, type(other)): return self.creation_counter == other.creation_counter return NotImplemented @@ -87,7 +87,8 @@ class ArgumentType(MirroredType): def as_argument(self): from .argument import Argument - return Argument(self, _creation_counter=self.creation_counter, *self.args, **self.kwargs) + return Argument( + self, _creation_counter=self.creation_counter, *self.args, **self.kwargs) class FieldType(MirroredType): @@ -103,11 +104,13 @@ class FieldType(MirroredType): def as_field(self): from .field import Field - return Field(self, _creation_counter=self.creation_counter, *self.args, **self.kwargs) + return Field(self, _creation_counter=self.creation_counter, + *self.args, **self.kwargs) def as_inputfield(self): from .field import InputField - return InputField(self, _creation_counter=self.creation_counter, *self.args, **self.kwargs) + return InputField( + self, _creation_counter=self.creation_counter, *self.args, **self.kwargs) class MountedType(FieldType, ArgumentType): diff --git a/graphene/core/types/field.py b/graphene/core/types/field.py index ea83aeb9..5b91a1c7 100644 --- a/graphene/core/types/field.py +++ b/graphene/core/types/field.py @@ -26,7 +26,9 @@ class Empty(object): class Field(OrderedType): - def __init__(self, type, description=None, args=None, name=None, resolver=None, required=False, default=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 @@ -43,7 +45,8 @@ class Field(OrderedType): def contribute_to_class(self, cls, attname): assert issubclass( - cls, BaseObjectType), 'Field {} cannot be mounted in {}'.format(self, cls) + cls, BaseObjectType), 'Field {} cannot be mounted in {}'.format( + self, cls) if not self.name: self.name = to_camel_case(attname) self.attname = attname @@ -109,7 +112,7 @@ class Field(OrderedType): def __eq__(self, other): eq = super(Field, self).__eq__(other) - if type(self) == type(other): + if isinstance(self, type(other)): return eq and self.object_type == other.object_type return NotImplemented @@ -119,7 +122,8 @@ class Field(OrderedType): class InputField(OrderedType): - def __init__(self, type, description=None, default=None, name=None, _creation_counter=None, required=False): + 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: @@ -130,7 +134,8 @@ class InputField(OrderedType): def contribute_to_class(self, cls, attname): assert issubclass( - cls, InputObjectType), 'InputField {} cannot be mounted in {}'.format(self, cls) + cls, InputObjectType), 'InputField {} cannot be mounted in {}'.format( + self, cls) if not self.name: self.name = to_camel_case(attname) self.attname = attname @@ -141,5 +146,6 @@ class InputField(OrderedType): cls._meta.add_field(self) def internal_type(self, schema): - return GraphQLInputObjectField(schema.T(self.type), default_value=self.default, - description=self.description) + return GraphQLInputObjectField( + schema.T(self.type), + default_value=self.default, description=self.description) diff --git a/graphene/core/types/objecttype.py b/graphene/core/types/objecttype.py index aebf8e17..7826929b 100644 --- a/graphene/core/types/objecttype.py +++ b/graphene/core/types/objecttype.py @@ -79,7 +79,8 @@ 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.type.__class__ != field_names[field.name].type.__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 ' @@ -111,7 +112,8 @@ class ObjectTypeMeta(type): 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'): + if not inspect.isclass(value) and hasattr( + value, 'contribute_to_class'): value.contribute_to_class(cls, name) else: setattr(cls, name, value) @@ -157,14 +159,16 @@ class BaseObjectType(BaseType): pass if kwargs: raise TypeError( - "'%s' is an invalid keyword argument for this function" % list(kwargs)[0]) + "'%s' is an invalid keyword argument for this function" % + list(kwargs)[0]) signals.post_init.send(self.__class__, instance=self) @classmethod def fields_as_arguments(cls, schema): - return OrderedDict([(f.attname, GraphQLArgument(f.internal_type(schema))) - for f in cls._meta.fields]) + return OrderedDict( + [(f.attname, GraphQLArgument(f.internal_type(schema))) + for f in cls._meta.fields]) @classmethod def resolve_objecttype(cls, schema, instance, *args): diff --git a/graphene/core/types/tests/test_base.py b/graphene/core/types/tests/test_base.py index 80be2d40..822a84f1 100644 --- a/graphene/core/types/tests/test_base.py +++ b/graphene/core/types/tests/test_base.py @@ -28,7 +28,11 @@ def test_type_as_field_called(Field): a = MountedType(2, description='A', resolver=resolver) a.as_field() Field.assert_called_with( - a, 2, _creation_counter=a.creation_counter, description='A', resolver=resolver) + a, + 2, + _creation_counter=a.creation_counter, + description='A', + resolver=resolver) @patch('graphene.core.types.argument.Argument') diff --git a/graphene/core/types/tests/test_scalars.py b/graphene/core/types/tests/test_scalars.py index d1fcc0ac..cedae96f 100644 --- a/graphene/core/types/tests/test_scalars.py +++ b/graphene/core/types/tests/test_scalars.py @@ -40,7 +40,8 @@ def test_custom_scalar(): @staticmethod def parse_literal(node): if isinstance(node, ast.StringValue): - return datetime.datetime.strptime(node.value, "%Y-%m-%dT%H:%M:%S.%f") + return datetime.datetime.strptime( + node.value, "%Y-%m-%dT%H:%M:%S.%f") @staticmethod def parse_value(value): diff --git a/graphene/relay/fields.py b/graphene/relay/fields.py index ca33a01d..98154fd3 100644 --- a/graphene/relay/fields.py +++ b/graphene/relay/fields.py @@ -10,12 +10,17 @@ class ConnectionField(Field): def __init__(self, field_type, resolver=None, description='', connection_type=None, edge_type=None, **kwargs): - super(ConnectionField, self).__init__(field_type, resolver=resolver, - before=String(), - after=String(), - first=Int(), - last=Int(), - description=description, **kwargs) + super( + ConnectionField, + self).__init__( + field_type, + resolver=resolver, + before=String(), + after=String(), + first=Int(), + last=Int(), + description=description, + **kwargs) self.connection_type = connection_type self.edge_type = edge_type @@ -37,8 +42,9 @@ class ConnectionField(Field): connection_type = self.get_connection_type(node) edge_type = self.get_edge_type(node) - connection = connection_from_list(resolved, args, connection_type=connection_type, - edge_type=edge_type, pageinfo_type=PageInfo) + connection = connection_from_list( + resolved, args, connection_type=connection_type, + edge_type=edge_type, pageinfo_type=PageInfo) connection.set_connection_data(resolved) return connection diff --git a/graphene/relay/types.py b/graphene/relay/types.py index 36820a75..b66e77fc 100644 --- a/graphene/relay/types.py +++ b/graphene/relay/types.py @@ -11,9 +11,11 @@ from graphql_relay.node.node import to_global_id class PageInfo(ObjectType): has_next_page = BooleanField( - required=True, description='When paginating forwards, are there more items?') + required=True, + description='When paginating forwards, are there more items?') has_previous_page = BooleanField( - required=True, description='When paginating backwards, are there more items?') + required=True, + description='When paginating backwards, are there more items?') start_cursor = StringField( description='When paginating backwards, the cursor to continue.') end_cursor = StringField( @@ -35,7 +37,10 @@ class Edge(ObjectType): def for_node(cls, node): from graphene.relay.utils import is_node assert is_node(node), 'ObjectTypes in a edge have to be Nodes' - return type('%s%s' % (node._meta.type_name, cls._meta.type_name), (cls, ), {'node_type': node}) + return type( + '%s%s' % (node._meta.type_name, cls._meta.type_name), + (cls,), + {'node_type': node}) class Connection(ObjectType): @@ -56,7 +61,10 @@ class Connection(ObjectType): from graphene.relay.utils import is_node edge_type = edge_type or Edge assert is_node(node), 'ObjectTypes in a connection have to be Nodes' - return type('%s%s' % (node._meta.type_name, cls._meta.type_name), (cls, ), {'edge_type': edge_type.for_node(node)}) + return type( + '%s%s' % (node._meta.type_name, cls._meta.type_name), + (cls,), + {'edge_type': edge_type.for_node(node)}) def set_connection_data(self, data): self._connection_data = data diff --git a/graphene/relay/utils.py b/graphene/relay/utils.py index 0689d86b..af9d0f6c 100644 --- a/graphene/relay/utils.py +++ b/graphene/relay/utils.py @@ -2,7 +2,8 @@ from graphene.relay.types import BaseNode def is_node(object_type): - return object_type and issubclass(object_type, BaseNode) and not is_node_type(object_type) + return object_type and issubclass( + object_type, BaseNode) and not is_node_type(object_type) def is_node_type(object_type): diff --git a/graphene/utils/tests/test_proxy_snake_dict.py b/graphene/utils/tests/test_proxy_snake_dict.py index 33b7a226..6020d52f 100644 --- a/graphene/utils/tests/test_proxy_snake_dict.py +++ b/graphene/utils/tests/test_proxy_snake_dict.py @@ -19,11 +19,14 @@ def test_proxy_snake_dict(): assert p.get('three_or_for') == 3 assert 'inside' in p assert 'other_camel_case' in p['inside'] - assert sorted(p.items()) == sorted(list([('inside', ProxySnakeDict({'other_camel_case': 3})), - ('none', None), - ('three_or_for', 3), - ('two', 2), - ('one', 1)])) + assert sorted( + p.items()) == sorted( + list( + [('inside', ProxySnakeDict({'other_camel_case': 3})), + ('none', None), + ('three_or_for', 3), + ('two', 2), + ('one', 1)])) def test_proxy_snake_dict_as_kwargs(): diff --git a/setup.cfg b/setup.cfg index 52475711..11382a78 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,6 +1,6 @@ [flake8] -exclude = tests/*,setup.py -max-line-length = 160 +exclude = setup.py +max-line-length = 120 [coverage:run] omit = core/ntypes/tests/* From bf168e7b12c02f7fb439f422811e6323974df0d0 Mon Sep 17 00:00:00 2001 From: Syrus Akbary Date: Wed, 11 Nov 2015 22:15:28 -0800 Subject: [PATCH 17/20] Use new syntax for fields and arguments --- README.md | 6 ++--- README.rst | 6 ++--- examples/starwars/schema.py | 16 ++++++------ examples/starwars_django/schema.py | 4 +-- examples/starwars_relay/schema.py | 8 +++--- graphene/__init__.py | 2 ++ graphene/contrib/django/tests/test_types.py | 4 +-- graphene/contrib/django/tests/test_urls.py | 2 +- graphene/core/tests/test_mutations.py | 6 ++--- graphene/core/tests/test_options.py | 4 +-- graphene/core/tests/test_query.py | 12 ++++----- graphene/core/tests/test_schema.py | 18 +++++++------- graphene/core/types/tests/test_objecttype.py | 11 ++++----- graphene/relay/tests/test_mutations.py | 26 ++++++++++---------- graphene/relay/tests/test_query.py | 6 ++--- graphene/relay/tests/test_types.py | 4 +-- graphene/relay/types.py | 23 +++++++++-------- 17 files changed, 78 insertions(+), 80 deletions(-) diff --git a/README.md b/README.md index 27fd62f0..e005d337 100644 --- a/README.md +++ b/README.md @@ -28,9 +28,9 @@ Here is one example for get you started: ```python class Query(graphene.ObjectType): - hello = graphene.StringField(description='A typical hello world') - ping = graphene.StringField(description='Ping someone', - to=graphene.Argument(graphene.String)) + hello = graphene.String(description='A typical hello world') + ping = graphene.String(description='Ping someone', + to=graphene.String()) def resolve_hello(self, args, info): return 'World' diff --git a/README.rst b/README.rst index 45706105..c21f2335 100644 --- a/README.rst +++ b/README.rst @@ -35,9 +35,9 @@ Here is one example for get you started: .. code:: python class Query(graphene.ObjectType): - hello = graphene.StringField(description='A typical hello world') - ping = graphene.StringField(description='Ping someone', - to=graphene.Argument(graphene.String)) + hello = graphene.String(description='A typical hello world') + ping = graphene.String(description='Ping someone', + to=graphene.String()) def resolve_hello(self, args, info): return 'World' diff --git a/examples/starwars/schema.py b/examples/starwars/schema.py index 6107a62d..b4f05fee 100644 --- a/examples/starwars/schema.py +++ b/examples/starwars/schema.py @@ -12,10 +12,10 @@ Episode = graphene.Enum('Episode', dict( class Character(graphene.Interface): - id = graphene.IDField() - name = graphene.StringField() - friends = graphene.ListField('Character') - appears_in = graphene.ListField(Episode) + id = graphene.ID() + name = graphene.String() + friends = graphene.List('Character') + appears_in = graphene.List(Episode) def resolve_friends(self, args, *_): # The character friends is a list of strings @@ -23,11 +23,11 @@ class Character(graphene.Interface): class Human(Character): - home_planet = graphene.StringField() + home_planet = graphene.String() class Droid(Character): - primary_function = graphene.StringField() + primary_function = graphene.String() class Query(graphene.ObjectType): @@ -35,10 +35,10 @@ class Query(graphene.ObjectType): episode=graphene.Argument(Episode) ) human = graphene.Field(Human, - id=graphene.Argument(graphene.String) + id=graphene.String() ) droid = graphene.Field(Droid, - id=graphene.Argument(graphene.String) + id=graphene.String() ) @resolve_only_args diff --git a/examples/starwars_django/schema.py b/examples/starwars_django/schema.py index 22e30e6a..7f267fed 100644 --- a/examples/starwars_django/schema.py +++ b/examples/starwars_django/schema.py @@ -40,8 +40,8 @@ class Faction(DjangoNode): class IntroduceShip(relay.ClientIDMutation): class Input: - ship_name = graphene.StringField(required=True) - faction_id = graphene.StringField(required=True) + ship_name = graphene.String(required=True) + faction_id = graphene.String(required=True) ship = graphene.Field(Ship) faction = graphene.Field(Faction) diff --git a/examples/starwars_relay/schema.py b/examples/starwars_relay/schema.py index b5d07cbc..da31a6f7 100644 --- a/examples/starwars_relay/schema.py +++ b/examples/starwars_relay/schema.py @@ -8,7 +8,7 @@ schema = graphene.Schema(name='Starwars Relay Schema') class Ship(relay.Node): '''A ship in the Star Wars saga''' - name = graphene.StringField(description='The name of the ship.') + name = graphene.String(description='The name of the ship.') @classmethod def get_node(cls, id): @@ -17,7 +17,7 @@ class Ship(relay.Node): class Faction(relay.Node): '''A faction in the Star Wars saga''' - name = graphene.StringField(description='The name of the faction.') + name = graphene.String(description='The name of the faction.') ships = relay.ConnectionField( Ship, description='The ships used by the faction.') @@ -34,8 +34,8 @@ class Faction(relay.Node): class IntroduceShip(relay.ClientIDMutation): class Input: - ship_name = graphene.StringField(required=True) - faction_id = graphene.StringField(required=True) + ship_name = graphene.String(required=True) + faction_id = graphene.String(required=True) ship = graphene.Field(Ship) faction = graphene.Field(Faction) diff --git a/graphene/__init__.py b/graphene/__init__.py index 7f58b988..61e73c6d 100644 --- a/graphene/__init__.py +++ b/graphene/__init__.py @@ -10,6 +10,7 @@ from graphene.core.schema import ( from graphene.core.types import ( ObjectType, + InputObjectType, Interface, Mutation, BaseType, @@ -55,6 +56,7 @@ __all__ = [ 'BaseType', 'LazyType', 'ObjectType', + 'InputObjectType', 'Interface', 'Mutation', 'Field', diff --git a/graphene/contrib/django/tests/test_types.py b/graphene/contrib/django/tests/test_types.py index f1af176d..2cfabfcb 100644 --- a/graphene/contrib/django/tests/test_types.py +++ b/graphene/contrib/django/tests/test_types.py @@ -2,7 +2,7 @@ from graphene import Schema from graphene.contrib.django.types import DjangoInterface, DjangoNode -from graphene.core.fields import Field, IntField +from graphene.core.fields import Field from graphene.core.types.scalars import Int from graphene.relay.fields import GlobalIDField from graphql.core.type import GraphQLInterfaceType, GraphQLObjectType @@ -23,7 +23,7 @@ class Character(DjangoInterface): class Human(DjangoNode): '''Human description''' - pub_date = IntField() + pub_date = Int() def get_node(self, id): pass diff --git a/graphene/contrib/django/tests/test_urls.py b/graphene/contrib/django/tests/test_urls.py index 2f0801ee..409471d4 100644 --- a/graphene/contrib/django/tests/test_urls.py +++ b/graphene/contrib/django/tests/test_urls.py @@ -18,7 +18,7 @@ class Character(DjangoNode): class Human(DjangoNode): - raises = graphene.StringField() + raises = graphene.String() class Meta: model = Article diff --git a/graphene/core/tests/test_mutations.py b/graphene/core/tests/test_mutations.py index d0efac76..c5a26daa 100644 --- a/graphene/core/tests/test_mutations.py +++ b/graphene/core/tests/test_mutations.py @@ -5,15 +5,15 @@ my_id = 0 class Query(graphene.ObjectType): - base = graphene.StringField() + base = graphene.String() class ChangeNumber(graphene.Mutation): '''Result mutation''' class Input: - to = graphene.IntField() + to = graphene.Int() - result = graphene.StringField() + result = graphene.String() @classmethod def mutate(cls, instance, args, info): diff --git a/graphene/core/tests/test_options.py b/graphene/core/tests/test_options.py index 009ae25d..3b656bd4 100644 --- a/graphene/core/tests/test_options.py +++ b/graphene/core/tests/test_options.py @@ -1,6 +1,6 @@ from py.test import raises -from graphene.core.fields import StringField +from graphene.core.fields import Field from graphene.core.options import Options @@ -20,7 +20,7 @@ def test_field_added_in_meta(): pass opt.contribute_to_class(ObjectType, '_meta') - f = StringField() + f = Field(None) f.attname = 'string_field' opt.add_field(f) assert f in opt.fields diff --git a/graphene/core/tests/test_query.py b/graphene/core/tests/test_query.py index 739bbdc6..d56b66f7 100644 --- a/graphene/core/tests/test_query.py +++ b/graphene/core/tests/test_query.py @@ -1,22 +1,22 @@ -from graphene.core.fields import Field, ListField, StringField +from graphene.core.fields import Field from graphene.core.schema import Schema -from graphene.core.types import Interface, ObjectType +from graphene.core.types import Interface, List, ObjectType, String from graphql.core import graphql from graphql.core.type import GraphQLSchema class Character(Interface): - name = StringField() + name = String() class Pet(ObjectType): - type = StringField(resolve=lambda *_: 'Dog') + type = String(resolver=lambda *_: 'Dog') class Human(Character): - friends = ListField(Character) + friends = List(Character) pet = Field(Pet) def resolve_name(self, *args): @@ -27,8 +27,6 @@ class Human(Character): def resolve_pet(self, *args): return Pet(object()) - # def resolve_friends(self, *args, **kwargs): - # return 'HEY YOU!' schema = Schema() diff --git a/graphene/core/tests/test_schema.py b/graphene/core/tests/test_schema.py index 03f7732a..3dcb0098 100644 --- a/graphene/core/tests/test_schema.py +++ b/graphene/core/tests/test_schema.py @@ -1,7 +1,7 @@ from py.test import raises -from graphene import Interface, ObjectType, Schema -from graphene.core.fields import Field, ListField, StringField +from graphene import Interface, List, ObjectType, Schema, String +from graphene.core.fields import Field from graphene.core.types.base import LazyType from graphql.core import graphql from tests.utils import assert_equal_lists @@ -10,15 +10,15 @@ schema = Schema(name='My own schema') class Character(Interface): - name = StringField() + name = String() class Pet(ObjectType): - type = StringField(resolve=lambda *_: 'Dog') + type = String(resolver=lambda *_: 'Dog') class Human(Character): - friends = ListField(Character) + friends = List(Character) pet = Field(Pet) def resolve_name(self, *args): @@ -109,7 +109,7 @@ def test_schema_register(): @schema.register class MyType(ObjectType): - type = StringField(resolve=lambda *_: 'Dog') + type = String(resolver=lambda *_: 'Dog') schema.query = MyType @@ -121,7 +121,7 @@ def test_schema_register_no_query_type(): @schema.register class MyType(ObjectType): - type = StringField(resolve=lambda *_: 'Dog') + type = String(resolver=lambda *_: 'Dog') with raises(Exception) as excinfo: schema.get_type('MyType') @@ -132,7 +132,7 @@ def test_schema_introspect(): schema = Schema(name='My own schema') class MyType(ObjectType): - type = StringField(resolve=lambda *_: 'Dog') + type = String(resolver=lambda *_: 'Dog') schema.query = MyType @@ -147,7 +147,7 @@ def test_lazytype(): @schema.register class MyType(ObjectType): - type = StringField(resolve=lambda *_: 'Dog') + type = String(resolver=lambda *_: 'Dog') schema.query = MyType diff --git a/graphene/core/types/tests/test_objecttype.py b/graphene/core/types/tests/test_objecttype.py index 125df407..06ac1626 100644 --- a/graphene/core/types/tests/test_objecttype.py +++ b/graphene/core/types/tests/test_objecttype.py @@ -1,8 +1,7 @@ from py.test import raises -from graphene.core.fields import IntField, StringField from graphene.core.schema import Schema -from graphene.core.types import Interface +from graphene.core.types import Int, Interface, String from graphql.core.execution.middlewares.utils import (resolver_has_tag, tag_resolver) from graphql.core.type import GraphQLInterfaceType, GraphQLObjectType @@ -10,7 +9,7 @@ from graphql.core.type import GraphQLInterfaceType, GraphQLObjectType class Character(Interface): '''Character description''' - name = StringField() + name = String() class Meta: type_name = 'core_Character' @@ -18,7 +17,7 @@ class Character(Interface): class Human(Character): '''Human description''' - friends = StringField() + friends = String() class Meta: type_name = 'core_Human' @@ -93,7 +92,7 @@ def test_object_type_container_too_many_args(): def test_field_clashes(): with raises(Exception) as excinfo: class Droid(Character): - name = IntField() + name = Int() assert 'clashes' in str(excinfo.value) @@ -104,7 +103,7 @@ def test_fields_inherited_should_be_different(): def test_field_mantain_resolver_tags(): class Droid(Character): - name = StringField() + name = String() def resolve_name(self, *args): return 'My Droid' diff --git a/graphene/relay/tests/test_mutations.py b/graphene/relay/tests/test_mutations.py index 313a2c1d..f8c840f2 100644 --- a/graphene/relay/tests/test_mutations.py +++ b/graphene/relay/tests/test_mutations.py @@ -1,20 +1,21 @@ import graphene from graphene import relay from graphene.core.schema import Schema +from graphql.core.type import GraphQLInputObjectField my_id = 0 class Query(graphene.ObjectType): - base = graphene.StringField() + base = graphene.String() class ChangeNumber(relay.ClientIDMutation): '''Result mutation''' class Input: - to = graphene.IntField() + to = graphene.Int() - result = graphene.StringField() + result = graphene.String() @classmethod def mutate_and_get_payload(cls, input, info): @@ -33,16 +34,15 @@ schema = Schema(query=Query, mutation=MyResultMutation) def test_mutation_arguments(): assert ChangeNumber.arguments assert list(ChangeNumber.arguments) == ['input'] - ChangeNumber.arguments['input'] - - # inner_type = _input.get_object_type(schema) - # client_mutation_id_field = inner_type._meta.fields_map[ - # 'client_mutation_id'] - # assert issubclass(inner_type, InputObjectType) - # assert isinstance(client_mutation_id_field, graphene.StringField) - # assert client_mutation_id_field.object_type == inner_type - # assert isinstance(client_mutation_id_field.internal_field( - # schema), GraphQLInputObjectField) + assert 'input' in ChangeNumber.arguments + inner_type = ChangeNumber.input_type + client_mutation_id_field = inner_type._meta.fields_map[ + 'client_mutation_id'] + assert issubclass(inner_type, graphene.InputObjectType) + assert isinstance(client_mutation_id_field.type, graphene.NonNull) + assert isinstance(client_mutation_id_field.type.of_type, graphene.String) + assert client_mutation_id_field.object_type == inner_type + assert isinstance(schema.T(client_mutation_id_field), GraphQLInputObjectField) def test_execute_mutations(): diff --git a/graphene/relay/tests/test_query.py b/graphene/relay/tests/test_query.py index 92e9d6f7..5a78d4ba 100644 --- a/graphene/relay/tests/test_query.py +++ b/graphene/relay/tests/test_query.py @@ -6,12 +6,12 @@ schema = graphene.Schema() class MyConnection(relay.Connection): - my_custom_field = graphene.StringField( - resolve=lambda instance, *_: 'Custom') + my_custom_field = graphene.String( + resolver=lambda instance, *_: 'Custom') class MyNode(relay.Node): - name = graphene.StringField() + name = graphene.String() @classmethod def get_node(cls, id): diff --git a/graphene/relay/tests/test_types.py b/graphene/relay/tests/test_types.py index 0c011b0e..19c055a9 100644 --- a/graphene/relay/tests/test_types.py +++ b/graphene/relay/tests/test_types.py @@ -7,7 +7,7 @@ schema = graphene.Schema() class OtherNode(relay.Node): - name = graphene.StringField() + name = graphene.String() @classmethod def get_node(cls, id): @@ -17,7 +17,7 @@ class OtherNode(relay.Node): def test_field_no_contributed_raises_error(): with raises(Exception) as excinfo: class Part(relay.Node): - x = graphene.StringField() + x = graphene.String() assert 'get_node' in str(excinfo.value) diff --git a/graphene/relay/types.py b/graphene/relay/types.py index b66e77fc..91bc9210 100644 --- a/graphene/relay/types.py +++ b/graphene/relay/types.py @@ -1,6 +1,5 @@ -from graphene.core.fields import BooleanField, Field, ListField, StringField -from graphene.core.types import (InputObjectType, Interface, Mutation, - ObjectType) +from graphene.core.types import (Boolean, Field, InputObjectType, Interface, + List, Mutation, ObjectType, String) from graphene.core.types.argument import ArgumentsGroup from graphene.core.types.base import LazyType from graphene.core.types.definitions import NonNull @@ -10,15 +9,15 @@ from graphql_relay.node.node import to_global_id class PageInfo(ObjectType): - has_next_page = BooleanField( + has_next_page = Boolean( required=True, description='When paginating forwards, are there more items?') - has_previous_page = BooleanField( + has_previous_page = Boolean( required=True, description='When paginating backwards, are there more items?') - start_cursor = StringField( + start_cursor = String( description='When paginating backwards, the cursor to continue.') - end_cursor = StringField( + end_cursor = String( description='When paginating forwards, the cursor to continue.') @@ -29,7 +28,7 @@ class Edge(ObjectType): node = Field(LazyType(lambda object_type: object_type.node_type), description='The item at the end of the edge') - cursor = StringField( + cursor = String( required=True, description='A cursor for use in pagination') @classmethod @@ -50,8 +49,8 @@ class Connection(ObjectType): page_info = Field(PageInfo, required=True, description='The Information to aid in pagination') - edges = ListField(LazyType(lambda object_type: object_type.edge_type), - description='Information to aid in pagination.') + edges = List(LazyType(lambda object_type: object_type.edge_type), + description='Information to aid in pagination.') _connection_data = None @@ -104,11 +103,11 @@ class Node(BaseNode, Interface): class MutationInputType(InputObjectType): - client_mutation_id = StringField(required=True) + client_mutation_id = String(required=True) class ClientIDMutation(Mutation): - client_mutation_id = StringField(required=True) + client_mutation_id = String(required=True) @classmethod def _prepare_class(cls): From 1956c1fb032cbff5bb60d5f057704daae9c9f81f Mon Sep 17 00:00:00 2001 From: Syrus Akbary Date: Wed, 11 Nov 2015 22:35:03 -0800 Subject: [PATCH 18/20] Added NonNull and List to Types --- graphene/core/types/base.py | 10 ++++++++++ graphene/core/types/objecttype.py | 4 ++++ graphene/core/types/tests/test_base.py | 21 ++++++++++++++++++++ graphene/core/types/tests/test_objecttype.py | 14 +++++++++++++ 4 files changed, 49 insertions(+) diff --git a/graphene/core/types/base.py b/graphene/core/types/base.py index cec83117..fe261790 100644 --- a/graphene/core/types/base.py +++ b/graphene/core/types/base.py @@ -82,6 +82,16 @@ class MirroredType(OrderedType): self.args = args self.kwargs = kwargs + @property + def List(self): # noqa + from .definitions import List + return List(self, *self.args, **self.kwargs) + + @property + def NonNull(self): # noqa + from .definitions import NonNull + return NonNull(self, *self.args, **self.kwargs) + class ArgumentType(MirroredType): diff --git a/graphene/core/types/objecttype.py b/graphene/core/types/objecttype.py index 7826929b..363d881b 100644 --- a/graphene/core/types/objecttype.py +++ b/graphene/core/types/objecttype.py @@ -10,6 +10,7 @@ from graphene.core.exceptions import SkipField from graphene.core.options import Options from graphene.core.types.argument import ArgumentsGroup from graphene.core.types.base import BaseType +from graphene.core.types.definitions import List, NonNull from graphql.core.type import (GraphQLArgument, GraphQLInputObjectType, GraphQLInterfaceType, GraphQLObjectType) @@ -99,6 +100,9 @@ class ObjectTypeMeta(type): new_class._meta.interfaces.append(base) # new_class._meta.parents.extend(base._meta.parents) + setattr(new_class, 'NonNull', NonNull(new_class)) + setattr(new_class, 'List', List(new_class)) + new_class._prepare() return new_class diff --git a/graphene/core/types/tests/test_base.py b/graphene/core/types/tests/test_base.py index 822a84f1..79d24aca 100644 --- a/graphene/core/types/tests/test_base.py +++ b/graphene/core/types/tests/test_base.py @@ -5,6 +5,7 @@ from graphene.core.types import InputObjectType, ObjectType from ..argument import Argument from ..base import MountedType, OrderedType from ..field import Field, InputField +from ..definitions import List, NonNull def test_orderedtype_equal(): @@ -71,3 +72,23 @@ def test_type_as_argument(): a = MountedType(description='A') argument = a.as_argument() assert isinstance(argument, Argument) + + +def test_type_as_list(): + m = MountedType(2, 3, my_c='A') + a = m.List + + assert isinstance(a, List) + assert a.of_type == m + assert a.args == (2, 3) + assert a.kwargs == {'my_c': 'A'} + + +def test_type_as_nonnull(): + m = MountedType(2, 3, my_c='A') + a = m.NonNull + + assert isinstance(a, NonNull) + assert a.of_type == m + assert a.args == (2, 3) + assert a.kwargs == {'my_c': 'A'} diff --git a/graphene/core/types/tests/test_objecttype.py b/graphene/core/types/tests/test_objecttype.py index 06ac1626..88532e12 100644 --- a/graphene/core/types/tests/test_objecttype.py +++ b/graphene/core/types/tests/test_objecttype.py @@ -112,3 +112,17 @@ def test_field_mantain_resolver_tags(): field = schema.T(Droid._meta.fields_map['name']) assert resolver_has_tag(field.resolver, 'test') + + +def test_type_has_nonnull(): + class Droid(Character): + name = String() + + assert Droid.NonNull.of_type == Droid + + +def test_type_has_list(): + class Droid(Character): + name = String() + + assert Droid.List.of_type == Droid From ae667f3915bafae6c65f8939508c2e39c1a9a05c Mon Sep 17 00:00:00 2001 From: Syrus Akbary Date: Thu, 12 Nov 2015 23:18:17 -0800 Subject: [PATCH 19/20] Reuse objecttype fields --- graphene/core/types/objecttype.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/graphene/core/types/objecttype.py b/graphene/core/types/objecttype.py index 363d881b..9745d3c5 100644 --- a/graphene/core/types/objecttype.py +++ b/graphene/core/types/objecttype.py @@ -244,10 +244,8 @@ class InputObjectType(ObjectType): @classmethod def internal_type(cls, schema): - fields = lambda: OrderedDict([(f.name, schema.T(f)) - for f in cls._meta.fields]) return GraphQLInputObjectType( cls._meta.type_name, description=cls._meta.description, - fields=fields, + fields=partial(cls.get_fields, schema), ) From cc8a494753574da71515f135b755beb35b62a4c3 Mon Sep 17 00:00:00 2001 From: Syrus Akbary Date: Thu, 12 Nov 2015 23:21:58 -0800 Subject: [PATCH 20/20] Improved arguments construction --- graphene/core/types/objecttype.py | 6 ++++-- graphene/relay/types.py | 24 +++++++----------------- 2 files changed, 11 insertions(+), 19 deletions(-) diff --git a/graphene/core/types/objecttype.py b/graphene/core/types/objecttype.py index 9745d3c5..27628222 100644 --- a/graphene/core/types/objecttype.py +++ b/graphene/core/types/objecttype.py @@ -221,6 +221,9 @@ class ObjectType(six.with_metaclass(ObjectTypeMeta, BaseObjectType)): class Mutation(six.with_metaclass(ObjectTypeMeta, BaseObjectType)): + @classmethod + def _construct_arguments(cls, items): + return ArgumentsGroup(**items) @classmethod def _prepare_class(cls): @@ -231,8 +234,7 @@ class Mutation(six.with_metaclass(ObjectTypeMeta, BaseObjectType)): items.pop('__doc__', None) items.pop('__module__', None) items.pop('__weakref__', None) - arguments = ArgumentsGroup(**items) - cls.add_to_class('arguments', arguments) + cls.add_to_class('arguments', cls._construct_arguments(items)) delattr(cls, 'Input') @classmethod diff --git a/graphene/relay/types.py b/graphene/relay/types.py index 91bc9210..f1dfacc0 100644 --- a/graphene/relay/types.py +++ b/graphene/relay/types.py @@ -110,23 +110,13 @@ class ClientIDMutation(Mutation): client_mutation_id = String(required=True) @classmethod - def _prepare_class(cls): - input_class = getattr(cls, 'Input', None) - if input_class: - assert hasattr( - cls, 'mutate_and_get_payload'), 'You have to implement mutate_and_get_payload' - - items = dict(vars(input_class)) - items.pop('__dict__', None) - items.pop('__doc__', None) - items.pop('__module__', None) - items.pop('__weakref__', None) - new_input_type = type('{}Input'.format( - cls._meta.type_name), (MutationInputType, ), items) - cls.add_to_class('input_type', new_input_type) - arguments = ArgumentsGroup(input=NonNull(new_input_type)) - cls.add_to_class('arguments', arguments) - delattr(cls, 'Input') + def _construct_arguments(cls, items): + assert hasattr( + cls, 'mutate_and_get_payload'), 'You have to implement mutate_and_get_payload' + new_input_type = type('{}Input'.format( + cls._meta.type_name), (MutationInputType, ), items) + cls.add_to_class('input_type', new_input_type) + return ArgumentsGroup(input=NonNull(new_input_type)) @classmethod def mutate(cls, instance, args, info):