1
+ import copy
1
2
from functools import partial
3
+ from typing import Generic , Iterable , Iterator , List , Optional , Protocol , Tuple , TypeVar , Union , runtime_checkable
2
4
3
- from . import RequestOptions
5
+ from tableauserverclient .models .pagination_item import PaginationItem
6
+ from tableauserverclient .server .request_options import RequestOptions
4
7
5
8
6
- class Pager (object ):
9
+ T = TypeVar ("T" )
10
+ ReturnType = Tuple [List [T ], PaginationItem ]
11
+
12
+
13
+ @runtime_checkable
14
+ class Endpoint (Protocol ):
15
+ def get (self , req_options : Optional [RequestOptions ], ** kwargs ) -> ReturnType :
16
+ ...
17
+
18
+
19
+ @runtime_checkable
20
+ class CallableEndpoint (Protocol ):
21
+ def __call__ (self , __req_options : Optional [RequestOptions ], ** kwargs ) -> ReturnType :
22
+ ...
23
+
24
+
25
+ class Pager (Iterable [T ]):
7
26
"""
8
27
Generator that takes an endpoint (top level endpoints with `.get)` and lazily loads items from Server.
9
28
Supports all `RequestOptions` including starting on any page. Also used by models to load sub-models
@@ -12,60 +31,42 @@ class Pager(object):
12
31
Will loop over anything that returns (List[ModelItem], PaginationItem).
13
32
"""
14
33
15
- def __init__ (self , endpoint , request_opts = None , ** kwargs ):
16
- if hasattr (endpoint , "get" ):
34
+ def __init__ (
35
+ self ,
36
+ endpoint : Union [CallableEndpoint , Endpoint ],
37
+ request_opts : Optional [RequestOptions ] = None ,
38
+ ** kwargs ,
39
+ ) -> None :
40
+ if isinstance (endpoint , Endpoint ):
17
41
# The simpliest case is to take an Endpoint and call its get
18
42
endpoint = partial (endpoint .get , ** kwargs )
19
43
self ._endpoint = endpoint
20
- elif callable (endpoint ):
44
+ elif isinstance (endpoint , CallableEndpoint ):
21
45
# but if they pass a callable then use that instead (used internally)
22
46
endpoint = partial (endpoint , ** kwargs )
23
47
self ._endpoint = endpoint
24
48
else :
25
49
# Didn't get something we can page over
26
50
raise ValueError ("Pager needs a server endpoint to page through." )
27
51
28
- self ._options = request_opts
52
+ self ._options = request_opts or RequestOptions ()
29
53
30
- # If we have options we could be starting on any page, backfill the count
31
- if self ._options :
32
- self ._count = (self ._options .pagenumber - 1 ) * self ._options .pagesize
33
- else :
34
- self ._count = 0
35
- self ._options = RequestOptions ()
36
-
37
- def __iter__ (self ):
38
- # Fetch the first page
39
- current_item_list , last_pagination_item = self ._endpoint (self ._options )
40
-
41
- if last_pagination_item .total_available is None :
42
- # This endpoint does not support pagination, drain the list and return
43
- while current_item_list :
44
- yield current_item_list .pop (0 )
45
-
46
- return
47
-
48
- # Get the rest on demand as a generator
49
- while self ._count < last_pagination_item .total_available :
50
- if (
51
- len (current_item_list ) == 0
52
- and (last_pagination_item .page_number * last_pagination_item .page_size )
53
- < last_pagination_item .total_available
54
- ):
55
- current_item_list , last_pagination_item = self ._load_next_page (last_pagination_item )
56
-
57
- try :
58
- yield current_item_list .pop (0 )
59
- self ._count += 1
60
-
61
- except IndexError :
62
- # The total count on Server changed while fetching exit gracefully
54
+ def __iter__ (self ) -> Iterator [T ]:
55
+ options = copy .deepcopy (self ._options )
56
+ while True :
57
+ # Fetch the first page
58
+ current_item_list , pagination_item = self ._endpoint (options )
59
+
60
+ if pagination_item .total_available is None :
61
+ # This endpoint does not support pagination, drain the list and return
62
+ yield from current_item_list
63
+ return
64
+ yield from current_item_list
65
+
66
+ if pagination_item .page_size * pagination_item .page_number >= pagination_item .total_available :
67
+ # Last page, exit
63
68
return
64
69
65
- def _load_next_page (self , last_pagination_item ):
66
- next_page = last_pagination_item .page_number + 1
67
- opts = RequestOptions (pagenumber = next_page , pagesize = last_pagination_item .page_size )
68
- if self ._options is not None :
69
- opts .sort , opts .filter = self ._options .sort , self ._options .filter
70
- current_item_list , last_pagination_item = self ._endpoint (opts )
71
- return current_item_list , last_pagination_item
70
+ # Update the options to fetch the next page
71
+ options .pagenumber = pagination_item .page_number + 1
72
+ options .pagesize = pagination_item .page_size
0 commit comments