1- from graphql_relay . utils import base64 , unbase64
1+ from promise import Promise
22
3+ from ..utils import base64 , unbase64
34from .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
92107PREFIX = '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+
95114def 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 )
0 commit comments