Configuration#
We use DMR_SETTINGS dictionary object to store all the configuration.
All keys are typed with Settings enum keys
which can be used to both set and get settings.
Note
Remember, that django-modern-rest settings
are cached after the first access.
If you need to modify settings dynamically
in runtime use clear_settings_cache().
You can modify the size of cache with adjusting
DMR_MAX_CACHE_SIZE value.
Here are all keys and values that can be set.
As usual, all settings go to settings.py file in your Django project.
See also
Official Django settings docs: https://docs.djangoproject.com/en/5.2/topics/settings
django-split-settingsconfiguration helper https://github.com/wemake-services/django-split-settings
We also validate defined settings in import time. See Settings validation for more details.
Configuration levels#
Most of the settings can be also configured on the controller level
with Controller attributes
and on the endpoint level with modify
and validate parameters.
The most specific level always wins:
Endpoint values override controller values
Controller values override settings values
Values are never merged, they always redefine each other.
Every value on every level defaults to EMPTY,
which means “not set on this level, use the next one”.
This is true for sequences, sets, booleans, and all other values.
None is never a default: it is a real value that disables something,
like auth=None or tags=None, and it is only allowed
where disabling makes sense.
Explicit empty values are real values as well, they are taken literally:
auth=[] means “no auth on this level”, it does not fall through
to the next level. So, auth=[] and auth=() work exactly
like auth=None does. The same is true for throttling,
security, responses, tags, and all other sequences and sets.
Only EMPTY uses the next level.
Here’s how one can use this system to achieve different strategies. Let’s use authentication as the example.
Overrides values from previous levels.
1from dmr import Controller, modify
2from dmr.plugins.pydantic import PydanticFastSerializer
3from dmr.security.jwt import CookieJWTSyncAuth, HeaderJWTSyncAuth
4
5
6class APIController(Controller[PydanticFastSerializer]):
7 auth = (HeaderJWTSyncAuth(),)
8
9 @modify(auth=[CookieJWTSyncAuth()])
10 def get(self) -> str: # has custom auth
11 assert self.request.user.is_authenticated
12 return 'get'
13
14 def post(self) -> str: # has auth from the controller level
15 assert self.request.user.is_authenticated
16 return 'post'
17
Run result
$ curl http://127.0.0.1:8000/api/users/ -X GET --cookie access_token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxIiwiZXhwIjoxNzkxMjg1NDI4LCJpYXQiOjE3OTExOTkwMjgsImp0aSI6ImYwNTJiYWFkMzQwYTQ3ZDI5NWIyMzk2MzA0N2NlODM0IiwiZXh0cmFzIjp7InR5cGUiOiJhY2Nlc3MifX0.Xnkk5cg1vvPTRT9zowxrsjhy7jPDngnV-kIBj0evl2s
"get"
$ curl http://127.0.0.1:8000/api/users/ -X GET -H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxIiwiZXhwIjoxNzkxMjg1NDI5LCJpYXQiOjE3OTExOTkwMjksImp0aSI6IjBiYmIxNTVhMDAzMjQ4MmY5ODA2OGNlMWNkOTFiODg3IiwiZXh0cmFzIjp7InR5cGUiOiJhY2Nlc3MifX0.lRXdMkBXchKoX7fywkS565uAA9EOme3nVe1QgEhklXE'
{"detail":[{"msg":"Not authenticated","type":"security"}]}
$ curl http://127.0.0.1:8000/api/users/ -X POST -H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxIiwiZXhwIjoxNzkxMjg1NDI5LCJpYXQiOjE3OTExOTkwMjksImp0aSI6IjA2ODE0ZDU3ZTVmZjQ0ZTc4NDkwOGUwZmJjZDNhNGE3IiwiZXh0cmFzIjp7InR5cGUiOiJhY2Nlc3MifX0.FUubMRqWZq-8Vaji3pfDnWSHY9tE5nVtg0td7jlio8s'
"post"
$ curl http://127.0.0.1:8000/api/users/ -X POST --cookie access_token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxIiwiZXhwIjoxNzkxMjg1NDMwLCJpYXQiOjE3OTExOTkwMzAsImp0aSI6ImNjODM1MzhmMTZhNDQ2NjJiY2Q0MzYyZWNlMWM3ZTg1IiwiZXh0cmFzIjp7InR5cGUiOiJhY2Nlc3MifX0.mFtybBnF9l8ZBWb4bmbpXOTLoWj6t8LknLugcmkGZLE
{"detail":[{"msg":"Not authenticated","type":"security"}]}
OpenAPI Schema
Preview openapi.json
{
"components": {
"schemas": {
"ErrorDetail": {
"description": "Base schema for error details description.",
"properties": {
"loc": {
"items": {
"anyOf": [
{
"type": "integer"
},
{
"type": "string"
}
]
},
"title": "Loc",
"type": "array"
},
"msg": {
"title": "Msg",
"type": "string"
},
"type": {
"title": "Type",
"type": "string"
}
},
"required": [
"msg"
],
"title": "ErrorDetail",
"type": "object"
},
"ErrorModel": {
"description": "Default error response schema.\n\nCan be customized.\nSee :ref:`customizing-error-messages` for more details.",
"properties": {
"detail": {
"items": {
"$ref": "#/components/schemas/ErrorDetail"
},
"title": "Detail",
"type": "array"
}
},
"required": [
"detail"
],
"title": "ErrorModel",
"type": "object"
}
},
"securitySchemes": {
"csrf": {
"description": "CSRF protection",
"in": "cookie",
"name": "csrftoken",
"type": "apiKey"
},
"jwt": {
"bearerFormat": "JWT",
"description": "JWT token auth",
"scheme": "Bearer",
"type": "http"
},
"jwt_cookie": {
"description": "JWT token auth via cookie",
"in": "cookie",
"name": "access_token",
"type": "apiKey"
}
}
},
"info": {
"title": "Django Modern Rest",
"version": "0.1.0"
},
"openapi": "3.2.0",
"paths": {
"/api/apicontroller/": {
"get": {
"operationId": "getAPIControllerApiApicontroller",
"responses": {
"200": {
"content": {
"application/json": {
"schema": {
"type": "string"
}
}
},
"description": "OK"
},
"401": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorModel"
}
}
},
"description": "Raised when auth was not successful"
},
"406": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorModel"
}
}
},
"description": "Raised when provided `Accept` header cannot be satisfied"
},
"422": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorModel"
}
}
},
"description": "Raised when returned response does not match the response schema"
}
},
"security": [
{
"jwt_cookie": []
}
]
},
"post": {
"operationId": "postAPIControllerApiApicontroller",
"responses": {
"201": {
"content": {
"application/json": {
"schema": {
"type": "string"
}
}
},
"description": "Created"
},
"401": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorModel"
}
}
},
"description": "Raised when auth was not successful",
"headers": {
"WWW-Authenticate": {
"description": "Challenges that the client can use to authenticate this request",
"required": true,
"schema": {
"type": "string"
}
}
}
},
"406": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorModel"
}
}
},
"description": "Raised when provided `Accept` header cannot be satisfied"
},
"422": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorModel"
}
}
},
"description": "Raised when returned response does not match the response schema"
}
},
"security": [
{
"jwt": []
}
]
}
}
}
}
Merge values from previous levels.
1from dmr import Controller, modify
2from dmr.plugins.pydantic import PydanticFastSerializer
3from dmr.security.jwt import CookieJWTSyncAuth, HeaderJWTSyncAuth
4
5
6class APIController(Controller[PydanticFastSerializer]):
7 auth = (HeaderJWTSyncAuth(),)
8
9 @modify(auth=[*auth, CookieJWTSyncAuth()])
10 def get(self) -> str: # has merged auth
11 assert self.request.user.is_authenticated
12 return 'get'
13
14 def post(self) -> str: # has auth from the controller level
15 assert self.request.user.is_authenticated
16 return 'post'
17
Run result
$ curl http://127.0.0.1:8000/api/users/ -X GET --cookie access_token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxIiwiZXhwIjoxNzkxMjg1NDMxLCJpYXQiOjE3OTExOTkwMzEsImp0aSI6IjNhNDM0MDdhZTEwYzQzMDU4ZTQyZjgwNGUxNzljNDI1IiwiZXh0cmFzIjp7InR5cGUiOiJhY2Nlc3MifX0.WU4_FcgK857CWAEXu0MsDyxSLdBYTV9n4cRRUYhc2YQ
"get"
$ curl http://127.0.0.1:8000/api/users/ -X GET -H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxIiwiZXhwIjoxNzkxMjg1NDMxLCJpYXQiOjE3OTExOTkwMzEsImp0aSI6Ijg3NDFlOTU5ZTExZTQxM2ZiZjRiYTJmYzdlNTAwYTYxIiwiZXh0cmFzIjp7InR5cGUiOiJhY2Nlc3MifX0.hUyKBVwj7aZvSwgCcRmABKtCyMnt8quG3ol87xCMyXk'
"get"
$ curl http://127.0.0.1:8000/api/users/ -X POST -H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxIiwiZXhwIjoxNzkxMjg1NDMyLCJpYXQiOjE3OTExOTkwMzIsImp0aSI6IjgwNTE4NDM1Yzg1YTRhYTNiYWRlZTdjMzhhZmIyOWUzIiwiZXh0cmFzIjp7InR5cGUiOiJhY2Nlc3MifX0.WdVp2Ef9ckhn4V2dcLZ-6OHPqdq_M-BEIWTn8x-ZFDE'
"post"
$ curl http://127.0.0.1:8000/api/users/ -X POST --cookie access_token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxIiwiZXhwIjoxNzkxMjg1NDMyLCJpYXQiOjE3OTExOTkwMzIsImp0aSI6Ijg2N2I1ZGU1N2UzNTRmZTI5MWYxYzYzMDJhOWEyOTM0IiwiZXh0cmFzIjp7InR5cGUiOiJhY2Nlc3MifX0.rb0OIeGpfAB7QHO_UFBZ8qvNrVsKBAewWTP-l9rx8GQ
{"detail":[{"msg":"Not authenticated","type":"security"}]}
OpenAPI Schema
Preview openapi.json
{
"components": {
"schemas": {
"ErrorDetail": {
"description": "Base schema for error details description.",
"properties": {
"loc": {
"items": {
"anyOf": [
{
"type": "integer"
},
{
"type": "string"
}
]
},
"title": "Loc",
"type": "array"
},
"msg": {
"title": "Msg",
"type": "string"
},
"type": {
"title": "Type",
"type": "string"
}
},
"required": [
"msg"
],
"title": "ErrorDetail",
"type": "object"
},
"ErrorModel": {
"description": "Default error response schema.\n\nCan be customized.\nSee :ref:`customizing-error-messages` for more details.",
"properties": {
"detail": {
"items": {
"$ref": "#/components/schemas/ErrorDetail"
},
"title": "Detail",
"type": "array"
}
},
"required": [
"detail"
],
"title": "ErrorModel",
"type": "object"
}
},
"securitySchemes": {
"csrf": {
"description": "CSRF protection",
"in": "cookie",
"name": "csrftoken",
"type": "apiKey"
},
"jwt": {
"bearerFormat": "JWT",
"description": "JWT token auth",
"scheme": "Bearer",
"type": "http"
},
"jwt_cookie": {
"description": "JWT token auth via cookie",
"in": "cookie",
"name": "access_token",
"type": "apiKey"
}
}
},
"info": {
"title": "Django Modern Rest",
"version": "0.1.0"
},
"openapi": "3.2.0",
"paths": {
"/api/apicontroller/": {
"get": {
"operationId": "getAPIControllerApiApicontroller",
"responses": {
"200": {
"content": {
"application/json": {
"schema": {
"type": "string"
}
}
},
"description": "OK"
},
"401": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorModel"
}
}
},
"description": "Raised when auth was not successful",
"headers": {
"WWW-Authenticate": {
"description": "Challenges that the client can use to authenticate this request",
"required": true,
"schema": {
"type": "string"
}
}
}
},
"406": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorModel"
}
}
},
"description": "Raised when provided `Accept` header cannot be satisfied"
},
"422": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorModel"
}
}
},
"description": "Raised when returned response does not match the response schema"
}
},
"security": [
{
"jwt": []
},
{
"jwt_cookie": []
}
]
},
"post": {
"operationId": "postAPIControllerApiApicontroller",
"responses": {
"201": {
"content": {
"application/json": {
"schema": {
"type": "string"
}
}
},
"description": "Created"
},
"401": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorModel"
}
}
},
"description": "Raised when auth was not successful",
"headers": {
"WWW-Authenticate": {
"description": "Challenges that the client can use to authenticate this request",
"required": true,
"schema": {
"type": "string"
}
}
}
},
"406": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorModel"
}
}
},
"description": "Raised when provided `Accept` header cannot be satisfied"
},
"422": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorModel"
}
}
},
"description": "Raised when returned response does not match the response schema"
}
},
"security": [
{
"jwt": []
}
]
}
}
}
}
Disable values from previous levels.
1from dmr import Controller, modify
2from dmr.plugins.pydantic import PydanticFastSerializer
3from dmr.security.jwt import HeaderJWTSyncAuth
4
5
6class APIController(Controller[PydanticFastSerializer]):
7 auth = (HeaderJWTSyncAuth(),)
8
9 @modify(auth=None)
10 def get(self) -> str: # has no auth
11 assert not self.request.user.is_authenticated
12 return 'public'
13
14 def post(self) -> str: # has auth from the controller level
15 assert self.request.user.is_authenticated
16 return 'authed'
17
Run result
$ curl http://127.0.0.1:8000/api/users/ -X GET
"public"
$ curl http://127.0.0.1:8000/api/users/ -X POST -H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxIiwiZXhwIjoxNzkxMjg1NDMzLCJpYXQiOjE3OTExOTkwMzMsImp0aSI6ImYwMWRlZjg0MDlmODQ4MjZiYzhjMmNiYjA1M2U0YWIyIiwiZXh0cmFzIjp7InR5cGUiOiJhY2Nlc3MifX0.V1UKtD8I4zdc9B1S_U0zU_Vd1L4hirhllcfi_z2mP8k'
"authed"
$ curl http://127.0.0.1:8000/api/users/ -X POST
{"detail":[{"msg":"Not authenticated","type":"security"}]}
OpenAPI Schema
Preview openapi.json
{
"components": {
"schemas": {
"ErrorDetail": {
"description": "Base schema for error details description.",
"properties": {
"loc": {
"items": {
"anyOf": [
{
"type": "integer"
},
{
"type": "string"
}
]
},
"title": "Loc",
"type": "array"
},
"msg": {
"title": "Msg",
"type": "string"
},
"type": {
"title": "Type",
"type": "string"
}
},
"required": [
"msg"
],
"title": "ErrorDetail",
"type": "object"
},
"ErrorModel": {
"description": "Default error response schema.\n\nCan be customized.\nSee :ref:`customizing-error-messages` for more details.",
"properties": {
"detail": {
"items": {
"$ref": "#/components/schemas/ErrorDetail"
},
"title": "Detail",
"type": "array"
}
},
"required": [
"detail"
],
"title": "ErrorModel",
"type": "object"
}
},
"securitySchemes": {
"jwt": {
"bearerFormat": "JWT",
"description": "JWT token auth",
"scheme": "Bearer",
"type": "http"
}
}
},
"info": {
"title": "Django Modern Rest",
"version": "0.1.0"
},
"openapi": "3.2.0",
"paths": {
"/api/apicontroller/": {
"get": {
"operationId": "getAPIControllerApiApicontroller",
"responses": {
"200": {
"content": {
"application/json": {
"schema": {
"type": "string"
}
}
},
"description": "OK"
},
"406": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorModel"
}
}
},
"description": "Raised when provided `Accept` header cannot be satisfied"
},
"422": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorModel"
}
}
},
"description": "Raised when returned response does not match the response schema"
}
}
},
"post": {
"operationId": "postAPIControllerApiApicontroller",
"responses": {
"201": {
"content": {
"application/json": {
"schema": {
"type": "string"
}
}
},
"description": "Created"
},
"401": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorModel"
}
}
},
"description": "Raised when auth was not successful",
"headers": {
"WWW-Authenticate": {
"description": "Challenges that the client can use to authenticate this request",
"required": true,
"schema": {
"type": "string"
}
}
}
},
"406": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorModel"
}
}
},
"description": "Raised when provided `Accept` header cannot be satisfied"
},
"422": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorModel"
}
}
},
"description": "Raised when returned response does not match the response schema"
}
},
"security": [
{
"jwt": []
}
]
}
}
}
}
One can customize metadata_merger_cls
from the default MetadataMerger
to change how values are merged into the final metadata.
For example, it is possible to restore 0.15.0 behavior
and merge all sequences in a custom subclass, if it is needed.
Changed in version 0.16.0: Values from different levels used to be merged.
Settings#
Class with all possible setting keys as enum:
- final class dmr.settings.Settings(*values)[source]#
Bases:
StrEnumKeys for all settings.
To get settings use
resolve_setting()function together withSettingskeys:>>> from dmr.settings import Settings, resolve_setting >>> resolve_setting(Settings.responses) []
To set settings use:
>>> DMR_SETTINGS = {Settings.responses: []}
Class that can be used to properly type settings in user’s code:
dmr.settings.SettingsDict.
Content negotiation#
Note
It is recommended to always install msgspec
with 'django-modern-rest[msgspec]' extra for better performance.
- dmr.settings.Settings.parsers#
Default:
dmr.parsers.JsonParserordmr.plugins.msgspec.MsgspecJsonParserif installed.A list of instances of subtypes of
Parserto serialize data from the requested text format, like json or xml, into python object.Custom configuration example, let’s say you want to always use
orjson:settings.py#import orjson from dmr.parsers import JsonParser DMR_SETTINGS = {Settings.parsers: [JsonParser(json_module=orjson)]}
See Configuration levels on how to merge / override / disable this setting on multiple levels.
Changed in version 0.16.0: Controller and endpoint values are not merged anymore.
- dmr.settings.Settings.renderers#
Default:
dmr.renderers.JsonRendererordmr.plugins.msgspec.MsgspecJsonRendererif installed.A list of instances of subtypes of
Rendererto serialize python objects to the requested text format, like json or xml.Custom configuration example, let’s say you want to always use
orjson:settings.py#import orjson from dmr.renderers import JsonRenderer DMR_SETTINGS = {Settings.renderers: [JsonRenderer(json_module=orjson)]}
See Configuration levels on how to merge / override / disable this setting on multiple levels.
Changed in version 0.16.0: Controller and endpoint values are not merged anymore.
- dmr.settings.Settings.validate_negotiation#
Default:
EMPTYShould we validate content negotiation? Meaning:
django-modern-restfinds which parser and which renderer to use based onContent-TypeandAcceptheaders. However, by mistake people can return responses with wrongContent-Typeif they construct responses manually. See Content negotiation for more info.Defaults to the value set in
validate_responsesfor convenience if this value isEMPTY.To disable the content negotiation validation globally, use:
settings.py#>>> DMR_SETTINGS = { ... Settings.validate_negotiation: False, ... }
Response handling#
- dmr.settings.Settings.responses#
Default:
[]The list of global
ResponseSpecobject that will be added to endpoints’ metadata as a possible response schema.Use it to set global responses’ status codes like
500:settings.py#>>> from http import HTTPStatus >>> from typing_extensions import TypedDict >>> from dmr import ResponseSpec >>> class Error(TypedDict): ... detail: str >>> # If our API can always return a 500 response with `{"detail": str}` >>> # error message: >>> DMR_SETTINGS = { ... Settings.responses: [ ... ResponseSpec( ... Error, ... status_code=HTTPStatus.INTERNAL_SERVER_ERROR, ... ), ... ], ... }
See Configuration levels on how to merge / override / disable this setting on multiple levels.
Changed in version 0.16.0: Controller and endpoint values are not merged anymore.
- dmr.settings.Settings.validate_responses#
Default:
TrueWhen some endpoint returns any data, we by default validate that this data matches the endpoint schema.
So, this code will produce not only static typing error, but also a runtime error:
>>> from dmr import Controller >>> from dmr.plugins.pydantic import PydanticFastSerializer >>> class MyController(Controller[PydanticFastSerializer]): ... def get(self) -> list[str]: ... return [1, 2] # <- both static typing and runtime error
But, there’s a runtime cost to this. It is recommended to switch this validation off for production:
settings.py#>>> DMR_SETTINGS = {Settings.validate_responses: False}Note
You can also switch off this validation per-controller with
validate_responsesand per-endpoint withvalidate_responsesargument tomodifyandvalidate.We recommend setting this value to
not DEBUG.
- dmr.settings.Settings.exclude_validate_responses#
Default:
frozenset()Pass any status code that you don’t want to be validated, even when
validate_responsesis enabled.Unlike describing a response, this does not add anything to the OpenAPI schema: such responses are simply returned to the client as-is. See Validating error responses for the details.
settings.py#>>> from http import HTTPStatus >>> DMR_SETTINGS = { ... Settings.exclude_validate_responses: { ... HTTPStatus.INTERNAL_SERVER_ERROR, ... }, ... }
See Configuration levels on how to merge / override / disable this setting on multiple levels.
Added in version 0.15.0.
Changed in version 0.16.0: Controller and endpoint values are not merged anymore.
Semantic schema generation#
- dmr.settings.Settings.semantic_schema#
Default:
TrueWhen
True, parsers, renderers, and authentication classes automatically inject their semantic schema like response specs, security schemes, and security requirements into every endpoint’s metadata. This data is then visible in the generated OpenAPI spec.Set to
Falseto disable this auto-injection globally. User-defined schema (via@modify,@validate, controllerresponses,Settings.responses, orsecurity) are not affected by this flag. Runtime response validation still works as configured bySettings.validate_responses.settings.py#>>> DMR_SETTINGS = {Settings.semantic_schema: False}Added in version 0.16.0.
- dmr.settings.Settings.semantic_responses#
Default:
EMPTYWhen
True, parsers, renderers, and authentication classes automatically inject their semantic responses spec (e.g.422 Unprocessable Entity,406 Not Acceptable) into every endpoint’s metadata. These responses are then visible in the generated OpenAPI spec.Set to
Falseto disable this auto-injection globally. User-defined responses (via@modify,@validate, controllerresponses, orSettings.responses) are not affected by this flag. Runtime response validation still works as configured bySettings.validate_responses.Defaults to the value set in
semantic_schemafor convenience if this value isEMPTY.settings.py#>>> DMR_SETTINGS = {Settings.semantic_responses: False}
- dmr.settings.Settings.exclude_semantic_responses#
Default:
frozenset()Pass any status code, that you wanna exclude from semantic responses.
settings.py#>>> from http import HTTPStatus >> DMR_SETTINGS = { ... Settings.exclude_semantic_responses: { ... HTTPStatus.CONFLICT, ... }, ... }
See Configuration levels on how to merge / override / disable this setting on multiple levels.
Changed in version 0.16.0: Controller and endpoint values are not merged anymore.
- dmr.settings.Settings.semantic_auth#
Default:
EMPTYWhen
True, authentication classes automatically inject their semantic security requirements and security schemes into the final OpenAPI spec.Set to
Falseto disable this auto-injection globally. User-defined schemes andsecurityfield (via@modify,@validate, etc) are not affected by this flag.Defaults to the value set in
semantic_schemafor convenience if this value isEMPTY.settings.py#>>> DMR_SETTINGS = {Settings.semantic_auth: False}Added in version 0.16.0.
- dmr.settings.Settings.exclude_semantic_auth#
Default:
frozenset()Exclude specific semantic security schemes and security requirements that auth instances produce from the spec.
settings.py#>>> DMR_SETTINGS = {Settings.exclude_semantic_auth: {'jwt'}}See Configuration levels on how to merge / override / disable this setting on multiple levels.
Added in version 0.16.0.
- dmr.settings.Settings.semantic_schema_providers#
Default:
[ResponseValidationSpecProvider(), CSRFSemanticSchemaProvider()]Sequence of default system-wide semantic schema providers. By default we use
ResponseValidationSpecProviderandCSRFSemanticSchemaProviderto provide common responses and auth specs that can happen with all endpoints. Each endpoint will have these parts of the semantic schema, if their conditions are met.For example,
CSRFSemanticSchemaProviderwill inject for controller that havecsrf_exempt=False: -403status code response spec -csrfsecurity requirement to all auth instances - Globalcsrfsecurity scheme componentexclude_semantic_responsesandexclude_semantic_authare also respected.settings.py#>>> from http import HTTPStatus >>> from dmr.security.csrf import CSRFSemanticSchemaProvider >> DMR_SETTINGS = { ... Settings.semantic_schema_providers: [ ... CSRFSemanticSchemaProvider(description='CSRF error'), ... ], ... }
You can customize instances of the response spec providers or add new ones.
Added in version 0.16.0.
Error handling#
- dmr.settings.Settings.global_error_handler#
Default:
'dmr.errors.global_error_handler'Globally handle all errors in the application. You can use real object or string path for the object to be imported. Here’s our error handling hierarchy:
Per-endpoint with
handle_error()andhandle_async_error()Per-controller with
handle_error()orhandle_async_error()If nothing helped,
'global_error_handler'is called
See
global_error_handler()for the callback type.settings.py#>>> DMR_SETTINGS = {Settings.global_error_handler: 'path.to.your.handler'}
Authentication#
- dmr.settings.Settings.auth#
Default:
[]Configure authentication rules for the whole API.
To enable auth for all endpoints you can use:
settings.py#>>> from dmr.security.django_session import DjangoSessionSyncAuth >>> DMR_SETTINGS = { ... Settings.auth: [ ... DjangoSessionSyncAuth(), ... ], ... }
If you use both sync and async controllers in your app, consider using
SyncOrAsyncAuthfor settings. All auth types must be importable in settings.See Configuration levels on how to merge / override / disable this setting on multiple levels.
Changed in version 0.16.0: Controller and endpoint values are not merged anymore.
Throttling#
- dmr.settings.Settings.throttling#
Default:
[]Configure throttling rules for the whole API.
To enable throttling for all endpoints you can use:
settings.py#>>> from dmr.settings import Settings >>> from dmr.throttling import SyncThrottle, Rate >>> DMR_SETTINGS = { ... Settings.throttling: [SyncThrottle(10, Rate.second)], ... }
If you use both sync and async controllers in your app, consider using
SyncOrAsyncThrottlefor settings. All throttle types must be importable in settings.See Configuration levels on how to merge / override / disable this setting on multiple levels.
Changed in version 0.16.0: Controller and endpoint values are not merged anymore.
HTTP Spec validation#
- dmr.settings.Settings.no_validate_http_spec#
Default:
frozenset()A set of unique
HttpSpeccodes to be globally disabled.We don’t recommend disabling any of these checks globally.
settings.py#>>> from dmr.settings import HttpSpec >>> DMR_SETTINGS = { ... Settings.no_validate_http_spec: { ... HttpSpec.empty_request_body, ... }, ... }
See Configuration levels on how to merge / override / disable this setting on multiple levels.
Changed in version 0.16.0: Controller and endpoint values are not merged anymore.
- final class dmr.settings.HttpSpec(*values)[source]#
Bases:
StrEnumKeys for our HTTP spec validation.
All rules can be disabled per endpoint and per controller. You can disable any of the validation rules we have here globally by:
>>> DMR_SETTINGS = { ... Settings.no_validate_http_spec: { ... HttpSpec.empty_response_body, ... }, ... }
- empty_request_body#
Disables validation that methods like
GETandHEADcan’t have request bodies.
- empty_response_body#
Disables validation that some status codes like
204must not have response bodies.
- header_name_server_managed#
Disables validation that headers like
Server,Keep-Alive, etc. can’t be included in response headers.
- header_name_syntax#
Disables validations that check name syntax to avoid headers like X Custom Name or @@@.
- cookie_name_syntax#
Disables validation that check name syntax for cookie names to avoid disallowed characters.
- cookie_semantics#
Disables validation that check if cookies semantix container proper values, like max_age must not be negative, or samesite=’none’ requires secure=True, etc
Streaming#
- dmr.settings.Settings.validate_events#
Default:
EMPTYShould we validate the events in all streams? Defaults to the value set in
validate_responsesfor convenience if this value isEMPTY.To disable the event validation globally, use:
settings.py#>>> DMR_SETTINGS = { ... Settings.validate_events: False, ... }
OpenAPI#
- dmr.settings.Settings.openapi_config#
Default:
OpenAPIConfig(title='Your Awesome Project', version='0.1.0')Metadata to be used in the OpenAPI schema.
See
OpenAPIConfigfor the available fields and their description. It can also be used to change the default OpenAPI spec version. Itssecurityfield is also the settings level ofsecurityfor all controllers and endpoints, see Customizing security.settings.py#>>> from dmr.openapi.config import OpenAPIConfig >>> DMR_SETTINGS = { ... Settings.openapi_config: OpenAPIConfig( ... title='My Amazing App', ... version='13.22.3', ... openapi_version='3.2.0', ... ), ... }
- dmr.settings.Settings.openapi_examples_seed#
Default:
EMPTYRandom seed to use when generating missing examples in the OpenAPI spec.
If set to
EMPTY, no examples are generated. Existing examples won’t be overridden.It only works if
'django-modern-rest[openapi]'extra is installed. If polyfactory package is missing, no examples are generated.settings.py#>>> DMR_SETTINGS = { ... Settings.openapi_examples_seed: 10, ... }
Note
This is a global setting, it cannot be changed per controller or per endpoint. Generated examples are stored on shared
components/schemasentries, which several endpoints and controllers can reference at once, so there’s no single endpoint to read a local seed from. See Examples generation.
- dmr.settings.Settings.openapi_static_cdn#
Default:
{}Optional mapping to switch OpenAPI renderers to CDN resources. Only renderers explicitly listed in this mapping will use CDN, the rest will use local bundled static files served by Django.
Supported keys are:
swagger: base URL toswagger-ui-dist(without file name)redoc: full URL toredoc.standalone.jsscalar: full URL to@scalar/api-referencestandalone bundlestoplight: base URL to@stoplight/elements(without file name)
You can also modify the exact versions that we use for each tool this way.
settings.py#>>> DMR_SETTINGS = { ... Settings.openapi_static_cdn: { ... 'swagger': 'https://cdn.jsdelivr.net/npm/swagger-ui-dist@5.32.1', ... 'redoc': 'https://cdn.redoc.ly/redoc/2.5.2/bundles/redoc.standalone.js', ... 'scalar': 'https://cdn.jsdelivr.net/npm/@scalar/api-reference@1.49.2/dist/browser/standalone.js', ... 'stoplight': 'https://unpkg.com/@stoplight/elements@9.0.16', ... }, ... }
Hacks#
- dmr.settings.Settings.django_treat_as_post#
Default:
frozenset({'PUT', 'PATCH'})By default Django only populates
django.http.HttpRequest.POSTanddjango.http.HttpRequest.FILESforPOSTrequests.Some parsers like
MultiPartParserrequire.POSTand.FILESto be set to work withBodyandFileMetadata.However, we build REST APIs where more methods are in use, not just
POST. So, we use this setting to populate.POSTand.FILESwhen parser is a subtype of eitherSupportsFileParsingorSupportsDjangoDefaultParsingand we work with components that require request body.Note
This is a really advanced setting. If you are not creating your own components or your own parsers - do not change it.
Environment variables#
- DMR_MAX_CACHE_SIZE#
Default:
1024We use
functools.lru_cache()in many places internally. For example:To create json encoders and decoders only once
To create type validation objects in
BaseEndpointOptimizerTo remember which parser and renderer were negotiated for a given
Content-Type/Acceptheader value inRequestNegotiatorandResponseNegotiator
You can control the size / memory usage with this setting.
Increase if you have a lot of different return types or if your clients send a lot of distinct
Acceptheaders.
- DMR_USE_COMPILED#
Default:
1We compile some modules to C-extensions with mypyc compilation. If you want to disable the extensions and fallback to pure Python implementation, set this variable to
0.It is only recommended for debugging. It should be set to
1in production for maximum speed.
API Reference#
- dmr.settings.resolve_setting(setting_name: Settings, *, import_string: bool = False) Any[source]#
Resolves setting by setting_name.
The result is cached using
@lru_cachefor performance. When testing with custom settings, you must callclear_settings_cache()before and after modifying Django settings to ensure the cache is invalidated properly.