Semantic schema#
django-modern-rest has a lot of special features around generating
internal schema for the response validation.
The same schema is later used to build OpenAPI spec.
Our design goal is to validate the most semantic schema possible.
Semantic schema generation#
First of all, what is a semantic schema? We define it as a schema that knows all the semantics of the given API.
What response schemas can it return?
What content types?
What status codes?
Which cookies and headers can it set?
In many frameworks these details are not important. However, in our experience – these details are very important when dealing with any big project / integration.
How do we build this semantic schema?
We enforce request and response validation. No status codes that are not specified in the schema are allowed. No extra / missing headers, no extra / missing cookies. If something goes against the schema – it is rejected by the validation
We try to make the schema building process user-friendly. For example, when you add auth to your endpoint, auth instance will inject its part of the schema into the main one. This way you will see
401response in the schema for all the endpoints which use auth. We surely allow to redefine any of this behavior
Note
We allow users to make their schemas as dumb as regular ones
with just a single setting: semantic_schema.
It will disable all semantic schema generation. Including response specs,
security requirements, security schemes.
You would still have the very basic OpenAPI schema, it would be similar to ones that FastAPI and others provide.
You can also have more control over schema generation with:
Tip
Turn it off together with dmr.settings.Settings.validate_responses
if you don’t need any of this schema generation / validation stuff.
The core part of the schema generation
is dmr.metadata.EndpointMetadata.collect_response_specs()
which collects all the responses’ metadata in a single place.
Each ResponseSpec knows what it returns in great detail.
And SecuritySchemeGenerator
for security requirements and security schemes generation.
Customizing schema generation#
Here’s how our settings priority works:
We check exact setting for the specific thing we try to generate, like
semantic_responsesandsemantic_authIf it is not set, we fallback to
semantic_schemavalue
For example, if you disabled semantic_schema=False generation globally
and then enabled semantic_auth=True for a single controller,
it will generate semantic security schemes and security requirements
just for endpoints in this controller.
Semantic schema#
To disable all semantic schema generation you can disable it on several levels:
Pass semantic_schema parameter
to modify or validate.
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 def get(self) -> str:
10 return 'will have semantic schema'
11
12 @modify(semantic_schema=False)
13 def post(self) -> str:
14 return 'will not have semantic schema at all'
15
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"
},
"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": []
}
]
},
"post": {
"operationId": "postAPIControllerApiApicontroller",
"responses": {
"201": {
"content": {
"application/json": {
"schema": {
"type": "string"
}
}
},
"description": "Created"
}
}
}
}
}
}
Customize semantic_schema attribute.
1from dmr import Controller
2from dmr.plugins.pydantic import PydanticFastSerializer
3from dmr.security.jwt import HeaderJWTSyncAuth
4
5
6class APIController(Controller[PydanticFastSerializer]):
7 auth = (HeaderJWTSyncAuth(),)
8 semantic_schema = False
9
10 def get(self) -> str:
11 return 'will not have semantic schema at all'
12
13 def post(self) -> str:
14 return 'will not have semantic schema at all'
15
OpenAPI Schema
Preview openapi.json
{
"components": {},
"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"
}
}
},
"post": {
"operationId": "postAPIControllerApiApicontroller",
"responses": {
"201": {
"content": {
"application/json": {
"schema": {
"type": "string"
}
}
},
"description": "Created"
}
}
}
}
}
}
Exclude some semantic responses globally:
1>>> from dmr.settings import Settings, DMR_SETTINGS
2
3>>> DMR_SETTINGS = {Settings.semantic_schema: False}
This will disable both semantic responses and semantic auth.
Semantic responses#
All endpoints by default generate semantic responses. However, we allow several customizations.
You can disable some specific semantic responses generation by status code:
Pass exclude_semantic_responses parameter
to modify or validate.
1from http import HTTPStatus
2
3from dmr import Controller, modify
4from dmr.plugins.pydantic import PydanticFastSerializer
5
6
7class APIController(Controller[PydanticFastSerializer]):
8 def get(self) -> str:
9 return 'will have semantic responses'
10
11 @modify(exclude_semantic_responses={HTTPStatus.UNPROCESSABLE_ENTITY})
12 def post(self) -> str:
13 return 'will not have semantic response with 422 status code'
14
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"
}
}
},
"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"
},
"406": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorModel"
}
}
},
"description": "Raised when provided `Accept` header cannot be satisfied"
}
}
}
}
}
}
Customize exclude_semantic_responses
attribute.
1from http import HTTPStatus
2
3from dmr import Controller
4from dmr.plugins.pydantic import PydanticFastSerializer
5
6
7class APIController(Controller[PydanticFastSerializer]):
8 exclude_semantic_responses = frozenset((HTTPStatus.UNPROCESSABLE_ENTITY,))
9
10 async def get(self) -> str:
11 return 'will not have semantic response with 422 status code'
12
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"
}
}
},
"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"
}
}
}
}
}
}
Exclude some semantic responses globally:
1>>> from dmr.settings import Settings, DMR_SETTINGS
2
3>>> DMR_SETTINGS = {Settings.exclude_semantic_responses: {422}}
Or disable semantic responses completely:
Pass semantic_responses parameter
to modify or validate.
1from dmr import Controller, modify
2from dmr.plugins.pydantic import PydanticFastSerializer
3
4
5class APIController(Controller[PydanticFastSerializer]):
6 def get(self) -> str:
7 return 'will have semantic responses'
8
9 @modify(semantic_responses=False)
10 def post(self) -> str:
11 return 'will not have semantic responses'
12
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"
}
}
},
"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"
}
}
}
}
}
}
Customize semantic_responses attribute.
1from dmr import Controller
2from dmr.plugins.pydantic import PydanticFastSerializer
3
4
5class APIController(Controller[PydanticFastSerializer]):
6 semantic_responses = False
7
8 async def get(self) -> str:
9 return 'will not have semantic responses'
10
OpenAPI Schema
Preview openapi.json
{
"components": {},
"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"
}
}
}
}
}
}
Disable semantic responses globally
via semantic_responses setting.
1>>> from dmr.settings import Settings, DMR_SETTINGS
2
3>>> DMR_SETTINGS = {Settings.semantic_responses: False}
Semantic auth#
All SyncAuth and AsyncAuth
instances by default generate semantic
security requirement and security schemes.
However, we allow several customizations.
You can disable some specific semantic security schemes and security requirements generation by scheme name:
Pass exclude_semantic_auth parameter
to modify or validate.
1from dmr import Controller, modify
2from dmr.plugins.pydantic import PydanticFastSerializer
3from dmr.security.django_session import DjangoSessionSyncAuth
4from dmr.security.jwt import HeaderJWTSyncAuth
5
6
7class APIController(Controller[PydanticFastSerializer]):
8 auth = (HeaderJWTSyncAuth(), DjangoSessionSyncAuth())
9
10 def get(self) -> str:
11 return 'will have all semantic auth'
12
13 @modify(exclude_semantic_auth={'jwt'})
14 def post(self) -> str:
15 return 'will not have jwt semantic auth'
16
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"
},
"django_session": {
"description": "Reusing standard Django auth flow for API",
"in": "cookie",
"name": "sessionid",
"type": "apiKey"
},
"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"
},
"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": []
},
{
"django_session": []
}
]
},
"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"
}
}
}
},
"403": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorModel"
}
}
},
"description": "Raised when CSRF check failed"
},
"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": [
{
"csrf": [],
"django_session": []
}
]
}
}
}
}
Customize exclude_semantic_auth
attribute.
1from dmr import Controller
2from dmr.plugins.pydantic import PydanticFastSerializer
3from dmr.security.django_session import DjangoSessionSyncAuth
4from dmr.security.jwt import HeaderJWTSyncAuth
5
6
7class APIController(Controller[PydanticFastSerializer]):
8 auth = (HeaderJWTSyncAuth(), DjangoSessionSyncAuth())
9 exclude_semantic_auth = frozenset(('jwt',))
10
11 def get(self) -> str:
12 return 'will not have jwt semantic auth'
13
14 def post(self) -> str:
15 return 'will not have jwt semantic auth'
16
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"
},
"django_session": {
"description": "Reusing standard Django auth flow for API",
"in": "cookie",
"name": "sessionid",
"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": [
{
"django_session": []
}
]
},
"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"
}
}
}
},
"403": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorModel"
}
}
},
"description": "Raised when CSRF check failed"
},
"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": [
{
"csrf": [],
"django_session": []
}
]
}
}
}
}
Exclude some semantic auth schemes and requirements globally:
1>>> from dmr.settings import Settings, DMR_SETTINGS
2
3>>> DMR_SETTINGS = {Settings.exclude_semantic_auth: {'jwt'}}
Or disable semantic auth completely:
Pass semantic_auth parameter
to modify or validate.
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 def get(self) -> str:
10 return 'will have semantic auth'
11
12 @modify(semantic_auth=False)
13 def post(self) -> str:
14 return 'will not have semantic auth'
15
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"
},
"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": []
}
]
},
"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"
}
}
}
}
}
}
Customize semantic_auth attribute.
1from dmr import Controller
2from dmr.plugins.pydantic import PydanticFastSerializer
3from dmr.security.jwt import HeaderJWTSyncAuth
4
5
6class APIController(Controller[PydanticFastSerializer]):
7 auth = (HeaderJWTSyncAuth(),)
8 semantic_auth = False
9
10 def get(self) -> str:
11 return 'will not have semantic auth'
12
13 def post(self) -> str:
14 return 'will not have semantic auth'
15
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"
}
}
},
"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"
}
}
},
"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"
}
}
}
}
}
}
Disable semantic auth globally
via semantic_auth setting.
1>>> from dmr.settings import Settings, DMR_SETTINGS
2
3>>> DMR_SETTINGS = {Settings.semantic_auth: False}
Note
When disabling semantic auth on controller / endpoint levels, security schemes can still be registered if some other endpoints need them.
You can mix these configurations in any way that makes sense for your application.
Excluding views from OpenAPI#
You can exclude individual endpoints, controllers, or entire routers from OpenAPI. Excluded routes continue to work normally (including schema validation), but are not visible in the generated specification.
We support three levels of configuration with this feature:
Pass ignore_from_spec parameter
to modify or validate
on endpoints that you want to ignore.
1from dmr import Controller, modify
2from dmr.plugins.pydantic import PydanticFastSerializer
3
4
5class BillController(Controller[PydanticFastSerializer]):
6 @modify(ignore_from_spec=True)
7 async def get(self) -> str:
8 return 'It works!'
9
Run result
$ curl http://127.0.0.1:8000/api/username/ -X GET
"It works!"
OpenAPI Schema
Preview openapi.json
{
"components": {},
"info": {
"title": "Django Modern Rest",
"version": "0.1.0"
},
"openapi": "3.2.0",
"paths": {}
}
Customize ignore_from_spec attribute.
Endpoints can override this value with ignore_from_spec parameter.
1from dmr import Controller, modify
2from dmr.plugins.pydantic import PydanticFastSerializer
3
4
5class BillController(Controller[PydanticFastSerializer]):
6 ignore_from_spec = True
7
8 def get(self) -> str:
9 return 'It is hidden'
10
11 @modify(ignore_from_spec=False)
12 def post(self) -> str:
13 return 'It is in OpenAPI'
14
Run result
$ curl http://127.0.0.1:8000/api/username/ -X GET
"It is hidden"
$ curl http://127.0.0.1:8000/api/username/ -X POST
"It is in OpenAPI"
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"
}
}
},
"info": {
"title": "Django Modern Rest",
"version": "0.1.0"
},
"openapi": "3.2.0",
"paths": {
"/api/billcontroller/": {
"post": {
"operationId": "postBillControllerApiBillcontroller",
"responses": {
"201": {
"content": {
"application/json": {
"schema": {
"type": "string"
}
}
},
"description": "Created"
},
"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"
}
}
}
}
}
}
Pass ignore_from_spec parameter
to Router to exclude all routes from this router.
When routers are nested, ignore_from_spec excludes the whole
router subtree. Runtime URL routing is not affected.
1from dmr.openapi import build_schema
2from dmr.openapi.views import OpenAPIJsonView
3from dmr.routing import Router, path
4from examples.getting_started.msgspec_controller import UserController
5
6private_router = Router(
7 'private/',
8 [
9 path('users/', UserController.as_view()),
10 ],
11 ignore_from_spec=True,
12)
13
14router = Router(
15 'api/',
16 [
17 path('users/', UserController.as_view()),
18 ],
19)
20router.include(private_router)
21
22schema = build_schema(router)
23
24urlpatterns = [
25 router.to_urlpatterns(namespace='api'),
26 path('docs/openapi.json/', OpenAPIJsonView.as_view(schema)),
27]
OpenAPI Schema
Preview openapi.json
{
"components": {
"schemas": {
"ErrorDetail": {
"description": "Base schema for error details description.",
"properties": {
"loc": {
"items": {
"anyOf": [
{
"type": "integer"
},
{
"type": "string"
}
]
},
"type": "array"
},
"msg": {
"type": "string"
},
"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"
},
"type": "array"
}
},
"required": [
"detail"
],
"title": "ErrorModel",
"type": "object"
},
"UserCreateModel": {
"properties": {
"email": {
"type": "string"
}
},
"required": [
"email"
],
"title": "UserCreateModel",
"type": "object"
},
"UserModel": {
"properties": {
"email": {
"type": "string"
},
"uid": {
"format": "uuid",
"type": "string"
}
},
"required": [
"email",
"uid"
],
"title": "UserModel",
"type": "object"
}
}
},
"info": {
"title": "Django Modern Rest",
"version": "0.1.0"
},
"openapi": "3.2.0",
"paths": {
"/api/users/": {
"post": {
"operationId": "postUserControllerApiUsers",
"requestBody": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UserCreateModel"
}
}
},
"required": true
},
"responses": {
"201": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UserModel"
}
}
},
"description": "Created"
},
"400": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorModel"
}
}
},
"description": "Raised when request components cannot be parsed"
},
"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"
}
}
}
}
}
}