From 4b714659225a93ea44e95ccdd9ad1b5ea9e36c05 Mon Sep 17 00:00:00 2001 From: Syrus Akbary Date: Thu, 6 Apr 2017 22:13:06 -0700 Subject: [PATCH] Improved lazy types support in Graphene This commit also adds support for string types in Field, InputField, List and NonNull, where the string will be import. Usage like: Field("graphene.String") --- graphene/types/field.py | 5 ++-- graphene/types/inputfield.py | 7 ++++- graphene/types/structures.py | 7 ++++- graphene/types/tests/test_field.py | 13 +++++++++ graphene/types/tests/test_inputfield.py | 30 +++++++++++++++++++++ graphene/types/tests/test_structures.py | 36 +++++++++++++++++++++++++ graphene/types/tests/utils.py | 1 + graphene/types/typemap.py | 2 -- graphene/types/utils.py | 12 +++++++++ 9 files changed, 106 insertions(+), 7 deletions(-) create mode 100644 graphene/types/tests/test_inputfield.py create mode 100644 graphene/types/tests/utils.py diff --git a/graphene/types/field.py b/graphene/types/field.py index 7e603852..06632d35 100644 --- a/graphene/types/field.py +++ b/graphene/types/field.py @@ -6,6 +6,7 @@ from .argument import Argument, to_arguments from .mountedtype import MountedType from .structures import NonNull from .unmountedtype import UnmountedType +from .utils import get_type base_type = type @@ -60,9 +61,7 @@ class Field(MountedType): @property def type(self): - if inspect.isfunction(self._type) or type(self._type) is partial: - return self._type() - return self._type + return get_type(self._type) def get_resolver(self, parent_resolver): return self.resolver or parent_resolver diff --git a/graphene/types/inputfield.py b/graphene/types/inputfield.py index 8bf5973e..0510ab4a 100644 --- a/graphene/types/inputfield.py +++ b/graphene/types/inputfield.py @@ -1,5 +1,6 @@ from .mountedtype import MountedType from .structures import NonNull +from .utils import get_type class InputField(MountedType): @@ -11,7 +12,11 @@ class InputField(MountedType): self.name = name if required: type = NonNull(type) - self.type = type + self._type = type self.deprecation_reason = deprecation_reason self.default_value = default_value self.description = description + + @property + def type(self): + return get_type(self._type) diff --git a/graphene/types/structures.py b/graphene/types/structures.py index 1ecfa83d..38fa5609 100644 --- a/graphene/types/structures.py +++ b/graphene/types/structures.py @@ -1,4 +1,5 @@ from .unmountedtype import UnmountedType +from .utils import get_type class Structure(UnmountedType): @@ -18,7 +19,11 @@ class Structure(UnmountedType): cls_name, of_type_name, )) - self.of_type = of_type + self._of_type = of_type + + @property + def of_type(self): + return get_type(self._of_type) def get_type(self): ''' diff --git a/graphene/types/tests/test_field.py b/graphene/types/tests/test_field.py index e4ef03bf..80a32154 100644 --- a/graphene/types/tests/test_field.py +++ b/graphene/types/tests/test_field.py @@ -1,9 +1,11 @@ import pytest +from functools import partial from ..argument import Argument from ..field import Field from ..structures import NonNull from ..scalars import String +from .utils import MyLazyType class MyInstance(object): @@ -66,6 +68,17 @@ def test_field_with_lazy_type(): assert field.type == MyType +def test_field_with_lazy_partial_type(): + MyType = object() + field = Field(partial(lambda: MyType)) + assert field.type == MyType + + +def test_field_with_string_type(): + field = Field("graphene.types.tests.utils.MyLazyType") + assert field.type == MyLazyType + + def test_field_not_source_and_resolver(): MyType = object() with pytest.raises(Exception) as exc_info: diff --git a/graphene/types/tests/test_inputfield.py b/graphene/types/tests/test_inputfield.py new file mode 100644 index 00000000..a0888e44 --- /dev/null +++ b/graphene/types/tests/test_inputfield.py @@ -0,0 +1,30 @@ +import pytest +from functools import partial + +from ..inputfield import InputField +from ..structures import NonNull +from .utils import MyLazyType + + +def test_inputfield_required(): + MyType = object() + field = InputField(MyType, required=True) + assert isinstance(field.type, NonNull) + assert field.type.of_type == MyType + + +def test_inputfield_with_lazy_type(): + MyType = object() + field = InputField(lambda: MyType) + assert field.type == MyType + + +def test_inputfield_with_lazy_partial_type(): + MyType = object() + field = InputField(partial(lambda: MyType)) + assert field.type == MyType + + +def test_inputfield_with_string_type(): + field = InputField("graphene.types.tests.utils.MyLazyType") + assert field.type == MyLazyType diff --git a/graphene/types/tests/test_structures.py b/graphene/types/tests/test_structures.py index e45f09e2..082bf097 100644 --- a/graphene/types/tests/test_structures.py +++ b/graphene/types/tests/test_structures.py @@ -1,7 +1,9 @@ import pytest +from functools import partial from ..structures import List, NonNull from ..scalars import String +from .utils import MyLazyType def test_list(): @@ -17,6 +19,23 @@ def test_list_with_unmounted_type(): assert str(exc_info.value) == 'List could not have a mounted String() as inner type. Try with List(String).' +def test_list_with_lazy_type(): + MyType = object() + field = List(lambda: MyType) + assert field.of_type == MyType + + +def test_list_with_lazy_partial_type(): + MyType = object() + field = List(partial(lambda: MyType)) + assert field.of_type == MyType + + +def test_list_with_string_type(): + field = List("graphene.types.tests.utils.MyLazyType") + assert field.of_type == MyLazyType + + def test_list_inherited_works_list(): _list = List(List(String)) assert isinstance(_list.of_type, List) @@ -35,6 +54,23 @@ def test_nonnull(): assert str(nonnull) == 'String!' +def test_nonnull_with_lazy_type(): + MyType = object() + field = NonNull(lambda: MyType) + assert field.of_type == MyType + + +def test_nonnull_with_lazy_partial_type(): + MyType = object() + field = NonNull(partial(lambda: MyType)) + assert field.of_type == MyType + + +def test_nonnull_with_string_type(): + field = NonNull("graphene.types.tests.utils.MyLazyType") + assert field.of_type == MyLazyType + + def test_nonnull_inherited_works_list(): _list = NonNull(List(String)) assert isinstance(_list.of_type, List) diff --git a/graphene/types/tests/utils.py b/graphene/types/tests/utils.py new file mode 100644 index 00000000..83cf49e2 --- /dev/null +++ b/graphene/types/tests/utils.py @@ -0,0 +1 @@ +MyLazyType = object() diff --git a/graphene/types/typemap.py b/graphene/types/typemap.py index 16a9b3ca..0f75c113 100644 --- a/graphene/types/typemap.py +++ b/graphene/types/typemap.py @@ -283,6 +283,4 @@ class TypeMap(GraphQLTypeMap): return GraphQLList(self.get_field_type(map, type.of_type)) if isinstance(type, NonNull): return GraphQLNonNull(self.get_field_type(map, type.of_type)) - if inspect.isfunction(type): - type = type() return map.get(type._meta.name) diff --git a/graphene/types/utils.py b/graphene/types/utils.py index e2603155..19cc06e7 100644 --- a/graphene/types/utils.py +++ b/graphene/types/utils.py @@ -1,5 +1,9 @@ +import inspect from collections import OrderedDict +from functools import partial +from six import string_types +from ..utils.module_loading import import_string from .mountedtype import MountedType from .unmountedtype import UnmountedType @@ -62,3 +66,11 @@ def yank_fields_from_attrs(attrs, _as=None, delete=True, sort=True): if sort: fields_with_names = sorted(fields_with_names, key=lambda f: f[1]) return OrderedDict(fields_with_names) + + +def get_type(_type): + if isinstance(_type, string_types): + return import_string(_type) + if inspect.isfunction(_type) or type(_type) is partial: + return _type() + return _type