Typing

Unpaginate commes natively with type hints, which are checked by mypy.

The benefits are twofold:

  • Improve the correctness of the code of the library itself;
  • Allow users of the library to benefit from well-defined type hints on the public API.

As a rule of thumbs, we follow Postel's law by being as vague as possible for the arguments of functions, and as precise as possible for the returned values.

Pagination

The pagination parameter of the decorated functions can be annotated with Pagination, or if the context is used with Pagination[C] (replacing C with the type of the context):

>>> from unpaginate import Pagination, stop_on_falsy_context, unpaginate

>>> @unpaginate(page=1)
... def get_cities0(pagination: Pagination, country: str) -> Iterable[str]:
...     return requests.post(
...         "https://api.example.org/cities",
...         json={"country": country, "page": pagination.page},
...     ).json()["items"]
>>> @unpaginate(stop=stop_on_falsy_context)
... def get_cities2(pagination: Pagination[Optional[str]], country: str) -> Iterable[str]:
...     data = requests.post(
...         "https://api.example.org/cities",
...         json={"country": country, "cursor": pagination.context},
...     ).json()
...     pagination.context = data.get("next_cursor")
...     return data["items"]

Tip

In the second example, since no context_factory is specified, the initial value of the context is None. This is why the parameter of Pagination is Optional[str] and not simply str.

This is not needed when a context_factory is specified:

@unpaginate(context_factory=str)
def foo(pagination: Pagination[str]) -> Iterable[str]:
    raise NotImplementedError

Note

Depending on the configuration of the tool you use for validating type hints, you may need to use the / syntax from PEP 570 to change the pagination parameter into a positional-only parameter, like so:

>>> @unpaginate(page=1)
... def get_cities1(pagination: Pagination, /, country: str) -> Iterable[str]:
...     ...

Stop

Annotate the two parameters of the stop callable with either Pagination or Pagination[C] (same C value for both parameters):

>>> from unpaginate import Pagination

>>> def my_stop_without_context(
...     pagination_before: Pagination,
...     pagination_after: Pagination,
... ) -> bool:
...     return pagination_before.offset * 2 > pagination_after.offset
>>> def my_stop_with_context(
...     pagination_before: Pagination[int],
...     pagination_after: Pagination[int],
... ) -> bool:
...     return pagination_before.context + 1 != pagination_after.context