Improved relay resolvers

This commit is contained in:
Syrus Akbary 2015-10-20 21:32:15 -07:00
parent 526d34d009
commit 464002c2db
8 changed files with 111 additions and 69 deletions

View File

@ -73,8 +73,7 @@ class Field(object):
@wraps(resolve_fn) @wraps(resolve_fn)
def custom_resolve_fn(instance, args, info): def custom_resolve_fn(instance, args, info):
custom_fn = getattr(instance, custom_resolve_fn_name) return resolve_fn(instance, args, info)
return custom_fn(args, info)
return custom_resolve_fn return custom_resolve_fn
def get_object_type(self, schema): def get_object_type(self, schema):

View File

@ -1,5 +1,5 @@
from graphene.utils import cached_property from graphene.utils import cached_property
from collections import OrderedDict from collections import OrderedDict, namedtuple
DEFAULT_NAMES = ('description', 'name', 'interface', DEFAULT_NAMES = ('description', 'name', 'interface',
'type_name', 'interfaces', 'proxy') 'type_name', 'interfaces', 'proxy')
@ -59,6 +59,10 @@ class Options(object):
del self.meta del self.meta
@cached_property
def object(self):
return namedtuple(self.type_name, self.fields_map.keys())
def add_field(self, field): def add_field(self, field):
self.local_fields.append(field) self.local_fields.append(field)

View File

@ -113,17 +113,23 @@ class ObjectTypeMeta(type):
class BaseObjectType(object): class BaseObjectType(object):
def __new__(cls, instance=None, *args, **kwargs): def __new__(cls, instance=None, **kwargs):
if cls._meta.interface: if cls._meta.interface:
raise Exception("An interface cannot be initialized") raise Exception("An interface cannot be initialized")
if instance is None: if instance is None:
if not kwargs:
return None return None
elif type(instance) is cls: elif type(instance) is cls:
instance = instance.instance instance = instance.instance
return super(BaseObjectType, cls).__new__(cls, *args, **kwargs)
def __init__(self, instance): return super(BaseObjectType, cls).__new__(cls)
def __init__(self, instance=None, **kwargs):
signals.pre_init.send(self.__class__, instance=instance) signals.pre_init.send(self.__class__, instance=instance)
assert instance or kwargs
if not instance:
init_kwargs = dict({k: None for k in self._meta.fields_map.keys()}, **kwargs)
instance = self._meta.object(**init_kwargs)
self.instance = instance self.instance = instance
signals.post_init.send(self.__class__, instance=self) signals.post_init.send(self.__class__, instance=self)

View File

