Skip to content

Commit ad6cb90

Browse files
committed
Updated to being compatible with GraphQL 0.5.0
1 parent 9097fad commit ad6cb90

20 files changed

+1203
-443
lines changed

README.md

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ member object, and returns a cursor for use in the mutation payload.
6666
An example usage of these methods from the [test schema](tests/starwars/schema.py):
6767

6868
```python
69-
shipConnection = connection_definitions('Ship', shipType).connection_type
69+
ship_edge, ship_connection = connection_definitions('Ship', shipType)
7070

7171
factionType = GraphQLObjectType(
7272
name= 'Faction',
@@ -118,7 +118,7 @@ objects.
118118
An example usage of these methods from the [test schema](tests/starwars/schema.py):
119119

120120
```python
121-
def get_node(global_id, *args):
121+
def get_node(global_id, context, info):
122122
resolvedGlobalId = from_global_id(global_id)
123123
_type, _id = resolvedGlobalId.type, resolvedGlobalId.id
124124
if _type == 'Faction':
@@ -128,14 +128,13 @@ def get_node(global_id, *args):
128128
else:
129129
return None
130130

131-
def get_node_type(obj):
131+
def get_node_type(obj, context, info):
132132
if isinstance(obj, Faction):
133133
return factionType
134134
else:
135135
return shipType
136136

137-
_node_definitions = node_definitions(get_node, get_node_type)
138-
node_field, node_interface = _node_definitions.node_field, _node_definitions.node_interface
137+
node_interface, node_field = node_definitions(get_node, get_node_type)
139138

