Validation

django-modern-rest has several layers of import-time and runtime validation. We try to do everything that we can during import-time, so it won’t affect your requests and responses.

However, we have to validate requests and responses during the runtime. Responses validation can be turned off in production for speed.

What do we validate and how?

Settings validation

We start with settings validation. We only validate settings once per application, we do it when the first Controller is created.

We also validate our own default values to be correct.

See SettingsValidator for the API.

Endpoint validation

Next, when controller is being created, we run Endpoint validation.

Here we can detect all kinds of problems with how endpoints are defined:

See EndpointMetadataBuilder and EndpointMetadataBuilder for the API.

HttpSpec validation

You can customize the strictness of HTTP Spec validation with overriding disabled HttpSpec options per-endpoint, per-controller, and globally.

Warning

We don’t recommend overriding any of these settings by default. It only makes sense to change, when implementing some old legacy API the “same” way as it used to be.

And only when you need this for a very specific reason.

Run result

$ curl http://127.0.0.1:8000/api/job/ -D - -X POST
HTTP/1.1 204 No Content
date: Tue, 26 May 2026 19:09:25 GMT
server: uvicorn
Content-Type: application/json
X-Frame-Options: DENY
Vary: Accept-Language
Content-Language: en
Content-Length: 0
X-Content-Type-Options: nosniff
Referrer-Policy: same-origin
Cross-Origin-Opener-Policy: same-origin

Controller validation

The last step is the final Controller validation which has everything ready:

Here we validate:

  • That all Controller classes have unique methods

  • That all endpoints are either sync or async

  • All per-controller and per-endpoint error handling

See ControllerValidator for the API.

Response validation

The last step is to validate the response when returning it from the endpoint in runtime. We need this to make sure that API responses always match response schemas.

It can be turned off.

See ResponseValidator for the API.

API Reference

Settings

class dmr.validation.settings.SettingsValidator(*, serializer: type[BaseSerializer])[source]

Validates defined settings once.

__call__() None[source]

Collect and validate settings.

Endpoint

class dmr.validation.endpoint_metadata.EndpointMetadataBuilder(*, payload: ValidateEndpointPayload | ModifyEndpointPayload | None, controller_cls: type[Controller[BaseSerializer]], func: Callable[[...], Any], metadata_cls: type[EndpointMetadata], response_modification_cls: type[ResponseModification], component_parsers: list[tuple[ComponentParser, Any, tuple[Any, ...]]], type_annotations: dict[str, Any])[source]

Validate the metadata definition.

It is done during import-time only once, so it can be not blazing fast. It is better to be precise here than to be fast.

Here we only do structure and required validation. All semantic validation will be performed later on.

Metadata will NOT be considered ready after running this process.

__call__() EndpointMetadata[source]

Do the validation.

class dmr.validation.endpoint_metadata.EndpointMetadataValidator(*, metadata: EndpointMetadata)[source]

Builds responses for the endpoint metadata.

Runs semantic validation.

Metadata will be considered ready after running this process.

__call__(func: Callable[[...], Any], payload: ValidateEndpointPayload | ModifyEndpointPayload | None, *, controller_cls: type[Controller[BaseSerializer]]) None[source]

Collect and validate all responses.

response_list_validator_cls

alias of _ResponseListValidator

Controller

class dmr.validation.controller.ControllerValidator[source]

Validates that controller is created correctly.

__call__(controller: type[Controller[BaseSerializer]]) bool | None[source]

Run the validation.

Response

class dmr.validation.response.ResponseValidator(metadata: EndpointMetadata, serializer: type[BaseSerializer])[source]

Response validator.

Can validate responses that return raw data as well as real HttpResponse that are returned from endpoints.

validate_modification(endpoint: Endpoint, controller: Controller[BaseSerializer], structured: Any) ValidatedModification[source]

Validate structured data before dumping it to json.

validate_response(endpoint: Endpoint, controller: Controller[BaseSerializer], response: _ResponseT) _ResponseT[source]

Validate response based on provided schema.

final class dmr.validation.response.ValidatedModification(*, raw_data: Any, status_code: HTTPStatus, headers: dict[str, str], cookies: Mapping[str, NewCookie] | None, renderer: Renderer)[source]

Combines all validated data together.