from collections.abc import Callable, Generator from typing import Any, Protocol from config import app_logger as logger from src.constants import ACCOUNT_BATCH_SIZE class FetchPageReturnType[T](Protocol): items: list[T] total_count: int def fetch_all[T]( fetch_page: Callable[..., FetchPageReturnType[T]], *fetch_args: Any, limit: int = ACCOUNT_BATCH_SIZE, **fetch_kwargs: Any, ) -> Generator[T, None, None]: """ Generic offset/limit paginator. `fetch_page` must return an object with `.items` and `.total_count`. """ offset = 0 fetch_page_name = ( getattr(fetch_page, '__qualname__', None) or getattr(fetch_page, '__name__', None) or type(fetch_page).__name__ ) fetch_page_module = getattr(fetch_page, '__module__', None) log_prefix = ( f'{fetch_page_module}.{fetch_page_name}' if fetch_page_module else fetch_page_name ) logger.debug('%s started.', log_prefix) while True: batch = fetch_page(*fetch_args, limit=limit, offset=offset, **fetch_kwargs) items = batch.items or [] for item in items: yield item offset += limit logger.debug( '%s fetched %d of %d entries.', log_prefix, min(offset, batch.total_count), batch.total_count, ) if offset >= batch.total_count: break logger.debug('%s completed.', log_prefix)