Source code for openapi.pagination.offset

from dataclasses import dataclass
from typing import Dict, NamedTuple, Optional, Type

from multidict import MultiDict
from yarl import URL

from openapi.data.fields import Choice, integer_field, str_field
from openapi.utils import docjoin

from .pagination import (
    DEF_PAGINATION_LIMIT,
    MAX_PAGINATION_LIMIT,
    Pagination,
    PaginationVisitor,
    from_filters_and_dataclass,
)


[docs]def offsetPagination( *order_by_fields: str, default_limit: int = DEF_PAGINATION_LIMIT, max_limit: int = MAX_PAGINATION_LIMIT, ) -> Type[Pagination]: """Crate a limit/offset :class:`.Pagination` dataclass""" if len(order_by_fields) == 0: raise ValueError("orderable_fields must be specified") @dataclass class OffsetPagination(Pagination): limit: int = integer_field( min_value=1, max_value=max_limit, default=default_limit, description="Limit the number of objects returned from the endpoint", ) offset: int = integer_field( min_value=0, default=0, description=( "Number of objects to exclude. " "Use in conjunction with limit to paginate results" ), ) order_by: str = str_field( validator=Choice(order_by_fields), default=order_by_fields[0], description=( "Order results by given column (default ascending order). " f"Possible values are {docjoin(order_by_fields)}" ), ) def apply(self, visitor: PaginationVisitor) -> None: visitor.apply_offset_pagination( limit=self.limit, offset=self.offset, order_by=self.order_by ) @classmethod def create_pagination(cls, data: dict) -> "OffsetPagination": return from_filters_and_dataclass(OffsetPagination, data) def links( self, url: URL, data: list, total: Optional[int] = None ) -> Dict[str, str]: """Return links for paginated data""" return Links(url=url, query=MultiDict(url.query)).links( total, self.limit, self.offset ) return OffsetPagination
class Links(NamedTuple): url: URL query: MultiDict def first_link(self, total, limit, offset): n = self._count_part(offset, limit, 0) if n: offset -= n * limit if offset > 0: return self.link(0, min(limit, offset)) def prev_link(self, total, limit, offset): if offset: olimit = min(limit, offset) prev_offset = offset - olimit return self.link(prev_offset, olimit) def next_link(self, total, limit, offset): next_offset = offset + limit if total > next_offset: return self.link(next_offset, limit) def last_link(self, total, limit, offset): n = self._count_part(total, limit, offset) if n > 0: return self.link(offset + n * limit, limit) def link(self, offset, limit): query = self.query.copy() query.update({"offset": offset, "limit": limit}) return self.url.with_query(query) def _count_part(self, total, limit, offset): n = (total - offset) // limit # make sure we account for perfect matching if n * limit + offset == total: n -= 1 return max(0, n) def links(self, total, limit, offset): links = {} first = self.first_link(total, limit, offset) if first: links["first"] = first links["prev"] = self.prev_link(total, limit, offset) next_ = self.next_link(total, limit, offset) if next_: links["next"] = next_ links["last"] = self.last_link(total, limit, offset) return links