Requests#
Request body#
The body of HTTP requests can be accessed using the special data
parameter in a handler function.
from typing import Dict
from litestar import Litestar, post
@post(path="/")
async def index(data: Dict[str, str]) -> Dict[str, str]:
return data
app = Litestar(route_handlers=[index])
from litestar import Litestar, post
@post(path="/")
async def index(data: dict[str, str]) -> dict[str, str]:
return data
app = Litestar(route_handlers=[index])
The type of data
can be any supported type, including
Pydantic models
Arbitrary stdlib types
Types supported via plugins
from dataclasses import dataclass
from litestar import Litestar, post
@dataclass
class User:
id: int
name: str
@post(path="/")
async def index(data: User) -> User:
return data
app = Litestar(route_handlers=[index])
Validation and customization of OpenAPI documentation#
With the help of Body
, you have fine-grained control over the validation
of the request body, and can also customize the OpenAPI documentation:
from dataclasses import dataclass
from typing_extensions import Annotated
from litestar import Litestar, post
from litestar.params import Body
@dataclass
class User:
id: int
name: str
@post(path="/")
async def create_user(
data: Annotated[User, Body(title="Create User", description="Create a new user.")],
) -> User:
return data
app = Litestar(route_handlers=[create_user])
from dataclasses import dataclass
from typing import Annotated
from litestar import Litestar, post
from litestar.params import Body
@dataclass
class User:
id: int
name: str
@post(path="/")
async def create_user(
data: Annotated[User, Body(title="Create User", description="Create a new user.")],
) -> User:
return data
app = Litestar(route_handlers=[create_user])
Content-type#
By default, Litestar will try to parse the request body as JSON. While this may be desired
in most cases, you might want to specify a different type. You can do so by passing a
RequestEncodingType
to Body
. This will also
help to generate the correct media-type in the OpenAPI schema.
URL Encoded Form Data#
To access data sent as url-encoded form data,
i.e. application/x-www-form-urlencoded
Content-Type header, use Body
and specify
RequestEncodingType.URL_ENCODED
as the media_type
:
from dataclasses import dataclass
from typing_extensions import Annotated
from litestar import Litestar, post
from litestar.enums import RequestEncodingType
from litestar.params import Body
@dataclass
class User:
id: int
name: str
@post(path="/")
async def create_user(
data: Annotated[User, Body(media_type=RequestEncodingType.URL_ENCODED)],
) -> User:
return data
app = Litestar(route_handlers=[create_user])
from dataclasses import dataclass
from typing import Annotated
from litestar import Litestar, post
from litestar.enums import RequestEncodingType
from litestar.params import Body
@dataclass
class User:
id: int
name: str
@post(path="/")
async def create_user(
data: Annotated[User, Body(media_type=RequestEncodingType.URL_ENCODED)],
) -> User:
return data
app = Litestar(route_handlers=[create_user])
Note
URL encoded data is inherently less versatile than JSON data - for example, it cannot handle complex dictionaries and deeply nested data. It should only be used for simple data structures.
MultiPart Form Data#
You can access data uploaded using a request with a
multipart/form-data
Content-Type header by specifying it in the Body
function:
from dataclasses import dataclass
from typing_extensions import Annotated
from litestar import Litestar, post
from litestar.enums import RequestEncodingType
from litestar.params import Body
@dataclass
class User:
id: int
name: str
@post(path="/")
async def create_user(
data: Annotated[User, Body(media_type=RequestEncodingType.MULTI_PART)],
) -> User:
return data
app = Litestar(route_handlers=[create_user])
from dataclasses import dataclass
from typing import Annotated
from litestar import Litestar, post
from litestar.enums import RequestEncodingType
from litestar.params import Body
@dataclass
class User:
id: int
name: str
@post(path="/")
async def create_user(
data: Annotated[User, Body(media_type=RequestEncodingType.MULTI_PART)],
) -> User:
return data
app = Litestar(route_handlers=[create_user])
File uploads#
In case of files uploaded, Litestar transforms the results into an instance
of UploadFile
class, which offer a convenient
interface for working with files. Therefore, you need to type your file uploads accordingly.
To access a single file simply type data
as UploadFile
:
from typing_extensions import Annotated
from litestar import Litestar, MediaType, post
from litestar.datastructures import UploadFile
from litestar.enums import RequestEncodingType
from litestar.params import Body
@post(path="/", media_type=MediaType.TEXT)
async def handle_file_upload(
data: Annotated[UploadFile, Body(media_type=RequestEncodingType.MULTI_PART)],
) -> str:
content = await data.read()
filename = data.filename
return f"{filename}, {content.decode()}"
app = Litestar(route_handlers=[handle_file_upload])
from typing import Annotated
from litestar import Litestar, MediaType, post
from litestar.datastructures import UploadFile
from litestar.enums import RequestEncodingType
from litestar.params import Body
@post(path="/", media_type=MediaType.TEXT)
async def handle_file_upload(
data: Annotated[UploadFile, Body(media_type=RequestEncodingType.MULTI_PART)],
) -> str:
content = await data.read()
filename = data.filename
return f"{filename}, {content.decode()}"
app = Litestar(route_handlers=[handle_file_upload])
from typing_extensions import Annotated
from litestar import Litestar, MediaType, post
from litestar.datastructures import UploadFile
from litestar.enums import RequestEncodingType
from litestar.params import Body
@post(path="/", media_type=MediaType.TEXT, sync_to_thread=False)
def handle_file_upload(
data: Annotated[UploadFile, Body(media_type=RequestEncodingType.MULTI_PART)],
) -> str:
content = data.file.read()
filename = data.filename
return f"{filename}, {content.decode()}"
app = Litestar(route_handlers=[handle_file_upload])
from typing import Annotated
from litestar import Litestar, MediaType, post
from litestar.datastructures import UploadFile
from litestar.enums import RequestEncodingType
from litestar.params import Body
@post(path="/", media_type=MediaType.TEXT, sync_to_thread=False)
def handle_file_upload(
data: Annotated[UploadFile, Body(media_type=RequestEncodingType.MULTI_PART)],
) -> str:
content = data.file.read()
filename = data.filename
return f"{filename}, {content.decode()}"
app = Litestar(route_handlers=[handle_file_upload])
Technical details
UploadFile
wraps
SpooledTemporaryFile
so it can be used asynchronously. Inside a synchronous
function we don’t need this wrapper, so we can use its read
method directly.
Multiple files#
To access multiple files with known filenames, you can use a pydantic model:
from typing import Any, Dict
from pydantic import BaseConfig, BaseModel
from typing_extensions import Annotated
from litestar import Litestar, post
from litestar.datastructures import UploadFile
from litestar.enums import RequestEncodingType
from litestar.params import Body
class FormData(BaseModel):
cv: UploadFile
diploma: UploadFile
class Config(BaseConfig):
arbitrary_types_allowed = True
@post(path="/")
async def handle_file_upload(
data: Annotated[FormData, Body(media_type=RequestEncodingType.MULTI_PART)],
) -> Dict[str, Any]:
cv_content = await data.cv.read()
diploma_content = await data.diploma.read()
return {"cv": cv_content.decode(), "diploma": diploma_content.decode()}
app = Litestar(route_handlers=[handle_file_upload])
from typing import Any
from pydantic import BaseConfig, BaseModel
from typing import Annotated
from litestar import Litestar, post
from litestar.datastructures import UploadFile
from litestar.enums import RequestEncodingType
from litestar.params import Body
class FormData(BaseModel):
cv: UploadFile
diploma: UploadFile
class Config(BaseConfig):
arbitrary_types_allowed = True
@post(path="/")
async def handle_file_upload(
data: Annotated[FormData, Body(media_type=RequestEncodingType.MULTI_PART)],
) -> dict[str, Any]:
cv_content = await data.cv.read()
diploma_content = await data.diploma.read()
return {"cv": cv_content.decode(), "diploma": diploma_content.decode()}
app = Litestar(route_handlers=[handle_file_upload])
Files as a dictionary#
If you do not care about parsing and validation and only want to access the form data as a dictionary, you can use a dict
instead:
from typing import Dict
from typing_extensions import Annotated
from litestar import Litestar, post
from litestar.datastructures import UploadFile
from litestar.enums import RequestEncodingType
from litestar.params import Body
@post(path="/")
async def handle_file_upload(
data: Annotated[Dict[str, UploadFile], Body(media_type=RequestEncodingType.MULTI_PART)],
) -> Dict[str, str]:
file_contents = {}
for name, file in data.items():
content = await file.read()
file_contents[name] = content.decode()
return file_contents
app = Litestar(route_handlers=[handle_file_upload])
from typing import Annotated
from litestar import Litestar, post
from litestar.datastructures import UploadFile
from litestar.enums import RequestEncodingType
from litestar.params import Body
@post(path="/")
async def handle_file_upload(
data: Annotated[dict[str, UploadFile], Body(media_type=RequestEncodingType.MULTI_PART)],
) -> dict[str, str]:
file_contents = {}
for name, file in data.items():
content = await file.read()
file_contents[name] = content.decode()
return file_contents
app = Litestar(route_handlers=[handle_file_upload])
Files as a list#
Finally, you can also access the files as a list without the filenames:
from typing import Dict, List
from typing_extensions import Annotated
from litestar import Litestar, post
from litestar.datastructures import UploadFile
from litestar.enums import RequestEncodingType
from litestar.params import Body
@post(path="/")
async def handle_file_upload(
data: Annotated[List[UploadFile], Body(media_type=RequestEncodingType.MULTI_PART)],
) -> Dict[str, str]:
file_contents = {}
for file in data:
content = await file.read()
file_contents[file.filename] = content.decode()
return file_contents
app = Litestar(route_handlers=[handle_file_upload])
from typing import Annotated
from litestar import Litestar, post
from litestar.datastructures import UploadFile
from litestar.enums import RequestEncodingType
from litestar.params import Body
@post(path="/")
async def handle_file_upload(
data: Annotated[list[UploadFile], Body(media_type=RequestEncodingType.MULTI_PART)],
) -> dict[str, str]:
file_contents = {}
for file in data:
content = await file.read()
file_contents[file.filename] = content.decode()
return file_contents
app = Litestar(route_handlers=[handle_file_upload])
MessagePack data#
To receive MessagePack data, specify the appropriate Content-Type
for Body
, by using RequestEncodingType.MESSAGEPACK
:
from typing import Any, Dict
from typing_extensions import Annotated
from litestar import Litestar, post
from litestar.enums import RequestEncodingType
from litestar.params import Body
@post(path="/", sync_to_thread=False)
def msgpack_handler(
data: Annotated[Dict[str, Any], Body(media_type=RequestEncodingType.MESSAGEPACK)],
) -> Dict[str, Any]:
# This will try to parse the request body as `MessagePack` regardless of the
# `Content-Type`
return data
app = Litestar(route_handlers=[msgpack_handler])
from typing import Any
from typing import Annotated
from litestar import Litestar, post
from litestar.enums import RequestEncodingType
from litestar.params import Body
@post(path="/", sync_to_thread=False)
def msgpack_handler(
data: Annotated[dict[str, Any], Body(media_type=RequestEncodingType.MESSAGEPACK)],
) -> dict[str, Any]:
# This will try to parse the request body as `MessagePack` regardless of the
# `Content-Type`
return data
app = Litestar(route_handlers=[msgpack_handler])
Custom Request#
New in version 2.7.0.
Litestar supports custom request_class
instances, which can be used to further configure the default Request
.
The example below illustrates how to implement custom request class for the whole application.
Example of a custom request at the application level
from litestar import Litestar, Request, get
from litestar.connection.base import empty_receive, empty_send
from litestar.enums import HttpMethod
from litestar.types import Receive, Scope, Send
KITTEN_NAMES_MAP = {
HttpMethod.GET: "Whiskers",
}
class CustomRequest(Request):
"""Enrich request with the kitten name."""
__slots__ = ("kitten_name",)
def __init__(self, scope: Scope, receive: Receive = empty_receive, send: Send = empty_send) -> None:
"""Initialize CustomRequest class."""
super().__init__(scope=scope, receive=receive, send=send)
self.kitten_name = KITTEN_NAMES_MAP.get(scope["method"], "Mittens")
@get(path="/kitten-name", sync_to_thread=False)
def get_kitten_name(request: CustomRequest) -> str:
"""Get kitten name based on the HTTP method."""
return request.kitten_name
app = Litestar(
route_handlers=[get_kitten_name],
request_class=CustomRequest,
debug=True,
)
Layered architecture
Request classes are part of Litestar’s layered architecture, which means you can set a request class on every layer of the application. If you have set a request class on multiple layers, the layer closest to the route handler will take precedence.
You can read more about this in the Layered architecture section
Limits#
Body size#
A limit for the allowed request body size can be set on all layers via the
request_max_body_size
parameter and defaults to 10MB. If a request body exceeds this
limit, a 413 - Request Entity Too Large
response will be returned. This limit applies to all methods of consuming the request
body, including requesting it via the body
parameter in a route handler and
consuming it through a manually constructed Request
instance, e.g. in a middleware.
To disable this limit for a specific handler / router / controller, it can be set to
None
.
Danger
Setting request_max_body_size=None
is strongly discouraged as it exposes the
application to a denial of service (DoS) attack by sending arbitrarily large
request bodies to the affected endpoint. Because Litestar has to read the whole body
to perform certain actions, such as parsing JSON, it will fill up all the available
memory / swap until the application / server crashes, should no outside limits be
imposed.
This is generally only recommended in environments where the application is running behind a reverse proxy such as NGINX, where a size limit is already set.
Danger
Since request_max_body_size
is handled on a per-request basis, it won’t affect
middlewares or ASGI handlers when they try to access the request body via the raw
ASGI events. To avoid this, middlewares and ASGI handlers should construct a
Request
instance and use the regular
stream()
/
body()
or content-appropriate method to consume
the request body in a safe manner.
Tip
For requests that define a Content-Length
header, Litestar will not attempt to
read the request body should the header value exceed the request_max_body_size
.
If the header value is within the allowed bounds, Litestar will verify during the
streaming of the request body that it does not exceed the size specified in the
header. Should the request exceed this size, it will abort the request with a
400 - Bad Request
.