@ -4,7 +4,7 @@ from graphql_relay.connection.arrayconnection import (
connection_from_list connection_from_list
) )
from graphql_relay.connection.connection import ( from graphql_relay.connection.connection import (
connectionArgs connection_args
) )
from graphql_relay.node.node import ( from graphql_relay.node.node import (
from_global_id from_global_id
@ -21,25 +21,45 @@ from graphene.utils import memoize
class ConnectionField(Field): class ConnectionField(Field):
def __init__(self, field_type, resolve=None, description='', connection_type=None, edge_type=None, **kwargs): def __init__(self, field_type, resolve=None, description='',
from graphene.relay.types import Connection, Edge connection_type=None, edge_type=None, **kwargs):
super(ConnectionField, self).__init__(field_type, resolve=resolve, super(ConnectionField, self).__init__(field_type, resolve=resolve,
args=connectionArgs, description=description, **kwargs) args=connection_args,
self.connection_type = connection_type or Connection description=description, **kwargs)
self.edge_type = edge_type or Edge self.connection_type = connection_type
assert issubclass(self.connection_type, Connection), 'connection_type in %r must be a subclass of Connection' % self self.edge_type = edge_type
assert issubclass(self.edge_type, Edge), 'edge_type in %r must be a subclass of Edge' % self
def wrap_resolved(self, value, instance, args, info): def wrap_resolved(self, value, instance, args, info):
return value return value
def resolve(self, instance, args, info): def resolve(self, instance, args, info):
resolved = super(ConnectionField, self).resolve(instance, args, info) from graphene.relay.types import PageInfo
if resolved: schema = info.schema.graphene_schema
resolved = self.wrap_resolved(resolved, instance, args, info)
orig_resolved = super(ConnectionField, self).resolve(instance, args, info)
if orig_resolved:
resolved = self.wrap_resolved(orig_resolved, instance, args, info)
assert isinstance( assert isinstance(
resolved, Iterable), 'Resolved value from the connection field have to be iterable' resolved, Iterable), 'Resolved value from the connection field have to be iterable'
return connection_from_list(resolved, args)
node = self.get_object_type(schema)
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(orig_resolved)
return connection
@memoize
def get_connection_type(self, node):
connection_type = self.connection_type or node.get_connection_type()
edge_type = self.get_edge_type(node)
return connection_type.for_node(node, edge_type=edge_type)
@memoize
def get_edge_type(self, node):
return self.edge_type or node.get_edge_type()
@memoize @memoize
def internal_type(self, schema): def internal_type(self, schema):
@ -47,9 +67,8 @@ class ConnectionField(Field):
node = self.get_object_type(schema) node = self.get_object_type(schema)
assert is_node(node), 'Only nodes have connections.' assert is_node(node), 'Only nodes have connections.'
schema.register(node) schema.register(node)
edge_node_type = self.edge_type.for_node(node)
connection_node_type = self.connection_type.for_node(node, edge_type=edge_node_type) return self.get_connection_type(node).internal_type(schema)
return connection_node_type.internal_type(schema)
class NodeField(Field): class NodeField(Field):

View File

@ -1,9 +1,6 @@
from graphql_relay.node.node import ( from graphql_relay.node.node import (
to_global_id to_global_id
) )
from graphql_relay.connection.connection import (
connection_definitions
)
from graphene.core.types import Interface, ObjectType from graphene.core.types import Interface, ObjectType
from graphene.core.fields import BooleanField, StringField, ListField, Field from graphene.core.fields import BooleanField, StringField, ListField, Field
@ -11,15 +8,55 @@ from graphene.relay.fields import GlobalIDField
from graphene.utils import memoize from graphene.utils import memoize
class BaseNode(object): class PageInfo(ObjectType):
has_next_page = BooleanField(required=True, description='When paginating forwards, are there more items?')
has_previous_page = BooleanField(required=True, description='When paginating backwards, are there more items?')
start_cursor = StringField(description='When paginating backwards, the cursor to continue.')
end_cursor = StringField(description='When paginating forwards, the cursor to continue.')
class Edge(ObjectType):
'''An edge in a connection.'''
class Meta:
type_name = 'DefaultEdge'
node = Field(lambda field: field.object_type.node_type, description='The item at the end of the edge')
cursor = StringField(required=True, description='A cursor for use in pagination')
@classmethod @classmethod
@memoize @memoize
def get_connection(cls, schema): def for_node(cls, node):
_type = cls.internal_type(schema) from graphene.relay.utils import is_node
type_name = cls._meta.type_name assert is_node(node), 'ObjectTypes in a edge have to be Nodes'
connection = connection_definitions(type_name, _type).connection_type return type('%s%s' % (node._meta.type_name, cls._meta.type_name), (cls, ), {'node_type': node})
return connection
class Connection(ObjectType):
'''A connection to a list of items.'''
class Meta:
type_name = 'DefaultConnection'
page_info = Field(PageInfo, required=True, description='The Information to aid in pagination')
edges = ListField(lambda field: field.object_type.edge_type, description='Information to aid in pagination.')
_connection_data = None
@classmethod
@memoize
def for_node(cls, node, edge_type=None):
from graphene.relay.utils import is_node
edge_type = edge_type or Edge
assert is_node(node), 'ObjectTypes in a connection have to be Nodes'
return type('%s%s' % (node._meta.type_name, cls._meta.type_name), (cls, ), {'edge_type': edge_type.for_node(node)})
def set_connection_data(self, data):
self._connection_data = data
def get_connection_data(self):
return self._connection_data
class BaseNode(object):
@classmethod @classmethod
def _prepare_class(cls): def _prepare_class(cls):
from graphene.relay.utils import is_node from graphene.relay.utils import is_node
@ -32,41 +69,18 @@ class BaseNode(object):
type_name = cls._meta.type_name type_name = cls._meta.type_name
return to_global_id(type_name, instance.id) return to_global_id(type_name, instance.id)
connection_type = Connection
edge_type = Edge
@classmethod
def get_connection_type(cls):
return cls.connection_type
@classmethod
def get_edge_type(cls):
return cls.edge_type
class Node(BaseNode, Interface): class Node(BaseNode, Interface):
'''An object with an ID''' '''An object with an ID'''
id = GlobalIDField() id = GlobalIDField()
class PageInfo(ObjectType):
has_next_page = BooleanField(required=True, description='When paginating forwards, are there more items?')
has_previous_page = BooleanField(required=True, description='When paginating backwards, are there more items?')
start_cursor = StringField(description='When paginating backwards, the cursor to continue.')
end_cursor = StringField(description='When paginating forwards, the cursor to continue.')
class Edge(ObjectType):
'''An edge in a connection.'''
node = Field(lambda field: field.object_type.node_type, description='The item at the end of the edge')
end_cursor = StringField(required=True, description='A cursor for use in pagination')
@classmethod
@memoize
def for_node(cls, node):
from graphene.relay.utils import is_node
assert is_node(node), 'ObjectTypes in a edge have to be Nodes'
return type('%sEdge' % node._meta.type_name, (cls, ), {'node_type': node})
class Connection(ObjectType):
'''A connection to a list of items.'''
page_info = Field(PageInfo, required=True, description='The Information to aid in pagination')
edges = ListField(lambda field: field.object_type.edge_type, description='Information to aid in pagination.')
@classmethod
@memoize
def for_node(cls, node, edge_type=None):
from graphene.relay.utils import is_node
edge_type = edge_type or Edge
assert is_node(node), 'ObjectTypes in a connection have to be Nodes'
return type('%sConnection' % node._meta.type_name, (cls, ), {'edge_type': edge_type.for_node(node)})

View File

@ -57,7 +57,7 @@ setup(
'six>=1.10.0', 'six>=1.10.0',
'blinker', 'blinker',
'graphql-core==0.4.7b0', 'graphql-core==0.4.7b0',
'graphql-relay==0.2.0' 'graphql-relay==0.3.3'
], ],
tests_require=[ tests_require=[
'pytest>=2.7.2', 'pytest>=2.7.2',

View File

@ -24,8 +24,8 @@ def test_field_no_contributed_raises_error():
def test_node_should_have_same_connection_always(): def test_node_should_have_same_connection_always():
s = object() s = object()
connection1 = OtherNode.get_connection(s) connection1 = relay.Connection.for_node(OtherNode)
connection2 = OtherNode.get_connection(s) connection2 = relay.Connection.for_node(OtherNode)
assert connection1 == connection2 assert connection1 == connection2

View File

@ -7,7 +7,7 @@ deps=
django>=1.8.0,<1.9 django>=1.8.0,<1.9
pytest-django pytest-django
graphql-core==0.4.7b0 graphql-core==0.4.7b0
graphql-relay==0.2.0 graphql-relay==0.3.3
six six
blinker blinker
singledispatch singledispatch