Skip to content

Commit 464002c

Browse files
committed
Improved relay resolvers
1 parent 526d34d commit 464002c

File tree

8 files changed

+101
-59
lines changed

8 files changed

+101
-59
lines changed

graphene/core/fields.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -73,8 +73,7 @@ def get_resolve_fn(self):
7373

7474
@wraps(resolve_fn)
7575
def custom_resolve_fn(instance, args, info):
76-
custom_fn = getattr(instance, custom_resolve_fn_name)
77-
return custom_fn(args, info)
76+
return resolve_fn(instance, args, info)
7877
return custom_resolve_fn
7978

8079
def get_object_type(self, schema):

graphene/core/options.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
from graphene.utils import cached_property
2-
from collections import OrderedDict
2+
from collections import OrderedDict, namedtuple
33

44
DEFAULT_NAMES = ('description', 'name', 'interface',
55
'type_name', 'interfaces', 'proxy')
@@ -59,6 +59,10 @@ def contribute_to_class(self, cls, name):
5959

6060
del self.meta
6161

62+
@cached_property
63+
def object(self):
64+
return namedtuple(self.type_name, self.fields_map.keys())
65+
6266
def add_field(self, field):
6367
self.local_fields.append(field)
6468

graphene/core/types.py

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -113,17 +113,23 @@ def add_to_class(cls, name, value):
113113

114114
class BaseObjectType(object):
115115

116-
def __new__(cls, instance=None, *args, **kwargs):
116+
def __new__(cls, instance=None, **kwargs):
117117
if cls._meta.interface:
118118
raise Exception("An interface cannot be initialized")
119119
if instance is None:
120-
return None
120+
if not kwargs:
121+
return None
121122
elif type(instance) is cls:
122123
instance = instance.instance
123-
return super(BaseObjectType, cls).__new__(cls, *args, **kwargs)
124124

125-
def __init__(self, instance):
125+
return super(BaseObjectType, cls).__new__(cls)
126+
127+
def __init__(self, instance=None, **kwargs):
126128
signals.pre_init.send(self.__class__, instance=instance)
129+
assert instance or kwargs
130+
if not instance:
131+
init_kwargs = dict({k: None for k in self._meta.fields_map.keys()}, **kwargs)
132+
instance = self._meta.object(**init_kwargs)
127133
self.instance = instance
128134
signals.post_init.send(self.__class__, instance=self)
129135

graphene/relay/fields.py

Lines changed: 34 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
connection_from_list
55
)
66
from graphql_relay.connection.connection import (
7-
connectionArgs
7+
connection_args
88
)
99
from graphql_relay.node.node import (
1010
from_global_id
@@ -21,35 +21,54 @@
2121

2222
class ConnectionField(Field):
2323

24-
def __init__(self, field_type, resolve=None, description='', connection_type=None, edge_type=None, **kwargs):
25-
from graphene.relay.types import Connection, Edge
24+
def __init__(self, field_type, resolve=None, description='',
25+
connection_type=None, edge_type=None, **kwargs):
2626
super(ConnectionField, self).__init__(field_type, resolve=resolve,
27-
args=connectionArgs, description=description, **kwargs)
28-
self.connection_type = connection_type or Connection
29-
self.edge_type = edge_type or Edge
30-
assert issubclass(self.connection_type, Connection), 'connection_type in %r must be a subclass of Connection' % self
31-
assert issubclass(self.edge_type, Edge), 'edge_type in %r must be a subclass of Edge' % self
27+
args=connection_args,
28+
description=description, **kwargs)
29+
self.connection_type = connection_type
30+
self.edge_type = edge_type
3231

3332
def wrap_resolved(self, value, instance, args, info):
3433
return value
3534

3635
def resolve(self, instance, args, info):
37-
resolved = super(ConnectionField, self).resolve(instance, args, info)
38-
if resolved:
39-
resolved = self.wrap_resolved(resolved, instance, args, info)
36+
from graphene.relay.types import PageInfo
37+
schema = info.schema.graphene_schema
38+
39+
orig_resolved = super(ConnectionField, self).resolve(instance, args, info)
40+
if orig_resolved:
41+
resolved = self.wrap_resolved(orig_resolved, instance, args, info)
4042
assert isinstance(
4143
resolved, Iterable), 'Resolved value from the connection field have to be iterable'
42-
return connection_from_list(resolved, args)
44+
45+
node = self.get_object_type(schema)
46+
connection_type = self.get_connection_type(node)
47+
edge_type = self.get_edge_type(node)
48+
49+
connection = connection_from_list(resolved, args, connection_type=connection_type,
50+
edge_type=edge_type, pageinfo_type=PageInfo)
51+
connection.set_connection_data(orig_resolved)
52+
return connection
53+
54+
@memoize
55+
def get_connection_type(self, node):
56+
connection_type = self.connection_type or node.get_connection_type()
57+
edge_type = self.get_edge_type(node)
58+
return connection_type.for_node(node, edge_type=edge_type)
59+
60+
@memoize
61+
def get_edge_type(self, node):
62+
return self.edge_type or node.get_edge_type()
4363

4464
@memoize
4565
def internal_type(self, schema):
4666
from graphene.relay.utils import is_node
4767
node = self.get_object_type(schema)
4868
assert is_node(node), 'Only nodes have connections.'
4969
schema.register(node)
50-
edge_node_type = self.edge_type.for_node(node)
51-
connection_node_type = self.connection_type.for_node(node, edge_type=edge_node_type)
52-
return connection_node_type.internal_type(schema)
70+
71+
return self.get_connection_type(node).internal_type(schema)
5372

