Skip to content

Commit b00ea9c

Browse files
Handle non-string cursors in pagination
1 parent 4e1d307 commit b00ea9c

File tree

1 file changed

+25
-5
lines changed

1 file changed

+25
-5
lines changed

src/api_to_dataframe/models/pagination.py

Lines changed: 25 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,10 @@
44

55
from dataclasses import dataclass
66
from enum import Enum
7-
from typing import Any, Callable, Dict, Iterator, List, Optional
7+
from typing import Any, Callable, Dict, Iterator, List, Optional, Union
8+
9+
10+
JSONValue = Union[None, bool, int, float, str, List["JSONValue"], Dict[str, "JSONValue"]]
811

912

1013
class PaginationStrategy(str, Enum):
@@ -29,7 +32,7 @@ class PaginationStep:
2932

3033
payload: Any
3134
params: Dict[str, Any]
32-
cursor: Optional[str] = None
35+
cursor: Optional[JSONValue] = None
3336

3437

3538
@dataclass
@@ -108,7 +111,7 @@ def cursor_iterator(
108111
fetch_page: Callable[[Dict[str, Any]], Any],
109112
*,
110113
cursor_param: str = "cursor",
111-
initial_cursor: Optional[str] = None,
114+
initial_cursor: Optional[JSONValue] = None,
112115
next_cursor_key: str = "next_cursor",
113116
max_pages: Optional[int] = None,
114117
results_key: Optional[str] = None,
@@ -182,11 +185,28 @@ def _extract_items(page: Any, results_key: Optional[str]) -> Optional[List[Any]]
182185
return None
183186

184187

185-
def _extract_next_cursor(page: Any, next_cursor_key: str) -> Optional[str]:
188+
def _extract_next_cursor(page: Any, next_cursor_key: str) -> Optional[JSONValue]:
186189
"""Extract the cursor value from a response payload."""
187190

188191
if isinstance(page, dict):
189192
cursor = page.get(next_cursor_key)
190-
if isinstance(cursor, str) or cursor is None:
193+
if cursor is None:
194+
return None
195+
if _is_json_serializable(cursor):
191196
return cursor
192197
return None
198+
199+
200+
def _is_json_serializable(value: Any) -> bool:
201+
"""Check whether a value can be represented as JSON."""
202+
203+
if value is None or isinstance(value, (str, int, float, bool)):
204+
return True
205+
206+
if isinstance(value, list):
207+
return all(_is_json_serializable(item) for item in value)
208+
209+
if isinstance(value, dict):
210+
return all(isinstance(key, str) and _is_json_serializable(item) for key, item in value.items())
211+
212+
return False

0 commit comments

Comments
 (0)