Public API¶
Controller¶
- class dmr.controller.Controller(**kwargs)[source]¶
Bases:
Generic[_SerializerT_co],ViewDefines API views as controllers.
Controller is a
django.views.generic.base.Viewsubclass that should be used as a base for all REST endpoints.- endpoint_cls¶
Class to create endpoints with.
- Type:
ClassVar[type[dmr.endpoint.Endpoint]]
- serializer¶
Serializer that is passed via type parameters. The main goal of the serializer is to serialize object to json and deserialize them from json. You can’t change the serializer simply by modifying the attribute in the controller class. Because it is already passed to many other places. To customize it: create a new class, subclass
BaseSerializer, and pass the new type as a type argument to the controller.- Type:
ClassVar[type[dmr.serializer.BaseSerializer]]
- settings_validator_cls¶
Runs settings validation once the first controller is created.
- Type:
ClassVar[type[dmr.validation.settings.SettingsValidator]]
- no_validate_http_spec¶
Set of http spec validation checks that we disable for this class.
- Type:
ClassVar[collections.abc.Set[dmr.settings.HttpSpec] | None]
- validate_responses¶
Boolean whether or not validating responses. Works in runtime, can be disabled for better performance.
- Type:
ClassVar[bool | None]
- semantic_responses¶
Should semantic responses be collected from different providers for all endpoints in this class.
- Type:
ClassVar[bool | None]
- exclude_semantic_responses¶
Set of semantic responses that user wants to disable.
- Type:
ClassVar[collections.abc.Set[http.HTTPStatus] | None]
- validate_events¶
Should this endpoint validate events? If not set, defaults to the
validate_responsesvalue. This value only matters if the response will be a streaming response that supports event validation.- Type:
ClassVar[bool | None]
- responses¶
List of responses schemas that this controller can return. Also customizable in endpoints and globally with
'responses'key in the settings.- Type:
ClassVar[collections.abc.Sequence[dmr.metadata.ResponseSpec]]
- allowed_http_methods¶
Set of names to be treated as names for endpoints. Does not include
options, but includesmeta.- Type:
ClassVar[collections.abc.Set[str]]
- parsers¶
Sequence of parsers to be used for this controller to parse incoming request’s body. All instances must be of subtypes of
Parser.- Type:
ClassVar[collections.abc.Sequence[dmr.parsers.Parser]]
- renderers¶
Sequence of renderers to be used for this controller to render response’s body. All instances must be of subtypes of
Renderer.- Type:
ClassVar[collections.abc.Sequence[dmr.renderers.Renderer]]
- auth¶
Sequence of auth instances to be used for this controller. Sync controllers must use instances of
dmr.security.SyncAuth. Async controllers must use instances ofdmr.security.AsyncAuth. Set it toNoneto disable auth of this controller.- Type:
ClassVar[collections.abc.Sequence[dmr.security.base.SyncAuth] | collections.abc.Sequence[dmr.security.base.AsyncAuth] | None]
- error_model¶
Schema type that represents and validates common error responses.
- Type:
ClassVar[Any]
- is_abstract¶
Whether or not this controller is abstract. We consider controller “abstract” when it does not have exact serializer type.
- Type:
ClassVar[bool]
- controller_validator_cls¶
Runs full controller validation on definition.
- Type:
ClassVar[type[dmr.validation.controller.ControllerValidator]]
- annotations_context¶
Inference context to call
typing.get_type_hints()for this controller.- Type:
ClassVar[dmr.types.AnnotationsContext]
- api_endpoints¶
Dictionary of HTTPMethod name to controller instance.
- Type:
ClassVar[collections.abc.Mapping[str, dmr.endpoint.Endpoint]]
- csrf_exempt¶
Should this controller be exempted from the CSRF check? Is
Trueby default.- Type:
ClassVar[bool]
- servers¶
An alternative servers array to service this path item.
- Type:
ClassVar[collections.abc.Sequence[dmr.openapi.objects.server.Server] | None]
- request¶
Current
HttpRequestinstance.
- classmethod as_view(**initkwargs: Any) Callable[[...], HttpResponseBase][source]¶
Returns a view function for the class-based view.
This override applies CSRF exemption to the view. Session-based authentication will still be explicitly validated for CSRF, while all other authentication methods will be CSRF-exempt.
- dispatch(request: HttpRequest, *args: Any, **kwargs: Any) HttpResponseBase[source]¶
Find an endpoint that serves this HTTP method and call it.
Return 405 if this method is not allowed.
- format_error(error: str | Exception, *, loc: str | list[str | int] | None = None, error_type: str | ErrorType | None = None) Any[source]¶
Convert error to the common format.
- Parameters:
error – A serialization exception like a validation error.
loc – Location where this error happened. Like
"headers", or"field_name", or["parsed_headers", "header_name"].error_type – Optional type of the error for extra metadata.
- Returns:
Simple python object - exception converted to a common format.
- classmethod get_path_item(path: str, pattern: URLPattern, context: OpenAPIContext) PathItem[source]¶
Generate OpenAPI spec for path items.
- async handle_async_error(endpoint: Endpoint, controller: Controller[_SerializerT_co], exc: Exception) HttpResponse[source]¶
Return error response if possible. Async case.
Override this method to add custom error handling for async execution. By default - does nothing, only re-raises the passed error. Won’t be called when using sync endpoints.
- handle_error(endpoint: Endpoint, controller: Controller[_SerializerT_co], exc: Exception) HttpResponse[source]¶
Return error response if possible. Sync case.
Override this method to add custom error handling for sync execution. By default - does nothing, only re-raises the passed error. Won’t be called when using async endpoints.
- handle_method_not_allowed(method: str) HttpResponse[source]¶
Return error response for 405 response code.
It is special in way that we don’t have an endpoint associated with it.
- http_method_not_allowed(request: HttpRequest, *args: Any, **kwargs: Any) HttpResponse[source]¶
Do not use, use
handle_method_not_allowed()instead.View.http_method_not_allowedraises an error in a wrong format.
- options(request: HttpRequest, *args: Any, **kwargs: Any) HttpResponse[source]¶
Do not use, define your own meta method instead.
Django’s View.options has incompatible signature with
django-modern-rest. It would be a typing error to define something like:Warning
Don’t do this!
>>> from http import HTTPStatus >>> from dmr import Controller, validate >>> from dmr.plugins.pydantic import ( ... PydanticSerializer, ... ) >>> class MyController(Controller[PydanticSerializer]): ... @validate( ... ResponseSpec( ... None, ... status_code=HTTPStatus.NO_CONTENT, ... ), ... ) ... def options(self) -> HttpResponse: # <- typing problem ... ...
That’s why instead of
optionsyou should define our ownmetamethod:>>> class MyController(Controller[PydanticSerializer]): ... @validate( ... ResponseSpec( ... None, ... status_code=HTTPStatus.NO_CONTENT, ... ), ... ) ... def meta(self) -> HttpResponse: ... allow = ','.join( ... method.upper() ... for method in self.allowed_http_methods ... ) ... return self.to_response( ... None, ... status_code=HTTPStatus.NO_CONTENT, ... headers={'Allow': allow}, ... )
Note
By default
metamethod is not provided for you. If you want to supportOPTIONShttp method with the default implementation, use:>>> from dmr.options_mixins import MetaMixin >>> class ControllerWithMeta( ... MetaMixin, ... Controller[PydanticSerializer], ... ): ...
- setup(request: HttpRequest, *args: Any, **kwargs: Any) None[source]¶
Set request context.
Unlike
setup()does not setheadmethod automatically.Thread safety: there’s only one controller instance per request.
- to_error(raw_data: Any, *, status_code: HTTPStatus, headers: Mapping[str, str] | None = None, cookies: Mapping[str, NewCookie] | None = None, renderer: Renderer | None = None) HttpResponse[source]¶
Helpful method to convert API error parts into an actual error.
Always requires the error code to be passed.
Should be always used instead of using raw
django.http.HttpResponseobjects. Does the usual validation, no “second validation” problem exists.
- to_response(raw_data: Any, *, headers: Mapping[str, str] | None = None, cookies: Mapping[str, NewCookie] | None = None, status_code: HTTPStatus | None = None, renderer: Renderer | None = None) HttpResponse[source]¶
Helpful method to convert response parts into an actual response.
Should be always used instead of using raw
django.http.HttpResponseobjects. Has better serialization speed and semantics than manual. Does the usual validation, no “second validation” problem exists.
Endpoint¶
- class dmr.endpoint.Endpoint(func: Callable[[...], Any], *, controller_cls: type[Controller[BaseSerializer]])[source]¶
Represents the single API endpoint.
Is built during the import time. In the runtime only does response validate, which can be disabled.
- __call__(controller: Controller[BaseSerializer], *args: Any, **kwargs: Any) HttpResponseBase[source]¶
Run the endpoint and return the response.
- get_operation_id(path: str, controller_name: str, serializer: type[BaseSerializer], context: OpenAPIContext) str[source]¶
Customize how OperationId is generated for the OpenAPI.
- get_schema(path: str, pattern: URLPattern, controller_name: str, serializer: type[BaseSerializer], context: OpenAPIContext) Operation[source]¶
Build an OpenAPI Operation from an endpoint.
- async handle_async_error(controller: Controller[BaseSerializer], exc: Exception) HttpResponse[source]¶
Return error response if possible.
Override this method to add custom async error handling.
- handle_error(controller: Controller[BaseSerializer], exc: Exception) HttpResponseBase[source]¶
Return error response if possible.
Override this method to add custom error handling.
- metadata_builder_cls¶
alias of
EndpointMetadataBuilder
- metadata_cls¶
alias of
EndpointMetadata
- metadata_validator_cls¶
alias of
EndpointMetadataValidator
- request_negotiator_cls¶
alias of
RequestNegotiator
- response_modification_cls¶
alias of
ResponseModification
- response_negotiator_cls¶
alias of
ResponseNegotiator
- response_validator_cls¶
alias of
ResponseValidator
- serializer_context_cls¶
alias of
SerializerContext
- class dmr.metadata.EndpointMetadata(*, endpoint_name: str, type_annotations: dict[str, Any], responses: dict[HTTPStatus, ResponseSpec], validate_responses: bool | None, method: str, modification: ResponseModification | None, error_handler: SyncErrorHandler | AsyncErrorHandler | None, component_parsers: list[tuple[ComponentParser, Any, tuple[Any, ...]]], parsers: dict[str, Parser], renderers: dict[str, Renderer], auth: list[SyncAuth | AsyncAuth] | None, no_validate_http_spec: frozenset[HttpSpec], allowed_http_methods: frozenset[str], semantic_responses: bool, exclude_semantic_responses: frozenset[HTTPStatus], validate_events: bool, summary: str | None = None, description: str | None = None, tags: list[str] | None = None, operation_id: str | None = None, deprecated: bool = False, external_docs: ExternalDocumentation | None = None, callbacks: dict[str, Callback | Reference] | None = None, servers: list[Server] | None = None)[source]¶
Base class for common endpoint metadata.
- type_annotations¶
Unmodified unnotations of the endpoint function, returned by the resolution method.
- responses¶
Mapping of HTTP method to response description. All possible responses that this API can return. Used for OpenAPI spec generation and for response validation.
- validate_responses¶
Do we have to run runtime validation of responses for this endpoint? Customizable via global setting, per controller, and per endpoint. Here we only store the per endpoint information.
- Type:
bool | None
- modification¶
Default modifications that are applied to the returned data. Can be
None, when@validateis used.- Type:
- error_handler¶
Callback function to be called when this endpoint faces an exception.
- Type:
SyncErrorHandler | AsyncErrorHandler | None
- component_parsers¶
List of component parser specifications from the controller. Each spec is a tuple of (ComponentParser class, type args).
- Type:
list[tuple[ComponentParser, Any, tuple[Any, …]]]
- parsers¶
List of instances to be used for this endpoint to parse incoming request’s body. All instances must be of subtypes of
Parser.
- renderers¶
List of instances to be used for this endpoint to render response’s body. All instances must be of subtypes of
Renderer.
- auth¶
list of auth instances to be used for this endpoint. Sync endpoints must use instances of
dmr.security.SyncAuth. Async endpoints must use instances ofdmr.security.AsyncAuth. When set it toNoneit means that auth is disabled for this endpoint.
- no_validate_http_spec¶
Set of checks that user wants to disable for validation in this endpoint.
- allowed_http_methods¶
Set of extra HTTP methods that are allowed for this endpoint.
- exclude_semantic_responses¶
Set of semantic responses that user wants to disable.
- Type:
- validate_events¶
Should this endpoint validate events? If not set, defaults to the
validate_responsesvalue. This value only matters if the response will be a streaming response that supports event validation.- Type:
- tags¶
A list of tags for API documentation control. Used to group operations in OpenAPI documentation.
- security¶
A declaration of which security mechanisms can be used for this operation. List of security requirement objects.
- external_docs¶
Additional external documentation for this operation.
- Type:
ExternalDocumentation | None
- callbacks¶
A map of possible out-of band callbacks related to the parent operation. The key is a unique identifier for the Callback Object. Each value in the map is a Callback Object that describes a request that may be initiated by the API provider and the expected responses.
- servers¶
An alternative servers array to service this operation. If a servers array is specified at the Path Item Object or OpenAPI Object level, it will be overridden by this value.
methodcan be a custom name, not specified inhttp.HTTPMethodenum, whenallowed_http_methodsis used for endpoint definition. This might be useful for cases like when you need to define a method likequery, which is not yet formally accepted. Or provide domain specific HTTP methods.- collect_response_specs(controller_cls: type[Controller[BaseSerializer]], existing_responses: dict[HTTPStatus, ResponseSpec]) list[ResponseSpec][source]¶
Collect unique responses for all possible response providers.
- response_spec_providers() list[type[ResponseSpecProvider]][source]¶
Determine: from where we should collect response schemas.
Override this method in your own metadata classes if you want more or less response spec providers.
For example: you can add some custom field to
Controllerlikechecks=. And you can subclassEndpointMetadatato also containchecksfield and override this method to also include response specs from this field.Define
semantic_responsestoFalseon settings or controller level to disable semantic responses collection.
- @dmr.endpoint.modify(*, error_handler: AsyncErrorHandler, status_code: HTTPStatus | None = None, headers: Mapping[str, NewHeader | HeaderSpec] | None = None, cookies: Mapping[str, NewCookie | CookieSpec] | None = None, validate_responses: bool | None = None, semantic_responses: bool | None = None, exclude_semantic_responses: Set[HTTPStatus] | None = frozenset(), validate_events: bool | None = None, extra_responses: list[ResponseSpec] | None = None, no_validate_http_spec: Set[HttpSpec] | None = frozenset(), parsers: Sequence[Parser] | None = None, renderers: Sequence[Renderer] | None = None, auth: Sequence[AsyncAuth] | Sequence[SyncAuth] | None = (), summary: str | None = None, description: str | None = None, tags: list[str] | None = None, operation_id: str | None = None, deprecated: bool = False, external_docs: ExternalDocumentation | None = None, callbacks: dict[str, Callback | Reference] | None = None, servers: list[Server] | None = None, response_description: str | None = None) _ModifyAsyncCallable[source]¶
- @dmr.endpoint.modify(*, error_handler: SyncErrorHandler, status_code: HTTPStatus | None = None, headers: Mapping[str, NewHeader | HeaderSpec] | None = None, cookies: Mapping[str, NewCookie | CookieSpec] | None = None, validate_responses: bool | None = None, semantic_responses: bool | None = None, exclude_semantic_responses: Set[HTTPStatus] | None = frozenset(), validate_events: bool | None = None, extra_responses: list[ResponseSpec] | None = None, no_validate_http_spec: Set[HttpSpec] | None = frozenset(), parsers: Sequence[Parser] | None = None, renderers: Sequence[Renderer] | None = None, auth: Sequence[AsyncAuth] | Sequence[SyncAuth] | None = (), summary: str | None = None, description: str | None = None, tags: list[str] | None = None, operation_id: str | None = None, deprecated: bool = False, external_docs: ExternalDocumentation | None = None, callbacks: dict[str, Callback | Reference] | None = None, servers: list[Server] | None = None, links: dict[str, Link | Reference] | None = None, response_description: str | None = None) _ModifySyncCallable
- @dmr.endpoint.modify(*, status_code: HTTPStatus | None = None, headers: Mapping[str, NewHeader | HeaderSpec] | None = None, cookies: Mapping[str, NewCookie | CookieSpec] | None = None, validate_responses: bool | None = None, semantic_responses: bool | None = None, exclude_semantic_responses: Set[HTTPStatus] | None = frozenset(), validate_events: bool | None = None, extra_responses: list[ResponseSpec] | None = None, no_validate_http_spec: Set[HttpSpec] | None = frozenset(), error_handler: None = None, parsers: Sequence[Parser] | None = None, renderers: Sequence[Renderer] | None = None, auth: Sequence[AsyncAuth] | Sequence[SyncAuth] | None = (), summary: str | None = None, description: str | None = None, tags: list[str] | None = None, operation_id: str | None = None, deprecated: bool = False, external_docs: ExternalDocumentation | None = None, callbacks: dict[str, Callback | Reference] | None = None, servers: list[Server] | None = None, links: dict[str, Link | Reference] | None = None, response_description: str | None = None) _ModifyAnyCallable
Decorator to modify endpoints that return raw model data.
Apply it to change some API parts:
>>> from http import HTTPStatus >>> from dmr import Controller, modify >>> from dmr.plugins.pydantic import PydanticSerializer >>> class TaskController(Controller[PydanticSerializer]): ... @modify(status_code=HTTPStatus.ACCEPTED) ... def post(self) -> list[int]: ... return [1, 2] # id of tasks you have started
- Parameters:
status_code – Shows status_code in the documentation. When status_code is passed, always use it by default. When not provided, we use smart inference based on the HTTP method name for default returned response.
headers – Shows headers in the documentation. When headers are passed we will add them for the default response.
cookies – Shows cookies in the documentation. When cookies are passed we will add them for the default response.
validate_responses – Do we have to run runtime validation of responses for this endpoint? Customizable via global setting, per controller, and per endpoint. Here we only store the per endpoint information.
semantic_responses – Should semantic responses be collected from different providers for this endpoint.
exclude_semantic_responses – Set of semantic responses status codes that user wants to disable.
validate_events – Should this endpoint validate events? If not set, defaults to the
validate_responsesvalue. This value only matters if the response will be a streaming response that supports event validation.extra_responses – List of extra responses that this endpoint can return.
no_validate_http_spec – Set of http spec validation checks that we disable for this endpoint.
error_handler – Callback function to be called when this endpoint faces an exception.
parsers – Sequence of types to be used for this endpoint to parse incoming request’s body. All types must be subtypes of
Parser.renderers – Sequence of types to be used for this endpoint to render response’s body. All types must be subtypes of
Renderer.auth – Sequence of auth instances to be used for this endpoint. Sync endpoints must use instances of
dmr.security.SyncAuth. Async endpoints must use instances ofdmr.security.AsyncAuth. Set it toNoneto disable auth for this endpoint.summary – A short summary of what the operation does.
description – A verbose explanation of the operation behavior.
tags – A list of tags for API documentation control. Used to group operations in OpenAPI documentation.
operation_id – Unique string used to identify the operation.
deprecated – Declares this operation to be deprecated.
external_docs – Additional external documentation for this operation.
callbacks – A map of possible out-of band callbacks related to the parent operation. The key is a unique identifier for the Callback Object. Each value in the map is a Callback Object that describes a request that may be initiated by the API provider and the expected responses.
servers – An alternative servers array to service this operation.
links – Possible links to other OpenAPI operations.
response_description – Description for the generated response object.
- Returns:
The same function with
__dmr_payload__payload instance.
Warning
Do not disable
validate_responsesunless this is performance critical for you!
- @dmr.endpoint.validate(response: ResponseSpec, /, *responses: ResponseSpec, error_handler: AsyncErrorHandler, validate_responses: bool | None = None, semantic_responses: bool | None = None, exclude_semantic_responses: Set[HTTPStatus] | None = frozenset(), validate_events: bool | None = None, no_validate_http_spec: Set[HttpSpec] | None = frozenset(), parsers: Sequence[Parser] | None = None, renderers: Sequence[Renderer] | None = None, auth: Sequence[AsyncAuth] | Sequence[SyncAuth] | None = (), summary: str | None = None, description: str | None = None, tags: list[str] | None = None, operation_id: str | None = None, deprecated: bool = False, external_docs: ExternalDocumentation | None = None, callbacks: dict[str, Callback | Reference] | None = None, servers: list[Server] | None = None) Callable[[Callable[[_ParamT], Awaitable[HttpResponseBase]]], Callable[[_ParamT], Awaitable[HttpResponseBase]]][source]¶
- @dmr.endpoint.validate(response: ResponseSpec, /, *responses: ResponseSpec, error_handler: SyncErrorHandler, validate_responses: bool | None = None, semantic_responses: bool | None = None, exclude_semantic_responses: Set[HTTPStatus] | None = frozenset(), validate_events: bool | None = None, no_validate_http_spec: Set[HttpSpec] | None = frozenset(), parsers: Sequence[Parser] | None = None, renderers: Sequence[Renderer] | None = None, auth: Sequence[AsyncAuth] | Sequence[SyncAuth] | None = (), summary: str | None = None, description: str | None = None, tags: list[str] | None = None, operation_id: str | None = None, deprecated: bool = False, external_docs: ExternalDocumentation | None = None, callbacks: dict[str, Callback | Reference] | None = None, servers: list[Server] | None = None) Callable[[Callable[[_ParamT], HttpResponseBase]], Callable[[_ParamT], HttpResponseBase]]
- @dmr.endpoint.validate(response: ResponseSpec, /, *responses: ResponseSpec, validate_responses: bool | None = None, semantic_responses: bool | None = None, exclude_semantic_responses: Set[HTTPStatus] | None = frozenset(), validate_events: bool | None = None, no_validate_http_spec: Set[HttpSpec] | None = frozenset(), error_handler: None = None, parsers: Sequence[Parser] | None = None, renderers: Sequence[Renderer] | None = None, auth: Sequence[AsyncAuth] | Sequence[SyncAuth] | None = (), summary: str | None = None, description: str | None = None, tags: list[str] | None = None, operation_id: str | None = None, deprecated: bool = False, external_docs: ExternalDocumentation | None = None, callbacks: dict[str, Callback | Reference] | None = None, servers: list[Server] | None = None) Callable[[Callable[[_ParamT], _ResponseT]], Callable[[_ParamT], _ResponseT]]
Decorator to validate responses from endpoints that return
HttpResponse.Apply it to validate important API parts:
>>> from http import HTTPStatus >>> from django.http import HttpResponse >>> from dmr import Controller, validate, ResponseSpec >>> from dmr.plugins.pydantic import PydanticSerializer >>> class TaskController(Controller[PydanticSerializer]): ... @validate( ... ResponseSpec( ... return_type=list[int], ... status_code=HTTPStatus.OK, ... ), ... ) ... def post(self) -> HttpResponse: ... return HttpResponse(b'[1, 2]', status=HTTPStatus.OK)
Response validation can be disabled for extra speed by sending validate_responses falsy parameter or by setting this configuration in your
settings.pyfile:settings.py¶>>> DMR_SETTINGS = {'validate_responses': False}- Parameters:
response – The main response that this endpoint is allowed to return.
responses – A collection of other responses that are allowed to be returned from this endpoint.
validate_responses – Do we have to run runtime validation of responses for this endpoint? Customizable via global setting, per controller, and per endpoint. Here we only store the per endpoint information.
semantic_responses – Should semantic responses be collected from different providers for this endpoint.
exclude_semantic_responses – Set of semantic responses status codes that user wants to disable.
validate_events – Should this endpoint validate events? If not set, defaults to the
validate_responsesvalue. This value only matters if the response will be a streaming response that supports event validation.no_validate_http_spec – Set of http spec validation checks that we disable for this endpoint.
error_handler – Callback function to be called when this endpoint faces an exception.
parsers – Sequence of types to be used for this endpoint to parse incoming request’s body. All types must be subtypes of
Parser.renderers – Sequence of types to be used for this endpoint to render response’s body. All types must be subtypes of
Renderer.auth – Sequence of auth instances to be used for this endpoint. Sync endpoints must use instances of
dmr.security.SyncAuth. Async endpoints must use instances ofdmr.security.AsyncAuth. Set it toNoneto disable auth for this endpoint.summary – A short summary of what the operation does.
description – A verbose explanation of the operation behavior.
tags – A list of tags for API documentation control. Used to group operations in OpenAPI documentation.
operation_id – Unique string used to identify the operation.
deprecated – Declares this operation to be deprecated.
external_docs – Additional external documentation for this operation.
callbacks – A map of possible out-of band callbacks related to the parent operation. The key is a unique identifier for the Callback Object. Each value in the map is a Callback Object that describes a request that may be initiated by the API provider and the expected responses.
servers – An alternative servers array to service this operation.
- Returns:
The same function with
__dmr_payload__payload instance.
Warning
Do not disable
validate_responsesunless this is performance critical for you!
Validation¶
- class dmr.validation.ModifyEndpointPayload(*, summary: str | None = None, description: str | None = None, tags: list[str] | None = None, operation_id: str | None = None, deprecated: bool = False, security: list[SecurityRequirement] | None = None, external_docs: ExternalDocumentation | None = None, callbacks: dict[str, Callback | Reference] | None = None, servers: list[Server] | None = None, validate_responses: bool | None = None, semantic_responses: bool | None = None, exclude_semantic_responses: Set[HTTPStatus] | None = None, validate_events: bool | None = None, error_handler: Callable[[Endpoint, Controller[BaseSerializer], Exception], HttpResponse] | Callable[[Endpoint, Controller[BaseSerializer], Exception], Awaitable[HttpResponse]] | None = None, no_validate_http_spec: Set[HttpSpec] | None = None, parsers: Sequence[Parser] | None = None, renderers: Sequence[Renderer] | None = None, auth: Sequence[SyncAuth] | Sequence[AsyncAuth] | None = (), responses: list[ResponseSpec] | None, status_code: HTTPStatus | None, headers: Mapping[str, NewHeader | HeaderSpec] | None, cookies: Mapping[str, NewCookie | CookieSpec] | None, response_description: str | None, links: dict[str, Link | Reference] | None)[source]¶
Payload created by
@modify.
- class dmr.validation.ValidateEndpointPayload(*, summary: str | None = None, description: str | None = None, tags: list[str] | None = None, operation_id: str | None = None, deprecated: bool = False, security: list[SecurityRequirement] | None = None, external_docs: ExternalDocumentation | None = None, callbacks: dict[str, Callback | Reference] | None = None, servers: list[Server] | None = None, validate_responses: bool | None = None, semantic_responses: bool | None = None, exclude_semantic_responses: Set[HTTPStatus] | None = None, validate_events: bool | None = None, error_handler: Callable[[Endpoint, Controller[BaseSerializer], Exception], HttpResponse] | Callable[[Endpoint, Controller[BaseSerializer], Exception], Awaitable[HttpResponse]] | None = None, no_validate_http_spec: Set[HttpSpec] | None = None, parsers: Sequence[Parser] | None = None, renderers: Sequence[Renderer] | None = None, auth: Sequence[SyncAuth] | Sequence[AsyncAuth] | None = (), responses: list[ResponseSpec])[source]¶
Payload created by
@validate.
Serialization¶
- class dmr.serializer.BaseSerializer[source]¶
Abstract base class for data serialization.
What serializer does?
It provides serialization and deserialization hooks for parser and renderer. So different parsers and renderers will work similarly. This way you can modify all the serialization logic in one place and not adjust all possible parsers or renderers
It provides validation for raw python data, see
dmr.serializer.BaseSerializer.from_python()methodIt provides serialization for related complex utility objects like validation errors and responses that don’t have
.contentattribute. For example: file and sse responses
- validation_error¶
Exception type that is used for validation errors. Required to be set in subclasses.
- optimizer¶
Endpoint optimizer. Type that pre-compiles / creates / caches models in import time. Required to be set in subclasses.
- Type:
ClassVar[type[dmr.serializer.BaseEndpointOptimizer]]
- openapi¶
- abstractmethod classmethod deserialize(buffer: bytes | bytearray, *, parser: Parser, request: HttpRequest, model: Any) Any[source]¶
Convert json bytestring to structured data.
- classmethod deserialize_hook(target_type: type[Any], to_deserialize: Any) Any[source]¶
Customize how some objects are deserialized from json.
Only add types that are common for all potential plugins here. Should be called inside
deserialize().
- abstractmethod classmethod from_python(unstructured: Any, model: Any, *, strict: bool | None) Any[source]¶
Parse unstructured data from python primitives into model.
Raises
cls.validation_errorwhen something cannot be parsed.- Parameters:
unstructured – Python objects to be parsed / validated.
model – Python type to serve as a model. Can be any type hints that user can theoretically supply. Depends on the serialization plugin.
strict – Whether we use more strict validation rules. For example, it is fine for a request validation to be less strict in some cases and allow type coercition. But, response types need to be strongly validated.
- Returns:
Structured and validated data.
- classmethod is_supported(pluggable: Parser | Renderer) bool[source]¶
Is this parser or renderer supported?
When defining custom serializers you can specify what kind of parser and renders you support. Adding a combination of unsupported serializer and parser / render will raise an import-time validation error.
- abstractmethod classmethod serialize(structure: Any, *, renderer: Renderer) bytes[source]¶
Convert structured data to json bytestring.
- classmethod serialize_hook(to_serialize: Any) Any[source]¶
Customize how some objects are serialized into json.
Only add types that are common for all potential plugins here. Should be called inside
serialize().
- abstractmethod classmethod serialize_validation_error(exc: Exception) list[ErrorDetail][source]¶
Convert specific serializer’s validation errors into simple python data.
- Parameters:
exc – A serialization exception to be serialized into simpler type. For example, pydantic has a complex
pydantic_core.ValidationErrortype. That can’t be converted to a simpler error message easily.- Returns:
Simple python object - exception converted to json.
- class dmr.serializer.BaseEndpointOptimizer[source]¶
Plugins might often need to run some specific preparations for endpoints.
To achieve that we provide an explicit API for that.
- abstractmethod classmethod optimize_endpoint(metadata: EndpointMetadata) None[source]¶
Optimize the endpoint.
- Parameters:
metadata – Endpoint metadata to optimize.
- class dmr.endpoint.SerializerContext(func: Callable[[...], Any], controller_cls: type[Controller[BaseSerializer]], type_annotations: dict[str, Any])[source]¶
Parse and bind request components for a controller.
This context collects raw data for all registered components, validates the combined payload in a single call using a cached TypedDict model, and then binds the parsed values back to the controller.
- strict_validation¶
Whether or not to validate payloads in strict mode. Strict mode in some serializers does not allow implicit type conversions. Defaults to
None, which means that we decide on a per-field basis if it is set, if not then on a per-model basis.- Type:
ClassVar[bool | None]
- __call__(endpoint: Endpoint, controller: Controller[BaseSerializer]) dict[str, Any][source]¶
Collect, validate, and bind component data to the controller.
Raises
serializer.validation_errorwhen provided data does not match the expected model.
- component_builder_cls¶
alias of
ComponentParserBuilder
- class dmr.serializer.BaseSchemaGenerator[source]¶
Generates JSON schema by the native serializer API.
- abstractmethod classmethod get_schema(model: Any, ref_template: str, *, used_for_response: bool = False) tuple[dict[str, Any], dict[str, Any]] | None[source]¶
Provide JSON schema / OpenAPI spec for the given model.
- Parameters:
model – Model to generate JSON schema for.
ref_template – Reference template to use for the references.
used_for_response – Is this schema used for the response or request.
- class dmr.components.ComponentParserBuilder(func: Callable[[...], Any], controller_cls: type[Controller[BaseSerializer]])[source]¶
Find the component parser types in the MRO and find model types for them.
Validates that component parsers can’t have type vars as models at this point.
- __call__(type_annotations: dict[str, Any]) list[tuple[ComponentParser, Any, tuple[Any, ...]]][source]¶
Run the building process, infer type vars if needed.
- type_var_inference_cls¶
alias of
TypeVarInference
Routing¶
- class dmr.routing.Router(prefix: str, urls: Sequence[URLPattern | URLResolver])[source]¶
Collection of HTTP routes for REST framework.
- get_schema(context: OpenAPIContext) OpenAPI[source]¶
Builds OpenAPI specification.
This class orchestrates the process of generating a complete OpenAPI specification by collecting controllers from the router, generating path items for each controller, extracting shared components, and merging everything together with the configuration.
- dmr.routing.build_404_handler(prefix: str, /, *prefixes: str, serializer: type[BaseSerializer], format_error: FormatError = <function format_error>, renderers: ~collections.abc.Sequence[Renderer] | None = None) Callable[[HttpRequest, Exception], HttpResponse][source]¶
Create a 404 handler that returns a response with content negotiation.
All prefixes are normalized to start with a leading slash. If the request path matches any of them, a 404 response is returned using the same serializer and renderers as your API. If the client’s
Acceptdoes not match any renderer, the first configured renderer is used. For non-matching paths, Django’s defaultpage_not_foundhandler is used.- Parameters:
prefix – Path prefix (e.g.
'api/') for which to return API 404.*prefixes – Additional path prefixes.
format_error – Callable used to build the error body for the response.
serializer – Serializer class used to serialize the error body.
renderers – Optional sequence of renderers. If omitted, uses
renderersfrom settings.
- dmr.routing.build_500_handler(prefix: str, /, *prefixes: str, serializer: type[BaseSerializer], format_error: FormatError = <function format_error>, renderers: ~collections.abc.Sequence[Renderer] | None = None) Callable[[HttpRequest], HttpResponse][source]¶
Create a 500 handler that returns a response with content negotiation.
All prefixes are normalized to start with a leading slash. If the request path matches any of them, a 500 response is returned using the same serializer and renderers as your API. If the client’s
Acceptdoes not match any renderer, the first configured renderer is used. For non-matching paths, Django’s defaultserver_errorhandler is used.- Parameters:
prefix – Path prefix (e.g.
'api/') for which to return API 500.*prefixes – Additional path prefixes.
format_error – Callable used to build the error body for the response.
serializer – Serializer class used to serialize the error body.
renderers – Optional sequence of renderers. If omitted, uses
renderersfrom settings.
- dmr.routing.path(route: _StrOrPromise, view: Callable[[...], HttpResponseBase], kwargs: dict[str, Any] | None = None, name: str | None = None) URLPattern[source]¶
- dmr.routing.path(route: _StrOrPromise, view: Callable[[...], Coroutine[Any, Any, HttpResponseBase]], kwargs: dict[str, Any] | None = None, name: str | None = None) URLPattern
- dmr.routing.path(route: _StrOrPromise, view: tuple[Sequence[URLPattern | URLResolver], str | None, str | None], kwargs: dict[str, Any] | None = None, name: str | None = None) URLResolver
- dmr.routing.path(route: _StrOrPromise, view: Sequence[URLResolver | str], kwargs: dict[str, Any] | None = None, name: str | None = None) URLResolver
Creates URL pattern using prefix-based matching for faster routing.
Meta mixins¶
- class dmr.options_mixins.MetaMixin[source]¶
Mixin that provides default
metamethod orOPTIONShttp method.Use it for sync controllers.
It just returns the list of allowed methods. Use it as a mixin with the
dmr.controller.Controllertype:>>> from dmr import Controller >>> from dmr.options_mixins import MetaMixin >>> from dmr.plugins.pydantic import PydanticSerializer >>> class SupportsOptionsHttpMethod( ... MetaMixin, ... Controller[PydanticSerializer], ... ): ...
- meta() HttpResponse[source]¶
Default sync implementation for
OPTIONShttp method.
- class dmr.options_mixins.AsyncMetaMixin[source]¶
Mixin that provides default
metamethod orOPTIONShttp method.Use it for async controllers.
It just returns the list of allowed methods. Use it as a mixin with the
dmr.controller.Controllertype:>>> from dmr import Controller >>> from dmr.options_mixins import AsyncMetaMixin >>> from dmr.plugins.pydantic import PydanticSerializer >>> class SupportsOptionsHttpMethod( ... AsyncMetaMixin, ... Controller[PydanticSerializer], ... ): ...
- async meta() HttpResponse[source]¶
Default async implementation for
OPTIONShttp method.
Exceptions¶
- final exception dmr.exceptions.UnsolvableAnnotationsError[source]¶
Raised when we can’t solve function’s annotations using
get_type_hints.Only raised when there are no other options.
- final exception dmr.exceptions.EndpointMetadataError[source]¶
Raised when user didn’t specify some required endpoint metadata.
- final exception dmr.exceptions.RequestSerializationError[source]¶
Raised when we fail to parse some request part.
- final exception dmr.exceptions.ResponseSchemaError[source]¶
Raised when we fail to validate some response part.
Can only happen when response validation is enabled. Does not show up in the response schema if validation is disabled.
- final exception dmr.exceptions.ValidationError(payload: list[ErrorDetail], *, status_code: HTTPStatus = HTTPStatus.UNPROCESSABLE_ENTITY)[source]¶
Raised when we cannot properly validate request or response models.
It should be only raised when serializer raise its internal validation error.
It is an universal way of handling validation errors from different serializers.
- final exception dmr.exceptions.NotAcceptableError[source]¶
Raised when client provides wrong
Acceptheader.
Utilities¶
- dmr.types.Json: TypeAlias = typing.Any¶
Recursive type alias for JSON data.
What is JSON? Integers, floats, booleans, strings, list of them and dicts of them, which keys are always strings.
In runtime it is always
typing.Anybecause of the parsing complexity, while in type checking it correctly defined.We don’t recommend using it for anything serious, it is better to define real models instead.
- class dmr.types.AnnotationsContext(*, globalns: dict[str, Any] | None = None, localns: Mapping[str, Any] | None = None, include_extras: bool = True, format: Format | None = None)[source]¶
Annotation evaluation context.
Use this type to change how controllers resolve type hints of their endpoints.
For example, one can change this function to use
inspect.get_annotations()function. Or to have some pre-defined global names.- __call__(endpoint_func: Callable[[...], Any]) dict[str, Any][source]¶
Get the annotations.
- Parameters:
endpoint_func – function with return type annotation.
- Returns:
Function’s parsed and solved return type.
- Raises:
UnsolvableAnnotationsError – when annotation can’t be solved or when the annotation does not exist.
Decorators¶
- dmr.decorators.dispatch_decorator(func: Callable[[...], Any]) Callable[[_TypeT], _TypeT][source]¶
Special helper to decorate class-based view’s
dispatchmethod.Use it directly on controllers, like so:
>>> from dmr import Controller >>> from dmr.decorators import dispatch_decorator >>> from dmr.plugins.pydantic import PydanticSerializer >>> from django.contrib.auth.decorators import login_required >>> @dispatch_decorator(login_required()) ... class MyController(Controller[PydanticSerializer]): ... def get(self) -> str: ... return 'Logged in!'
In this example we would require all calls to all methods of
MyControllerto require an existing authentication.It also works for things like: -
django.contrib.auth.decorators.login_not_required()-django.contrib.auth.decorators.user_passes_test()-django.contrib.auth.decorators.permission_required()- and any other default or custom django decoratorDanger
This will return non-json responses, without respecting your spec! Use with caution!
If you want full spec support, use middleware wrappers. You would probably want to use
wrap_middleware()as well. Or useendpoint_decorator().
- dmr.decorators.endpoint_decorator(original_decorator: Callable[[_ViewT], _ViewT]) Callable[[Callable[[_ParamT], _ReturnT]], Callable[[_ParamT], _ReturnT]][source]¶
Apply regular Django-styled decorator to a single endpoint.
Example:
>>> from http import HTTPStatus >>> from dmr import Controller, HeaderSpec, modify >>> from dmr.decorators import endpoint_decorator >>> from dmr.plugins.pydantic import PydanticSerializer >>> from django.contrib.auth.decorators import login_required >>> class MyController(Controller[PydanticSerializer]): ... @endpoint_decorator(login_required()) ... @modify( ... extra_responses=[ ... ResponseSpec( ... None, ... status_code=HTTPStatus.FOUND, ... headers={'Location': HeaderSpec()}, ... ), ... ], ... ) ... def get(self) -> str: ... return 'Logged in!'
It also works for things like: -
django.contrib.auth.decorators.login_not_required()-django.contrib.auth.decorators.user_passes_test()-django.contrib.auth.decorators.permission_required()- and any other default or custom django decoratorWarning
Be careful with decorators that you apply. They will not escape the response validation, but will return unmodified responses from the original decorators.
For example:
login_requiredwill return a redirect. You can describe it with the extra metadata.
- dmr.decorators.wrap_middleware(middleware: Callable[[Callable[[...], Any]], Callable[[...], Any]], response: ResponseSpec, *responses: ResponseSpec) Callable[[Callable[[HttpResponse], HttpResponse]], DecoratorWithResponses][source]¶
Factory function that creates a decorator with pre-configured middleware.
This allows creating reusable decorators with specific middleware and response handling.
- Parameters:
middleware – Django middleware to apply
response – ResponseSpec for the middleware response
responses – Others ResponseSpec
- Returns:
A function that takes a converter and returns a class decorator
>>> from django.views.decorators.csrf import csrf_protect >>> from django.http import HttpResponse >>> from http import HTTPStatus >>> from dmr import Controller, ResponseSpec >>> from dmr.response import build_response >>> from dmr.plugins.pydantic import PydanticSerializer >>> from dmr.errors import ErrorType, ErrorModel, format_error >>> @wrap_middleware( ... csrf_protect, ... ResponseSpec( ... return_type=ErrorModel, ... status_code=HTTPStatus.FORBIDDEN, ... ), ... ) ... def csrf_protect_json(response: HttpResponse) -> HttpResponse: ... return build_response( ... PydanticSerializer, ... raw_data=format_error( ... 'CSRF verification failed. Request aborted.', ... error_type=ErrorType.user_msg, ... ), ... status_code=HTTPStatus(response.status_code), ... ) >>> @csrf_protect_json ... class MyController(Controller[PydanticSerializer]): ... responses = [ ... *csrf_protect_json.responses, ... ] ... ... def post(self) -> dict[str, str]: ... return {'message': 'ok'}
Testing¶
- class dmr.test.DMRRequestFactory(*, json_encoder=<class 'django.core.serializers.json.DjangoJSONEncoder'>, headers=None, query_params=None, **defaults)[source]¶
Test utility for testing apps using
django-modern-rest.Based on
django.test.RequestFactory. See their docs for advanced usage: https://docs.djangoproject.com/en/dev/topics/testing/toolsThis type, in contrast to a regular
RequestFactory, setscontent-typeasapplication/json.Sets WSGI environment.
- class dmr.test.DMRAsyncRequestFactory(*, json_encoder=<class 'django.core.serializers.json.DjangoJSONEncoder'>, headers=None, query_params=None, **defaults)[source]¶
Version of
DMRRequestFactorybut for ASGI environment.Uses the exactly the same API.
- class dmr.test.DMRClient(enforce_csrf_checks=False, raise_request_exception=True, *, headers=None, query_params=None, **defaults)[source]¶
Test utility for testing apps using
django-modern-rest.Based on
django.test.Client. See their docs for advanced usage: https://docs.djangoproject.com/en/dev/topics/testing/tools/This type, in contrast to a regular
Client, setscontent-typeasapplication/json.
Plugins¶
Pydantic¶
- class dmr.plugins.pydantic.PydanticSerializer[source]¶
Serialize and deserialize objects using pydantic.
Pydantic support is optional. To install it run:
pip install 'django-modern-rest[pydantic]'- classmethod deserialize(buffer: bytes | bytearray, *, parser: Parser, request: HttpRequest, model: Any) Any[source]¶
Convert string or bytestring to simple python object.
- classmethod from_python(unstructured: Any, model: Any, *, strict: bool | None, rebuild_namespace: Mapping[str, Any] | None = None) Any[source]¶
Parse unstructured data from python primitives into model.
- Parameters:
unstructured – Python objects to be parsed / validated.
model – Python type to serve as a model. Can be any type that
pydanticsupports. Examples:dict[str, int]andBaseModelsubtypes.strict – Whether we use more strict validation rules. For example, it is fine for a request validation to be less strict in some cases and allow type coercition. But, response types need to be strongly validated.
rebuild_namespace – Optional namespace to rebuild the type adapter. Should be used when there are forward references that pydantic cannot solve by itself.
- Returns:
Structured and validated data.
- Raises:
pydantic_core.ValidationError – When parsing can’t be done.
- optimizer¶
alias of
PydanticEndpointOptimizer
- schema_generator¶
alias of
PydanticSchemaGenerator
- classmethod serialize(structure: Any, *, renderer: Renderer) bytes[source]¶
Convert any object to raw bytestring.
- classmethod serialize_hook(to_serialize: Any) Any[source]¶
Customize how some objects are serialized into simple objects.
- classmethod serialize_validation_error(exc: Exception) list[ErrorDetail][source]¶
Serialize validation error.
- class dmr.plugins.pydantic.serializer.PydanticEndpointOptimizer[source]¶
Optimize endpoints that are parsed with pydantic.
- classmethod optimize_endpoint(metadata: EndpointMetadata) None[source]¶
Create models for return types for validation.
- class dmr.plugins.pydantic.schema.PydanticSchemaGenerator[source]¶
Generates JSON schema for pydantic objects.
Msgspec¶
- class dmr.plugins.msgspec.MsgspecSerializer[source]¶
Serialize and deserialize objects using msgspec.
Msgspec support is optional. To install it run:
pip install 'django-modern-rest[msgspec]'- classmethod deserialize(buffer: bytes | bytearray, *, parser: Parser, request: HttpRequest, model: Any) Any[source]¶
Convert string or bytestring to simple python object.
- classmethod from_python(unstructured: Any, model: Any, *, strict: bool | None) Any[source]¶
Parse unstructured data from python primitives into model.
- Parameters:
unstructured – Python objects to be parsed / validated.
model – Python type to serve as a model. Can be any type that
msgspecsupports. Examples:dict[str, int]andBaseModelsubtypes.strict – Whether we use more strict validation rules. For example, it is fine for a request validation to be less strict in some cases and allow type coercition. But, response types need to be strongly validated.
- Returns:
Structured and validated data.
- Raises:
msgspec.ValidationError – When parsing can’t be done.
- optimizer¶
alias of
MsgspecEndpointOptimizer
- schema_generator¶
alias of
MsgspecSchemaGenerator
- classmethod serialize(structure: Any, *, renderer: Renderer) bytes[source]¶
Convert any object to a raw bytestring.
- classmethod serialize_validation_error(exc: Exception) list[ErrorDetail][source]¶
Serialize validation error.
- classmethod to_python(structured: Any) Any[source]¶
Unparse structured data from a model into Python primitives.
- Parameters:
structured – Model instance.
- Returns:
Unstructured data.
- validation_error¶
alias of
ValidationError
- class dmr.plugins.msgspec.serializer.MsgspecEndpointOptimizer[source]¶
Optimize endpoints that are parsed with Msgspec.
- classmethod optimize_endpoint(metadata: EndpointMetadata) None[source]¶
Does nothing for msgspec.
- class dmr.plugins.msgspec.schema.MsgspecSchemaGenerator[source]¶
Generates JSON schema for msgspec objects.