Compare commits

..

No commits in common. "31826120699efb0def40cc5ff2902e78c922385f" and "abea9a244afa082e78e5ca6e46ad1f8112e91651" have entirely different histories.

3 changed files with 28 additions and 34 deletions

View File

@ -2,28 +2,18 @@
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.
`PaginatorIterator` is unbounded, so wrap it with `itertools.islice` to cap the number of items and avoid making unintended, potentially expensive API calls. Usage example for iterating paginated responses:
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 Configuration, ApiClient, PaginatorIterator from thousandeyes_sdk.core import PaginatorIterator
from thousandeyes_sdk.dashboards import DashboardsApi from thousandeyes_sdk.dashboards.api.dashboards_api import DashboardsApi
from itertools import islice
configuration = Configuration( dashboards_api = DashboardsApi()
host = "https://api.thousandeyes.com/v7", for widget_data in PaginatorIterator(
access_token = "an_access_token", dashboards_api.get_dashboard_widget_data,
) lambda response: response.data.tests if response.data else [],
dashboard_id="dashboard-id",
widget_id="widget-id",
def get_dashboard_widget_data(): ):
with ApiClient(configuration) as client: print(widget_data)
dashboards_api = DashboardsApi(client)
for item in list(islice(PaginatorIterator(
dashboards_api.get_dashboard_widget_data,
lambda response: response.data.tests,
dashboard_id="a_dashboard_id",
widget_id="a_widget_id",
), 20)):
print(item.test_id)
``` ```

View File

@ -60,8 +60,7 @@ class PaginatorIterator(Generic[P, R, I]):
while True: while True:
response = self._method(**params) response = self._method(**params)
items = self._items_getter(response) for item in 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)
@ -72,10 +71,14 @@ 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]:
links = getattr(response, "links", None) data = getattr(response, "data", response)
links = getattr(data, "links", None)
if links is None: if links is None:
links = getattr(response, "_links", None) links = getattr(data, "_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,7 +14,8 @@ def test_iterator_uses_cursor_from_next_href():
else: else:
links = SimpleNamespace(next=None) links = SimpleNamespace(next=None)
items = ["third"] items = ["third"]
return SimpleNamespace(links=links, items=items) data = SimpleNamespace(links=links)
return SimpleNamespace(data=data, items=items)
responses = list(PaginatorIterator(method, lambda response: response.items)) responses = list(PaginatorIterator(method, lambda response: response.items))
@ -28,12 +29,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:
links = {"next": {"href": "https://example.com?foo=1&pageCursor=xyz"}} data = {"_links": {"next": {"href": "https://example.com?foo=1&pageCursor=xyz"}}}
items = ["alpha"] items = ["alpha"]
else: else:
links = {"next": None} data = {"_links": {"next": None}}
items = ["beta"] items = ["beta"]
return SimpleNamespace(links=links, items=items) return SimpleNamespace(data=data, items=items)
responses = list(PaginatorIterator(method, lambda response: response.items, cursor_param="pageCursor")) responses = list(PaginatorIterator(method, lambda response: response.items, cursor_param="pageCursor"))
@ -47,12 +48,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:
links = {"next": "/next/page"} data = {"links": {"next": "/next/page"}}
items = ["one"] items = ["one"]
else: else:
links = {"next": None} data = {"links": {"next": None}}
items = ["two"] items = ["two"]
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))
@ -65,8 +66,8 @@ def test_iterator_stops_on_repeated_cursor():
def method(**params): def method(**params):
calls.append(params.copy()) calls.append(params.copy())
links = {"next": "https://example.com?cursor=same"} data = {"links": {"next": "https://example.com?cursor=same"}}
return SimpleNamespace(links=links, items=["only"]) return SimpleNamespace(data=data, items=["only"])
responses = list(PaginatorIterator(method, lambda response: response.items, cursor="same")) responses = list(PaginatorIterator(method, lambda response: response.items, cursor="same"))