Defining OPTIONS or meta method#
RFC 9110
defines the OPTIONS HTTP method, but sadly Django’s
View which we use as a base class
for all controllers, already has
options() method.
It would generate a typing error to redefine it with a different signature that we need for our endpoints.
That’s why we created our own meta controller method as a replacement
for older Django’s options name.
To use it you have two options:
Use
MetaMixinorAsyncMetaMixinwith the default implementation: we provideAllowheader with all the allowed HTTP methods in this controllerDefine the
metaendpoint yourself and provide a custom implementation
Using pre-defined mixins#
We have two versions: for sync and async controllers. Their features are identical:
1from dmr import Controller
2from dmr.options_mixins import MetaMixin
3from dmr.plugins.pydantic import PydanticSerializer
4
5
6class SyncMetaController(
7 MetaMixin,
8 Controller[PydanticSerializer],
9):
10 def get(self) -> str:
11 return 'response from GET'
12
Run result
$ curl http://127.0.0.1:8000/api/settings/ -D - -X OPTIONS
HTTP/1.1 204 No Content
date: Thu, 16 Jul 2026 09:35:58 GMT
server: uvicorn
Allow: GET, OPTIONS
Content-Type: application/json
X-Frame-Options: DENY
Vary: Accept-Language
Content-Language: en
Content-Length: 0
X-Content-Type-Options: nosniff
Referrer-Policy: same-origin
Cross-Origin-Opener-Policy: same-origin
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": {}
},
"info": {
"title": "Django Modern Rest",
"version": "0.1.0"
},
"openapi": "3.2.0",
"paths": {
"/api/syncmetacontroller/": {
"get": {
"deprecated": false,
"operationId": "getSyncmetacontrollerApiSyncmetacontroller",
"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"
}
}
},
"options": {
"deprecated": false,
"operationId": "optionsSyncmetacontrollerApiSyncmetacontroller",
"responses": {
"204": {
"content": {
"application/json": {
"schema": {
"type": "null"
}
}
},
"description": "No Content",
"headers": {
"Allow": {
"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"
}
},
"summary": "Default sync implementation for ``OPTIONS`` http method."
}
}
}
}
1from dmr import Controller
2from dmr.options_mixins import AsyncMetaMixin
3from dmr.plugins.pydantic import PydanticSerializer
4
5
6class AsyncMetaController(
7 AsyncMetaMixin,
8 Controller[PydanticSerializer],
9):
10 async def get(self) -> str:
11 return 'response from GET'
12
Run result
$ curl http://127.0.0.1:8000/api/settings/ -D - -X OPTIONS
HTTP/1.1 204 No Content
date: Thu, 16 Jul 2026 09:35:58 GMT
server: uvicorn
Allow: GET, OPTIONS
Content-Type: application/json
X-Frame-Options: DENY
Vary: Accept-Language
Content-Language: en
Content-Length: 0
X-Content-Type-Options: nosniff
Referrer-Policy: same-origin
Cross-Origin-Opener-Policy: same-origin
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": {}
},
"info": {
"title": "Django Modern Rest",
"version": "0.1.0"
},
"openapi": "3.2.0",
"paths": {
"/api/asyncmetacontroller/": {
"get": {
"deprecated": false,
"operationId": "getAsyncmetacontrollerApiAsyncmetacontroller",
"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"
}
}
},
"options": {
"deprecated": false,
"operationId": "optionsAsyncmetacontrollerApiAsyncmetacontroller",
"responses": {
"204": {
"content": {
"application/json": {
"schema": {
"type": "null"
}
}
},
"description": "No Content",
"headers": {
"Allow": {
"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"
}
},
"summary": "Default async implementation for ``OPTIONS`` http method."
}
}
}
}
Both of them:
Provide
metamethod sync or asyncProvide the same response spec for the OpenAPI schema
Custom meta implementation#
Since our mixins do not anything magical, you can write our own version, if you need a behavior change, for example.
Here’s an example of a custom meta implementation:
1from http import HTTPStatus
2
3from django.http import HttpResponse
4
5from dmr import Controller, HeaderSpec, ResponseSpec, validate
6from dmr.plugins.msgspec import MsgspecSerializer
7
8
9class SettingsController(Controller[MsgspecSerializer]):
10 def get(self) -> str:
11 return 'default get setting'
12
13 def post(self) -> str:
14 return 'default post setting'
15
16 # `meta` response is also validated, schema is required:
17 @validate(
18 ResponseSpec(
19 None,
20 status_code=HTTPStatus.NO_CONTENT,
21 headers={'Allow': HeaderSpec()},
22 ),
23 )
24 def meta(self) -> HttpResponse: # Handles `OPTIONS` http method
25 return self.to_response(
26 None,
27 status_code=HTTPStatus.NO_CONTENT,
28 headers={
29 'Allow': ', '.join(
30 method for method in sorted(self.api_endpoints.keys())
31 ),
32 },
33 )
34
Run result
$ curl http://127.0.0.1:8000/api/settings/ -D - -X OPTIONS
HTTP/1.1 204 No Content
date: Thu, 16 Jul 2026 09:35:59 GMT
server: uvicorn
Allow: GET, OPTIONS, POST
Content-Type: application/json
X-Frame-Options: DENY
Vary: Accept-Language
Content-Language: en
Content-Length: 0
X-Content-Type-Options: nosniff
Referrer-Policy: same-origin
Cross-Origin-Opener-Policy: same-origin
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"
}
},
"securitySchemes": {}
},
"info": {
"title": "Django Modern Rest",
"version": "0.1.0"
},
"openapi": "3.2.0",
"paths": {
"/api/settingscontroller/": {
"get": {
"deprecated": false,
"operationId": "getSettingscontrollerApiSettingscontroller",
"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"
}
}
},
"options": {
"deprecated": false,
"operationId": "optionsSettingscontrollerApiSettingscontroller",
"responses": {
"204": {
"content": {
"application/json": {
"schema": {
"type": "null"
}
}
},
"description": "No Content",
"headers": {
"Allow": {
"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": {
"deprecated": false,
"operationId": "postSettingscontrollerApiSettingscontroller",
"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"
}
}
}
}
}
}
You would need to:
Define
metamethod (sync or async) with the desired implementationProvide the required response spec