Adds the `Page[T]` envelope `{items, total, page, page_size}` documented in
D-016, the matching `PageQuery` for `?page=&page_size=` parsing (default 50,
max 200), and `parse_page_query()` helper for blueprints.
DTOs:
- `UserRead` / `UserCreate` / `UserUpdate` (sprint 2). `UserRead` never
exposes `local_password_hash`. `UserCreate` validates email via
pydantic-email-validator and pins password to 8..128 chars.
- `EngagementMemberRead` / `EngagementMemberCreate`. `role` is a free-form
string ≤ 40 chars (D-017), defaulting to `"member"`.
- `AuditLogEntry` for the upcoming audit viewer.
41 lines
1.1 KiB
Python
41 lines
1.1 KiB
Python
"""Generic pagination envelope (D-016).
|
|
|
|
Frontend reads `{items, total, page, page_size}`. Sprint 2 uses this on
|
|
`/users` and `/audit/log`; existing endpoints (`/engagements`) stay
|
|
non-paginated for backwards-compatibility and will migrate together in a
|
|
later opt-in (`?paginate=true` or `/api/v2/`).
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from pydantic import BaseModel, ConfigDict, Field
|
|
|
|
DEFAULT_PAGE_SIZE = 50
|
|
MAX_PAGE_SIZE = 200
|
|
|
|
|
|
class Page[T](BaseModel):
|
|
"""Paginated response envelope."""
|
|
|
|
model_config = ConfigDict(arbitrary_types_allowed=True)
|
|
|
|
items: list[T]
|
|
total: int = Field(ge=0)
|
|
page: int = Field(ge=1)
|
|
page_size: int = Field(ge=1, le=MAX_PAGE_SIZE)
|
|
|
|
|
|
class PageQuery(BaseModel):
|
|
"""Parsed `?page=` / `?page_size=` query string (always normalised)."""
|
|
|
|
page: int = Field(default=1, ge=1)
|
|
page_size: int = Field(default=DEFAULT_PAGE_SIZE, ge=1, le=MAX_PAGE_SIZE)
|
|
|
|
@property
|
|
def offset(self) -> int:
|
|
return (self.page - 1) * self.page_size
|
|
|
|
@property
|
|
def limit(self) -> int:
|
|
return self.page_size
|