From 70a9de63f96a1cce74a6273ab95878be2d7e4bf4 Mon Sep 17 00:00:00 2001 From: Syrus Akbary Date: Fri, 12 Aug 2016 19:23:33 -0700 Subject: [PATCH] Added InputObjectType and InputField --- graphene/new_types/inputfield.py | 17 ++++ graphene/new_types/inputobjecttype.py | 36 ++++++++ graphene/new_types/interface.py | 1 - .../new_types/tests/test_inputobjecttype.py | 83 +++++++++++++++++++ graphene/new_types/unmountedtype.py | 21 ++--- graphene/new_types/utils.py | 8 +- 6 files changed, 152 insertions(+), 14 deletions(-) create mode 100644 graphene/new_types/inputfield.py create mode 100644 graphene/new_types/inputobjecttype.py create mode 100644 graphene/new_types/tests/test_inputobjecttype.py diff --git a/graphene/new_types/inputfield.py b/graphene/new_types/inputfield.py new file mode 100644 index 00000000..09ccf5a8 --- /dev/null +++ b/graphene/new_types/inputfield.py @@ -0,0 +1,17 @@ +from ..utils.orderedtype import OrderedType +from .structures import NonNull + + +class InputField(OrderedType): + + def __init__(self, type, name=None, default_value=None, + deprecation_reason=None, description=None, + required=False, _creation_counter=None, **extra_args): + super(InputField, self).__init__(_creation_counter=_creation_counter) + self.name = name + if required: + type = NonNull(type) + self.type = type + self.deprecation_reason = deprecation_reason + self.default_value = default_value + self.description = description diff --git a/graphene/new_types/inputobjecttype.py b/graphene/new_types/inputobjecttype.py new file mode 100644 index 00000000..aa8f24cc --- /dev/null +++ b/graphene/new_types/inputobjecttype.py @@ -0,0 +1,36 @@ +import six + +from ..utils.is_base_type import is_base_type +from .options import Options + +from .abstracttype import AbstractTypeMeta +from .utils import get_fields_in_type, yank_fields_from_attrs, merge_fields_in_attrs + + +class InputObjectTypeMeta(AbstractTypeMeta): + + def __new__(cls, name, bases, attrs): + # Also ensure initialization is only performed for subclasses of + # InputObjectType + if not is_base_type(bases, InputObjectTypeMeta): + return type.__new__(cls, name, bases, attrs) + + options = Options( + attrs.pop('Meta', None), + name=name, + description=attrs.get('__doc__'), + ) + + attrs = merge_fields_in_attrs(bases, attrs) + options.fields = get_fields_in_type(InputObjectType, attrs) + yank_fields_from_attrs(attrs, options.fields) + + return type.__new__(cls, name, bases, dict(attrs, _meta=options)) + + def __str__(cls): + return cls._meta.name + + +class InputObjectType(six.with_metaclass(InputObjectTypeMeta)): + def __init__(self, *args, **kwargs): + raise Exception("An InputObjectType cannot be intitialized") diff --git a/graphene/new_types/interface.py b/graphene/new_types/interface.py index e4975e14..d6c1fe37 100644 --- a/graphene/new_types/interface.py +++ b/graphene/new_types/interface.py @@ -19,7 +19,6 @@ class InterfaceMeta(AbstractTypeMeta): attrs.pop('Meta', None), name=name, description=attrs.get('__doc__'), - interfaces=(), ) attrs = merge_fields_in_attrs(bases, attrs) diff --git a/graphene/new_types/tests/test_inputobjecttype.py b/graphene/new_types/tests/test_inputobjecttype.py new file mode 100644 index 00000000..52796749 --- /dev/null +++ b/graphene/new_types/tests/test_inputobjecttype.py @@ -0,0 +1,83 @@ +import pytest + +from ..field import Field +from ..inputfield import InputField +from ..inputobjecttype import InputObjectType +from ..unmountedtype import UnmountedType +from ..abstracttype import AbstractType + + +class MyType(object): + pass + + +class MyScalar(UnmountedType): + def get_type(self): + return MyType + + +def test_generate_inputobjecttype(): + class MyInputObjectType(InputObjectType): + '''Documentation''' + + assert MyInputObjectType._meta.name == "MyInputObjectType" + assert MyInputObjectType._meta.description == "Documentation" + assert MyInputObjectType._meta.fields == {} + + +def test_generate_inputobjecttype_with_meta(): + class MyInputObjectType(InputObjectType): + + class Meta: + name = 'MyOtherInputObjectType' + description = 'Documentation' + + assert MyInputObjectType._meta.name == "MyOtherInputObjectType" + assert MyInputObjectType._meta.description == "Documentation" + + +def test_generate_inputobjecttype_with_fields(): + class MyInputObjectType(InputObjectType): + field = Field(MyType) + + assert 'field' in MyInputObjectType._meta.fields + + +def test_ordered_fields_in_inputobjecttype(): + class MyInputObjectType(InputObjectType): + b = InputField(MyType) + a = InputField(MyType) + field = MyScalar() + asa = InputField(MyType) + + assert list(MyInputObjectType._meta.fields.keys()) == ['b', 'a', 'field', 'asa'] + + +def test_generate_inputobjecttype_unmountedtype(): + class MyInputObjectType(InputObjectType): + field = MyScalar(MyType) + + assert 'field' in MyInputObjectType._meta.fields + assert isinstance(MyInputObjectType._meta.fields['field'], InputField) + + +def test_generate_inputobjecttype_inherit_abstracttype(): + class MyAbstractType(AbstractType): + field1 = MyScalar(MyType) + + class MyInputObjectType(InputObjectType, MyAbstractType): + field2 = MyScalar(MyType) + + assert MyInputObjectType._meta.fields.keys() == ['field1', 'field2'] + assert [type(x) for x in MyInputObjectType._meta.fields.values()] == [InputField, InputField] + + +def test_generate_inputobjecttype_inherit_abstracttype_reversed(): + class MyAbstractType(AbstractType): + field1 = MyScalar(MyType) + + class MyInputObjectType(MyAbstractType, InputObjectType): + field2 = MyScalar(MyType) + + assert MyInputObjectType._meta.fields.keys() == ['field1', 'field2'] + assert [type(x) for x in MyInputObjectType._meta.fields.values()] == [InputField, InputField] diff --git a/graphene/new_types/unmountedtype.py b/graphene/new_types/unmountedtype.py index 080f867d..84f98fc3 100644 --- a/graphene/new_types/unmountedtype.py +++ b/graphene/new_types/unmountedtype.py @@ -37,16 +37,17 @@ class UnmountedType(OrderedType): **self.kwargs ) - # def as_inputfield(self): - # ''' - # Mount the UnmountedType as InputField - # ''' - # return InputField( - # self.get_type(), - # *self.args, - # _creation_counter=self.creation_counter, - # **self.kwargs - # ) + def as_inputfield(self): + ''' + Mount the UnmountedType as InputField + ''' + from .inputfield import InputField + return InputField( + self.get_type(), + *self.args, + _creation_counter=self.creation_counter, + **self.kwargs + ) # def as_argument(self): # ''' diff --git a/graphene/new_types/utils.py b/graphene/new_types/utils.py index c5443e3a..f672a943 100644 --- a/graphene/new_types/utils.py +++ b/graphene/new_types/utils.py @@ -2,6 +2,7 @@ from collections import OrderedDict from .unmountedtype import UnmountedType from .field import Field +from .inputfield import InputField def merge_fields_in_attrs(bases, attrs): @@ -28,14 +29,15 @@ def unmounted_field_in_type(attname, unmounted_field, type): from ..new_types.objecttype import ObjectType from ..new_types.interface import Interface from ..new_types.abstracttype import AbstractType + from ..new_types.inputobjecttype import InputObjectType if issubclass(type, (ObjectType, Interface)): return unmounted_field.as_field() elif issubclass(type, (AbstractType)): return unmounted_field - # elif issubclass(type, (InputObjectType)): - # return unmounted_field.as_inputfield() + elif issubclass(type, (InputObjectType)): + return unmounted_field.as_inputfield() raise Exception( 'Unmounted field "{}" cannot be mounted in {}.{}.'.format( @@ -47,7 +49,7 @@ def unmounted_field_in_type(attname, unmounted_field, type): def get_fields_in_type(in_type, attrs): fields_with_names = [] for attname, value in list(attrs.items()): - if isinstance(value, (Field)): # , InputField + if isinstance(value, (Field, InputField)): fields_with_names.append( (attname, value) )