Source code for clash_royale.pagination
from __future__ import annotations
from collections.abc import Generator
from typing import TYPE_CHECKING, Generic, TypeVar, overload
from .models.base import CRBaseModel
from .types import ClanSearchParams, PaginationParams
if TYPE_CHECKING:
from .client import Client
ResourceType = TypeVar("ResourceType", bound="CRBaseModel")
[docs]
class PaginatedList(Generic[ResourceType]):
"""Lazy-loading paginated list that fetches pages on demand."""
[docs]
def __init__(
self,
client: Client,
endpoint: str,
model: type[ResourceType],
params: PaginationParams | ClanSearchParams | None = None,
):
self._client = client
self._endpoint = endpoint
self._model = model
# Extract client-side pagination control params
_params = params.copy() if params else {}
self._limit: int | None = _params.pop("limit", None)
self._page_size: int | None = _params.pop("page_size", None)
# Remaining params are for the API (after, before, etc.)
self._params: dict[str, str | int] = _params
self._elements: list[ResourceType] = []
self._after_cursor: str | None = None
self._has_more: bool = True
self._iter = iter(self)
def __repr__(self) -> str:
repr_size = 5
data: list[ResourceType | str] = list(self[: repr_size + 1])
if len(data) > repr_size:
data[-1] = "..."
return f"<{self.__class__.__name__} {data!r}>"
@overload
def __getitem__(self, index: int) -> ResourceType: ...
@overload
def __getitem__(self, index: slice) -> list[ResourceType]: ...
def __getitem__(self, index: int | slice) -> ResourceType | list[ResourceType]:
if isinstance(index, int):
self._fetch_to_index(index)
return self._elements[index]
if index.stop is not None:
self._fetch_to_index(index.stop)
else:
self._fetch_all()
return self._elements[index]
def __iter__(self) -> Generator[ResourceType, None, None]:
yield from self._elements
while self._has_more and (
self._limit is None or len(self._elements) < self._limit
):
yield from self._grow()
def __next__(self) -> ResourceType:
return next(self._iter)
def _could_grow(self) -> bool:
if self._limit is not None and len(self._elements) >= self._limit:
return False
return self._has_more
def _grow(self) -> list[ResourceType]:
new_elements = self._fetch_next_page()
# If limit is set, only add elements up to the limit
if self._limit is not None:
remaining = self._limit - len(self._elements)
if remaining <= 0:
return []
new_elements = new_elements[:remaining]
self._elements.extend(new_elements)
return new_elements
def _fetch_next_page(self) -> list[ResourceType]:
params = self._params.copy()
if self._after_cursor:
params["after"] = self._after_cursor
if self._page_size:
# Send page_size as "limit" to the API
params["limit"] = self._page_size
response = self._client._request("GET", self._endpoint, params=params)
# Update cursor for next page
paging = response.get("paging", {})
cursors = paging.get("cursors", {})
self._after_cursor = cursors.get("after")
self._has_more = self._after_cursor is not None
# Parse items with model
items = response.get("items", [])
return [self._model.model_validate(item) for item in items]
def _fetch_to_index(self, index: int) -> None:
while len(self._elements) <= index and self._could_grow():
self._grow()
def _fetch_all(self) -> None:
while self._could_grow():
self._grow()