Returning files¶
We support file and other binary responses.
Warning
Returning files via Python and Django in particular is very performance inefficient. It should not be used for anything serious.
Instead return files with S3-like systems or at least on a proxy-server level.
To do so, you indicate that you will return a file with
dmr.files.FileResponseSpec and specify a file renderer.
We provide dmr.renderers.FileRenderer for this case.
By default, FileResponseSpec() describes an inline file response.
It matches Django’s FileResponse default behavior:
1import io
2
3from django.http import FileResponse
4
5from dmr import Controller, validate
6from dmr.files import FileResponseSpec
7from dmr.plugins.pydantic import PydanticSerializer
8from dmr.renderers import FileRenderer
9
10
11class InlineFileController(Controller[PydanticSerializer]):
12 @validate(
13 FileResponseSpec(),
14 renderers=[FileRenderer('text/plain')],
15 )
16 def get(self) -> FileResponse:
17 return FileResponse(
18 io.BytesIO(b'Hello'),
19 content_type='text/plain',
20 )
21
Run result
$ curl http://127.0.0.1:8000/api/file/ -D - -X GET
HTTP/1.1 200 OK
date: Tue, 26 May 2026 19:09:13 GMT
server: uvicorn
Content-Type: text/plain
Content-Length: 5
X-Frame-Options: DENY
Vary: Accept-Language
Content-Language: en
X-Content-Type-Options: nosniff
Referrer-Policy: same-origin
Cross-Origin-Opener-Policy: same-origin
Hello
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/inlinefilecontroller/": {
"get": {
"deprecated": false,
"operationId": "getInlinefilecontrollerApiInlinefilecontroller",
"responses": {
"200": {
"content": {
"text/plain": {
"schema": {
"format": "binary",
"type": "string"
}
}
},
"description": "OK",
"headers": {
"Content-Length": {
"required": true,
"schema": {
"type": "string"
}
}
}
},
"406": {
"content": {
"text/plain": {
"schema": {
"$ref": "#/components/schemas/ErrorModel"
}
}
},
"description": "Raised when provided `Accept` header cannot be satisfied"
},
"422": {
"content": {
"text/plain": {
"schema": {
"$ref": "#/components/schemas/ErrorModel"
}
}
},
"description": "Raised when returned response does not match the response schema"
}
}
}
}
}
}
Set as_attachment=True when Django’s
django.http.FileResponse is returned as an attachment. In this mode
Content-Disposition
is always set and usually contains the filename sent to the client:
1import pathlib
2from typing import Final
3
4from django.http import FileResponse
5
6from dmr import Controller, validate
7from dmr.files import FileResponseSpec
8from dmr.plugins.pydantic import PydanticSerializer
9from dmr.renderers import FileRenderer
10
11_FILEPATH: Final = pathlib.Path('examples/components/receipt.txt')
12
13
14class FileController(Controller[PydanticSerializer]):
15 @validate(
16 FileResponseSpec(as_attachment=True),
17 renderers=[FileRenderer('text/plain')],
18 )
19 def get(self) -> FileResponse:
20 return FileResponse(
21 _FILEPATH.open(mode='rb'),
22 filename='receipt.txt',
23 as_attachment=True,
24 content_type='text/plain',
25 )
26
Run result
$ curl http://127.0.0.1:8000/api/file/ -D - -X GET
HTTP/1.1 200 OK
date: Tue, 26 May 2026 19:09:13 GMT
server: uvicorn
Content-Type: text/plain
Content-Length: 16
Content-Disposition: attachment; filename="receipt.txt"
X-Frame-Options: DENY
Vary: Accept-Language
Content-Language: en
X-Content-Type-Options: nosniff
Referrer-Policy: same-origin
Cross-Origin-Opener-Policy: same-origin
Example receipt
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/filecontroller/": {
"get": {
"deprecated": false,
"operationId": "getFilecontrollerApiFilecontroller",
"responses": {
"200": {
"content": {
"text/plain": {
"schema": {
"format": "binary",
"type": "string"
}
}
},
"description": "OK",
"headers": {
"Content-Disposition": {
"required": true,
"schema": {
"type": "string"
}
},
"Content-Length": {
"required": true,
"schema": {
"type": "string"
}
}
}
},
"406": {
"content": {
"text/plain": {
"schema": {
"$ref": "#/components/schemas/ErrorModel"
}
}
},
"description": "Raised when provided `Accept` header cannot be satisfied"
},
"422": {
"content": {
"text/plain": {
"schema": {
"$ref": "#/components/schemas/ErrorModel"
}
}
},
"description": "Raised when returned response does not match the response schema"
}
}
}
}
}
}
The difference comes from the Content-Disposition HTTP header:
it tells clients whether the response body is expected to be displayed inline
or downloaded as an attachment.
API Reference¶
- class dmr.files.FileBody[source]¶
Special type that indicates that response returns a file body.
- classmethod encoding(model: Any, schema: Schema) dict[str, Encoding] | None[source]¶
Returns the openapi encoding for the defined media type.
- class dmr.files.FileResponseSpec(return_type: type[FileBody] = <class 'dmr.files.FileBody'>, *, status_code: HTTPStatus = HTTPStatus.OK, headers: Mapping[str, ~dmr.headers.HeaderSpec] | None=None, cookies: Mapping[str, CookieSpec] | None=None, limit_to_content_types: Set[str] | None = None, streaming: bool = False, description: str | None = None, links: dict[str, Link | Reference] | None=None, as_attachment: bool = False, file_body: type[FileBody] = <class 'dmr.files.FileBody'>)[source]¶
Special
ResponseSpecsubclass for files.- as_attachment¶
Marks responses with
Content-Dispositionheader as required. Use together withFileResponse(as_attachment=True).- Type:
- file_body¶
Model to be used for file body schema generation.
- Type:
Changed in version 0.10.0: Added
as_attachmentparameter that can mark files that should be sent viaContent-Dispositionheader. Similar to Django’sas_attachmentparameter indjango.http.FileResponse.- get_schema(metadata: EndpointMetadata, serializer: type[BaseSerializer], context: OpenAPIContext) Response[source]¶
Customize schema for the file response.
- dmr.files.file_response_headers(headers: Mapping[str, HeaderSpec] | None, *, as_attachment: bool) Mapping[str, HeaderSpec][source]¶
Build headers expected from
FileResponse.