Skip to content

Models

Shared Pydantic model base classes, the RFC 9457 ErrorDetails model, response-header schemas and the Server-Sent Event types. Import from fastapi_views.models.

Exports

fastapi_views.models re-exports:

Name Kind
BaseSchema base model
CamelCaseSchema, IdSchema, CreatedUpdatedSchema, IdCreatedUpdatedSchema common models
ErrorDetails, ErrorDetailsType, const_type, create_error_model error model + helpers
ResponseHeaders OpenAPI response-header schema
BaseServerSentEvent, IdBaseServerSentEvent, AnyServerSentEvent SSE event models

Two more model modules are not re-exported and must be imported from their submodule:

  • fastapi_views.models.base.OpenAPIBase — the self-documenting schema base
  • fastapi_views.models.jsonpatchJsonPatchModel, JsonPatch, PatchOperation, apply() (see JSON Patch)
  • fastapi_views.models.streamingResponseStarted / ResponseResult / ResponseError / ResponseCancelled / ResponseFinished and the ResponseEvent union used for streaming responses (see Server Side Events)

Base schemas

Class Description
BaseSchema Pydantic BaseModel with use_enum_values, populate_by_name, and from_attributes enabled
OpenAPIBase BaseSchema that can render itself as OpenAPI content, keyed by its __content_type__
CamelCaseSchema BaseSchema with alias_generator = to_camel for camelCase JSON keys
IdSchema BaseSchema with a UUID id field
CreatedUpdatedSchema BaseSchema with created_at and updated_at datetime fields
IdCreatedUpdatedSchema Combines IdSchema and CreatedUpdatedSchema

OpenAPIBase

OpenAPIBase declares the media type a schema is documented and served under, and turns itself into an OpenAPI response entry. It is the base of ErrorDetails, ResponseHeaders and the SSE event models.

Member Description
__content_type__ media type used when rendering OpenAPI content (default application/json)
get_openapi_schema(title=None) JSON schema in serialization mode, with $refs pointing at #/components/schemas/{model} and nested models in $defs; title overrides the schema title
get_openapi_content(title=None) {__content_type__: {"schema": ...}}, ready to drop into a route's responses

$defs produced this way are relocated into the application's components/schemas by custom_openapi, which configure_app installs, so the references stay resolvable.

Error model

ErrorDetails is the base model for all RFC 9457 problem-details responses; its __content_type__ is application/problem+json.

Field Type Default
type Url \| Literal["about:blank"] "about:blank"
title str required
status int required
detail str required
instance str \| None None
correlation_id str \| None declared only when opentelemetry-instrumentation-fastapi is importable; default_factory=get_correlation_id, i.e. the active trace id at construction time
errors list[Any] []

ErrorDetails.new(detail, **kwargs) is a convenience constructor that takes detail positionally. ErrorDetailsType is the alias type[ErrorDetails].

correlation_id is omitted, not null

When the field exists but no OpenTelemetry span is active, get_correlation_id() returns None and the key is dropped from the serialized payload rather than rendered as "correlation_id": null. ErrorDetails overrides model_dump and model_dump_json to add "correlation_id" to exclude in that case, merging with whatever exclude the caller passed (set, dict or None all work). The field is still advertised in the OpenAPI schema, so clients can rely on it being documented while treating it as optional.

>>> details = ErrorDetails(title="Test", status=400, detail="detail")
>>> details.correlation_id is None
True
>>> "correlation_id" in details.model_dump()
False
>>> details.model_dump(exclude={"instance"}).keys()          # user exclude preserved
dict_keys(['type', 'title', 'status', 'detail', 'errors'])

Without the OpenTelemetry extra installed the field does not exist at all, and neither the body nor the schema mentions it.

Two helpers build error models dynamically — they are what APIError subclassing uses internally:

  • const_type(value, description=None, **kwargs) returns a (Literal[value], Field(value, ...)) tuple, i.e. a constant field definition for create_model.
  • create_error_model(status, type="about:blank", name=None, title=None, detail=None, **kwargs) builds an ErrorDetails subclass whose title, status and type are constants. title defaults to the HTTPStatus phrase, name to that phrase without spaces, and detail to the HTTPStatus description — or to the phrase when that description is empty, so the default detail is never an empty string. Extra keyword arguments are passed to create_model as field definitions; __base__ selects a different ErrorDetails subclass to inherit from.
from fastapi_views.models import create_error_model

NotFoundModel = create_error_model(404)                     # name "NotFound", title "Not Found"
Custom = create_error_model(400, name="QuotaExceeded", title="Quota Exceeded")

create_error_model(422)().detail                            # "Unprocessable Entity", not ""

Response headers

ResponseHeaders is a schema whose fields describe HTTP response headers. get_openapi_headers() renders it as a mapping of OpenAPI Header Objects: description is lifted to the top level, the remaining JSON schema is nested under schema, required fields get required: true, and nullable unions (X | None) are collapsed to X since a response header is never null.

from pydantic import Field

from fastapi_views.models import ResponseHeaders


class LocationHeaders(ResponseHeaders):
    location: str = Field(description="URL of the created resource")
    x_request_id: str | None = None
>>> LocationHeaders.get_openapi_headers()
{'location': {'description': 'URL of the created resource',
              'required': True,
              'schema': {'type': 'string'}},
 'x_request_id': {'schema': {'type': 'string'}}}

Note that the field name is used verbatim as the header name, so declare headers exactly as they should appear.

Three places consume a ResponseHeaders subclass:

Where How
Route decorators @get(...), @post(...), @route(...), @action(...) accept response_headers= — see Decorators
ViewRouter ViewRouter(prefix="/items", response_headers=...) documents them on every route it registers
Views override get_response_headers(action) to return the class per action; the headers are documented on that action's success status code

