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
Typed 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