mirror of
https://github.com/thousandeyes/thousandeyes-sdk-python.git
synced 2026-02-04 10:15:30 +00:00
Compare commits
2 Commits
b230eb0227
...
a895731873
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a895731873 | ||
|
|
abea9a244a |
@ -6,13 +6,14 @@ Usage example for iterating paginated responses:
|
|||||||
|
|
||||||
```python
|
```python
|
||||||
from thousandeyes_sdk.core import PaginatorIterator
|
from thousandeyes_sdk.core import PaginatorIterator
|
||||||
from thousandeyes_sdk.usage.api.usage_api import UsageApi
|
from thousandeyes_sdk.dashboards.api.dashboards_api import DashboardsApi
|
||||||
|
|
||||||
usage_api = UsageApi()
|
dashboards_api = DashboardsApi()
|
||||||
for page in PaginatorIterator(
|
for widget_data in PaginatorIterator(
|
||||||
usage_api.get_enterprise_agents_units_usage,
|
dashboards_api.get_dashboard_widget_data,
|
||||||
start_date="2024-01-01T00:00:00Z",
|
lambda response: response.data.tests if response.data else [],
|
||||||
end_date="2024-01-31T23:59:59Z",
|
dashboard_id="dashboard-id",
|
||||||
|
widget_id="widget-id",
|
||||||
):
|
):
|
||||||
print(page)
|
print(widget_data)
|
||||||
```
|
```
|
||||||
|
|||||||
@ -16,19 +16,21 @@
|
|||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from typing import Any, Callable, Iterator, Mapping, Optional, TypeVar, Generic
|
from collections.abc import Callable, Iterable, Iterator
|
||||||
|
from typing import Any, Mapping, Optional, TypeVar, Generic
|
||||||
from urllib.parse import parse_qs, urlparse
|
from urllib.parse import parse_qs, urlparse
|
||||||
|
|
||||||
from typing_extensions import ParamSpec
|
from typing_extensions import ParamSpec
|
||||||
|
|
||||||
P = ParamSpec("P")
|
P = ParamSpec("P")
|
||||||
R = TypeVar("R")
|
R = TypeVar("R")
|
||||||
|
I = TypeVar("I")
|
||||||
|
|
||||||
|
class PaginatorIterator(Generic[P, R, I]):
|
||||||
class PaginatorIterator(Generic[P, R]):
|
|
||||||
"""Iterate over cursor-paginated responses.
|
"""Iterate over cursor-paginated responses.
|
||||||
|
|
||||||
Calls ``method`` repeatedly, passing a cursor parameter between calls.
|
Calls ``method`` repeatedly, passing a cursor parameter between calls,
|
||||||
|
and yields items obtained from ``items_getter``.
|
||||||
The next cursor is derived from ``response.data.links`` or ``response.data._links``
|
The next cursor is derived from ``response.data.links`` or ``response.data._links``
|
||||||
(or mapping equivalents), supporting these link formats:
|
(or mapping equivalents), supporting these link formats:
|
||||||
|
|
||||||
@ -42,21 +44,24 @@ class PaginatorIterator(Generic[P, R]):
|
|||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
method: Callable[P, R],
|
method: Callable[P, R],
|
||||||
|
items_getter: Callable[[R], Iterable[I]],
|
||||||
*,
|
*,
|
||||||
cursor_param: str = "cursor",
|
cursor_param: str = "cursor",
|
||||||
**params: P.kwargs,
|
**params: P.kwargs,
|
||||||
) -> None:
|
) -> None:
|
||||||
self._method = method
|
self._method = method
|
||||||
|
self._items_getter = items_getter
|
||||||
self._cursor_param = cursor_param
|
self._cursor_param = cursor_param
|
||||||
self._params: dict[str, Any] = dict(params)
|
self._params: dict[str, Any] = dict(params)
|
||||||
|
|
||||||
def __iter__(self) -> Iterator[R]:
|
def __iter__(self) -> Iterator[I]:
|
||||||
params = dict(self._params)
|
params = dict(self._params)
|
||||||
last_cursor = params.get(self._cursor_param)
|
last_cursor = params.get(self._cursor_param)
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
response = self._method(**params)
|
response = self._method(**params)
|
||||||
yield response
|
for item in self._items_getter(response):
|
||||||
|
yield item
|
||||||
|
|
||||||
next_cursor = self._next_cursor_from_response(response)
|
next_cursor = self._next_cursor_from_response(response)
|
||||||
if not next_cursor or next_cursor == last_cursor:
|
if not next_cursor or next_cursor == last_cursor:
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
from types import SimpleNamespace
|
from types import SimpleNamespace
|
||||||
|
|
||||||
from thousandeyes_sdk.core.iterable import PaginatorIterator
|
from thousandeyes_sdk.core.pagination_iterator import PaginatorIterator
|
||||||
|
|
||||||
|
|
||||||
def test_iterator_uses_cursor_from_next_href():
|
def test_iterator_uses_cursor_from_next_href():
|
||||||
@ -10,14 +10,16 @@ def test_iterator_uses_cursor_from_next_href():
|
|||||||
calls.append(params.copy())
|
calls.append(params.copy())
|
||||||
if params.get("cursor") is None:
|
if params.get("cursor") is None:
|
||||||
links = SimpleNamespace(next="https://example.com/items?cursor=abc")
|
links = SimpleNamespace(next="https://example.com/items?cursor=abc")
|
||||||
|
items = ["first", "second"]
|
||||||
else:
|
else:
|
||||||
links = SimpleNamespace(next=None)
|
links = SimpleNamespace(next=None)
|
||||||
|
items = ["third"]
|
||||||
data = SimpleNamespace(links=links)
|
data = SimpleNamespace(links=links)
|
||||||
return SimpleNamespace(data=data)
|
return SimpleNamespace(data=data, items=items)
|
||||||
|
|
||||||
responses = list(PaginatorIterator(method))
|
responses = list(PaginatorIterator(method, lambda response: response.items))
|
||||||
|
|
||||||
assert len(responses) == 2
|
assert responses == ["first", "second", "third"]
|
||||||
assert calls == [{}, {"cursor": "abc"}]
|
assert calls == [{}, {"cursor": "abc"}]
|
||||||
|
|
||||||
|
|
||||||
@ -28,12 +30,15 @@ def test_iterator_reads_cursor_from_links_mapping():
|
|||||||
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"}}}
|
data = {"_links": {"next": {"href": "https://example.com?foo=1&pageCursor=xyz"}}}
|
||||||
|
items = ["alpha"]
|
||||||
else:
|
else:
|
||||||
data = {"_links": {"next": None}}
|
data = {"_links": {"next": None}}
|
||||||
return SimpleNamespace(data=data)
|
items = ["beta"]
|
||||||
|
return SimpleNamespace(data=data, items=items)
|
||||||
|
|
||||||
list(PaginatorIterator(method, cursor_param="pageCursor"))
|
responses = list(PaginatorIterator(method, lambda response: response.items, cursor_param="pageCursor"))
|
||||||
|
|
||||||
|
assert responses == ["alpha", "beta"]
|
||||||
assert calls == [{}, {"pageCursor": "xyz"}]
|
assert calls == [{}, {"pageCursor": "xyz"}]
|
||||||
|
|
||||||
|
|
||||||
@ -44,12 +49,15 @@ def test_iterator_stops_when_no_cursor_param_present():
|
|||||||
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"}}
|
data = {"links": {"next": "/next/page"}}
|
||||||
|
items = ["one"]
|
||||||
else:
|
else:
|
||||||
data = {"links": {"next": None}}
|
data = {"links": {"next": None}}
|
||||||
return SimpleNamespace(data=data)
|
items = ["two"]
|
||||||
|
return SimpleNamespace(data=data, items=items)
|
||||||
|
|
||||||
list(PaginatorIterator(method))
|
responses = list(PaginatorIterator(method, lambda response: response.items))
|
||||||
|
|
||||||
|
assert responses == ["one"]
|
||||||
assert calls == [{}]
|
assert calls == [{}]
|
||||||
|
|
||||||
|
|
||||||
@ -59,8 +67,9 @@ 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"}}
|
data = {"links": {"next": "https://example.com?cursor=same"}}
|
||||||
return SimpleNamespace(data=data)
|
return SimpleNamespace(data=data, items=["only"])
|
||||||
|
|
||||||
list(PaginatorIterator(method, cursor="same"))
|
responses = list(PaginatorIterator(method, lambda response: response.items, cursor="same"))
|
||||||
|
|
||||||
|
assert responses == ["only"]
|
||||||
assert calls == [{"cursor": "same"}]
|
assert calls == [{"cursor": "same"}]
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user