1+ import copy
12from functools import partial
3+ from typing import Generic , Iterable , Iterator , List , Optional , Protocol , Tuple , TypeVar , Union , runtime_checkable
24
3- from . import RequestOptions
5+ from tableauserverclient .models .pagination_item import PaginationItem
6+ from tableauserverclient .server .request_options import RequestOptions
47
58
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 ]):
726 """
827 Generator that takes an endpoint (top level endpoints with `.get)` and lazily loads items from Server.
928 Supports all `RequestOptions` including starting on any page. Also used by models to load sub-models
@@ -12,60 +31,42 @@ class Pager(object):
1231 Will loop over anything that returns (List[ModelItem], PaginationItem).
1332 """
1433
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 ):
1741 # The simpliest case is to take an Endpoint and call its get
1842 endpoint = partial (endpoint .get , ** kwargs )
1943 self ._endpoint = endpoint
20- elif callable (endpoint ):
44+ elif isinstance (endpoint , CallableEndpoint ):
2145 # but if they pass a callable then use that instead (used internally)
2246 endpoint = partial (endpoint , ** kwargs )
2347 self ._endpoint = endpoint
2448 else :
2549 # Didn't get something we can page over
2650 raise ValueError ("Pager needs a server endpoint to page through." )
2751
28- self ._options = request_opts
52+ self ._options = request_opts or RequestOptions ()
2953
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
6368 return
6469
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