From 3363f588fee79a333ee9fc8820d4b7e17e4de803 Mon Sep 17 00:00:00 2001 From: Syrus Akbary Date: Wed, 2 Dec 2015 20:06:15 -0800 Subject: [PATCH 01/10] First working version class types --- graphene/core/classtypes/__init__.py | 0 graphene/core/classtypes/base.py | 109 ++++++++++++++++++ graphene/core/classtypes/inputobjecttype.py | 24 ++++ graphene/core/classtypes/interface.py | 54 +++++++++ graphene/core/classtypes/mutation.py | 30 +++++ graphene/core/classtypes/objecttype.py | 99 ++++++++++++++++ graphene/core/classtypes/options.py | 47 ++++++++ graphene/core/classtypes/scalar.py | 21 ++++ graphene/core/classtypes/tests/__init__.py | 0 graphene/core/classtypes/tests/test_base.py | 40 +++++++ .../classtypes/tests/test_inputobjecttype.py | 21 ++++ .../core/classtypes/tests/test_interface.py | 85 ++++++++++++++ .../core/classtypes/tests/test_mutation.py | 27 +++++ .../core/classtypes/tests/test_objecttype.py | 89 ++++++++++++++ graphene/core/classtypes/tests/test_scalar.py | 32 +++++ .../core/classtypes/tests/test_uniontype.py | 27 +++++ graphene/core/classtypes/uniontype.py | 39 +++++++ graphene/core/schema.py | 3 +- graphene/core/types/base.py | 7 +- graphene/core/types/field.py | 6 +- 20 files changed, 755 insertions(+), 5 deletions(-) create mode 100644 graphene/core/classtypes/__init__.py create mode 100644 graphene/core/classtypes/base.py create mode 100644 graphene/core/classtypes/inputobjecttype.py create mode 100644 graphene/core/classtypes/interface.py create mode 100644 graphene/core/classtypes/mutation.py create mode 100644 graphene/core/classtypes/objecttype.py create mode 100644 graphene/core/classtypes/options.py create mode 100644 graphene/core/classtypes/scalar.py create mode 100644 graphene/core/classtypes/tests/__init__.py create mode 100644 graphene/core/classtypes/tests/test_base.py create mode 100644 graphene/core/classtypes/tests/test_inputobjecttype.py create mode 100644 graphene/core/classtypes/tests/test_interface.py create mode 100644 graphene/core/classtypes/tests/test_mutation.py create mode 100644 graphene/core/classtypes/tests/test_objecttype.py create mode 100644 graphene/core/classtypes/tests/test_scalar.py create mode 100644 graphene/core/classtypes/tests/test_uniontype.py create mode 100644 graphene/core/classtypes/uniontype.py diff --git a/graphene/core/classtypes/__init__.py b/graphene/core/classtypes/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/graphene/core/classtypes/base.py b/graphene/core/classtypes/base.py new file mode 100644 index 00000000..a662ec96 --- /dev/null +++ b/graphene/core/classtypes/base.py @@ -0,0 +1,109 @@ +from collections import OrderedDict +import inspect +import six + +from ..exceptions import SkipField +from .options import Options + + +class ClassTypeMeta(type): + options_class = Options + + def __new__(mcs, name, bases, attrs): + super_new = super(ClassTypeMeta, mcs).__new__ + + module = attrs.pop('__module__', None) + doc = attrs.pop('__doc__', None) + new_class = super_new(mcs, name, bases, { + '__module__': module, + '__doc__': doc + }) + attr_meta = attrs.pop('Meta', None) + if not attr_meta: + meta = getattr(new_class, 'Meta', None) + else: + meta = attr_meta + + new_class.add_to_class('_meta', new_class.get_options(meta)) + + return mcs.construct(new_class, bases, attrs) + + def get_options(cls, meta): + return cls.options_class(meta) + + 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'): + value.contribute_to_class(cls, name) + else: + setattr(cls, name, value) + + def construct(cls, bases, attrs): + # Add all attributes to the class. + for obj_name, obj in attrs.items(): + cls.add_to_class(obj_name, obj) + return cls + + +class ClassType(six.with_metaclass(ClassTypeMeta)): + @classmethod + def internal_type(cls, schema): + raise NotImplementedError("Function internal_type not implemented in type {}".format(cls)) + + +class FieldsOptions(Options): + def __init__(self, *args, **kwargs): + super(FieldsOptions, self).__init__(*args, **kwargs) + self.local_fields = [] + + def add_field(self, field): + self.local_fields.append(field) + + @property + def fields(self): + return sorted(self.local_fields) + + @property + def fields_map(self): + return OrderedDict([(f.attname, f) for f in self.fields]) + + +class FieldsClassTypeMeta(ClassTypeMeta): + options_class = FieldsOptions + + +class FieldsClassType(six.with_metaclass(FieldsClassTypeMeta, ClassType)): + @classmethod + def fields_internal_types(cls, schema): + fields = [] + for field in cls._meta.fields: + try: + fields.append((field.name, schema.T(field))) + except SkipField: + continue + + return OrderedDict(fields) + +# class NamedClassType(ClassType): +# pass + + +# class UnionType(NamedClassType): +# class Meta: +# abstract = True + + +# class ObjectType(NamedClassType): +# class Meta: +# abstract = True + + +# class InputObjectType(NamedClassType): +# class Meta: +# abstract = True + + +# class Mutation(ObjectType): +# class Meta: +# abstract = True diff --git a/graphene/core/classtypes/inputobjecttype.py b/graphene/core/classtypes/inputobjecttype.py new file mode 100644 index 00000000..393173e8 --- /dev/null +++ b/graphene/core/classtypes/inputobjecttype.py @@ -0,0 +1,24 @@ +from functools import partial + +from graphql.core.type import GraphQLInputObjectType + +from .base import FieldsClassType + + +class InputObjectType(FieldsClassType): + class Meta: + abstract = True + + def __init__(self, *args, **kwargs): + raise Exception("An InputObjectType cannot be initialized") + + @classmethod + def internal_type(cls, schema): + if cls._meta.abstract: + raise Exception("Abstract InputObjectTypes don't have a specific type.") + + return GraphQLInputObjectType( + cls._meta.type_name, + description=cls._meta.description, + fields=partial(cls.fields_internal_types, schema), + ) diff --git a/graphene/core/classtypes/interface.py b/graphene/core/classtypes/interface.py new file mode 100644 index 00000000..f689c709 --- /dev/null +++ b/graphene/core/classtypes/interface.py @@ -0,0 +1,54 @@ +import six +from functools import partial + +from graphql.core.type import GraphQLInterfaceType + +from .base import FieldsClassTypeMeta +from .objecttype import ObjectType, ObjectTypeMeta + + +class InterfaceMeta(ObjectTypeMeta): + def construct(cls, bases, attrs): + if cls._meta.abstract or Interface in bases: + # Return Interface type + cls = FieldsClassTypeMeta.construct(cls, bases, attrs) + setattr(cls._meta, 'interface', True) + return cls + else: + # Return ObjectType class with all the inherited interfaces + cls = super(InterfaceMeta, cls).construct(bases, attrs) + for interface in bases: + is_interface = issubclass(interface, Interface) and getattr(interface._meta, 'interface', False) + if not is_interface: + continue + cls._meta.interfaces.append(interface) + return cls + + +class Interface(six.with_metaclass(InterfaceMeta, ObjectType)): + class Meta: + abstract = True + + def __init__(self, *args, **kwargs): + if self._meta.interface: + raise Exception("An interface cannot be initialized") + return super(Interface, self).__init__(*args, **kwargs) + + @classmethod + def _resolve_type(cls, schema, instance, *args): + return schema.T(instance.__class__) + + @classmethod + def internal_type(cls, schema): + if cls._meta.abstract: + raise Exception("Abstract Interfaces don't have a specific type.") + + if not cls._meta.interface: + return super(Interface, cls).internal_type(schema) + + return GraphQLInterfaceType( + cls._meta.type_name, + description=cls._meta.description, + resolve_type=partial(cls._resolve_type, schema), + fields=partial(cls.fields_internal_types, schema) + ) diff --git a/graphene/core/classtypes/mutation.py b/graphene/core/classtypes/mutation.py new file mode 100644 index 00000000..90aa5cf8 --- /dev/null +++ b/graphene/core/classtypes/mutation.py @@ -0,0 +1,30 @@ +import six + +from ..types.argument import ArgumentsGroup +from .objecttype import ObjectType, ObjectTypeMeta + + +class MutationMeta(ObjectTypeMeta): + def construct(cls, bases, attrs): + input_class = attrs.pop('Input', None) + if input_class: + items = dict(vars(input_class)) + items.pop('__dict__', None) + items.pop('__doc__', None) + items.pop('__module__', None) + items.pop('__weakref__', None) + cls.add_to_class('arguments', cls.construct_arguments(items)) + cls = super(MutationMeta, cls).construct(bases, attrs) + return cls + + def construct_arguments(cls, items): + return ArgumentsGroup(**items) + + +class Mutation(six.with_metaclass(MutationMeta, ObjectType)): + class Meta: + abstract = True + + @classmethod + def get_arguments(cls): + return cls.arguments diff --git a/graphene/core/classtypes/objecttype.py b/graphene/core/classtypes/objecttype.py new file mode 100644 index 00000000..93d2286e --- /dev/null +++ b/graphene/core/classtypes/objecttype.py @@ -0,0 +1,99 @@ +import six +from functools import partial + +from graphql.core.type import GraphQLObjectType + +from graphene import signals +from .base import FieldsOptions, FieldsClassType, FieldsClassTypeMeta +from .uniontype import UnionType + + +def is_objecttype(cls): + if not issubclass(cls, ObjectType): + return False + return not cls._meta.interface + + +class ObjectTypeOptions(FieldsOptions): + def __init__(self, *args, **kwargs): + super(ObjectTypeOptions, self).__init__(*args, **kwargs) + self.interface = False + self.interfaces = [] + + +class ObjectTypeMeta(FieldsClassTypeMeta): + def construct(cls, bases, attrs): + cls = super(ObjectTypeMeta, cls).construct(bases, attrs) + if not cls._meta.abstract: + union_types = list(filter(is_objecttype, bases)) + if len(union_types) > 1: + meta_attrs = dict(cls._meta.original_attrs, types=union_types) + Meta = type('Meta', (object, ), meta_attrs) + attrs['Meta'] = Meta + attrs['__module__'] = cls.__module__ + attrs['__doc__'] = cls.__doc__ + return type(cls.__name__, (UnionType, ), attrs) + return cls + + options_class = ObjectTypeOptions + + +class ObjectType(six.with_metaclass(ObjectTypeMeta, FieldsClassType)): + class Meta: + abstract = True + + def __init__(self, *args, **kwargs): + signals.pre_init.send(self.__class__, args=args, kwargs=kwargs) + self._root = kwargs.pop('_root', None) + args_len = len(args) + fields = self._meta.fields + if args_len > len(fields): + # Daft, but matches old exception sans the err msg. + raise IndexError("Number of args exceeds number of fields") + fields_iter = iter(fields) + + if not kwargs: + for val, field in zip(args, fields_iter): + setattr(self, field.attname, val) + else: + for val, field in zip(args, fields_iter): + setattr(self, field.attname, val) + kwargs.pop(field.attname, None) + + for field in fields_iter: + try: + val = kwargs.pop(field.attname) + setattr(self, field.attname, val) + except KeyError: + pass + + if kwargs: + for prop in list(kwargs): + try: + if isinstance(getattr(self.__class__, prop), property): + setattr(self, prop, kwargs.pop(prop)) + except AttributeError: + pass + if kwargs: + raise TypeError( + "'%s' is an invalid keyword argument for this function" % + list(kwargs)[0]) + + signals.post_init.send(self.__class__, instance=self) + + @classmethod + def internal_type(cls, schema): + if cls._meta.abstract: + raise Exception("Abstract ObjectTypes don't have a specific type.") + + return GraphQLObjectType( + cls._meta.type_name, + description=cls._meta.description, + interfaces=[schema.T(i) for i in cls._meta.interfaces], + fields=partial(cls.fields_internal_types, schema), + is_type_of=getattr(cls, 'is_type_of', None) + ) + + @classmethod + def wrap(cls, instance, args, info): + return cls(_root=instance) diff --git a/graphene/core/classtypes/options.py b/graphene/core/classtypes/options.py new file mode 100644 index 00000000..7a965a2c --- /dev/null +++ b/graphene/core/classtypes/options.py @@ -0,0 +1,47 @@ +class Options(object): + def __init__(self, meta=None, **defaults): + self.meta = meta + self.abstract = False + for name, value in defaults.items(): + setattr(self, name, value) + self.valid_attrs = list(defaults.keys()) + ['type_name', 'description', 'abstract'] + + def contribute_to_class(self, cls, name): + cls._meta = self + self.parent = cls + # First, construct the default values for these options. + self.object_name = cls.__name__ + self.type_name = self.object_name + + self.description = cls.__doc__ + # Store the original user-defined values for each option, + # for use when serializing the model definition + self.original_attrs = {} + + # Next, apply any overridden values from 'class Meta'. + if self.meta: + meta_attrs = self.meta.__dict__.copy() + for name in self.meta.__dict__: + # Ignore any private attributes that Django doesn't care about. + # NOTE: We can't modify a dictionary's contents while looping + # over it, so we loop over the *original* dictionary instead. + if name.startswith('_'): + del meta_attrs[name] + for attr_name in self.valid_attrs: + if attr_name in meta_attrs: + setattr(self, attr_name, meta_attrs.pop(attr_name)) + self.original_attrs[attr_name] = getattr(self, attr_name) + elif hasattr(self.meta, attr_name): + setattr(self, attr_name, getattr(self.meta, attr_name)) + self.original_attrs[attr_name] = getattr(self, attr_name) + + del self.valid_attrs + + # Any leftover attributes must be invalid. + if meta_attrs != {}: + raise TypeError( + "'class Meta' got invalid attribute(s): %s" % + ','.join( + meta_attrs.keys())) + + del self.meta diff --git a/graphene/core/classtypes/scalar.py b/graphene/core/classtypes/scalar.py new file mode 100644 index 00000000..fe4087e8 --- /dev/null +++ b/graphene/core/classtypes/scalar.py @@ -0,0 +1,21 @@ +from graphql.core.type import GraphQLScalarType + +from .base import ClassType +from ..types.base import MountedType + + +class Scalar(ClassType, 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._meta.type_name, + description=cls._meta.description, + serialize=serialize, + parse_value=parse_value, + parse_literal=parse_literal + ) diff --git a/graphene/core/classtypes/tests/__init__.py b/graphene/core/classtypes/tests/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/graphene/core/classtypes/tests/test_base.py b/graphene/core/classtypes/tests/test_base.py new file mode 100644 index 00000000..2830b9bb --- /dev/null +++ b/graphene/core/classtypes/tests/test_base.py @@ -0,0 +1,40 @@ +from ..base import ClassType, FieldsClassType +from ...types import Field, String +from ...schema import Schema + + +def test_classtype_basic(): + class Character(ClassType): + '''Character description''' + pass + assert Character._meta.type_name == 'Character' + assert Character._meta.description == 'Character description' + + +def test_classtype_advanced(): + class Character(ClassType): + class Meta: + type_name = 'OtherCharacter' + description = 'OtherCharacter description' + assert Character._meta.type_name == 'OtherCharacter' + assert Character._meta.description == 'OtherCharacter description' + + +def test_fieldsclasstype(): + f = Field(String()) + + class Character(FieldsClassType): + field_name = f + + assert Character._meta.fields == [f] + + +def test_fieldsclasstype_fieldtype(): + f = Field(String()) + + class Character(FieldsClassType): + field_name = f + + schema = Schema(query=Character) + assert Character.fields_internal_types(schema)['fieldName'] == schema.T(f) + assert Character._meta.fields_map['field_name'] == f diff --git a/graphene/core/classtypes/tests/test_inputobjecttype.py b/graphene/core/classtypes/tests/test_inputobjecttype.py new file mode 100644 index 00000000..67306bc3 --- /dev/null +++ b/graphene/core/classtypes/tests/test_inputobjecttype.py @@ -0,0 +1,21 @@ +from py.test import raises + +from graphql.core.type import GraphQLInputObjectType + +from graphene.core.schema import Schema +from graphene.core.types import String +from ..inputobjecttype import InputObjectType + + +def test_inputobjecttype(): + class InputCharacter(InputObjectType): + '''InputCharacter description''' + name = String() + + schema = Schema() + + object_type = schema.T(InputCharacter) + assert isinstance(object_type, GraphQLInputObjectType) + assert InputCharacter._meta.type_name == 'InputCharacter' + assert object_type.description == 'InputCharacter description' + assert list(object_type.get_fields().keys()) == ['name'] diff --git a/graphene/core/classtypes/tests/test_interface.py b/graphene/core/classtypes/tests/test_interface.py new file mode 100644 index 00000000..43e9e503 --- /dev/null +++ b/graphene/core/classtypes/tests/test_interface.py @@ -0,0 +1,85 @@ +from py.test import raises + +from graphql.core.type import GraphQLInterfaceType, GraphQLObjectType + +from graphene.core.schema import Schema +from graphene.core.types import String +from ..interface import Interface +from ..objecttype import ObjectType + + +def test_interface(): + class Character(Interface): + '''Character description''' + name = String() + + schema = Schema() + + object_type = schema.T(Character) + assert issubclass(Character, Interface) + assert isinstance(object_type, GraphQLInterfaceType) + assert Character._meta.interface + assert Character._meta.type_name == 'Character' + assert object_type.description == 'Character description' + assert list(object_type.get_fields().keys()) == ['name'] + + +def test_interface_cannot_initialize(): + class Character(Interface): + pass + + with raises(Exception) as excinfo: + Character() + assert 'An interface cannot be initialized' == str(excinfo.value) + + +def test_interface_inheritance_abstract(): + class Character(Interface): + pass + + class ShouldBeInterface(Character): + class Meta: + abstract = True + + class ShouldBeObjectType(ShouldBeInterface): + pass + + assert ShouldBeInterface._meta.interface + assert not ShouldBeObjectType._meta.interface + assert issubclass(ShouldBeObjectType, ObjectType) + + +def test_interface_inheritance(): + class Character(Interface): + pass + + class GeneralInterface(Interface): + pass + + class ShouldBeObjectType(GeneralInterface, Character): + pass + + schema = Schema() + + assert Character._meta.interface + assert not ShouldBeObjectType._meta.interface + assert issubclass(ShouldBeObjectType, ObjectType) + assert Character in ShouldBeObjectType._meta.interfaces + assert GeneralInterface in ShouldBeObjectType._meta.interfaces + assert isinstance(schema.T(Character), GraphQLInterfaceType) + assert isinstance(schema.T(ShouldBeObjectType), GraphQLObjectType) + + +def test_interface_inheritance_non_objects(): + class ComonClass(object): + common_attr = True + + class Character(ComonClass, Interface): + pass + + class ShouldBeObjectType(Character): + pass + + assert Character._meta.interface + assert Character.common_attr + assert ShouldBeObjectType.common_attr diff --git a/graphene/core/classtypes/tests/test_mutation.py b/graphene/core/classtypes/tests/test_mutation.py new file mode 100644 index 00000000..ded164ea --- /dev/null +++ b/graphene/core/classtypes/tests/test_mutation.py @@ -0,0 +1,27 @@ +from py.test import raises + +from graphql.core.type import GraphQLObjectType + +from graphene.core.schema import Schema +from graphene.core.types import String +from ..mutation import Mutation +from ...types.argument import ArgumentsGroup + + +def test_mutation(): + class MyMutation(Mutation): + '''MyMutation description''' + class Input: + arg_name = String() + name = String() + + schema = Schema() + + object_type = schema.T(MyMutation) + assert MyMutation._meta.type_name == 'MyMutation' + assert isinstance(object_type, GraphQLObjectType) + assert object_type.description == 'MyMutation description' + assert list(object_type.get_fields().keys()) == ['name'] + assert MyMutation._meta.fields_map['name'].object_type == MyMutation + assert isinstance(MyMutation.arguments, ArgumentsGroup) + assert 'argName' in MyMutation.arguments diff --git a/graphene/core/classtypes/tests/test_objecttype.py b/graphene/core/classtypes/tests/test_objecttype.py new file mode 100644 index 00000000..95134bef --- /dev/null +++ b/graphene/core/classtypes/tests/test_objecttype.py @@ -0,0 +1,89 @@ +from py.test import raises + +from graphql.core.type import GraphQLObjectType + +from graphene.core.schema import Schema +from graphene.core.types import Int, String +from ..objecttype import ObjectType +from ..uniontype import UnionType + + +def test_object_type(): + class Human(ObjectType): + '''Human description''' + name = String() + friends = String() + + schema = Schema() + + object_type = schema.T(Human) + assert Human._meta.type_name == 'Human' + assert isinstance(object_type, GraphQLObjectType) + assert object_type.description == 'Human description' + assert list(object_type.get_fields().keys()) == ['name', 'friends'] + assert Human._meta.fields_map['name'].object_type == Human + + +def test_object_type_container(): + class Human(ObjectType): + name = String() + friends = String() + + h = Human(name='My name') + assert h.name == 'My name' + + +def test_object_type_set_properties(): + class Human(ObjectType): + name = String() + friends = String() + + @property + def readonly_prop(self): + return 'readonly' + + @property + def write_prop(self): + return self._write_prop + + @write_prop.setter + def write_prop(self, value): + self._write_prop = value + + h = Human(readonly_prop='custom', write_prop='custom') + assert h.readonly_prop == 'readonly' + assert h.write_prop == 'custom' + + +def test_object_type_container_invalid_kwarg(): + class Human(ObjectType): + name = String() + + with raises(TypeError): + Human(invalid='My name') + + +def test_object_type_container_too_many_args(): + class Human(ObjectType): + name = String() + + with raises(IndexError): + Human('Peter', 'No friends :(', None) + + +def test_object_type_union(): + class Human(ObjectType): + name = String() + + class Pet(ObjectType): + name = String() + + class Thing(Human, Pet): + '''Thing union description''' + my_attr = True + + assert issubclass(Thing, UnionType) + assert Thing._meta.types == [Human, Pet] + assert Thing._meta.type_name == 'Thing' + assert Thing._meta.description == 'Thing union description' + assert Thing.my_attr diff --git a/graphene/core/classtypes/tests/test_scalar.py b/graphene/core/classtypes/tests/test_scalar.py new file mode 100644 index 00000000..a6e37881 --- /dev/null +++ b/graphene/core/classtypes/tests/test_scalar.py @@ -0,0 +1,32 @@ +from graphql.core.type import GraphQLScalarType + +from ...schema import Schema +from ..scalar import Scalar + + +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") + + schema = Schema() + + 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/classtypes/tests/test_uniontype.py b/graphene/core/classtypes/tests/test_uniontype.py new file mode 100644 index 00000000..d19615fa --- /dev/null +++ b/graphene/core/classtypes/tests/test_uniontype.py @@ -0,0 +1,27 @@ +from graphql.core.type import GraphQLUnionType + +from graphene.core.schema import Schema +from graphene.core.types import String +from ..objecttype import ObjectType +from ..uniontype import UnionType + + +def test_uniontype(): + class Human(ObjectType): + name = String() + + class Pet(ObjectType): + name = String() + + class Thing(UnionType): + '''Thing union description''' + class Meta: + types = [Human, Pet] + + schema = Schema() + + object_type = schema.T(Thing) + assert isinstance(object_type, GraphQLUnionType) + assert Thing._meta.type_name == 'Thing' + assert object_type.description == 'Thing union description' + assert object_type.get_possible_types() == [schema.T(Human), schema.T(Pet)] diff --git a/graphene/core/classtypes/uniontype.py b/graphene/core/classtypes/uniontype.py new file mode 100644 index 00000000..c1fb86e3 --- /dev/null +++ b/graphene/core/classtypes/uniontype.py @@ -0,0 +1,39 @@ +import six + +from graphql.core.type import GraphQLUnionType + +from .base import FieldsOptions, FieldsClassType, FieldsClassTypeMeta + + +class UnionTypeOptions(FieldsOptions): + def __init__(self, *args, **kwargs): + super(UnionTypeOptions, self).__init__(*args, **kwargs) + self.types = [] + + +class UnionTypeMeta(FieldsClassTypeMeta): + options_class = UnionTypeOptions + + def get_options(cls, meta): + return cls.options_class(meta, types=[]) + + +class UnionType(six.with_metaclass(UnionTypeMeta, FieldsClassType)): + class Meta: + abstract = True + + @classmethod + def _resolve_type(cls, schema, instance, *args): + return schema.T(instance.__class__) + + @classmethod + def internal_type(cls, schema): + if cls._meta.abstract: + raise Exception("Abstract ObjectTypes don't have a specific type.") + + return GraphQLUnionType( + cls._meta.type_name, + types=map(schema.T, cls._meta.types), + resolve_type=cls._resolve_type, + description=cls._meta.description, + ) diff --git a/graphene/core/schema.py b/graphene/core/schema.py index eef79678..ec9d1bf5 100644 --- a/graphene/core/schema.py +++ b/graphene/core/schema.py @@ -12,6 +12,7 @@ from graphene import signals from .types.base import BaseType from .types.objecttype import BaseObjectType +from .classtypes.base import ClassType class GraphQLSchema(_GraphQLSchema): @@ -42,7 +43,7 @@ class Schema(object): if not object_type: return if inspect.isclass(object_type) and issubclass( - object_type, BaseType) or isinstance( + object_type, (BaseType, ClassType)) or isinstance( object_type, BaseType): if object_type not in self._types: internal_type = object_type.internal_type(self) diff --git a/graphene/core/types/base.py b/graphene/core/types/base.py index fe261790..2b9a4e01 100644 --- a/graphene/core/types/base.py +++ b/graphene/core/types/base.py @@ -2,6 +2,9 @@ from functools import total_ordering import six +from ..classtypes.base import FieldsClassType +from ..classtypes.inputobjecttype import InputObjectType as NewInputObjectType + class BaseType(object): @@ -105,10 +108,10 @@ class FieldType(MirroredType): def contribute_to_class(self, cls, name): from ..types import BaseObjectType, InputObjectType - if issubclass(cls, InputObjectType): + if issubclass(cls, (InputObjectType, NewInputObjectType)): inputfield = self.as_inputfield() return inputfield.contribute_to_class(cls, name) - elif issubclass(cls, BaseObjectType): + elif issubclass(cls, (BaseObjectType, FieldsClassType)): field = self.as_field() return field.contribute_to_class(cls, name) diff --git a/graphene/core/types/field.py b/graphene/core/types/field.py index db52086a..489516e1 100644 --- a/graphene/core/types/field.py +++ b/graphene/core/types/field.py @@ -5,6 +5,8 @@ import six from graphql.core.type import GraphQLField, GraphQLInputObjectField from ...utils import to_camel_case +from ..classtypes.base import FieldsClassType +from ..classtypes.inputobjecttype import InputObjectType as NewInputObjectType from ..types import BaseObjectType, InputObjectType from .argument import ArgumentsGroup, snake_case_args from .base import LazyType, MountType, OrderedType @@ -32,7 +34,7 @@ class Field(OrderedType): def contribute_to_class(self, cls, attname): assert issubclass( - cls, BaseObjectType), 'Field {} cannot be mounted in {}'.format( + cls, (BaseObjectType, FieldsClassType)), 'Field {} cannot be mounted in {}'.format( self, cls) if not self.name: self.name = to_camel_case(attname) @@ -126,7 +128,7 @@ class InputField(OrderedType): def contribute_to_class(self, cls, attname): assert issubclass( - cls, InputObjectType), 'InputField {} cannot be mounted in {}'.format( + cls, (InputObjectType, NewInputObjectType)), 'InputField {} cannot be mounted in {}'.format( self, cls) if not self.name: self.name = to_camel_case(attname) From afc5e2720bb21bfc5ac365565b3c9d9a22345f7e Mon Sep 17 00:00:00 2001 From: Syrus Akbary Date: Wed, 2 Dec 2015 20:28:53 -0800 Subject: [PATCH 02/10] Added NonNull, List definitions to ClassTypes --- graphene/core/classtypes/base.py | 12 ++++++++++++ graphene/core/classtypes/tests/test_base.py | 16 +++++++++++++++- 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/graphene/core/classtypes/base.py b/graphene/core/classtypes/base.py index a662ec96..5e338ebe 100644 --- a/graphene/core/classtypes/base.py +++ b/graphene/core/classtypes/base.py @@ -43,10 +43,19 @@ class ClassTypeMeta(type): # Add all attributes to the class. for obj_name, obj in attrs.items(): cls.add_to_class(obj_name, obj) + + if not cls._meta.abstract: + from ..types import List, NonNull + setattr(cls, 'NonNull', NonNull(cls)) + setattr(cls, 'List', List(cls)) + return cls class ClassType(six.with_metaclass(ClassTypeMeta)): + class Meta: + abstract = True + @classmethod def internal_type(cls, schema): raise NotImplementedError("Function internal_type not implemented in type {}".format(cls)) @@ -74,6 +83,9 @@ class FieldsClassTypeMeta(ClassTypeMeta): class FieldsClassType(six.with_metaclass(FieldsClassTypeMeta, ClassType)): + class Meta: + abstract = True + @classmethod def fields_internal_types(cls, schema): fields = [] diff --git a/graphene/core/classtypes/tests/test_base.py b/graphene/core/classtypes/tests/test_base.py index 2830b9bb..2564a606 100644 --- a/graphene/core/classtypes/tests/test_base.py +++ b/graphene/core/classtypes/tests/test_base.py @@ -1,5 +1,5 @@ from ..base import ClassType, FieldsClassType -from ...types import Field, String +from ...types import Field, String, List, NonNull from ...schema import Schema @@ -20,6 +20,20 @@ def test_classtype_advanced(): assert Character._meta.description == 'OtherCharacter description' +def test_classtype_definition_list(): + class Character(ClassType): + '''Character description''' + pass + assert isinstance(Character.List, List) + + +def test_classtype_definition_nonnull(): + class Character(ClassType): + '''Character description''' + pass + assert isinstance(Character.NonNull, NonNull) + + def test_fieldsclasstype(): f = Field(String()) From 398f7da24cbfd92a1800606ad61fc76159920df2 Mon Sep 17 00:00:00 2001 From: Syrus Akbary Date: Wed, 2 Dec 2015 20:39:20 -0800 Subject: [PATCH 03/10] Added FieldsClassType inheritance --- graphene/core/classtypes/base.py | 56 +++++++++++-------- graphene/core/classtypes/objecttype.py | 2 +- graphene/core/classtypes/tests/test_base.py | 15 +++++ .../core/classtypes/tests/test_interface.py | 4 +- 4 files changed, 51 insertions(+), 26 deletions(-) diff --git a/graphene/core/classtypes/base.py b/graphene/core/classtypes/base.py index 5e338ebe..c6d4dc58 100644 --- a/graphene/core/classtypes/base.py +++ b/graphene/core/classtypes/base.py @@ -1,6 +1,7 @@ from collections import OrderedDict import inspect import six +import copy from ..exceptions import SkipField from .options import Options @@ -81,6 +82,38 @@ class FieldsOptions(Options): class FieldsClassTypeMeta(ClassTypeMeta): options_class = FieldsOptions + def extend_fields(cls, bases): + new_fields = cls._meta.local_fields + field_names = {f.name: f for f in new_fields} + + for base in bases: + if not issubclass(base, FieldsClassType): + continue + + parent_fields = base._meta.local_fields + for field in parent_fields: + 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 ' + 'Interface %s (%r)' % ( + field.name, + cls.__name__, + field.__class__, + base.__name__, + field_names[field.name].__class__) + ) + new_field = copy.copy(field) + cls.add_to_class(field.attname, new_field) + + + def construct(cls, bases, attrs): + cls = super(FieldsClassTypeMeta, cls).construct(bases, attrs) + if not cls._meta.abstract: + cls.extend_fields(bases) + return cls + class FieldsClassType(six.with_metaclass(FieldsClassTypeMeta, ClassType)): class Meta: @@ -96,26 +129,3 @@ class FieldsClassType(six.with_metaclass(FieldsClassTypeMeta, ClassType)): continue return OrderedDict(fields) - -# class NamedClassType(ClassType): -# pass - - -# class UnionType(NamedClassType): -# class Meta: -# abstract = True - - -# class ObjectType(NamedClassType): -# class Meta: -# abstract = True - - -# class InputObjectType(NamedClassType): -# class Meta: -# abstract = True - - -# class Mutation(ObjectType): -# class Meta: -# abstract = True diff --git a/graphene/core/classtypes/objecttype.py b/graphene/core/classtypes/objecttype.py index 93d2286e..13e93cb3 100644 --- a/graphene/core/classtypes/objecttype.py +++ b/graphene/core/classtypes/objecttype.py @@ -89,7 +89,7 @@ class ObjectType(six.with_metaclass(ObjectTypeMeta, FieldsClassType)): return GraphQLObjectType( cls._meta.type_name, description=cls._meta.description, - interfaces=[schema.T(i) for i in cls._meta.interfaces], + interfaces=map(schema.T, cls._meta.interfaces), fields=partial(cls.fields_internal_types, schema), is_type_of=getattr(cls, 'is_type_of', None) ) diff --git a/graphene/core/classtypes/tests/test_base.py b/graphene/core/classtypes/tests/test_base.py index 2564a606..546cd943 100644 --- a/graphene/core/classtypes/tests/test_base.py +++ b/graphene/core/classtypes/tests/test_base.py @@ -25,6 +25,7 @@ def test_classtype_definition_list(): '''Character description''' pass assert isinstance(Character.List, List) + assert Character.List.of_type == Character def test_classtype_definition_nonnull(): @@ -32,6 +33,7 @@ def test_classtype_definition_nonnull(): '''Character description''' pass assert isinstance(Character.NonNull, NonNull) + assert Character.NonNull.of_type == Character def test_fieldsclasstype(): @@ -52,3 +54,16 @@ def test_fieldsclasstype_fieldtype(): schema = Schema(query=Character) assert Character.fields_internal_types(schema)['fieldName'] == schema.T(f) assert Character._meta.fields_map['field_name'] == f + + +def test_fieldsclasstype_inheritfields(): + name_field = Field(String()) + last_name_field = Field(String()) + + class Fields1(FieldsClassType): + name = name_field + + class Fields2(Fields1): + last_name = last_name_field + + assert list(Fields2._meta.fields_map.keys()) == ['name', 'last_name'] diff --git a/graphene/core/classtypes/tests/test_interface.py b/graphene/core/classtypes/tests/test_interface.py index 43e9e503..3a07f172 100644 --- a/graphene/core/classtypes/tests/test_interface.py +++ b/graphene/core/classtypes/tests/test_interface.py @@ -71,10 +71,10 @@ def test_interface_inheritance(): def test_interface_inheritance_non_objects(): - class ComonClass(object): + class CommonClass(object): common_attr = True - class Character(ComonClass, Interface): + class Character(CommonClass, Interface): pass class ShouldBeObjectType(Character): From 8abcaff02b34c61d006a87ac5b27ff9cc93e67cd Mon Sep 17 00:00:00 2001 From: Syrus Akbary Date: Wed, 2 Dec 2015 21:11:24 -0800 Subject: [PATCH 04/10] Improved classtypes core support --- graphene/__init__.py | 7 +- graphene/core/classtypes/__init__.py | 16 ++ graphene/core/options.py | 72 ----- graphene/core/schema.py | 5 +- graphene/core/types/__init__.py | 6 +- graphene/core/types/base.py | 10 +- graphene/core/types/field.py | 10 +- graphene/core/types/objecttype.py | 283 +------------------ graphene/core/types/tests/test_objecttype.py | 194 ------------- 9 files changed, 38 insertions(+), 565 deletions(-) delete mode 100644 graphene/core/options.py delete mode 100644 graphene/core/types/tests/test_objecttype.py diff --git a/graphene/__init__.py b/graphene/__init__.py index 520b2f75..88d3a365 100644 --- a/graphene/__init__.py +++ b/graphene/__init__.py @@ -8,11 +8,15 @@ from graphene.core.schema import ( Schema ) -from graphene.core.types import ( +from graphene.core.classtypes import ( ObjectType, InputObjectType, Interface, Mutation, + Scalar +) + +from graphene.core.types import ( BaseType, LazyType, Argument, @@ -59,6 +63,7 @@ __all__ = [ 'InputObjectType', 'Interface', 'Mutation', + 'Scalar', 'Field', 'InputField', 'StringField', diff --git a/graphene/core/classtypes/__init__.py b/graphene/core/classtypes/__init__.py index e69de29b..488ccbb2 100644 --- a/graphene/core/classtypes/__init__.py +++ b/graphene/core/classtypes/__init__.py @@ -0,0 +1,16 @@ +from .inputobjecttype import InputObjectType +from .interface import Interface +from .mutation import Mutation +from .objecttype import ObjectType +from .options import Options +from .scalar import Scalar +from .uniontype import UnionType + +__all__ = [ + 'InputObjectType', + 'Interface', + 'Mutation', + 'ObjectType', + 'Options', + 'Scalar', + 'UnionType'] diff --git a/graphene/core/options.py b/graphene/core/options.py deleted file mode 100644 index 4f1bf4e4..00000000 --- a/graphene/core/options.py +++ /dev/null @@ -1,72 +0,0 @@ -from collections import OrderedDict - -from ..utils import cached_property - -DEFAULT_NAMES = ('description', 'name', 'is_interface', 'is_mutation', - 'type_name', 'interfaces', 'abstract') - - -class Options(object): - - def __init__(self, meta=None): - self.meta = meta - self.local_fields = [] - self.is_interface = False - self.is_mutation = False - self.is_union = False - self.abstract = False - self.interfaces = [] - self.parents = [] - self.types = [] - self.valid_attrs = DEFAULT_NAMES - - def contribute_to_class(self, cls, name): - cls._meta = self - self.parent = cls - # First, construct the default values for these options. - self.object_name = cls.__name__ - self.type_name = self.object_name - - self.description = cls.__doc__ - # Store the original user-defined values for each option, - # for use when serializing the model definition - self.original_attrs = {} - - # Next, apply any overridden values from 'class Meta'. - if self.meta: - meta_attrs = self.meta.__dict__.copy() - for name in self.meta.__dict__: - # Ignore any private attributes that Django doesn't care about. - # NOTE: We can't modify a dictionary's contents while looping - # over it, so we loop over the *original* dictionary instead. - if name.startswith('_'): - del meta_attrs[name] - for attr_name in self.valid_attrs: - if attr_name in meta_attrs: - setattr(self, attr_name, meta_attrs.pop(attr_name)) - self.original_attrs[attr_name] = getattr(self, attr_name) - elif hasattr(self.meta, attr_name): - setattr(self, attr_name, getattr(self.meta, attr_name)) - self.original_attrs[attr_name] = getattr(self, attr_name) - - del self.valid_attrs - - # Any leftover attributes must be invalid. - if meta_attrs != {}: - raise TypeError( - "'class Meta' got invalid attribute(s): %s" % - ','.join( - meta_attrs.keys())) - - del self.meta - - def add_field(self, field): - self.local_fields.append(field) - - @cached_property - def fields(self): - return sorted(self.local_fields) - - @cached_property - def fields_map(self): - return OrderedDict([(f.attname, f) for f in self.fields]) diff --git a/graphene/core/schema.py b/graphene/core/schema.py index ec9d1bf5..48c9cf1e 100644 --- a/graphene/core/schema.py +++ b/graphene/core/schema.py @@ -11,7 +11,6 @@ from graphql.core.utils.schema_printer import print_schema from graphene import signals from .types.base import BaseType -from .types.objecttype import BaseObjectType from .classtypes.base import ClassType @@ -49,7 +48,7 @@ class Schema(object): internal_type = object_type.internal_type(self) self._types[object_type] = internal_type is_objecttype = inspect.isclass( - object_type) and issubclass(object_type, BaseObjectType) + object_type) and issubclass(object_type, ClassType) if is_objecttype: self.register(object_type) return self._types[object_type] @@ -91,7 +90,7 @@ class Schema(object): if name: objecttype = self._types_names.get(name, None) if objecttype and inspect.isclass( - objecttype) and issubclass(objecttype, BaseObjectType): + objecttype) and issubclass(objecttype, ClassType): return objecttype def __str__(self): diff --git a/graphene/core/types/__init__.py b/graphene/core/types/__init__.py index 5bd49f2f..9260476c 100644 --- a/graphene/core/types/__init__.py +++ b/graphene/core/types/__init__.py @@ -1,7 +1,9 @@ from .base import BaseType, LazyType, OrderedType from .argument import Argument, ArgumentsGroup, to_arguments from .definitions import List, NonNull -from .objecttype import ObjectTypeMeta, BaseObjectType, Interface, ObjectType, Mutation, InputObjectType +# Compatibility import +from .objecttype import Interface, ObjectType, Mutation, InputObjectType + from .scalars import String, ID, Boolean, Int, Float, Scalar from .field import Field, InputField @@ -17,8 +19,6 @@ __all__ = [ 'Field', 'InputField', 'Interface', - 'BaseObjectType', - 'ObjectTypeMeta', 'ObjectType', 'Mutation', 'InputObjectType', diff --git a/graphene/core/types/base.py b/graphene/core/types/base.py index 2b9a4e01..2b4078e4 100644 --- a/graphene/core/types/base.py +++ b/graphene/core/types/base.py @@ -2,9 +2,6 @@ from functools import total_ordering import six -from ..classtypes.base import FieldsClassType -from ..classtypes.inputobjecttype import InputObjectType as NewInputObjectType - class BaseType(object): @@ -107,11 +104,12 @@ class ArgumentType(MirroredType): class FieldType(MirroredType): def contribute_to_class(self, cls, name): - from ..types import BaseObjectType, InputObjectType - if issubclass(cls, (InputObjectType, NewInputObjectType)): + from ..classtypes.base import FieldsClassType + from ..classtypes.inputobjecttype import InputObjectType + if issubclass(cls, (InputObjectType)): inputfield = self.as_inputfield() return inputfield.contribute_to_class(cls, name) - elif issubclass(cls, (BaseObjectType, FieldsClassType)): + elif issubclass(cls, (FieldsClassType)): field = self.as_field() return field.contribute_to_class(cls, name) diff --git a/graphene/core/types/field.py b/graphene/core/types/field.py index 489516e1..5a429b64 100644 --- a/graphene/core/types/field.py +++ b/graphene/core/types/field.py @@ -6,8 +6,8 @@ from graphql.core.type import GraphQLField, GraphQLInputObjectField from ...utils import to_camel_case from ..classtypes.base import FieldsClassType -from ..classtypes.inputobjecttype import InputObjectType as NewInputObjectType -from ..types import BaseObjectType, InputObjectType +from ..classtypes.mutation import Mutation +from ..classtypes.inputobjecttype import InputObjectType from .argument import ArgumentsGroup, snake_case_args from .base import LazyType, MountType, OrderedType from .definitions import NonNull @@ -34,7 +34,7 @@ class Field(OrderedType): def contribute_to_class(self, cls, attname): assert issubclass( - cls, (BaseObjectType, FieldsClassType)), 'Field {} cannot be mounted in {}'.format( + cls, (FieldsClassType)), 'Field {} cannot be mounted in {}'.format( self, cls) if not self.name: self.name = to_camel_case(attname) @@ -71,7 +71,7 @@ class Field(OrderedType): description = resolver.__doc__ type = schema.T(self.get_type(schema)) type_objecttype = schema.objecttype(type) - if type_objecttype and type_objecttype._meta.is_mutation: + if type_objecttype and issubclass(type_objecttype, Mutation): assert len(arguments) == 0 arguments = type_objecttype.get_arguments() resolver = getattr(type_objecttype, 'mutate') @@ -128,7 +128,7 @@ class InputField(OrderedType): def contribute_to_class(self, cls, attname): assert issubclass( - cls, (InputObjectType, NewInputObjectType)), 'InputField {} cannot be mounted in {}'.format( + cls, (InputObjectType)), 'InputField {} cannot be mounted in {}'.format( self, cls) if not self.name: self.name = to_camel_case(attname) diff --git a/graphene/core/types/objecttype.py b/graphene/core/types/objecttype.py index a9ac43f1..140b291e 100644 --- a/graphene/core/types/objecttype.py +++ b/graphene/core/types/objecttype.py @@ -1,282 +1,3 @@ -import copy -import inspect -from collections import OrderedDict -from functools import partial +from ..classtypes import ObjectType, Interface, Mutation, InputObjectType -import six -from graphql.core.type import (GraphQLInputObjectType, GraphQLInterfaceType, - GraphQLObjectType, GraphQLUnionType) - -from graphene import signals - -from ..exceptions import SkipField -from ..options import Options -from .argument import ArgumentsGroup -from .base import BaseType -from .definitions import List, NonNull - - -def is_objecttype(cls): - if not issubclass(cls, BaseObjectType): - return False - _meta = getattr(cls, '_meta', None) - return not(_meta and (_meta.abstract or _meta.is_interface)) - - -class ObjectTypeMeta(type): - options_cls = Options - - def is_interface(cls, parents): - return Interface in parents - - def is_mutation(cls, parents): - return issubclass(cls, Mutation) - - def __new__(cls, name, bases, attrs): - super_new = super(ObjectTypeMeta, cls).__new__ - parents = [b for b in bases if isinstance(b, cls)] - if not parents: - # If this isn't a subclass of Model, don't do anything special. - return super_new(cls, name, bases, attrs) - - module = attrs.pop('__module__', None) - doc = attrs.pop('__doc__', None) - new_class = super_new(cls, name, bases, { - '__module__': module, - '__doc__': doc - }) - attr_meta = attrs.pop('Meta', None) - abstract = getattr(attr_meta, 'abstract', False) - if not attr_meta: - meta = getattr(new_class, 'Meta', None) - else: - meta = attr_meta - - base_meta = getattr(new_class, '_meta', None) - - new_class.add_to_class('_meta', new_class.options_cls(meta)) - - new_class._meta.is_interface = new_class.is_interface(parents) - new_class._meta.is_mutation = new_class.is_mutation(parents) or (base_meta and base_meta.is_mutation) - union_types = list(filter(is_objecttype, parents)) - - new_class._meta.is_union = len(union_types) > 1 - new_class._meta.types = union_types - - assert not ( - new_class._meta.is_interface and new_class._meta.is_mutation) - - assert not ( - new_class._meta.is_interface and new_class._meta.is_union) - - # Add all attributes to the class. - for obj_name, obj in attrs.items(): - new_class.add_to_class(obj_name, obj) - - if abstract: - new_class._prepare() - return new_class - - if new_class._meta.is_mutation: - assert hasattr( - new_class, 'mutate'), "All mutations must implement mutate method" - - new_class.add_extra_fields() - - new_fields = new_class._meta.local_fields - assert not(new_class._meta.is_union and new_fields), 'An union cannot have extra fields' - - field_names = {f.name: f for f in new_fields} - - for base in parents: - if not hasattr(base, '_meta'): - # Things without _meta aren't functional models, so they're - # uninteresting parents. - continue - # if base._meta.schema != new_class._meta.schema: - # raise Exception('The parent schema is not the same') - - parent_fields = base._meta.local_fields - # Check for clashes between locally declared fields and those - # 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__: - raise Exception( - 'Local field %r in class %r (%r) clashes ' - 'with field with similar name from ' - 'Interface %s (%r)' % ( - field.name, - new_class.__name__, - field.__class__, - base.__name__, - field_names[field.name].__class__) - ) - new_field = copy.copy(field) - new_class.add_to_class(field.attname, new_field) - - new_class._meta.parents.append(base) - if base._meta.is_interface: - 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 - - def add_extra_fields(cls): - pass - - def _prepare(cls): - if hasattr(cls, '_prepare_class'): - cls._prepare_class() - signals.class_prepared.send(cls) - - 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'): - value.contribute_to_class(cls, name) - else: - setattr(cls, name, value) - - -class BaseObjectType(BaseType): - - def __new__(cls, *args, **kwargs): - if cls._meta.is_interface: - raise Exception("An interface cannot be initialized") - elif cls._meta.is_union: - raise Exception("An union cannot be initialized") - elif cls._meta.abstract: - raise Exception("An abstract ObjectType cannot be initialized") - return super(BaseObjectType, cls).__new__(cls) - - def __init__(self, *args, **kwargs): - signals.pre_init.send(self.__class__, args=args, kwargs=kwargs) - self._root = kwargs.pop('_root', None) - args_len = len(args) - fields = self._meta.fields - if args_len > len(fields): - # Daft, but matches old exception sans the err msg. - raise IndexError("Number of args exceeds number of fields") - fields_iter = iter(fields) - - if not kwargs: - for val, field in zip(args, fields_iter): - setattr(self, field.attname, val) - else: - for val, field in zip(args, fields_iter): - setattr(self, field.attname, val) - kwargs.pop(field.attname, None) - - for field in fields_iter: - try: - val = kwargs.pop(field.attname) - setattr(self, field.attname, val) - except KeyError: - pass - - if kwargs: - for prop in list(kwargs): - try: - if isinstance(getattr(self.__class__, prop), property): - setattr(self, prop, kwargs.pop(prop)) - except AttributeError: - pass - if kwargs: - raise TypeError( - "'%s' is an invalid keyword argument for this function" % - list(kwargs)[0]) - - signals.post_init.send(self.__class__, instance=self) - - @classmethod - def _resolve_type(cls, schema, instance, *args): - return schema.T(instance.__class__) - - @classmethod - def internal_type(cls, schema): - if cls._meta.abstract: - raise Exception("Abstract ObjectTypes don't have a specific type.") - - if cls._meta.is_interface: - return GraphQLInterfaceType( - cls._meta.type_name, - description=cls._meta.description, - resolve_type=partial(cls._resolve_type, schema), - fields=partial(cls.get_fields, schema) - ) - elif cls._meta.is_union: - return GraphQLUnionType( - cls._meta.type_name, - types=cls._meta.types, - description=cls._meta.description, - ) - return GraphQLObjectType( - cls._meta.type_name, - description=cls._meta.description, - interfaces=[schema.T(i) for i in cls._meta.interfaces], - 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) - - @classmethod - def wrap(cls, instance, args, info): - return cls(_root=instance) - - -class Interface(six.with_metaclass(ObjectTypeMeta, BaseObjectType)): - pass - - -class ObjectType(six.with_metaclass(ObjectTypeMeta, BaseObjectType)): - pass - - -class Mutation(six.with_metaclass(ObjectTypeMeta, BaseObjectType)): - - @classmethod - def _construct_arguments(cls, items): - return ArgumentsGroup(**items) - - @classmethod - def _prepare_class(cls): - input_class = getattr(cls, 'Input', None) - if input_class: - items = dict(vars(input_class)) - items.pop('__dict__', None) - items.pop('__doc__', None) - items.pop('__module__', None) - items.pop('__weakref__', None) - cls.add_to_class('arguments', cls._construct_arguments(items)) - delattr(cls, 'Input') - - @classmethod - def get_arguments(cls): - return cls.arguments - - -class InputObjectType(ObjectType): - - @classmethod - def internal_type(cls, schema): - return GraphQLInputObjectType( - cls._meta.type_name, - description=cls._meta.description, - fields=partial(cls.get_fields, schema), - ) +__all__ = ['ObjectType', 'Interface', 'Mutation', 'InputObjectType'] diff --git a/graphene/core/types/tests/test_objecttype.py b/graphene/core/types/tests/test_objecttype.py deleted file mode 100644 index 51b84f61..00000000 --- a/graphene/core/types/tests/test_objecttype.py +++ /dev/null @@ -1,194 +0,0 @@ -from graphql.core.execution.middlewares.utils import (resolver_has_tag, - tag_resolver) -from graphql.core.type import (GraphQLInterfaceType, GraphQLObjectType, - GraphQLUnionType) -from py.test import raises - -from graphene.core.schema import Schema -from graphene.core.types import Int, Interface, ObjectType, String - - -class Character(Interface): - '''Character description''' - name = String() - - class Meta: - type_name = 'core_Character' - - -class Human(Character): - '''Human description''' - friends = String() - - class Meta: - type_name = 'core_Human' - - @property - def readonly_prop(self): - return 'readonly' - - @property - def write_prop(self): - return self._write_prop - - @write_prop.setter - def write_prop(self, value): - self._write_prop = value - - -class Droid(Character): - '''Droid description''' - - -class CharacterType(Droid, Human): - '''Union Type''' - -schema = Schema() - - -def test_interface(): - object_type = schema.T(Character) - assert Character._meta.is_interface is True - assert isinstance(object_type, GraphQLInterfaceType) - assert Character._meta.type_name == 'core_Character' - assert object_type.description == 'Character description' - assert list(object_type.get_fields().keys()) == ['name'] - - -def test_interface_cannot_initialize(): - with raises(Exception) as excinfo: - Character() - assert 'An interface cannot be initialized' == str(excinfo.value) - - -def test_union(): - object_type = schema.T(CharacterType) - assert CharacterType._meta.is_union is True - assert isinstance(object_type, GraphQLUnionType) - assert object_type.description == 'Union Type' - - -def test_union_cannot_initialize(): - with raises(Exception) as excinfo: - CharacterType() - assert 'An union cannot be initialized' == str(excinfo.value) - - -def test_interface_resolve_type(): - resolve_type = Character._resolve_type(schema, Human(object())) - assert isinstance(resolve_type, GraphQLObjectType) - - -def test_object_type(): - object_type = schema.T(Human) - assert Human._meta.is_interface is False - assert Human._meta.type_name == 'core_Human' - assert isinstance(object_type, GraphQLObjectType) - assert object_type.description == 'Human description' - assert list(object_type.get_fields().keys()) == ['name', 'friends'] - assert object_type.get_interfaces() == [schema.T(Character)] - assert Human._meta.fields_map['name'].object_type == Human - - -def test_object_type_container(): - h = Human(name='My name') - assert h.name == 'My name' - - -def test_object_type_set_properties(): - h = Human(readonly_prop='custom', write_prop='custom') - assert h.readonly_prop == 'readonly' - assert h.write_prop == 'custom' - - -def test_object_type_container_invalid_kwarg(): - with raises(TypeError): - Human(invalid='My name') - - -def test_object_type_container_too_many_args(): - with raises(IndexError): - Human('Peter', 'No friends :(', None) - - -def test_field_clashes(): - with raises(Exception) as excinfo: - class Droid(Character): - name = Int() - - assert 'clashes' in str(excinfo.value) - - -def test_fields_inherited_should_be_different(): - assert Character._meta.fields_map['name'] != Human._meta.fields_map['name'] - - -def test_field_mantain_resolver_tags(): - class Droid(Character): - name = String() - - def resolve_name(self, *args): - return 'My Droid' - - tag_resolver(resolve_name, 'test') - - 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 - - -def test_abstracttype(): - class MyObject1(ObjectType): - class Meta: - abstract = True - name1 = String() - - class MyObject2(ObjectType): - class Meta: - abstract = True - name2 = String() - - class MyObject(MyObject1, MyObject2): - pass - - object_type = schema.T(MyObject) - - assert list(MyObject._meta.fields_map.keys()) == ['name1', 'name2'] - assert MyObject._meta.fields_map['name1'].object_type == MyObject - assert MyObject._meta.fields_map['name2'].object_type == MyObject - assert isinstance(object_type, GraphQLObjectType) - - -def test_abstracttype_initialize(): - class MyAbstractObjectType(ObjectType): - class Meta: - abstract = True - - with raises(Exception) as excinfo: - MyAbstractObjectType() - - assert 'An abstract ObjectType cannot be initialized' == str(excinfo.value) - - -def test_abstracttype_type(): - class MyAbstractObjectType(ObjectType): - class Meta: - abstract = True - - with raises(Exception) as excinfo: - schema.T(MyAbstractObjectType) - - assert 'Abstract ObjectTypes don\'t have a specific type.' == str(excinfo.value) From 5b3000f734559219174a9ef3f98ed7aacab93db5 Mon Sep 17 00:00:00 2001 From: Syrus Akbary Date: Wed, 2 Dec 2015 22:47:37 -0800 Subject: [PATCH 05/10] Improved classtypes relay support --- graphene/core/classtypes/interface.py | 3 - graphene/relay/fields.py | 6 -- graphene/relay/types.py | 89 ++++++++++++++++----------- graphene/relay/utils.py | 7 ++- 4 files changed, 57 insertions(+), 48 deletions(-) diff --git a/graphene/core/classtypes/interface.py b/graphene/core/classtypes/interface.py index f689c709..2586bfce 100644 --- a/graphene/core/classtypes/interface.py +++ b/graphene/core/classtypes/interface.py @@ -40,9 +40,6 @@ class Interface(six.with_metaclass(InterfaceMeta, ObjectType)): @classmethod def internal_type(cls, schema): - if cls._meta.abstract: - raise Exception("Abstract Interfaces don't have a specific type.") - if not cls._meta.interface: return super(Interface, cls).internal_type(schema) diff --git a/graphene/relay/fields.py b/graphene/relay/fields.py index b3829ad8..dc8c4973 100644 --- a/graphene/relay/fields.py +++ b/graphene/relay/fields.py @@ -90,11 +90,5 @@ class GlobalIDField(Field): def __init__(self, *args, **kwargs): super(GlobalIDField, self).__init__(NonNull(ID()), *args, **kwargs) - 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) - def resolver(self, instance, args, info): return instance.to_global_id() diff --git a/graphene/relay/types.py b/graphene/relay/types.py index 9fa673ef..4cece989 100644 --- a/graphene/relay/types.py +++ b/graphene/relay/types.py @@ -1,4 +1,5 @@ import inspect +import six import warnings from collections import Iterable from functools import wraps @@ -6,8 +7,10 @@ from functools import wraps from graphql_relay.connection.arrayconnection import connection_from_list from graphql_relay.node.node import to_global_id -from ..core.types import (Boolean, Field, InputObjectType, Interface, List, - Mutation, ObjectType, String) +from ..core.classtypes import InputObjectType, Interface, Mutation, ObjectType +from ..core.classtypes.mutation import MutationMeta +from ..core.classtypes.interface import InterfaceMeta +from ..core.types import Boolean, Field, List, String from ..core.types.argument import ArgumentsGroup from ..core.types.definitions import NonNull from ..utils import memoize @@ -83,33 +86,43 @@ class Connection(ObjectType): return self._connection_data -class BaseNode(object): +class NodeMeta(InterfaceMeta): + def construct_get_node(cls): + get_node = getattr(cls, 'get_node', None) + assert get_node, 'get_node classmethod not found in %s Node' % cls + assert callable(get_node), 'get_node have to be callable' + args = 3 + if isinstance(get_node, staticmethod): + args -= 1 - @classmethod - def _prepare_class(cls): - from graphene.relay.utils import is_node - if is_node(cls): - get_node = getattr(cls, 'get_node') - assert get_node, 'get_node classmethod not found in %s Node' % cls - assert callable(get_node), 'get_node have to be callable' - args = 3 - if isinstance(get_node, staticmethod): - args -= 1 + get_node_num_args = len(inspect.getargspec(get_node).args) + if get_node_num_args < args: + warnings.warn("get_node will receive also the info arg" + " in future versions of graphene".format(cls.__name__), + FutureWarning) - get_node_num_args = len(inspect.getargspec(get_node).args) - if get_node_num_args < args: - warnings.warn("get_node will receive also the info arg" - " in future versions of graphene".format(cls.__name__), - FutureWarning) + @staticmethod + @wraps(get_node) + def wrapped_node(*node_args): + if len(node_args) < args: + node_args += (None, ) + return get_node(*node_args[:-1]) - @staticmethod - @wraps(get_node) - def wrapped_node(*node_args): - if len(node_args) < args: - node_args += (None, ) - return get_node(*node_args[:-1]) + setattr(cls, 'get_node', wrapped_node) - setattr(cls, 'get_node', wrapped_node) + def construct(cls, *args, **kwargs): + cls = super(NodeMeta, cls).construct(*args, **kwargs) + if not cls._meta.abstract: + cls.construct_get_node() + return cls + + +class Node(six.with_metaclass(NodeMeta, Interface)): + '''An object with an ID''' + id = GlobalIDField() + + class Meta: + abstract = True def to_global_id(self): type_name = self._meta.type_name @@ -127,27 +140,31 @@ class BaseNode(object): return cls.edge_type -class Node(BaseNode, Interface): - '''An object with an ID''' - id = GlobalIDField() - - class MutationInputType(InputObjectType): client_mutation_id = String(required=True) -class ClientIDMutation(Mutation): - client_mutation_id = String(required=True) +class RelayMutationMeta(MutationMeta): + def construct(cls, *args, **kwargs): + cls = super(RelayMutationMeta, cls).construct(*args, **kwargs) + if not cls._meta.abstract: + assert hasattr( + cls, 'mutate_and_get_payload'), 'You have to implement mutate_and_get_payload' + return cls - @classmethod - def _construct_arguments(cls, items): - assert hasattr( - cls, 'mutate_and_get_payload'), 'You have to implement mutate_and_get_payload' + def construct_arguments(cls, items): 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)) + +class ClientIDMutation(six.with_metaclass(RelayMutationMeta, Mutation)): + client_mutation_id = String(required=True) + + class Meta: + abstract = True + @classmethod def mutate(cls, instance, args, info): input = args.get('input') diff --git a/graphene/relay/utils.py b/graphene/relay/utils.py index dc281830..98cb81dd 100644 --- a/graphene/relay/utils.py +++ b/graphene/relay/utils.py @@ -1,10 +1,11 @@ -from .types import BaseNode +from .types import Node def is_node(object_type): return object_type and issubclass( - object_type, BaseNode) and not is_node_type(object_type) + object_type, Node) and not object_type._meta.abstract def is_node_type(object_type): - return BaseNode in object_type.__bases__ + return object_type and issubclass( + object_type, Node) and object_type._meta.abstract From f5837ac4f3560d7d9d471164f79b7d3d296fe9e9 Mon Sep 17 00:00:00 2001 From: Syrus Akbary Date: Wed, 2 Dec 2015 23:36:51 -0800 Subject: [PATCH 06/10] Improved classtypes django support --- graphene/contrib/django/__init__.py | 5 +- graphene/contrib/django/options.py | 23 ++------- graphene/contrib/django/tests/test_types.py | 37 +++---------- graphene/contrib/django/types.py | 57 ++++++++++++++------- graphene/core/classtypes/base.py | 5 +- 5 files changed, 53 insertions(+), 74 deletions(-) diff --git a/graphene/contrib/django/__init__.py b/graphene/contrib/django/__init__.py index ec39b7f9..11720f9f 100644 --- a/graphene/contrib/django/__init__.py +++ b/graphene/contrib/django/__init__.py @@ -1,7 +1,6 @@ from graphene.contrib.django.types import ( DjangoConnection, DjangoObjectType, - DjangoInterface, DjangoNode ) from graphene.contrib.django.fields import ( @@ -9,5 +8,5 @@ from graphene.contrib.django.fields import ( DjangoModelField ) -__all__ = ['DjangoObjectType', 'DjangoInterface', 'DjangoNode', - 'DjangoConnection', 'DjangoConnectionField', 'DjangoModelField'] +__all__ = ['DjangoObjectType', 'DjangoNode', 'DjangoConnection', + 'DjangoConnectionField', 'DjangoModelField'] diff --git a/graphene/contrib/django/options.py b/graphene/contrib/django/options.py index 812e2b03..61dd37a3 100644 --- a/graphene/contrib/django/options.py +++ b/graphene/contrib/django/options.py @@ -1,24 +1,15 @@ -import inspect - -from django.db import models - -from ...core.options import Options +from ...core.classtypes.objecttype import ObjectTypeOptions from ...relay.types import Node from ...relay.utils import is_node VALID_ATTRS = ('model', 'only_fields', 'exclude_fields') -def is_base(cls): - from graphene.contrib.django.types import DjangoObjectType - return DjangoObjectType in cls.__bases__ - - -class DjangoOptions(Options): +class DjangoOptions(ObjectTypeOptions): def __init__(self, *args, **kwargs): - self.model = None super(DjangoOptions, self).__init__(*args, **kwargs) + self.model = None self.valid_attrs += VALID_ATTRS self.only_fields = None self.exclude_fields = [] @@ -28,11 +19,3 @@ class DjangoOptions(Options): if is_node(cls): self.exclude_fields = list(self.exclude_fields) + ['id'] self.interfaces.append(Node) - if not is_node(cls) and not is_base(cls): - return - if not self.model: - raise Exception( - '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_types.py b/graphene/contrib/django/tests/test_types.py index 4d709580..959a7d45 100644 --- a/graphene/contrib/django/tests/test_types.py +++ b/graphene/contrib/django/tests/test_types.py @@ -3,7 +3,7 @@ from mock import patch from pytest import raises from graphene import Schema -from graphene.contrib.django.types import DjangoInterface, DjangoNode +from graphene.contrib.django.types import DjangoNode, DjangoObjectType from graphene.core.fields import Field from graphene.core.types.scalars import Int from graphene.relay.fields import GlobalIDField @@ -14,7 +14,8 @@ from .models import Article, Reporter schema = Schema() -class Character(DjangoInterface): +@schema.register +class Character(DjangoObjectType): '''Character description''' class Meta: model = Reporter @@ -31,7 +32,7 @@ class Human(DjangoNode): def test_django_interface(): - assert DjangoNode._meta.is_interface is True + assert DjangoNode._meta.interface is True @patch('graphene.contrib.django.tests.models.Article.objects.get', return_value=Article(id=1)) @@ -41,17 +42,6 @@ def test_django_get_node(get): assert human.id == 1 -def test_pseudo_interface_registered(): - object_type = schema.T(Character) - assert Character._meta.is_interface is True - assert isinstance(object_type, GraphQLInterfaceType) - assert Character._meta.model == Reporter - assert_equal_lists( - object_type.get_fields().keys(), - ['articles', 'firstName', 'lastName', 'email', 'pets', 'id'] - ) - - def test_djangonode_idfield(): idfield = DjangoNode._meta.fields_map['id'] assert isinstance(idfield, GlobalIDField) @@ -68,32 +58,21 @@ def test_node_replacedfield(): assert schema.T(idfield).type == schema.T(Int()) -def test_interface_resolve_type(): - resolve_type = Character._resolve_type(schema, Human()) - assert isinstance(resolve_type, GraphQLObjectType) - - -def test_interface_objecttype_init_none(): +def test_objecttype_init_none(): h = Human() assert h._root is None -def test_interface_objecttype_init_good(): +def test_objecttype_init_good(): instance = Article() h = Human(instance) assert h._root == instance -def test_interface_objecttype_init_unexpected(): - with raises(AssertionError) as excinfo: - Human(object()) - assert str(excinfo.value) == "Human received a non-compatible instance (object) when expecting Article" - - def test_object_type(): object_type = schema.T(Human) Human._meta.fields_map - assert Human._meta.is_interface is False + assert Human._meta.interface is False assert isinstance(object_type, GraphQLObjectType) assert_equal_lists( object_type.get_fields().keys(), @@ -103,5 +82,5 @@ def test_object_type(): def test_node_notinterface(): - assert Human._meta.is_interface is False + assert Human._meta.interface is False assert DjangoNode in Human._meta.interfaces diff --git a/graphene/contrib/django/types.py b/graphene/contrib/django/types.py index b5097c72..0d7b75fe 100644 --- a/graphene/contrib/django/types.py +++ b/graphene/contrib/django/types.py @@ -1,22 +1,19 @@ import six +import inspect -from ...core.types import BaseObjectType, ObjectTypeMeta -from ...relay.fields import GlobalIDField -from ...relay.types import BaseNode, Connection +from django.db import models + +from ...core.classtypes.objecttype import ObjectTypeMeta, ObjectType +from ...relay.types import Node, NodeMeta, Connection from .converter import convert_django_field from .options import DjangoOptions from .utils import get_reverse_fields, maybe_queryset class DjangoObjectTypeMeta(ObjectTypeMeta): - options_cls = DjangoOptions + options_class = DjangoOptions - def is_interface(cls, parents): - return DjangoInterface in parents - - def add_extra_fields(cls): - if not cls._meta.model: - return + def construct_fields(cls): only_fields = cls._meta.only_fields reverse_fields = get_reverse_fields(cls._meta.model) all_fields = sorted(list(cls._meta.model._meta.fields) + @@ -35,8 +32,23 @@ class DjangoObjectTypeMeta(ObjectTypeMeta): converted_field = convert_django_field(field) cls.add_to_class(field.name, converted_field) + def construct(cls, *args, **kwargs): + cls = super(DjangoObjectTypeMeta, cls).construct(*args, **kwargs) + if not cls._meta.abstract: + if not cls._meta.model: + raise Exception( + 'Django ObjectType %s must have a model in the Meta class attr' % + cls) + elif not inspect.isclass(cls._meta.model) or not issubclass(cls._meta.model, models.Model): + raise Exception('Provided model in %s is not a Django model' % cls) -class InstanceObjectType(BaseObjectType): + cls.construct_fields() + return cls + + +class InstanceObjectType(ObjectType): + class Meta: + abstract = True def __init__(self, _root=None): if _root: @@ -63,12 +75,8 @@ class InstanceObjectType(BaseObjectType): class DjangoObjectType(six.with_metaclass( DjangoObjectTypeMeta, InstanceObjectType)): - pass - - -class DjangoInterface(six.with_metaclass( - DjangoObjectTypeMeta, InstanceObjectType)): - pass + class Meta: + abstract = True class DjangoConnection(Connection): @@ -79,8 +87,19 @@ class DjangoConnection(Connection): return super(DjangoConnection, cls).from_list(iterable, *args, **kwargs) -class DjangoNode(BaseNode, DjangoInterface): - id = GlobalIDField() +class DjangoNodeMeta(DjangoObjectTypeMeta, NodeMeta): + pass + + +class NodeInstance(Node, InstanceObjectType): + class Meta: + abstract = True + + +class DjangoNode(six.with_metaclass( + DjangoNodeMeta, NodeInstance)): + class Meta: + abstract = True @classmethod def get_node(cls, id, info=None): diff --git a/graphene/core/classtypes/base.py b/graphene/core/classtypes/base.py index c6d4dc58..52588fc8 100644 --- a/graphene/core/classtypes/base.py +++ b/graphene/core/classtypes/base.py @@ -87,7 +87,7 @@ class FieldsClassTypeMeta(ClassTypeMeta): field_names = {f.name: f for f in new_fields} for base in bases: - if not issubclass(base, FieldsClassType): + if not isinstance(base, FieldsClassTypeMeta): continue parent_fields = base._meta.local_fields @@ -110,8 +110,7 @@ class FieldsClassTypeMeta(ClassTypeMeta): def construct(cls, bases, attrs): cls = super(FieldsClassTypeMeta, cls).construct(bases, attrs) - if not cls._meta.abstract: - cls.extend_fields(bases) + cls.extend_fields(bases) return cls From b417a65f19f1f422eaf9e17c004ead0fb119998d Mon Sep 17 00:00:00 2001 From: Syrus Akbary Date: Wed, 2 Dec 2015 23:42:15 -0800 Subject: [PATCH 07/10] Fixed options tests --- .../core/{tests => classtypes}/test_options.py | 16 +--------------- 1 file changed, 1 insertion(+), 15 deletions(-) rename graphene/core/{tests => classtypes}/test_options.py (76%) diff --git a/graphene/core/tests/test_options.py b/graphene/core/classtypes/test_options.py similarity index 76% rename from graphene/core/tests/test_options.py rename to graphene/core/classtypes/test_options.py index 3b656bd4..28340757 100644 --- a/graphene/core/tests/test_options.py +++ b/graphene/core/classtypes/test_options.py @@ -1,11 +1,10 @@ from py.test import raises from graphene.core.fields import Field -from graphene.core.options import Options +from graphene.core.classtypes import Options class Meta: - is_interface = True type_name = 'Character' @@ -13,19 +12,6 @@ class InvalidMeta: other_value = True -def test_field_added_in_meta(): - opt = Options(Meta) - - class ObjectType(object): - pass - - opt.contribute_to_class(ObjectType, '_meta') - f = Field(None) - f.attname = 'string_field' - opt.add_field(f) - assert f in opt.fields - - def test_options_contribute(): opt = Options(Meta) From 9b839f19e53fbb39c30ceb62094f780ce46745f8 Mon Sep 17 00:00:00 2001 From: Syrus Akbary Date: Wed, 2 Dec 2015 23:46:49 -0800 Subject: [PATCH 08/10] Fixed lint errors --- bin/autolinter | 6 +++--- graphene/contrib/django/tests/test_types.py | 3 +-- graphene/contrib/django/types.py | 10 +++++++--- graphene/core/classtypes/base.py | 11 +++++++---- graphene/core/classtypes/inputobjecttype.py | 1 + graphene/core/classtypes/interface.py | 4 +++- graphene/core/classtypes/mutation.py | 2 ++ graphene/core/classtypes/objecttype.py | 8 ++++++-- graphene/core/classtypes/options.py | 1 + graphene/core/classtypes/scalar.py | 2 +- graphene/core/classtypes/test_options.py | 1 - graphene/core/classtypes/tests/test_base.py | 8 +++----- .../core/classtypes/tests/test_inputobjecttype.py | 2 +- graphene/core/classtypes/tests/test_interface.py | 5 +++-- graphene/core/classtypes/tests/test_mutation.py | 4 ++-- graphene/core/classtypes/tests/test_objecttype.py | 6 +++--- graphene/core/classtypes/tests/test_uniontype.py | 1 + graphene/core/classtypes/uniontype.py | 5 +++-- graphene/core/schema.py | 2 +- graphene/core/types/field.py | 2 +- graphene/core/types/objecttype.py | 2 +- graphene/relay/types.py | 6 ++++-- graphene/signals.py | 1 + 23 files changed, 56 insertions(+), 37 deletions(-) diff --git a/bin/autolinter b/bin/autolinter index 164618ae..7f749242 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 --experimental --aggressive --max-line-length 120 -isort -rc . +autoflake ./examples/ ./graphene/ -r --remove-unused-variables --remove-all-unused-imports --in-place +autopep8 ./examples/ ./graphene/ -r --in-place --experimental --aggressive --max-line-length 120 +isort -rc ./examples/ ./graphene/ diff --git a/graphene/contrib/django/tests/test_types.py b/graphene/contrib/django/tests/test_types.py index 959a7d45..42028a5e 100644 --- a/graphene/contrib/django/tests/test_types.py +++ b/graphene/contrib/django/tests/test_types.py @@ -1,6 +1,5 @@ -from graphql.core.type import GraphQLInterfaceType, GraphQLObjectType +from graphql.core.type import GraphQLObjectType from mock import patch -from pytest import raises from graphene import Schema from graphene.contrib.django.types import DjangoNode, DjangoObjectType diff --git a/graphene/contrib/django/types.py b/graphene/contrib/django/types.py index 0d7b75fe..5b68ebbb 100644 --- a/graphene/contrib/django/types.py +++ b/graphene/contrib/django/types.py @@ -1,10 +1,10 @@ -import six import inspect +import six from django.db import models -from ...core.classtypes.objecttype import ObjectTypeMeta, ObjectType -from ...relay.types import Node, NodeMeta, Connection +from ...core.classtypes.objecttype import ObjectType, ObjectTypeMeta +from ...relay.types import Connection, Node, NodeMeta from .converter import convert_django_field from .options import DjangoOptions from .utils import get_reverse_fields, maybe_queryset @@ -47,6 +47,7 @@ class DjangoObjectTypeMeta(ObjectTypeMeta): class InstanceObjectType(ObjectType): + class Meta: abstract = True @@ -75,6 +76,7 @@ class InstanceObjectType(ObjectType): class DjangoObjectType(six.with_metaclass( DjangoObjectTypeMeta, InstanceObjectType)): + class Meta: abstract = True @@ -92,12 +94,14 @@ class DjangoNodeMeta(DjangoObjectTypeMeta, NodeMeta): class NodeInstance(Node, InstanceObjectType): + class Meta: abstract = True class DjangoNode(six.with_metaclass( DjangoNodeMeta, NodeInstance)): + class Meta: abstract = True diff --git a/graphene/core/classtypes/base.py b/graphene/core/classtypes/base.py index 52588fc8..31a2c8d2 100644 --- a/graphene/core/classtypes/base.py +++ b/graphene/core/classtypes/base.py @@ -1,7 +1,8 @@ -from collections import OrderedDict -import inspect -import six import copy +import inspect +from collections import OrderedDict + +import six from ..exceptions import SkipField from .options import Options @@ -54,6 +55,7 @@ class ClassTypeMeta(type): class ClassType(six.with_metaclass(ClassTypeMeta)): + class Meta: abstract = True @@ -63,6 +65,7 @@ class ClassType(six.with_metaclass(ClassTypeMeta)): class FieldsOptions(Options): + def __init__(self, *args, **kwargs): super(FieldsOptions, self).__init__(*args, **kwargs) self.local_fields = [] @@ -107,7 +110,6 @@ class FieldsClassTypeMeta(ClassTypeMeta): new_field = copy.copy(field) cls.add_to_class(field.attname, new_field) - def construct(cls, bases, attrs): cls = super(FieldsClassTypeMeta, cls).construct(bases, attrs) cls.extend_fields(bases) @@ -115,6 +117,7 @@ class FieldsClassTypeMeta(ClassTypeMeta): class FieldsClassType(six.with_metaclass(FieldsClassTypeMeta, ClassType)): + class Meta: abstract = True diff --git a/graphene/core/classtypes/inputobjecttype.py b/graphene/core/classtypes/inputobjecttype.py index 393173e8..43f4c897 100644 --- a/graphene/core/classtypes/inputobjecttype.py +++ b/graphene/core/classtypes/inputobjecttype.py @@ -6,6 +6,7 @@ from .base import FieldsClassType class InputObjectType(FieldsClassType): + class Meta: abstract = True diff --git a/graphene/core/classtypes/interface.py b/graphene/core/classtypes/interface.py index 2586bfce..c3aa3110 100644 --- a/graphene/core/classtypes/interface.py +++ b/graphene/core/classtypes/interface.py @@ -1,6 +1,6 @@ -import six from functools import partial +import six from graphql.core.type import GraphQLInterfaceType from .base import FieldsClassTypeMeta @@ -8,6 +8,7 @@ from .objecttype import ObjectType, ObjectTypeMeta class InterfaceMeta(ObjectTypeMeta): + def construct(cls, bases, attrs): if cls._meta.abstract or Interface in bases: # Return Interface type @@ -26,6 +27,7 @@ class InterfaceMeta(ObjectTypeMeta): class Interface(six.with_metaclass(InterfaceMeta, ObjectType)): + class Meta: abstract = True diff --git a/graphene/core/classtypes/mutation.py b/graphene/core/classtypes/mutation.py index 90aa5cf8..63743974 100644 --- a/graphene/core/classtypes/mutation.py +++ b/graphene/core/classtypes/mutation.py @@ -5,6 +5,7 @@ from .objecttype import ObjectType, ObjectTypeMeta class MutationMeta(ObjectTypeMeta): + def construct(cls, bases, attrs): input_class = attrs.pop('Input', None) if input_class: @@ -22,6 +23,7 @@ class MutationMeta(ObjectTypeMeta): class Mutation(six.with_metaclass(MutationMeta, ObjectType)): + class Meta: abstract = True diff --git a/graphene/core/classtypes/objecttype.py b/graphene/core/classtypes/objecttype.py index 13e93cb3..5658d570 100644 --- a/graphene/core/classtypes/objecttype.py +++ b/graphene/core/classtypes/objecttype.py @@ -1,10 +1,11 @@ -import six from functools import partial +import six from graphql.core.type import GraphQLObjectType from graphene import signals -from .base import FieldsOptions, FieldsClassType, FieldsClassTypeMeta + +from .base import FieldsClassType, FieldsClassTypeMeta, FieldsOptions from .uniontype import UnionType @@ -15,6 +16,7 @@ def is_objecttype(cls): class ObjectTypeOptions(FieldsOptions): + def __init__(self, *args, **kwargs): super(ObjectTypeOptions, self).__init__(*args, **kwargs) self.interface = False @@ -22,6 +24,7 @@ class ObjectTypeOptions(FieldsOptions): class ObjectTypeMeta(FieldsClassTypeMeta): + def construct(cls, bases, attrs): cls = super(ObjectTypeMeta, cls).construct(bases, attrs) if not cls._meta.abstract: @@ -39,6 +42,7 @@ class ObjectTypeMeta(FieldsClassTypeMeta): class ObjectType(six.with_metaclass(ObjectTypeMeta, FieldsClassType)): + class Meta: abstract = True diff --git a/graphene/core/classtypes/options.py b/graphene/core/classtypes/options.py index 7a965a2c..52e4536e 100644 --- a/graphene/core/classtypes/options.py +++ b/graphene/core/classtypes/options.py @@ -1,4 +1,5 @@ class Options(object): + def __init__(self, meta=None, **defaults): self.meta = meta self.abstract = False diff --git a/graphene/core/classtypes/scalar.py b/graphene/core/classtypes/scalar.py index fe4087e8..8d34eba0 100644 --- a/graphene/core/classtypes/scalar.py +++ b/graphene/core/classtypes/scalar.py @@ -1,7 +1,7 @@ from graphql.core.type import GraphQLScalarType -from .base import ClassType from ..types.base import MountedType +from .base import ClassType class Scalar(ClassType, MountedType): diff --git a/graphene/core/classtypes/test_options.py b/graphene/core/classtypes/test_options.py index 28340757..512c3e6a 100644 --- a/graphene/core/classtypes/test_options.py +++ b/graphene/core/classtypes/test_options.py @@ -1,6 +1,5 @@ from py.test import raises -from graphene.core.fields import Field from graphene.core.classtypes import Options diff --git a/graphene/core/classtypes/tests/test_base.py b/graphene/core/classtypes/tests/test_base.py index 546cd943..5017f94d 100644 --- a/graphene/core/classtypes/tests/test_base.py +++ b/graphene/core/classtypes/tests/test_base.py @@ -1,18 +1,18 @@ -from ..base import ClassType, FieldsClassType -from ...types import Field, String, List, NonNull from ...schema import Schema +from ...types import Field, List, NonNull, String +from ..base import ClassType, FieldsClassType def test_classtype_basic(): class Character(ClassType): '''Character description''' - pass assert Character._meta.type_name == 'Character' assert Character._meta.description == 'Character description' def test_classtype_advanced(): class Character(ClassType): + class Meta: type_name = 'OtherCharacter' description = 'OtherCharacter description' @@ -23,7 +23,6 @@ def test_classtype_advanced(): def test_classtype_definition_list(): class Character(ClassType): '''Character description''' - pass assert isinstance(Character.List, List) assert Character.List.of_type == Character @@ -31,7 +30,6 @@ def test_classtype_definition_list(): def test_classtype_definition_nonnull(): class Character(ClassType): '''Character description''' - pass assert isinstance(Character.NonNull, NonNull) assert Character.NonNull.of_type == Character diff --git a/graphene/core/classtypes/tests/test_inputobjecttype.py b/graphene/core/classtypes/tests/test_inputobjecttype.py index 67306bc3..2268d46f 100644 --- a/graphene/core/classtypes/tests/test_inputobjecttype.py +++ b/graphene/core/classtypes/tests/test_inputobjecttype.py @@ -1,9 +1,9 @@ -from py.test import raises from graphql.core.type import GraphQLInputObjectType from graphene.core.schema import Schema from graphene.core.types import String + from ..inputobjecttype import InputObjectType diff --git a/graphene/core/classtypes/tests/test_interface.py b/graphene/core/classtypes/tests/test_interface.py index 3a07f172..7994659a 100644 --- a/graphene/core/classtypes/tests/test_interface.py +++ b/graphene/core/classtypes/tests/test_interface.py @@ -1,9 +1,9 @@ -from py.test import raises - from graphql.core.type import GraphQLInterfaceType, GraphQLObjectType +from py.test import raises from graphene.core.schema import Schema from graphene.core.types import String + from ..interface import Interface from ..objecttype import ObjectType @@ -38,6 +38,7 @@ def test_interface_inheritance_abstract(): pass class ShouldBeInterface(Character): + class Meta: abstract = True diff --git a/graphene/core/classtypes/tests/test_mutation.py b/graphene/core/classtypes/tests/test_mutation.py index ded164ea..ac32585e 100644 --- a/graphene/core/classtypes/tests/test_mutation.py +++ b/graphene/core/classtypes/tests/test_mutation.py @@ -1,11 +1,11 @@ -from py.test import raises from graphql.core.type import GraphQLObjectType from graphene.core.schema import Schema from graphene.core.types import String -from ..mutation import Mutation + from ...types.argument import ArgumentsGroup +from ..mutation import Mutation def test_mutation(): diff --git a/graphene/core/classtypes/tests/test_objecttype.py b/graphene/core/classtypes/tests/test_objecttype.py index 95134bef..93cabc76 100644 --- a/graphene/core/classtypes/tests/test_objecttype.py +++ b/graphene/core/classtypes/tests/test_objecttype.py @@ -1,9 +1,9 @@ +from graphql.core.type import GraphQLObjectType from py.test import raises -from graphql.core.type import GraphQLObjectType - from graphene.core.schema import Schema -from graphene.core.types import Int, String +from graphene.core.types import String + from ..objecttype import ObjectType from ..uniontype import UnionType diff --git a/graphene/core/classtypes/tests/test_uniontype.py b/graphene/core/classtypes/tests/test_uniontype.py index d19615fa..308b2ce0 100644 --- a/graphene/core/classtypes/tests/test_uniontype.py +++ b/graphene/core/classtypes/tests/test_uniontype.py @@ -2,6 +2,7 @@ from graphql.core.type import GraphQLUnionType from graphene.core.schema import Schema from graphene.core.types import String + from ..objecttype import ObjectType from ..uniontype import UnionType diff --git a/graphene/core/classtypes/uniontype.py b/graphene/core/classtypes/uniontype.py index c1fb86e3..51d63121 100644 --- a/graphene/core/classtypes/uniontype.py +++ b/graphene/core/classtypes/uniontype.py @@ -1,11 +1,11 @@ import six - from graphql.core.type import GraphQLUnionType -from .base import FieldsOptions, FieldsClassType, FieldsClassTypeMeta +from .base import FieldsClassType, FieldsClassTypeMeta, FieldsOptions class UnionTypeOptions(FieldsOptions): + def __init__(self, *args, **kwargs): super(UnionTypeOptions, self).__init__(*args, **kwargs) self.types = [] @@ -19,6 +19,7 @@ class UnionTypeMeta(FieldsClassTypeMeta): class UnionType(six.with_metaclass(UnionTypeMeta, FieldsClassType)): + class Meta: abstract = True diff --git a/graphene/core/schema.py b/graphene/core/schema.py index 48c9cf1e..1b0ce8f9 100644 --- a/graphene/core/schema.py +++ b/graphene/core/schema.py @@ -10,8 +10,8 @@ from graphql.core.utils.schema_printer import print_schema from graphene import signals -from .types.base import BaseType from .classtypes.base import ClassType +from .types.base import BaseType class GraphQLSchema(_GraphQLSchema): diff --git a/graphene/core/types/field.py b/graphene/core/types/field.py index 5a429b64..d7643b5e 100644 --- a/graphene/core/types/field.py +++ b/graphene/core/types/field.py @@ -6,8 +6,8 @@ from graphql.core.type import GraphQLField, GraphQLInputObjectField from ...utils import to_camel_case from ..classtypes.base import FieldsClassType -from ..classtypes.mutation import Mutation from ..classtypes.inputobjecttype import InputObjectType +from ..classtypes.mutation import Mutation from .argument import ArgumentsGroup, snake_case_args from .base import LazyType, MountType, OrderedType from .definitions import NonNull diff --git a/graphene/core/types/objecttype.py b/graphene/core/types/objecttype.py index 140b291e..d7cf42ab 100644 --- a/graphene/core/types/objecttype.py +++ b/graphene/core/types/objecttype.py @@ -1,3 +1,3 @@ -from ..classtypes import ObjectType, Interface, Mutation, InputObjectType +from ..classtypes import InputObjectType, Interface, Mutation, ObjectType __all__ = ['ObjectType', 'Interface', 'Mutation', 'InputObjectType'] diff --git a/graphene/relay/types.py b/graphene/relay/types.py index 4cece989..672042e7 100644 --- a/graphene/relay/types.py +++ b/graphene/relay/types.py @@ -1,15 +1,15 @@ import inspect -import six import warnings from collections import Iterable from functools import wraps +import six from graphql_relay.connection.arrayconnection import connection_from_list from graphql_relay.node.node import to_global_id from ..core.classtypes import InputObjectType, Interface, Mutation, ObjectType -from ..core.classtypes.mutation import MutationMeta from ..core.classtypes.interface import InterfaceMeta +from ..core.classtypes.mutation import MutationMeta from ..core.types import Boolean, Field, List, String from ..core.types.argument import ArgumentsGroup from ..core.types.definitions import NonNull @@ -87,6 +87,7 @@ class Connection(ObjectType): class NodeMeta(InterfaceMeta): + def construct_get_node(cls): get_node = getattr(cls, 'get_node', None) assert get_node, 'get_node classmethod not found in %s Node' % cls @@ -145,6 +146,7 @@ class MutationInputType(InputObjectType): class RelayMutationMeta(MutationMeta): + def construct(cls, *args, **kwargs): cls = super(RelayMutationMeta, cls).construct(*args, **kwargs) if not cls._meta.abstract: diff --git a/graphene/signals.py b/graphene/signals.py index cdfa3d06..3183eeec 100644 --- a/graphene/signals.py +++ b/graphene/signals.py @@ -2,6 +2,7 @@ try: from blinker import Signal except ImportError: class Signal(object): + def send(self, *args, **kwargs): pass From df9dd33c747610db9950384c3a17a8b132161e21 Mon Sep 17 00:00:00 2001 From: Syrus Akbary Date: Wed, 2 Dec 2015 23:48:40 -0800 Subject: [PATCH 09/10] Fixed Python3 errors --- graphene/core/classtypes/objecttype.py | 2 +- graphene/core/classtypes/uniontype.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/graphene/core/classtypes/objecttype.py b/graphene/core/classtypes/objecttype.py index 5658d570..32294d4e 100644 --- a/graphene/core/classtypes/objecttype.py +++ b/graphene/core/classtypes/objecttype.py @@ -93,7 +93,7 @@ class ObjectType(six.with_metaclass(ObjectTypeMeta, FieldsClassType)): return GraphQLObjectType( cls._meta.type_name, description=cls._meta.description, - interfaces=map(schema.T, cls._meta.interfaces), + interfaces=list(map(schema.T, cls._meta.interfaces)), fields=partial(cls.fields_internal_types, schema), is_type_of=getattr(cls, 'is_type_of', None) ) diff --git a/graphene/core/classtypes/uniontype.py b/graphene/core/classtypes/uniontype.py index 51d63121..62003005 100644 --- a/graphene/core/classtypes/uniontype.py +++ b/graphene/core/classtypes/uniontype.py @@ -34,7 +34,7 @@ class UnionType(six.with_metaclass(UnionTypeMeta, FieldsClassType)): return GraphQLUnionType( cls._meta.type_name, - types=map(schema.T, cls._meta.types), + types=list(map(schema.T, cls._meta.types)), resolve_type=cls._resolve_type, description=cls._meta.description, ) From d251a5244283c718985d766223ad192c09a3549d Mon Sep 17 00:00:00 2001 From: Syrus Akbary Date: Wed, 2 Dec 2015 23:54:45 -0800 Subject: [PATCH 10/10] Fixed Python3 import error --- graphene/core/classtypes/mutation.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/graphene/core/classtypes/mutation.py b/graphene/core/classtypes/mutation.py index 63743974..ab443607 100644 --- a/graphene/core/classtypes/mutation.py +++ b/graphene/core/classtypes/mutation.py @@ -1,6 +1,5 @@ import six -from ..types.argument import ArgumentsGroup from .objecttype import ObjectType, ObjectTypeMeta @@ -19,6 +18,7 @@ class MutationMeta(ObjectTypeMeta): return cls def construct_arguments(cls, items): + from ..types.argument import ArgumentsGroup return ArgumentsGroup(**items)