1
- from graphql_relay . utils import base64 , unbase64
1
+ from promise import Promise
2
2
3
+ from ..utils import base64 , unbase64
3
4
from .connectiontypes import Connection , PageInfo , Edge
4
5
5
6
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 ):
8
8
'''
9
9
A simple function that accepts an array and connection arguments, and returns
10
10
a connection object for use in GraphQL. It uses array offsets as pagination,
11
11
so pagination will only work if the array is static.
12
12
'''
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
61
19
)
62
20
63
21
64
- def connection_from_promised_list (data_promise , args = {} , ** kwargs ):
22
+ def connection_from_promised_list (data_promise , args = None , ** kwargs ):
65
23
'''
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.
68
26
'''
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 ))
72
28
73
29
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 ):
75
33
'''
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`.
77
40
'''
78
41
connection_type = connection_type or Connection
42
+ edge_type = edge_type or Edge
79
43
pageinfo_type = pageinfo_type or PageInfo
80
44
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
+
81
96
return connection_type (
82
- edges = [] ,
97
+ edges = edges ,
83
98
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
88
103
)
89
104
)
90
105
91
106
92
107
PREFIX = 'arrayconnection:'
93
108
94
109
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
+
95
114
def offset_to_cursor (offset ):
96
115
'''
97
116
Creates the cursor string from an offset.
@@ -104,7 +123,7 @@ def cursor_to_offset(cursor):
104
123
Rederives the offset from the cursor string.
105
124
'''
106
125
try :
107
- return int (unbase64 (cursor )[len (PREFIX ):len ( PREFIX ) + 10 ])
126
+ return int (unbase64 (cursor )[len (PREFIX ):])
108
127
except :
109
128
return None
110
129
@@ -120,13 +139,13 @@ def cursor_for_object_in_connection(data, _object):
120
139
return offset_to_cursor (offset )
121
140
122
141
123
- def get_offset (cursor , default_offset = 0 ):
142
+ def get_offset_with_default (cursor = None , default_offset = 0 ):
124
143
'''
125
144
Given an optional cursor and a default offset, returns the offset
126
145
to use; if the cursor contains a valid offset, that will be used,
127
146
otherwise it will be the default.
128
147
'''
129
- if cursor is None :
148
+ if not isinstance ( cursor , str ) :
130
149
return default_offset
131
150
132
151
offset = cursor_to_offset (cursor )
0 commit comments