Compare commits

..

3 Commits

Author SHA1 Message Date
Rodrigo Rodrigues
8303751a45
Merge 3182612069 into c5916a3b66 2026-01-22 09:56:57 +00:00
Rodrigo Rodrigues
3182612069 CP-2451 fix tests 2026-01-22 09:56:49 +00:00
Rodrigo Rodrigues
3ad6e074bf CP-2451 fix bugs, add warning to readme and improve example 2026-01-22 09:49:29 +00:00
3 changed files with 34 additions and 28 deletions

View File

@ -2,18 +2,28 @@
This package provides core functionality for interacting with the ThousandEyes API and should be installed before using any of the published SDKs. This package provides core functionality for interacting with the ThousandEyes API and should be installed before using any of the published SDKs.
Usage example for iterating paginated responses: `PaginatorIterator` is unbounded, so wrap it with `itertools.islice` to cap the number of items and avoid making unintended, potentially expensive API calls.
Pick a slice size that matches your UI or batch size so you only fetch what you plan to process:
```python ```python
from thousandeyes_sdk.core import PaginatorIterator from thousandeyes_sdk.core import Configuration, ApiClient, PaginatorIterator
from thousandeyes_sdk.dashboards.api.dashboards_api import DashboardsApi from thousandeyes_sdk.dashboards import DashboardsApi
from itertools import islice
dashboards_api = DashboardsApi() configuration = Configuration(
for widget_data in PaginatorIterator( host = "https://api.thousandeyes.com/v7",
access_token = "an_access_token",
)
def get_dashboard_widget_data():
with ApiClient(configuration) as client:
dashboards_api = DashboardsApi(client)
for item in list(islice(PaginatorIterator(
dashboards_api.get_dashboard_widget_data, dashboards_api.get_dashboard_widget_data,
lambda response: response.data.tests if response.data else [], lambda response: response.data.tests,
dashboard_id="dashboard-id", dashboard_id="a_dashboard_id",
widget_id="widget-id", widget_id="a_widget_id",
): ), 20)):
print(widget_data) print(item.test_id)
``` ```

View File

@ -60,7 +60,8 @@ class PaginatorIterator(Generic[P, R, I]):
while True: while True:
response = self._method(**params) response = self._method(**params)
for item in self._items_getter(response): items = self._items_getter(response)
for item in items if items else []:
yield item yield item
next_cursor = self._next_cursor_from_response(response) next_cursor = self._next_cursor_from_response(response)
@ -71,14 +72,10 @@ class PaginatorIterator(Generic[P, R, I]):
last_cursor = next_cursor last_cursor = next_cursor
def _next_cursor_from_response(self, response: Any) -> Optional[str]: def _next_cursor_from_response(self, response: Any) -> Optional[str]:
data = getattr(response, "data", response) links = getattr(response, "links", None)
links = getattr(data, "links", None)
if links is None: if links is None:
links = getattr(data, "_links", None) links = getattr(response, "_links", None)
if links is None and isinstance(data, Mapping):
links = data.get("_links") or data.get("links")
if links is None: if links is None:
return None return None

View File

@ -14,8 +14,7 @@ def test_iterator_uses_cursor_from_next_href():
else: else:
links = SimpleNamespace(next=None) links = SimpleNamespace(next=None)
items = ["third"] items = ["third"]
data = SimpleNamespace(links=links) return SimpleNamespace(links=links, items=items)
return SimpleNamespace(data=data, items=items)
responses = list(PaginatorIterator(method, lambda response: response.items)) responses = list(PaginatorIterator(method, lambda response: response.items))
@ -29,12 +28,12 @@ def test_iterator_reads_cursor_from_links_mapping():
def method(**params): def method(**params):
calls.append(params.copy()) calls.append(params.copy())
if params.get("pageCursor") is None: if params.get("pageCursor") is None:
data = {"_links": {"next": {"href": "https://example.com?foo=1&pageCursor=xyz"}}} links = {"next": {"href": "https://example.com?foo=1&pageCursor=xyz"}}
items = ["alpha"] items = ["alpha"]
else: else:
data = {"_links": {"next": None}} links = {"next": None}
items = ["beta"] items = ["beta"]
return SimpleNamespace(data=data, items=items) return SimpleNamespace(links=links, items=items)
responses = list(PaginatorIterator(method, lambda response: response.items, cursor_param="pageCursor")) responses = list(PaginatorIterator(method, lambda response: response.items, cursor_param="pageCursor"))
@ -48,12 +47,12 @@ def test_iterator_stops_when_no_cursor_param_present():
def method(**params): def method(**params):
calls.append(params.copy()) calls.append(params.copy())
if params.get("cursor") is None: if params.get("cursor") is None:
data = {"links": {"next": "/next/page"}} links = {"next": "/next/page"}
items = ["one"] items = ["one"]
else: else:
data = {"links": {"next": None}} links = {"next": None}
items = ["two"] items = ["two"]
return SimpleNamespace(data=data, items=items) return SimpleNamespace(links=links, items=items)
responses = list(PaginatorIterator(method, lambda response: response.items)) responses = list(PaginatorIterator(method, lambda response: response.items))
@ -66,8 +65,8 @@ def test_iterator_stops_on_repeated_cursor():
def method(**params): def method(**params):
calls.append(params.copy()) calls.append(params.copy())
data = {"links": {"next": "https://example.com?cursor=same"}} links = {"next": "https://example.com?cursor=same"}
return SimpleNamespace(data=data, items=["only"]) return SimpleNamespace(links=links, items=["only"])
responses = list(PaginatorIterator(method, lambda response: response.items, cursor="same")) responses = list(PaginatorIterator(method, lambda response: response.items, cursor="same"))