Skip to content

Pagination

Tenchi provides one small limit-and-offset vocabulary that remains typed across the contract, use case, client, and OpenAPI document.

Declare a query and page

from tenchi.contracts import contract
from tenchi.pagination import Page, PageQuery


class ListTodosQuery(PageQuery):
    completed: bool | None = None


list_todos_contract = contract(
    method="GET",
    path="/todos",
    query=ListTodosQuery,
    response=Page[Todo],
)

PageQuery declares limit and offset with boundary validation. Subclass it to add filters or sorting fields owned by the operation.

Page[Todo] serializes one stable envelope:

{
  "items": [],
  "total": 0,
  "limit": 20,
  "offset": 0
}

Build the result

from tenchi.pagination import Page, page


async def list_todos(
    query: ListTodosQuery,
    context: AppContext,
) -> Page[Todo]:
    items, total = await context.todos.list_page(
        limit=query.limit,
        offset=query.offset,
        completed=query.completed,
    )
    return page(items, total=total, query=query)

page() copies the validated limit and offset into the response, keeping the repository result and public envelope aligned.

Keep pagination in the port

Memory tests may slice a list, but production repositories should apply limit, offset, and filters in the database or remote service. Return the current page plus the total matching count through the feature-owned port; the use case turns those values into the public Page.

The typed client returns Page[Todo], and OpenAPI includes both the query constraints and the generic item schema without another declaration.