Source code for psnawp_api.models.listing.pagination_iterator

"""Provides the PaginationIterator class."""

from __future__ import annotations

from abc import abstractmethod
from collections.abc import Iterator
from dataclasses import dataclass
from typing import TYPE_CHECKING, Generic, TypeVar

if TYPE_CHECKING:
    from psnawp_api.core import Authenticator

T = TypeVar("T")


[docs] class PaginationIterator(Iterator[T], Generic[T]): """An iterator for paginated API endpoints. This class simplifies pagination by handling iteration over API responses. Subclasses only need to implement the :py:meth:`~PaginationIterator.fetch_next_page` method, while this class manages the iteration logic. :var Authenticator authenticator: An instance of :py:class:`~psnawp_api.core.authenticator.Authenticator` used to authenticate and make HTTPS requests. :var str _url: URL for the paginated endpoint. :var PaginationArguments _pagination_args: Pagination-specific arguments, such as page size and limit, passed to the endpoint. :var Iterator[T] | None _per_page_iterator: A generator that iterates over items in the fetched page. :var bool _has_next: Indicates whether more pages are available. Is updated when :py:meth:`fetch_next_page` is called. :var int _total_item_count: The total number of items available in the paginated endpoint. .. warning:: This class is not meant to be used directly. """
[docs] def __init__( self, *, authenticator: Authenticator, url: str, pagination_args: PaginationArguments, ) -> None: """Initialize the PaginationIterator instance. :param authenticator: An instance of Authenticator for making API requests. :param url: The URL of the endpoint. :param pagination_args: Pagination-related arguments, such as page size and limit. """ self.authenticator = authenticator self._url = url self._pagination_args = pagination_args self._per_page_iterator: Iterator[T] | None = None self._has_next = False self._total_item_count = 0
def __iter__(self) -> PaginationIterator[T]: """Return the iterator object. :returns: The iterator object. """ return self def __next__(self) -> T: """Return the next item in iterator. When we exhaust the iterator, fetch the next page from API until end page is reached. """ if self._per_page_iterator is None: self._per_page_iterator = self.fetch_next_page() try: return self._per_page_iterator.__next__() except StopIteration: # If all items on single page have been yielded # If we run out of pages if not self._has_next: raise StopIteration from None # If limit is reached if self._pagination_args.total_limit is not None and self._pagination_args.is_limit_reached(): raise StopIteration from None # If there are pages remaining and limit is not reached self._per_page_iterator = self.fetch_next_page() return self.__next__() def __len__(self) -> int: """Return the total items that can be fetched from endpoint.""" return self._total_item_count
[docs] @abstractmethod def fetch_next_page(self) -> Iterator[T]: """Fetch the next page of items from the API. .. note:: The implementation of this methods are also responsible for incrementing the offset using :py:meth:`~PaginationArguments.increment_offset()`, setting the :py:attr:`~PaginationIterator._total_item_count`, and updating the :py:attr:`~PaginationIterator._has_next`. """ raise NotImplementedError( "Subclasses must implement the fetch_next_page method", )
[docs] def set_offset(self, offset: int) -> None: """Set the offset parameter for the API request. :param offset: The offset value to set. """ self._pagination_args.offset = offset
[docs] def set_page_size(self, page_size: int) -> None: """Set the page size (limit) parameter for the API request. :param page_size: The page size value to set. """ self._pagination_args.page_size = page_size
[docs] @dataclass class PaginationArguments: """Class representing the arguments PlayStation API needs for paginating over list items. Used by the implementations of :py:class:`~PaginationIterator`. :var int | None total_limit: The maximum number of items to retrieve across all pages. If ``None``, there is no limit. :var int page_size: The number of items to fetch per page. :var int offset: The starting index for fetching items. """ total_limit: int | None page_size: int offset: int
[docs] def get_params_dict(self) -> dict[str, int]: """Converts the object into serializable dict that can be passed as HTTPs request param. :returns: dict containing pagination params """ return {"limit": self.adjusted_page_size, "offset": self.offset}
[docs] def increment_offset(self) -> None: """Helper method to increment the offset class member.""" self.offset += 1
[docs] def is_limit_reached(self) -> bool: """Helper method to determine if we have reached end of pagination.""" return self.offset == self.total_limit
@property def adjusted_page_size(self) -> int: """Calculates the adjusted page size based on the total limit. If total_limit is None, returns the original page_size. If total_limit is less than page_size, returns total_limit. """ if self.total_limit is None: return self.page_size return min(self.page_size, self.total_limit - self.offset)