Skip to content

# Schemas

Standardized Pydantic response models for consistent API responses across your FastAPI application.

## Overview

The `schemas` module provides generic response wrappers that enforce a uniform response structure. All models use `from_attributes=True` for ORM compatibility and `validate_assignment=True` for runtime type safety.

## Response models

### `Response[T]`

The most common wrapper for a single resource response.

    from fastapi_toolsets.schemas import Response

    @router.get("/users/{id}")
    async def get_user(user: User = UserDep) -> Response[UserSchema]:
        return Response(data=user, message="User retrieved")

### Paginated response models

Three classes wrap paginated list results. Pick the one that matches your endpoint's strategy:

Class | `pagination` type | `pagination_type` field | Use when
--- | --- | --- | ---
`OffsetPaginatedResponse[T]` | `OffsetPagination` | `"offset"` (fixed) | endpoint always uses offset
`CursorPaginatedResponse[T]` | `CursorPagination` | `"cursor"` (fixed) | endpoint always uses cursor
`PaginatedResponse[T]` | `OffsetPagination \| CursorPagination` | — | unified endpoint supporting both strategies

#### `OffsetPaginatedResponse[T]`

Added in `v2.3.0`

Use as the return type when the endpoint always uses `offset_paginate`. The `pagination` field is guaranteed to be an `OffsetPagination` object; the response always includes a `pagination_type: "offset"` discriminator.

    from fastapi_toolsets.schemas import OffsetPaginatedResponse

    @router.get("/users")
    async def list_users(
        page: int = 1,
        items_per_page: int = 20,
    ) -> OffsetPaginatedResponse[UserSchema]:
        return await UserCrud.offset_paginate(
            session, page=page, items_per_page=items_per_page, schema=UserSchema
        )

Response shape:

    {
      "status": "SUCCESS",
      "pagination_type": "offset",
      "data": ["..."],
      "pagination": {
        "total_count": 100,
        "page": 1,
        "items_per_page": 20,
        "has_more": true
      }
    }

#### `CursorPaginatedResponse[T]`

Added in `v2.3.0`

Use as the return type when the endpoint always uses `cursor_paginate`. The `pagination` field is guaranteed to be a `CursorPagination` object; the response always includes a `pagination_type: "cursor"` discriminator.

    from fastapi_toolsets.schemas import CursorPaginatedResponse

    @router.get("/events")
    async def list_events(
        cursor: str | None = None,
        items_per_page: int = 20,
    ) -> CursorPaginatedResponse[EventSchema]:
        return await EventCrud.cursor_paginate(
            session, cursor=cursor, items_per_page=items_per_page, schema=EventSchema
        )

Response shape:

    {
      "status": "SUCCESS",
      "pagination_type": "cursor",
      "data": ["..."],
      "pagination": {
        "next_cursor": "eyJpZCI6IDQyfQ==",
        "prev_cursor": null,
        "items_per_page": 20,
        "has_more": true
      }
    }

#### `PaginatedResponse[T]`

Return type for endpoints that support both pagination strategies via a `pagination_type` query parameter (using `paginate()`).

When used as a return annotation, `PaginatedResponse[T]` automatically expands to `Annotated[Union[CursorPaginatedResponse[T], OffsetPaginatedResponse[T]], Field(discriminator="pagination_type")]`, so FastAPI emits a proper `oneOf` + discriminator in the OpenAPI schema with no extra boilerplate:

    from fastapi_toolsets.crud import PaginationType
    from fastapi_toolsets.schemas import PaginatedResponse

    @router.get("/users")
    async def list_users(
        pagination_type: PaginationType = PaginationType.OFFSET,
        page: int = 1,
        cursor: str | None = None,
        items_per_page: int = 20,
    ) -> PaginatedResponse[UserSchema]:
        return await UserCrud.paginate(
            session,
            pagination_type=pagination_type,
            page=page,
            cursor=cursor,
            items_per_page=items_per_page,
            schema=UserSchema,
        )

#### Pagination metadata models

The optional `filter_attributes` field is populated when `facet_fields` are configured on the CRUD class (see Filter attributes). It is `None` by default and can be hidden from API responses with `response_model_exclude_none=True`.

### `ErrorResponse`

Returned automatically by the exceptions handler.

API Reference