Both CacheHeaders (from the caching view mixin) and ConditionalHeaders (ETag/Last-Modified support) are ResponseHeaders subclasses.

Server-Sent Events

Class Fields
BaseServerSentEvent retry: int \| None; __content_type__ = "text/event-stream"
IdBaseServerSentEvent adds id: UUID (defaults to uuid4())
AnyServerSentEvent adds id: str (random UUID string), event: str, data: Any

Subclass BaseServerSentEvent (or IdBaseServerSentEvent) with a literal event and a typed data field for a strongly typed stream. get_openapi_schema() — inherited from OpenAPIBase — is what ServerSentEventsAPIView uses when registering the route. See Server Side Events.


ErrorDetails

Bases: OpenAPIBase

Base Model for https://www.rfc-editor.org/rfc/rfc9457.html

Source code in fastapi_views/models/errors.py
class ErrorDetails(OpenAPIBase):
    """Base Model for https://www.rfc-editor.org/rfc/rfc9457.html"""

    __content_type__ = "application/problem+json"

    @classmethod
    def new(cls: type[Self], detail: str, **kwargs: Any) -> Self:
        return cls(detail=detail, **kwargs)

    type: Url | Literal["about:blank"] = Field(
        "about:blank",
        description="Error type",
    )
    title: str = Field(description="Error title")
    status: int = Field(description="Error status")
    detail: str = Field(description="Error detail")
    instance: str | None = Field(None, description="Requested instance")

    if OPENTELEMETRY_INSTALLED:
        correlation_id: str | None = Field(
            default_factory=get_correlation_id,
            description="Request correlation identifier",
        )

        def _drop_empty_correlation_id(self, kwargs: dict[str, Any]) -> dict[str, Any]:
            if self.correlation_id is not None:
                return kwargs
            exclude = kwargs.get("exclude")
            if exclude is None:
                kwargs["exclude"] = {"correlation_id"}
            elif isinstance(exclude, dict):
                kwargs["exclude"] = {**exclude, "correlation_id": True}
            else:
                kwargs["exclude"] = {*exclude, "correlation_id"}
            return kwargs

        def model_dump(self, **kwargs: Any) -> dict[str, Any]:
            return super().model_dump(**self._drop_empty_correlation_id(kwargs))

        def model_dump_json(self, **kwargs: Any) -> str:
            return super().model_dump_json(**self._drop_empty_correlation_id(kwargs))

    errors: list[Any] = Field([], description="List of any additional errors")

ResponseHeaders

Bases: OpenAPIBase

Class used to specify OpenAPI for response headers.

get_openapi_headers renders each field as an OpenAPI Header Object <https://spec.openapis.org/oas/v3.1.0#header-object>_: description is lifted to the top level, the remaining JSON schema is nested under schema, and required fields are flagged with required: true. Referenced models travel in $defs, relocated to the application components by custom_openapi.

Source code in fastapi_views/models/headers.py
class ResponseHeaders(OpenAPIBase):
    """Class used to specify OpenAPI for response headers.

    `get_openapi_headers` renders each field as an OpenAPI `Header Object
    <https://spec.openapis.org/oas/v3.1.0#header-object>`_: ``description`` is
    lifted to the top level, the remaining JSON schema is nested under
    ``schema``, and required fields are flagged with ``required: true``.
    Referenced models travel in ``$defs``, relocated to the application
    components by ``custom_openapi``.
    """

    @classmethod
    def get_openapi_headers(cls) -> dict[str, Any]:
        base = cls.get_openapi_schema()
        defs = base.get("$defs", {})
        required = base.get("required", [])
        headers: dict[str, Any] = {}
        for name, prop in base.get("properties", {}).items():
            header: dict[str, Any] = {}
            description = prop.get("description")
            if description is not None:
                header["description"] = description
            if name in required:
                header["required"] = True
            schema = _simplify_header_schema(prop)
            if defs and _contains_refs(schema):
                schema["$defs"] = defs
            header["schema"] = schema
            headers[name] = header
        return headers

OpenAPIBase

Bases: BaseSchema

Schema which can render itself as an OpenAPI schema.

__content_type__ declares the media type under which the schema is documented (and served). Nested models are referenced via #/components/schemas/ and shipped in the $defs key, which custom_openapi merges into the application components.

Source code in fastapi_views/models/base.py
class OpenAPIBase(BaseSchema):
    """Schema which can render itself as an OpenAPI schema.

    `__content_type__` declares the media type under which the schema
    is documented (and served). Nested models are referenced via
    `#/components/schemas/` and shipped in the `$defs` key, which
    `custom_openapi` merges into the application components.
    """

    __content_type__: ClassVar[str] = "application/json"

    @classmethod
    def get_openapi_schema(cls, title: str | None = None) -> dict[str, Any]:
        schema_dump = cls.model_json_schema(
            ref_template="#/components/schemas/{model}",
            mode="serialization",
        )
        if title:
            schema_dump["title"] = title
        return schema_dump

    @classmethod
    def get_openapi_content(cls, title: str | None = None) -> dict[str, Any]:
        """Render the schema as OpenAPI response content keyed by `__content_type__`."""
        return {cls.__content_type__: {"schema": cls.get_openapi_schema(title)}}

get_openapi_content(title: str | None = None) -> dict[str, Any] classmethod

Render the schema as OpenAPI response content keyed by __content_type__.

Source code in fastapi_views/models/base.py
@classmethod
def get_openapi_content(cls, title: str | None = None) -> dict[str, Any]:
    """Render the schema as OpenAPI response content keyed by `__content_type__`."""
    return {cls.__content_type__: {"schema": cls.get_openapi_schema(title)}}