1
1
from abc import ABC , abstractmethod
2
2
from collections import namedtuple
3
+ from typing import Generic
4
+ from sinch .domains .numbers .models .numbers import BM
3
5
4
6
5
7
class PageIterator :
@@ -18,26 +20,35 @@ def __next__(self):
18
20
return self .paginator
19
21
20
22
if self .paginator .has_next_page :
21
- return self .paginator .next_page ()
23
+ self .paginator = self .paginator .next_page ()
24
+ return self .paginator
22
25
else :
23
26
raise StopIteration
24
27
25
28
26
29
class AsyncPageIterator :
27
30
def __init__ (self , paginator ):
28
31
self .paginator = paginator
32
+ self .first_yield = True
29
33
30
34
def __aiter__ (self ):
31
35
return self
32
36
33
37
async def __anext__ (self ):
38
+ if self .first_yield :
39
+ self .first_yield = False
40
+ return self .paginator
41
+
34
42
if self .paginator .has_next_page :
35
- return await self .paginator .next_page ()
36
- else :
37
- raise StopAsyncIteration
43
+ next_paginator = await self .paginator .next_page ()
44
+ if next_paginator :
45
+ self .paginator = next_paginator
46
+ return self .paginator
38
47
48
+ raise StopAsyncIteration
39
49
40
- class Paginator (ABC ):
50
+
51
+ class Paginator (ABC , Generic [BM ]):
41
52
"""
42
53
Pagination response object.
43
54
@@ -51,7 +62,7 @@ class Paginator(ABC):
51
62
if paginated_response.has_next_page:
52
63
paginated_response = paginated_response.next_page()
53
64
"""
54
- def __init__ (self , sinch , endpoint , result ):
65
+ def __init__ (self , sinch , endpoint , result : BM ):
55
66
self ._sinch = sinch
56
67
self .result = result
57
68
self .endpoint = endpoint
@@ -61,6 +72,16 @@ def __init__(self, sinch, endpoint, result):
61
72
def __repr__ (self ):
62
73
return "Paginated response content: " + str (self .result )
63
74
75
+ # TODO: Make content() method abstract in Parent class as we implement in the other domains:
76
+ # - Refactor pydantic models in other domains to have a content property.
77
+ def content (self ):
78
+ pass
79
+
80
+ # TODO: Make iterator() method abstract in Parent class as we implement in the other domains:
81
+ # - Refactor pydantic models in other domains to have a content property.
82
+ def iterator (self ):
83
+ pass
84
+
64
85
@abstractmethod
65
86
def auto_paging_iter (self ):
66
87
pass
@@ -121,116 +142,100 @@ async def _initialize(cls, sinch, endpoint):
121
142
return cls (sinch , endpoint , result )
122
143
123
144
124
- class TokenBasedPaginator (Paginator ):
125
- """Base paginator for token-based pagination."""
145
+ class TokenBasedPaginator (Paginator [ BM ] ):
146
+ """Base paginator for token-based pagination with explicit page navigation and metadata ."""
126
147
127
148
def __init__ (self , sinch , endpoint , yield_first_page = False , result = None ):
128
- self ._sinch = sinch
129
- self .endpoint = endpoint
130
- # Determines if the first page should be included
149
+ super ().__init__ (sinch , endpoint , result or sinch .configuration .transport .request (endpoint ))
131
150
self .yield_first_page = yield_first_page
132
- self .result = result or self ._sinch .configuration .transport .request (self .endpoint )
133
- self .has_next_page = bool (self .result .next_page_token )
134
151
135
- def _calculate_next_page (self ):
136
- self . has_next_page = bool (self .result . next_page_token )
152
+ def content (self ) -> list [ BM ] :
153
+ return getattr (self .result , "content" , [] )
137
154
138
155
def next_page (self ):
139
- """Fetches the next page and updates pagination state."""
156
+ """Returns a new paginator instance for the next page."""
157
+ if not self .has_next_page :
158
+ return None
159
+
140
160
self .endpoint .request_data .page_token = self .result .next_page_token
141
- self . result = self ._sinch .configuration .transport .request (self .endpoint )
142
- self . _calculate_next_page ()
143
- return self
161
+ next_result = self ._sinch .configuration .transport .request (self .endpoint )
162
+
163
+ return TokenBasedPaginator ( self . _sinch , self . endpoint , result = next_result )
144
164
145
165
def auto_paging_iter (self ):
146
166
"""Returns an iterator for automatic pagination."""
147
- return PageIterator (self , yield_first_page = self .yield_first_page )
148
-
149
- @classmethod
150
- def _initialize (cls , sinch , endpoint ):
151
- """Creates an instance of the paginator skipping first page."""
152
- result = sinch .configuration .transport .request (endpoint )
153
- return cls (sinch , endpoint , yield_first_page = False , result = result )
154
-
155
-
156
- class TokenBasedPaginatorNumbers (TokenBasedPaginator ):
157
- """
158
- Paginator for handling token-based pagination specifically for phone numbers.
167
+ return PageIterator (self , yield_first_page = True )
159
168
160
- This paginator is designed to iterate through phone numbers automatically or manually, fetching new pages as needed.
161
- It extends the TokenBasedPaginatorBase class and provides additional methods for number-specific pagination.
162
- """
163
-
164
- def __init__ (self , sinch , endpoint ):
165
- super ().__init__ (sinch , endpoint , yield_first_page = True )
169
+ def iterator (self ):
170
+ """Iterates over individual items across all pages."""
171
+ paginator = self
172
+ while paginator :
173
+ yield from paginator .content ()
166
174
167
- def numbers_iterator (self ):
168
- """Iterates through numbers individually, fetching new pages as needed."""
169
- while True :
170
- if self .result and self .result .active_numbers :
171
- yield from self .result .active_numbers
172
-
173
- if not self .has_next_page :
175
+ next_page_instance = paginator .next_page ()
176
+ if not next_page_instance :
174
177
break
175
-
176
- self .next_page ()
178
+ paginator = next_page_instance
177
179
178
180
def list (self ):
179
- """Returns the first page's numbers along with pagination metadata."""
181
+ """Returns structured pagination metadata along with the first page's content (sync)."""
182
+ next_page_instance = self .next_page ()
183
+ return self ._list (next_page_instance , sync = True )
180
184
185
+ def _list (self , next_page_instance , sync = True ):
186
+ """Core logic for `list()`, shared between sync and async versions."""
181
187
PagedListResponse = namedtuple (
182
188
"PagedResponse" , ["result" , "has_next_page" , "next_page_info" , "next_page" ]
183
189
)
184
190
185
- next_page_result = self ._get_next_page_result ()
191
+ next_page_info = {
192
+ "result" : self .content (),
193
+ "result.next" : (
194
+ self .content () + (next_page_instance .content () if next_page_instance else [])
195
+ ),
196
+ "has_next_page" : self .has_next_page ,
197
+ "has_next_page.next" : bool (next_page_instance and next_page_instance .has_next_page ),
198
+ }
199
+
200
+ next_page_wrapper = self ._get_next_page_wrapper (next_page_instance , sync )
186
201
187
202
return PagedListResponse (
188
- result = self .result . active_numbers ,
203
+ result = self .content () ,
189
204
has_next_page = self .has_next_page ,
190
- next_page_info = self . _build_next_pagination_info ( next_page_result ) ,
191
- next_page = self . _next_page_wrapper ()
205
+ next_page_info = next_page_info ,
206
+ next_page = next_page_wrapper
192
207
)
193
208
194
- def _get_next_page_result (self ):
195
- """Fetches the next page result."""
196
- if not self .has_next_page :
197
- return None
198
-
199
- current_state = self .result
200
- self .next_page ()
201
- next_page_result = self .result
202
- self .result = current_state
203
-
204
- return next_page_result
209
+ def _get_next_page_wrapper (self , next_page_instance , sync ):
210
+ """Returns a function for fetching the next page."""
211
+ if sync :
212
+ return lambda : next_page_instance .list () if next_page_instance else None
213
+ else :
214
+ async def async_next_page_wrapper ():
215
+ return await next_page_instance .list () if next_page_instance else None
216
+ return async_next_page_wrapper
205
217
206
- def _build_next_pagination_info (self , next_page_result ):
207
- """Constructs and returns structured pagination metadata."""
208
- return {
209
- "result" : self .result .active_numbers ,
210
- "result.next" : (
211
- self .result .active_numbers + next_page_result .active_numbers
212
- if next_page_result else self .result .active_numbers
213
- ),
214
- "has_next_page" : self .has_next_page ,
215
- "has_next_page.next" : bool (next_page_result and next_page_result .next_page_token ),
216
- }
218
+ def _calculate_next_page (self ):
219
+ self .has_next_page = bool (getattr (self .result , "next_page_token" , None ))
217
220
218
- def _next_page_wrapper (self ):
219
- """Fetches and returns the next page as a formatted PagedListResponse object."""
220
- def wrapper ():
221
- self .next_page ()
222
- return self .list ()
223
- return wrapper
221
+ @classmethod
222
+ def _initialize (cls , sinch , endpoint ):
223
+ """Creates an instance of the paginator skipping first page."""
224
+ result = sinch .configuration .transport .request (endpoint )
225
+ return cls (sinch , endpoint , yield_first_page = False , result = result )
224
226
225
227
226
228
class AsyncTokenBasedPaginator (TokenBasedPaginator ):
227
229
"""Asynchronous token-based paginator."""
228
230
229
231
async def next_page (self ):
232
+ if not self .has_next_page :
233
+ return None
234
+
230
235
self .endpoint .request_data .page_token = self .result .next_page_token
231
- self . result = await self ._sinch .configuration .transport .request (self .endpoint )
232
- self . _calculate_next_page ()
233
- return self
236
+ next_result = await self ._sinch .configuration .transport .request (self .endpoint )
237
+
238
+ return AsyncTokenBasedPaginator ( self . _sinch , self . endpoint , result = next_result )
234
239
235
240
def auto_paging_iter (self ):
236
241
return AsyncPageIterator (self )
@@ -239,3 +244,20 @@ def auto_paging_iter(self):
239
244
async def _initialize (cls , sinch , endpoint ):
240
245
result = await sinch .configuration .transport .request (endpoint )
241
246
return cls (sinch , endpoint , result = result )
247
+
248
+ async def list (self ):
249
+ """Returns structured pagination metadata"""
250
+ next_page_instance = await self .next_page ()
251
+ return self ._list (next_page_instance , sync = False )
252
+
253
+ async def iterator (self ):
254
+ """Iterates asynchronously over individual items across all pages."""
255
+ paginator = self
256
+ while paginator :
257
+ for item in paginator .content ():
258
+ yield item
259
+
260
+ next_page_instance = await paginator .next_page ()
261
+ if not next_page_instance :
262
+ break
263
+ paginator = next_page_instance
0 commit comments