5473

5574
class NodeField(Field):

graphene/relay/types.py

Lines changed: 47 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,43 +1,13 @@
11
from graphql_relay.node.node import (
22
to_global_id
33
)
4-
from graphql_relay.connection.connection import (
5-
connection_definitions
6-
)
74

85
from graphene.core.types import Interface, ObjectType
96
from graphene.core.fields import BooleanField, StringField, ListField, Field
107
from graphene.relay.fields import GlobalIDField
118
from graphene.utils import memoize
129

1310

14-
class BaseNode(object):
15-
@classmethod
16-
@memoize
17-
def get_connection(cls, schema):
18-
_type = cls.internal_type(schema)
19-
type_name = cls._meta.type_name
20-
connection = connection_definitions(type_name, _type).connection_type
21-
return connection
22-
23-
@classmethod
24-
def _prepare_class(cls):
25-
from graphene.relay.utils import is_node
26-
if is_node(cls):
27-
assert hasattr(
28-
cls, 'get_node'), 'get_node classmethod not found in %s Node' % cls
29-
30-
@classmethod
31-
def to_global_id(cls, instance, args, info):
32-
type_name = cls._meta.type_name
33-
return to_global_id(type_name, instance.id)
34-
35-
36-
class Node(BaseNode, Interface):
37-
'''An object with an ID'''
38-
id = GlobalIDField()
39-
40-
4111
class PageInfo(ObjectType):
4212
has_next_page = BooleanField(required=True, description='When paginating forwards, are there more items?')
4313
has_previous_page = BooleanField(required=True, description='When paginating backwards, are there more items?')
@@ -47,26 +17,70 @@ class PageInfo(ObjectType):
4717

4818
class Edge(ObjectType):
4919
'''An edge in a connection.'''
20+
class Meta:
21+
type_name = 'DefaultEdge'
22+
5023
node = Field(lambda field: field.object_type.node_type, description='The item at the end of the edge')
51-
end_cursor = StringField(required=True, description='A cursor for use in pagination')
24+
cursor = StringField(required=True, description='A cursor for use in pagination')
5225

5326
@classmethod
5427
@memoize
5528
def for_node(cls, node):
5629
from graphene.relay.utils import is_node
5730
assert is_node(node), 'ObjectTypes in a edge have to be Nodes'
58-
return type('%sEdge' % node._meta.type_name, (cls, ), {'node_type': node})
31+
return type('%s%s' % (node._meta.type_name, cls._meta.type_name), (cls, ), {'node_type': node})
5932

6033

6134
class Connection(ObjectType):
6235
'''A connection to a list of items.'''
36+
class Meta:
37+
type_name = 'DefaultConnection'
38+
6339
page_info = Field(PageInfo, required=True, description='The Information to aid in pagination')
6440
edges = ListField(lambda field: field.object_type.edge_type, description='Information to aid in pagination.')
6541

42+
_connection_data = None
43+
6644
@classmethod
6745
@memoize
6846
def for_node(cls, node, edge_type=None):
6947
from graphene.relay.utils import is_node
7048
edge_type = edge_type or Edge
7149
assert is_node(node), 'ObjectTypes in a connection have to be Nodes'
72-
return type('%sConnection' % node._meta.type_name, (cls, ), {'edge_type': edge_type.for_node(node)})
50+
return type('%s%s' % (node._meta.type_name, cls._meta.type_name), (cls, ), {'edge_type': edge_type.for_node(node)})
51+
52+
def set_connection_data(self, data):
53+
self._connection_data = data
54+
55+
def get_connection_data(self):
56+
return self._connection_data
57+
58+
59+
class BaseNode(object):
60+
@classmethod
61+
def _prepare_class(cls):
62+
from graphene.relay.utils import is_node
63+
if is_node(cls):
64+
assert hasattr(
65+
cls, 'get_node'), 'get_node classmethod not found in %s Node' % cls
66+
67+
@classmethod
68+
def to_global_id(cls, instance, args, info):
69+
type_name = cls._meta.type_name
70+
return to_global_id(type_name, instance.id)
71+
72+
connection_type = Connection
73+
edge_type = Edge
74+
75+
@classmethod
76+
def get_connection_type(cls):
77+
return cls.connection_type
78+
79+
@classmethod
80+
def get_edge_type(cls):
81+
return cls.edge_type
82+
83+
84+
class Node(BaseNode, Interface):
85+
'''An object with an ID'''
86+
id = GlobalIDField()

setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ def run_tests(self):
5757
'six>=1.10.0',
5858
'blinker',
5959
'graphql-core==0.4.7b0',
60-
'graphql-relay==0.2.0'
60+
'graphql-relay==0.3.3'
6161
],
6262
tests_require=[
6363
'pytest>=2.7.2',

tests/relay/test_relay.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,8 @@ class Part(relay.Node):
2424

2525
def test_node_should_have_same_connection_always():
2626
s = object()
27-
connection1 = OtherNode.get_connection(s)
28-
connection2 = OtherNode.get_connection(s)
27+
connection1 = relay.Connection.for_node(OtherNode)
28+
connection2 = relay.Connection.for_node(OtherNode)
2929

3030
assert connection1 == connection2
3131

tox.ini

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ deps=
77
django>=1.8.0,<1.9
88
pytest-django
99
graphql-core==0.4.7b0
10-
graphql-relay==0.2.0
10+
graphql-relay==0.3.3
1111
six
1212
blinker
1313
singledispatch

0 commit comments

Comments
 (0)