Source code for runic.orm.repository.pagination
"""Pageable and Page[T] for cursor-style pagination over Repository reads."""
from __future__ import annotations
import math
from collections.abc import Iterator
[docs]
class Pageable:
"""Describes a page request: zero-based page index, page size, and optional sort.
Example::
pageable = Pageable(page=0, size=25, sort_by="name", direction="ASC")
next_page = pageable.next()
"""
def __init__(
self,
page: int = 0,
size: int = 20,
sort_by: str | None = None,
direction: str = "ASC",
) -> None:
if page < 0:
raise ValueError("page must be >= 0")
if size <= 0:
raise ValueError("size must be > 0")
self.page = page
self.size = size
self.sort_by = sort_by
self.direction = direction
@property
def offset(self) -> int:
"""Zero-based item offset for SKIP in Cypher."""
return self.page * self.size
[docs]
def next(self) -> Pageable:
"""Return a Pageable for the next page."""
return Pageable(self.page + 1, self.size, self.sort_by, self.direction)
[docs]
def previous(self) -> Pageable:
"""Return a Pageable for the previous page (clamped to 0)."""
return Pageable(max(0, self.page - 1), self.size, self.sort_by, self.direction)
[docs]
def first(self) -> Pageable:
"""Return a Pageable for the first page."""
return Pageable(0, self.size, self.sort_by, self.direction)
def __repr__(self) -> str:
return (
f"Pageable(page={self.page}, size={self.size}, "
f"sort_by={self.sort_by!r}, direction={self.direction!r})"
)
[docs]
class Page[T]:
"""A single page of results from a paginated query.
Example::
for entity in page:
print(entity.name)
print(f"Page {page.page_number} of {page.total_pages}")
"""
def __init__(
self,
items: list[T],
page_number: int,
size: int,
total_elements: int,
) -> None:
self._items = items
self.page_number = page_number
self.size = size
self.total_elements = total_elements
@property
def total_pages(self) -> int:
"""Total number of pages given ``total_elements`` and ``size``."""
if self.size <= 0:
return 0
return math.ceil(self.total_elements / self.size)
[docs]
def has_next(self) -> bool:
"""True if there is at least one more page after this one."""
return self.page_number < self.total_pages - 1
[docs]
def has_previous(self) -> bool:
"""True if this is not the first page."""
return self.page_number > 0
def __iter__(self) -> Iterator[T]:
return iter(self._items)
def __len__(self) -> int:
return len(self._items)
def __repr__(self) -> str:
return (
f"Page(page={self.page_number}, size={self.size}, "
f"total_elements={self.total_elements}, items={len(self._items)})"
)