Basic Use#
Here we demonstrate how to declare DTO types to your route handlers. For demonstration purposes, we assume that we
are working with a data model User
, and already have two DTO types created in our application, UserDTO
, and
UserReturnDTO
.
DTO layer parameters#
On every Layer of the Litestar application there are two parameters that control the DTOs that will take responsibility for the data received and returned from handlers:
dto
: This parameter describes the DTO that will be used to parse inbound data to be injected as thedata
keyword argument for a handler. Additionally, if noreturn_dto
is declared on the handler, this will also be used to encode the return data for the handler.return_dto
: This parameter describes the DTO that will be used to encode data returned from the handler. If not provided, the DTO described by thedto
parameter is used.
The object provided to both of these parameters must be a class that conforms to the
AbstractDTO
protocol.
Defining DTOs on handlers#
The dto
parameter#
from litestar import post
from .models import User, UserDTO
@post(dto=UserDTO)
def create_user(data: User) -> User:
return data
In this example, UserDTO
performs decoding of client data into the User
type, and encoding of the returned
User
instance into a type that Litestar can encode into bytes.
The return_dto
parameter#
from litestar import post
from .models import User, UserDTO, UserReturnDTO
@post(dto=UserDTO, return_dto=UserReturnDTO)
def create_user(data: User) -> User:
return data
In this example, UserDTO
performs decoding of client data into the User
type, and UserReturnDTO
is
responsible for converting the User
instance into a type that Litestar can encode into bytes.
Overriding implicit return_dto
#
If a return_dto
type is not declared for a handler, the type declared for the dto
parameter is used for both
decoding and encoding request and response data. If this behavior is undesirable, it can be disabled by explicitly
setting the return_dto
to None
.
from dataclasses import dataclass, field
from uuid import UUID, uuid4
from litestar import Litestar, post
from litestar.dto import DataclassDTO
@dataclass
class User:
name: str
email: str
age: int
id: UUID = field(default_factory=uuid4)
UserDTO = DataclassDTO[User]
@post(dto=UserDTO, return_dto=None, sync_to_thread=False)
def create_user(data: User) -> bytes:
return data.name.encode(encoding="utf-8")
app = Litestar([create_user])
In this example, we use UserDTO
to decode request data, and convert it into the User
type, but we want to manage
encoding the response data ourselves, and so we explicitly declare the return_dto
as None
.
Defining DTOs on layers#
DTOs can be defined on any Layer of the application. The DTO type applied is the one defined in the ownership chain, closest to the handler in question.
from __future__ import annotations
from dataclasses import dataclass, field
from uuid import UUID, uuid4
from litestar import Controller, delete, get, post, put
from litestar.app import Litestar
from litestar.dto import DataclassDTO
from litestar.dto.config import DTOConfig
@dataclass
class User:
name: str
email: str
age: int
id: UUID = field(default_factory=uuid4)
class UserWriteDTO(DataclassDTO[User]):
config = DTOConfig(exclude={"id"})
class UserReadDTO(DataclassDTO[User]): ...
class UserController(Controller):
dto = UserWriteDTO
return_dto = UserReadDTO
@post("/", sync_to_thread=False)
def create_user(self, data: User) -> User:
return data
@get("/", sync_to_thread=False)
def get_users(self) -> list[User]:
return [User(name="Mr Sunglass", email="mr.sunglass@example.com", age=30)]
@get("/{user_id:uuid}", sync_to_thread=False)
def get_user(self, user_id: UUID) -> User:
return User(id=user_id, name="Mr Sunglass", email="mr.sunglass@example.com", age=30)
@put("/{user_id:uuid}", sync_to_thread=False)
def update_user(self, data: User) -> User:
return data
@delete("/{user_id:uuid}", return_dto=None, sync_to_thread=False)
def delete_user(self, user_id: UUID) -> None:
return None
app = Litestar([UserController])
In this example, the User
instance received by any handler that declares a data
kwarg, is converted by the
UserDTO
type, and all handler return values are converted into an encodable type by UserReturnDTO
(except for
the delete()
route, which has the return_dto
disabled).
DTOs can similarly be defined on Routers
and
The application
itself.
Improving performance with the codegen backend#
Note
This feature was introduced in 2.2.0
and was hidden behind the DTO_CODEGEN
feature flag. As of 2.8.0
it is considered stable and is enabled by default.
It can still be disabled selectively by using the
DTOConfig(experimental_codegen_backend=False)
override.
The DTO backend is the part that does the heavy lifting for all the DTO features. It is responsible for the transforming, validation and parsing. Because of this, it is also the part with the most significant performance impact. To reduce the overhead introduced by the DTOs, the DTO codegen backend was introduced; A DTO backend that increases efficiency by generating optimized Python code at runtime to perform all the necessary operations.
Disabling the backend#
You can use experimental_codegen_backend=False
to disable the codegen backend selectively:
from dataclasses import dataclass
from litestar.dto import DTOConfig, DataclassDTO
@dataclass
class Foo:
name: str
class FooDTO(DataclassDTO[Foo]):
config = DTOConfig(experimental_codegen_backend=False)
Enabling the backend#
Note
This is a historical document meant for Litestar versions prior to 2.8.0 This backend was enabled by default since 2.8.0
Warning
ExperimentalFeatures.DTO_CODEGEN
is deprecated and will be removed in 3.0.0
Enabling DTO codegen backend
You can enable this backend globally for all DTOs by passing the appropriate feature flag to your Litestar application:
from litestar import Litestar
from litestar.config.app import ExperimentalFeatures
app = Litestar(experimental_features=[ExperimentalFeatures.DTO_CODEGEN])
or selectively for individual DTOs:
from dataclasses import dataclass
from litestar.dto import DTOConfig, DataclassDTO
@dataclass
class Foo:
name: str
class FooDTO(DataclassDTO[Foo]):
config = DTOConfig(experimental_codegen_backend=True)
The same flag can be used to disable the backend selectively:
from dataclasses import dataclass
from litestar.dto import DTOConfig, DataclassDTO
@dataclass
class Foo:
name: str
class FooDTO(DataclassDTO[Foo]):
config = DTOConfig(experimental_codegen_backend=False)
Performance improvements#
These are some preliminary numbers showing the performance increase for certain operations:
operation |
improvement |
---|---|
JSON to Python |
~2.5x |
JSON to Python (collection) |
~3.5x |
Python to Python |
~2.5x |
Python to Python (collection) |
~5x |
Python to JSON |
~5.3x |
Python to JSON (collection) |
~5.4x |
See also
If you are interested in technical details, check out litestar-org/litestar#2388