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, 09 Apr 2026 14:42:19 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, 09 Apr 2026 14:42:20 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, 09 Apr 2026 14:42:20 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