diff --git a/examples/complex_example.py b/examples/complex_example.py new file mode 100644 index 00000000..dd801fff --- /dev/null +++ b/examples/complex_example.py @@ -0,0 +1,31 @@ +import graphene + + +class GeoInput(graphene.InputObjectType): + lat = graphene.Float(required=True) + lng = graphene.Float(required=True) + + +class Address(graphene.ObjectType): + latlng = graphene.String() + + +class Query(graphene.ObjectType): + address = graphene.Field(Address, geo=graphene.Argument(GeoInput)) + + def resolve_address(self, args, info): + geo = args.get('geo') + return Address(latlng="({},{})".format(geo.get('lat'), geo.get('lng'))) + + +schema = graphene.Schema(query=Query) +query = ''' + query something{ + address(geo: {lat:32.2, lng:12}) { + latlng + } + } +''' + +result = schema.execute(query) +print(result.data['address']['latlng']) diff --git a/examples/field_example.py b/examples/simple_example.py similarity index 98% rename from examples/field_example.py rename to examples/simple_example.py index be41969d..bc4b9d34 100644 --- a/examples/field_example.py +++ b/examples/simple_example.py @@ -21,7 +21,7 @@ query = ''' id name } -} + } ''' result = schema.execute(query) print(result.data['patron']) diff --git a/graphene/contrib/django/__init__.py b/graphene/contrib/django/__init__.py index ca89906a..ec39b7f9 100644 --- a/graphene/contrib/django/__init__.py +++ b/graphene/contrib/django/__init__.py @@ -1,4 +1,5 @@ from graphene.contrib.django.types import ( + DjangoConnection, DjangoObjectType, DjangoInterface, DjangoNode @@ -9,4 +10,4 @@ from graphene.contrib.django.fields import ( ) __all__ = ['DjangoObjectType', 'DjangoInterface', 'DjangoNode', - 'DjangoConnectionField', 'DjangoModelField'] + 'DjangoConnection', 'DjangoConnectionField', 'DjangoModelField'] diff --git a/graphene/contrib/django/fields.py b/graphene/contrib/django/fields.py index 62d376d7..7b990b86 100644 --- a/graphene/contrib/django/fields.py +++ b/graphene/contrib/django/fields.py @@ -1,26 +1,22 @@ +import warnings + from ...core.exceptions import SkipField from ...core.fields import Field from ...core.types.base import FieldType from ...core.types.definitions import List from ...relay import ConnectionField from ...relay.utils import is_node -from .utils import get_type_for_model, lazy_map +from .utils import get_type_for_model class DjangoConnectionField(ConnectionField): - def wrap_resolved(self, value, instance, args, info): - return lazy_map(value, self.type) - - -class LazyListField(Field): - - def get_type(self, schema): - return List(self.type) - - def resolver(self, instance, args, info): - resolved = super(LazyListField, self).resolver(instance, args, info) - return lazy_map(resolved, self.type) + def __init__(self, *args, **kwargs): + cls = self.__class__ + warnings.warn("Using {} will be not longer supported." + " Use relay.ConnectionField instead".format(cls.__name__), + FutureWarning) + return super(DjangoConnectionField, self).__init__(*args, **kwargs) class ConnectionOrListField(Field): @@ -33,8 +29,8 @@ class ConnectionOrListField(Field): if is_node(field_object_type): field = DjangoConnectionField(field_object_type) else: - field = LazyListField(field_object_type) - field.contribute_to_class(self.object_type, self.name) + field = Field(List(field_object_type)) + field.contribute_to_class(self.object_type, self.attname) return schema.T(field) diff --git a/graphene/contrib/django/tests/test_types.py b/graphene/contrib/django/tests/test_types.py index 5d7f582e..4d709580 100644 --- a/graphene/contrib/django/tests/test_types.py +++ b/graphene/contrib/django/tests/test_types.py @@ -69,7 +69,7 @@ def test_node_replacedfield(): def test_interface_resolve_type(): - resolve_type = Character.resolve_type(schema, Human()) + resolve_type = Character._resolve_type(schema, Human()) assert isinstance(resolve_type, GraphQLObjectType) diff --git a/graphene/contrib/django/types.py b/graphene/contrib/django/types.py index f17893f0..b5097c72 100644 --- a/graphene/contrib/django/types.py +++ b/graphene/contrib/django/types.py @@ -2,10 +2,10 @@ import six from ...core.types import BaseObjectType, ObjectTypeMeta from ...relay.fields import GlobalIDField -from ...relay.types import BaseNode +from ...relay.types import BaseNode, Connection from .converter import convert_django_field from .options import DjangoOptions -from .utils import get_reverse_fields +from .utils import get_reverse_fields, maybe_queryset class DjangoObjectTypeMeta(ObjectTypeMeta): @@ -30,7 +30,7 @@ class DjangoObjectTypeMeta(ObjectTypeMeta): is_excluded = field.name in cls._meta.exclude_fields or is_already_created if is_not_in_only or is_excluded: # We skip this field if we specify only_fields and is not - # in there. Or when we excldue this field in exclude_fields + # in there. Or when we exclude this field in exclude_fields continue converted_field = convert_django_field(field) cls.add_to_class(field.name, converted_field) @@ -71,6 +71,14 @@ class DjangoInterface(six.with_metaclass( pass +class DjangoConnection(Connection): + + @classmethod + def from_list(cls, iterable, *args, **kwargs): + iterable = maybe_queryset(iterable) + return super(DjangoConnection, cls).from_list(iterable, *args, **kwargs) + + class DjangoNode(BaseNode, DjangoInterface): id = GlobalIDField() @@ -81,3 +89,5 @@ class DjangoNode(BaseNode, DjangoInterface): return cls(instance) except cls._meta.model.DoesNotExist: return None + + connection_type = DjangoConnection diff --git a/graphene/contrib/django/utils.py b/graphene/contrib/django/utils.py index 0d557e9a..54c6420c 100644 --- a/graphene/contrib/django/utils.py +++ b/graphene/contrib/django/utils.py @@ -1,8 +1,5 @@ from django.db import models from django.db.models.manager import Manager -from django.db.models.query import QuerySet - -from ...utils import LazyMap def get_type_for_model(schema, model): @@ -22,9 +19,7 @@ def get_reverse_fields(model): yield related -def lazy_map(value, func): +def maybe_queryset(value): if isinstance(value, Manager): value = value.get_queryset() - if isinstance(value, QuerySet): - return LazyMap(value, func) return value diff --git a/graphene/core/tests/test_query.py b/graphene/core/tests/test_query.py index b9e8ee84..5fbe2faa 100644 --- a/graphene/core/tests/test_query.py +++ b/graphene/core/tests/test_query.py @@ -13,7 +13,10 @@ class Character(Interface): class Pet(ObjectType): - type = String(resolver=lambda *_: 'Dog') + type = String() + + def resolve_type(self, args, info): + return 'Dog' class Human(Character): diff --git a/graphene/core/types/objecttype.py b/graphene/core/types/objecttype.py index e18518e9..09f65ad0 100644 --- a/graphene/core/types/objecttype.py +++ b/graphene/core/types/objecttype.py @@ -182,7 +182,7 @@ class BaseObjectType(BaseType): signals.post_init.send(self.__class__, instance=self) @classmethod - def resolve_type(cls, schema, instance, *args): + def _resolve_type(cls, schema, instance, *args): return schema.T(instance.__class__) @classmethod @@ -191,7 +191,7 @@ class BaseObjectType(BaseType): return GraphQLInterfaceType( cls._meta.type_name, description=cls._meta.description, - resolve_type=partial(cls.resolve_type, schema), + resolve_type=partial(cls._resolve_type, schema), fields=partial(cls.get_fields, schema) ) elif cls._meta.is_union: @@ -219,6 +219,10 @@ class BaseObjectType(BaseType): return OrderedDict(fields) + @classmethod + def wrap(cls, instance, args, info): + return cls(_root=instance) + class Interface(six.with_metaclass(ObjectTypeMeta, BaseObjectType)): pass diff --git a/graphene/core/types/tests/test_objecttype.py b/graphene/core/types/tests/test_objecttype.py index add37bae..6bb157d6 100644 --- a/graphene/core/types/tests/test_objecttype.py +++ b/graphene/core/types/tests/test_objecttype.py @@ -75,7 +75,7 @@ def test_union_cannot_initialize(): def test_interface_resolve_type(): - resolve_type = Character.resolve_type(schema, Human(object())) + resolve_type = Character._resolve_type(schema, Human(object())) assert isinstance(resolve_type, GraphQLObjectType) diff --git a/graphene/relay/fields.py b/graphene/relay/fields.py index 58e0e8bd..b3829ad8 100644 --- a/graphene/relay/fields.py +++ b/graphene/relay/fields.py @@ -1,6 +1,3 @@ -from collections import Iterable - -from graphql_relay.connection.arrayconnection import connection_from_list from graphql_relay.node.node import from_global_id from ..core.fields import Field @@ -30,24 +27,12 @@ class ConnectionField(Field): return value def resolver(self, instance, args, info): - from graphene.relay.types import PageInfo schema = info.schema.graphene_schema - + connection_type = self.get_type(schema) resolved = super(ConnectionField, self).resolver(instance, args, info) - if resolved: - resolved = self.wrap_resolved(resolved, instance, args, info) - assert isinstance( - resolved, Iterable), 'Resolved value from the connection field have to be iterable' - type = schema.T(self.type) - node = schema.objecttype(type) - connection_type = self.get_connection_type(node) - edge_type = self.get_edge_type(node) - - connection = connection_from_list( - resolved, args, connection_type=connection_type, - edge_type=edge_type, pageinfo_type=PageInfo) - connection.set_connection_data(resolved) - return connection + if isinstance(resolved, connection_type): + return resolved + return connection_type.from_list(resolved, args, info) def get_connection_type(self, node): connection_type = self.connection_type or node.get_connection_type() diff --git a/graphene/relay/types.py b/graphene/relay/types.py index a803d2f3..9fa673ef 100644 --- a/graphene/relay/types.py +++ b/graphene/relay/types.py @@ -1,6 +1,9 @@ import inspect import warnings +from collections import Iterable 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, @@ -63,6 +66,16 @@ class Connection(ObjectType): (cls,), {'edge_type': edge_type, 'edges': edges}) + @classmethod + def from_list(cls, iterable, args, info): + assert isinstance( + iterable, Iterable), 'Resolved value from the connection field have to be iterable' + connection = connection_from_list( + iterable, args, connection_type=cls, + edge_type=cls.edge_type, pageinfo_type=PageInfo) + connection.set_connection_data(iterable) + return connection + def set_connection_data(self, data): self._connection_data = data diff --git a/graphene/signals.py b/graphene/signals.py index ccb9ef0f..cdfa3d06 100644 --- a/graphene/signals.py +++ b/graphene/signals.py @@ -1,4 +1,9 @@ -from blinker import Signal +try: + from blinker import Signal +except ImportError: + class Signal(object): + def send(self, *args, **kwargs): + pass init_schema = Signal() class_prepared = Signal() diff --git a/graphene/utils/__init__.py b/graphene/utils/__init__.py index bb1a2150..52fb6417 100644 --- a/graphene/utils/__init__.py +++ b/graphene/utils/__init__.py @@ -1,11 +1,10 @@ from .str_converters import to_camel_case, to_snake_case from .proxy_snake_dict import ProxySnakeDict from .caching import cached_property, memoize -from .lazymap import LazyMap from .misc import enum_to_graphql_enum from .resolve_only_args import resolve_only_args __all__ = ['to_camel_case', 'to_snake_case', 'ProxySnakeDict', - 'cached_property', 'memoize', 'LazyMap', 'enum_to_graphql_enum', + 'cached_property', 'memoize', 'enum_to_graphql_enum', 'resolve_only_args'] diff --git a/graphene/utils/lazymap.py b/graphene/utils/lazymap.py deleted file mode 100644 index 236f91eb..00000000 --- a/graphene/utils/lazymap.py +++ /dev/null @@ -1,43 +0,0 @@ -class LazyMap(object): - - def __init__(self, origin, _map, state=None): - self._origin = origin - self._origin_iter = origin.__iter__() - self._state = state or [] - self._finished = False - self._map = _map - - def __iter__(self): - return self if not self._finished else iter(self._state) - - def iter(self): - return self.__iter__() - - def __len__(self): - return self._origin.__len__() - - def __next__(self): - try: - n = next(self._origin_iter) - n = self._map(n) - except StopIteration as e: - self._finished = True - raise e - else: - self._state.append(n) - return n - - def next(self): - return self.__next__() - - def __getitem__(self, key): - item = self._origin[key] - if isinstance(key, slice): - return LazyMap(item, self._map) - return self._map(item) - - def __getattr__(self, name): - return getattr(self._origin, name) - - def __repr__(self): - return "" % repr(self._origin) diff --git a/graphene/utils/tests/test_lazymap.py b/graphene/utils/tests/test_lazymap.py deleted file mode 100644 index e6ee1f36..00000000 --- a/graphene/utils/tests/test_lazymap.py +++ /dev/null @@ -1,23 +0,0 @@ -from py.test import raises - -from ..lazymap import LazyMap - - -def test_lazymap(): - data = list(range(10)) - lm = LazyMap(data, lambda x: 2 * x) - assert len(lm) == 10 - assert lm[1] == 2 - assert isinstance(lm[1:4], LazyMap) - assert lm.append == data.append - assert repr(lm) == '' - - -def test_lazymap_iter(): - data = list(range(2)) - lm = LazyMap(data, lambda x: 2 * x) - iter_lm = iter(lm) - assert iter_lm.next() == 0 - assert iter_lm.next() == 2 - with raises(StopIteration): - iter_lm.next() diff --git a/setup.cfg b/setup.cfg index 11382a78..21db934b 100644 --- a/setup.cfg +++ b/setup.cfg @@ -4,3 +4,6 @@ max-line-length = 120 [coverage:run] omit = core/ntypes/tests/* + +[isort] +known_first_party=graphene diff --git a/setup.py b/setup.py index 100e2321..e8c61433 100644 --- a/setup.py +++ b/setup.py @@ -24,7 +24,7 @@ class PyTest(TestCommand): setup( name='graphene', - version='0.4.1.1', + version='0.4.2', description='Graphene: Python DSL for GraphQL', long_description=open('README.rst').read(), @@ -55,7 +55,6 @@ setup( install_requires=[ 'six>=1.10.0', - 'blinker', 'graphql-core==0.4.9', 'graphql-relay==0.3.3' ],