140139
factionType = GraphQLObjectType(
141140
name= 'Faction',

graphql_relay/connection/arrayconnection.py

Lines changed: 86 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -1,97 +1,116 @@
1-
from graphql_relay.utils import base64, unbase64
1+
from promise import Promise
22

3+
from ..utils import base64, unbase64
34
from .connectiontypes import Connection, PageInfo, Edge
45

56

6-
def connection_from_list(data, args={}, connection_type=None,
7-
edge_type=None, pageinfo_type=None, **kwargs):
7+
def connection_from_list(data, args=None, **kwargs):
88
'''
99
A simple function that accepts an array and connection arguments, and returns
1010
a connection object for use in GraphQL. It uses array offsets as pagination,
1111
so pagination will only work if the array is static.
1212
'''
13-
connection_type = connection_type or Connection
14-
edge_type = edge_type or Edge
15-
pageinfo_type = pageinfo_type or PageInfo
16-
17-
full_args = dict(args, **kwargs)
18-
19-
before = full_args.get('before')
20-
after = full_args.get('after')
21-
first = full_args.get('first')
22-
last = full_args.get('last')
23-
24-
count = len(data)
25-
# Slice with cursors
26-
begin = max(get_offset(after, -1), -1) + 1
27-
end = min(get_offset(before, count + 1), count)
28-
if begin >= count or begin >= end:
29-
return empty_connection(connection_type, pageinfo_type)
30-
31-
# Save the pre-slice cursors
32-
first_preslice_cursor = offset_to_cursor(begin)
33-
last_preslice_cursor = offset_to_cursor(min(end, count) - 1)
34-
35-
# Slice with limits
36-
if first is not None:
37-
end = min(begin + first, end)
38-
if last is not None:
39-
begin = max(end - last, begin)
40-
41-
if begin >= count or begin >= end:
42-
return empty_connection(connection_type, pageinfo_type)
43-
44-
sliced_data = data[begin:end]
45-
edges = [
46-
edge_type(node=node, cursor=offset_to_cursor(i + begin))
47-
for i, node in enumerate(sliced_data)
48-
]
49-
50-
# Construct the connection
51-
first_edge = edges[0]
52-
last_edge = edges[len(edges) - 1]
53-
return connection_type(
54-
edges=edges,
55-
page_info=pageinfo_type(
56-
start_cursor=first_edge.cursor,
57-
end_cursor=last_edge.cursor,
58-
has_previous_page=(first_edge.cursor != first_preslice_cursor),
59-
has_next_page=(last_edge.cursor != last_preslice_cursor)
60-
)
13+
return connection_from_list_slice(
14+
data,
15+
args,
16+
slice_start=0,
17+
list_length=len(data),
18+
**kwargs
6119
)
6220

6321

64-
def connection_from_promised_list(data_promise, args={}, **kwargs):
22+
def connection_from_promised_list(data_promise, args=None, **kwargs):
6523
'''
66-
A version of the above that takes a promised array, and returns a promised
67-
connection.
24+
A version of `connectionFromArray` that takes a promised array, and returns a
25+
promised connection.
6826
'''
69-
# TODO: Promises not implemented
70-
raise Exception('connection_from_promised_list is not implemented yet')
71-
# return dataPromise.then(lambda data:connection_from_list(data, args))
27+
return data_promise.then(lambda data: connection_from_list(data, args, **kwargs))
7228

7329

74-
def empty_connection(connection_type=None, pageinfo_type=None):
30+
def connection_from_list_slice(list_slice, args=None, connection_type=None,
31+
edge_type=None, pageinfo_type=None,
32+
slice_start=0, list_length=0):
7533
'''
76-
Helper to get an empty connection.
34+
Given a slice (subset) of an array, returns a connection object for use in
35+
GraphQL.
36+
This function is similar to `connectionFromArray`, but is intended for use
37+
cases where you know the cardinality of the connection, consider it too large
38+
to materialize the entire array, and instead wish pass in a slice of the
39+
total result large enough to cover the range specified in `args`.
7740
'''
7841
connection_type = connection_type or Connection
42+
edge_type = edge_type or Edge
7943
pageinfo_type = pageinfo_type or PageInfo
8044

45+
args = args or {}
46+
47+
before = args.get('before')
48+
after = args.get('after')
49+
first = args.get('first')
50+
last = args.get('last')
51+
list_slice_length = len(list_slice)
52+
slice_end = slice_start + list_slice_length
53+
before_offset = get_offset_with_default(before, list_length)
54+
after_offset = get_offset_with_default(after, -1)
55+
56+
start_offset = max(
57+
slice_start - 1,
58+
after_offset,
59+
-1
60+
) + 1
61+
end_offset = min(
62+
slice_end,
63+
before_offset,
64+
list_length
65+
)
66+
if isinstance(first, int):
67+
end_offset = min(
68+
end_offset,
69+
start_offset + first
70+
)
71+
if isinstance(last, int):
72+
start_offset = max(
73+
start_offset,
74+
end_offset - last
75+
)
76+
77+
# If supplied slice is too large, trim it down before mapping over it.
78+
_slice = list_slice[
79+
max(start_offset - slice_start, 0):
80+
list_slice_length - (slice_end - end_offset)
81+
]
82+
edges = [
83+
edge_type(
84+
node=node,
85+
cursor=offset_to_cursor(start_offset + i)
86+
)
87+
for i, node in enumerate(_slice)
88+
]
89+
90+
91+
first_edge_cursor = edges[0].cursor if edges else None
92+
last_edge_cursor = edges[-1].cursor if edges else None
93+
lower_bound = after_offset + 1 if after else 0
94+
upper_bound = before_offset if before else list_length
95+
8196
return connection_type(
82-
edges=[],
97+
edges=edges,
8398
page_info=pageinfo_type(
84-
start_cursor=None,
85-
end_cursor=None,
86-
has_previous_page=False,
87-
has_next_page=False,
99+
start_cursor=first_edge_cursor,
100+
end_cursor=last_edge_cursor,
101+
has_previous_page=isinstance(last, int) and start_offset > lower_bound,
102+
has_next_page=isinstance(first, int) and end_offset < upper_bound
88103
)
89104
)
90105

91106

92107
PREFIX = 'arrayconnection:'
93108

94109

110+
def connection_from_promised_list_slice(data_promise, args=None, **kwargs):
111+
return data_promise.then(lambda data: connection_from_list_slice(data, args, **kwargs))
112+
113+
95114
def offset_to_cursor(offset):
96115
'''
97116
Creates the cursor string from an offset.
@@ -104,7 +123,7 @@ def cursor_to_offset(cursor):
104123
Rederives the offset from the cursor string.
105124
'''
106125
try:
107-
return int(unbase64(cursor)[len(PREFIX):len(PREFIX) + 10])
126+
return int(unbase64(cursor)[len(PREFIX):])
108127
except:
109128
return None
110129

@@ -120,13 +139,13 @@ def cursor_for_object_in_connection(data, _object):
120139
return offset_to_cursor(offset)
121140

122141

123-
def get_offset(cursor, default_offset=0):
142+
def get_offset_with_default(cursor=None, default_offset=0):
124143
'''
125144
Given an optional cursor and a default offset, returns the offset
126145
to use; if the cursor contains a valid offset, that will be used,
127146
otherwise it will be the default.
128147
'''
129-
if cursor is None:
148+
if not isinstance(cursor, str):
130149
return default_offset
131150

132151
offset = cursor_to_offset(cursor)

graphql_relay/connection/connection.py

Lines changed: 9 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from graphql.core.type import (
1+
from graphql.type import (
22
GraphQLArgument,
33
GraphQLBoolean,
44
GraphQLInt,
@@ -8,27 +8,7 @@
88
GraphQLString,
99
GraphQLField
1010
)
11-
12-
13-
class ConnectionConfig(object):
14-
15-
'''
16-
Returns a GraphQLFieldConfigArgumentMap appropriate to include
17-
on a field whose return type is a connection type.
18-
'''
19-
20-
def __init__(self, name, node_type, edge_fields=None, connection_fields=None):
21-
self.name = name
22-
self.node_type = node_type
23-
self.edge_fields = edge_fields
24-
self.connection_fields = connection_fields
25-
26-
27-
class GraphQLConnection(object):
28-
29-
def __init__(self, edge_type, connection_type):
30-
self.edge_type = edge_type
31-
self.connection_type = connection_type
11+
from ..utils import resolve_maybe_thunk
3212

3313

3414
connection_args = {
@@ -39,30 +19,21 @@ def __init__(self, edge_type, connection_type):
3919
}
4020

4121

42-
def resolve_maybe_thunk(f):
43-
if hasattr(f, '__call__'):
44-
return f()
45-
return f
46-
47-
48-
def connection_definitions(*args, **kwargs):
49-
if len(args) == 1 and not kwargs and isinstance(args[0], ConnectionConfig):
50-
config = args[0]
51-
else:
52-
config = ConnectionConfig(*args, **kwargs)
53-
name, node_type = config.name, config.node_type
54-
edge_fields = config.edge_fields or {}
55-
connection_fields = config.connection_fields or {}
22+
def connection_definitions(name, node_type, resolve_node=None, resolve_cursor=None, edge_fields=None, connection_fields=None):
23+
edge_fields = edge_fields or {}
24+
connection_fields = connection_fields or {}
5625
edge_type = GraphQLObjectType(
5726
name + 'Edge',
5827
description='An edge in a connection.',
5928
fields=lambda: dict({
6029
'node': GraphQLField(
6130
node_type,
31+
resolver=resolve_node,
6232
description='The item at the end of the edge',
6333
),
6434
'cursor': GraphQLField(
6535
GraphQLNonNull(GraphQLString),
36+
resolver=resolve_cursor,
6637
description='A cursor for use in pagination',
6738
)
6839
}, **resolve_maybe_thunk(edge_fields))
@@ -78,16 +49,15 @@ def connection_definitions(*args, **kwargs):
7849
),
7950
'edges': GraphQLField(
8051
GraphQLList(edge_type),
81-
description='Information to aid in pagination.',
52+
description='A list of edges.',
8253
)
8354
}, **resolve_maybe_thunk(connection_fields))
8455
)
8556

86-
return GraphQLConnection(edge_type, connection_type)
57+
return edge_type, connection_type
8758

8859

8960
# The common page info type used by all connections.
90-
9161
page_info_type = GraphQLObjectType(
9262
'PageInfo',
9363
description='Information about pagination in a connection.',

graphql_relay/connection/tests/__init__.py

Whitespace-only changes.

0 commit comments

Comments
 (0)