88 Optional ,
99 Tuple ,
1010)
11- from json import JSONDecodeError
12- from httpx import Response
1311from .client import AsyncSeamHttpClient , SeamHttpClient
12+ from .exceptions import SeamHttpInvalidResponseError
1413from .pagination import Pagination
1514
1615
@@ -22,15 +21,29 @@ def parse_pagination(pagination: Dict[str, Any]) -> Pagination:
2221 )
2322
2423
24+ def read_pagination (data : Any , request : Callable ) -> Pagination :
25+ """Read the pagination envelope a paginated route attaches to its result."""
26+
27+ pagination = getattr (data , "pagination" , None )
28+
29+ if not isinstance (pagination , dict ):
30+ path = getattr (request , "__seam_path__" , "this endpoint" )
31+ raise SeamHttpInvalidResponseError (
32+ path ,
33+ "pagination" ,
34+ f"got { type (pagination ).__name__ } instead of a pagination object" ,
35+ )
36+
37+ return parse_pagination (pagination )
38+
39+
2540class SeamPaginator :
2641 """
2742 Handles pagination for API list endpoints.
2843
2944 Iterates through pages of results returned by a callable function.
3045 """
3146
32- _FIRST_PAGE = "FIRST_PAGE"
33-
3447 def __init__ (
3548 self ,
3649 client : SeamHttpClient ,
@@ -48,19 +61,12 @@ def __init__(
4861 self ._request = request
4962 self .client = client
5063 self ._params = params or {}
51- self ._pagination_cache : Dict [str , Pagination ] = {}
5264
5365 def first_page (self ) -> Tuple [List [Any ], Pagination | None ]:
5466 """Fetches the first page of results."""
55- self .client .event_hooks ["response" ].append (
56- lambda response : self ._cache_pagination (response , self ._FIRST_PAGE )
57- )
5867 data = self ._request (** self ._params )
59- self .client .event_hooks ["response" ].pop ()
6068
61- pagination = self ._pagination_cache .get (self ._FIRST_PAGE )
62-
63- return data , pagination
69+ return data , read_pagination (data , self ._request )
6470
6571 def next_page (
6672 self , next_page_cursor : str , /
@@ -74,15 +80,9 @@ def next_page(
7480 "page_cursor" : next_page_cursor ,
7581 }
7682
77- self .client .event_hooks ["response" ].append (
78- lambda response : self ._cache_pagination (response , next_page_cursor )
79- )
8083 data = self ._request (** params )
81- self .client .event_hooks ["response" ].pop ()
8284
83- pagination = self ._pagination_cache .get (next_page_cursor )
84-
85- return data , pagination
85+ return data , read_pagination (data , self ._request )
8686
8787 def flatten_to_list (self ) -> List [Any ]:
8888 """Fetches all pages and returns all items as a single list."""
@@ -110,18 +110,6 @@ def flatten(self) -> Generator[Any, None, None]:
110110 if current_items :
111111 yield from current_items
112112
113- def _cache_pagination (self , response : Response , page_key : str ) -> None :
114- """Extracts pagination dict from response, creates Pagination object, and caches it."""
115- try :
116- # httpx response hooks fire before the response body is read.
117- response .read ()
118- pagination = response .json ().get ("pagination" , {})
119- except JSONDecodeError :
120- pagination = {}
121-
122- if isinstance (pagination , dict ):
123- self ._pagination_cache [page_key ] = parse_pagination (pagination )
124-
125113
126114class AsyncSeamPaginator :
127115 """
@@ -130,8 +118,6 @@ class AsyncSeamPaginator:
130118 Iterates through pages of results returned by an awaitable function.
131119 """
132120
133- _FIRST_PAGE = "FIRST_PAGE"
134-
135121 def __init__ (
136122 self ,
137123 client : AsyncSeamHttpClient ,
@@ -149,21 +135,12 @@ def __init__(
149135 self ._request = request
150136 self .client = client
151137 self ._params = params or {}
152- self ._pagination_cache : Dict [str , Pagination ] = {}
153138
154139 async def first_page (self ) -> Tuple [List [Any ], Pagination | None ]:
155140 """Fetches the first page of results."""
156-
157- async def cache_pagination (response : Response ) -> None :
158- await self ._cache_pagination (response , self ._FIRST_PAGE )
159-
160- self .client .event_hooks ["response" ].append (cache_pagination )
161141 data = await self ._request (** self ._params )
162- self .client .event_hooks ["response" ].pop ()
163-
164- pagination = self ._pagination_cache .get (self ._FIRST_PAGE )
165142
166- return data , pagination
143+ return data , read_pagination ( data , self . _request )
167144
168145 async def next_page (
169146 self , next_page_cursor : str , /
@@ -177,16 +154,9 @@ async def next_page(
177154 "page_cursor" : next_page_cursor ,
178155 }
179156
180- async def cache_pagination (response : Response ) -> None :
181- await self ._cache_pagination (response , next_page_cursor )
182-
183- self .client .event_hooks ["response" ].append (cache_pagination )
184157 data = await self ._request (** params )
185- self .client .event_hooks ["response" ].pop ()
186158
187- pagination = self ._pagination_cache .get (next_page_cursor )
188-
189- return data , pagination
159+ return data , read_pagination (data , self ._request )
190160
191161 async def flatten_to_list (self ) -> List [Any ]:
192162 """Fetches all pages and returns all items as a single list."""
@@ -217,15 +187,3 @@ async def flatten(self) -> AsyncGenerator[Any, None]:
217187 )
218188 for item in current_items or []:
219189 yield item
220-
221- async def _cache_pagination (self , response : Response , page_key : str ) -> None :
222- """Extracts pagination dict from response, creates Pagination object, and caches it."""
223- try :
224- # httpx response hooks fire before the response body is read.
225- await response .aread ()
226- pagination = response .json ().get ("pagination" , {})
227- except JSONDecodeError :
228- pagination = {}
229-
230- if isinstance (pagination , dict ):
231- self ._pagination_cache [page_key ] = parse_pagination (pagination )
0 commit comments