Litestar DTO 教程:用 DTO 工厂构建灵活的数据传输层
【免费下载链接】litestarLight, flexible and extensible ASGI framework | Built to scale项目地址: https://gitcode.com/GitHub_Trending/li/litestar
本篇为 Litestar 官方 DTO 教程(Data Transfer Object Tutorial)的中文技术详解。教程面向已熟悉 Litestar 路由处理器(route handler)基础用法的开发者,完整覆盖从"返回 dataclass 的朴素模式"到"基于 DTO 的接收、校验、隐藏、重命名与分层复用"的全过程。读完本篇,你将掌握
DataclassDTO、DTOConfig、DTOData三个核心工具的实际用法,能独立用 DTO 工厂实现响应字段裁剪、请求数据校验、只读字段、PUT/PATCH 更新以及 Controller 分层 DTO 声明等实战能力。
前置要求与阅读路径
本教程假设你已经熟悉 Litestar 及路由处理器等基础概念。如果尚未接触,建议先阅读仓库中的 TODO 应用基础教程(Developing a basic TODO application)后再回到本篇。
教程的全部可运行示例代码位于仓库的docs/examples/data_transfer_objects/factory/tutorial/目录下,每节内容都有对应的独立脚本;核心 API 的参考文档可查阅 docs/reference/dto。
1. 起步:返回 dataclass 的朴素模式
我们从一个最简单的应用开始。定义一个名为Person的 Pythondataclass数据模型,包含name、age、email三个属性;再定义一个路径为/person/{name:str}的 GET 路由处理器:路径中的{name:str}表示名为name的字符串类型路径参数。最后创建Litestar应用实例并注册该路由处理器。
完整代码见 initial_pattern.py:
from __future__ import annotations from dataclasses import dataclass from litestar import Litestar, get from litestar.params import FromPath @dataclass class Person: name: str age: int email: str @get("/person/{name:str}", sync_to_thread=False) def get_person(name: FromPath[str]) -> Person: # Your logic to retrieve the person goes here # For demonstration purposes, a placeholder Person instance is returned return Person(name=name, age=30, email=f"email_of_{name}@example.com") app = Litestar(route_handlers=[get_person])在 Litestar 中,这种模式是"开箱即用"的:从路由处理器返回 dataclass 实例是原生支持的行为。Litestar 会自动将该 dataclass 实例序列化为可通过网络传输的bytes(默认输出为 JSON)。
将上面的脚本保存为app.py,使用litestar run命令启动,然后访问http://localhost:8000/person/peter,浏览器中会看到类似下面的输出:
可以看到,返回的 JSON 中包含了name、age、email全部字段。代码中的FromPath[str]用于声明路径参数并完成类型转换,sync_to_thread=False则让同步处理器直接在线程内执行(避免同步函数被调度到线程池,适用于本例这类纯计算场景)。
不过,真实世界的应用很少这么简单。例如我们可能希望在用户创建之后,限制对外暴露的信息——比如把用户的邮箱从响应中隐藏掉。这正是 Data Transfer Object(数据传输对象)要解决的问题。
2. 第一个 DTO:隐藏 email 字段
我们在脚本中引入一个 DTO 类ReadDTO,将其配置为排除Person.email字段,并让路由处理器使用该 DTO 处理响应。完整代码见 simple_dto_exclude.py:
from __future__ import annotations from dataclasses import dataclass from litestar import Litestar, get from litestar.dto import DataclassDTO, DTOConfig from litestar.params import FromPath @dataclass class Person: name: str age: int email: str class ReadDTO(DataclassDTO[Person]): config = DTOConfig(exclude={"email"}) @get("/person/{name:str}", return_dto=ReadDTO, sync_to_thread=False) def get_person(name: FromPath[str]) -> Person: # Your logic to retrieve the person goes here # For demonstration purposes, a placeholder Person instance is returned return Person(name=name, age=30, email=f"email_of_{name}@example.com") app = Litestar(route_handlers=[get_person])本节的改动集中在三处:
- 新增两个导入:
DTOConfig和DataclassDTO(均从litestar.dto导入)。 - 定义 DTO 类:
- DTOConfig 用于配置 DTO。本例使用
exclude={"email"}排除字段,此外它还有大量其他配置选项,本教程后续会逐一覆盖。 - DataclassDTO 是一个专门从 dataclass 生成 DTO 的工厂类,同时它是一个
typing.Generic泛型类,接受类型参数。当我们提供类型参数时,该类就变成泛型类的一个特化版本:DataclassDTO[Person]即"专门在Person实例与传输数据之间进行转换的 DTO 类型"。
- DTOConfig 用于配置 DTO。本例使用
- 路由处理器启用 DTO:通过
return_dto=ReadDTO让 DTO 负责处理处理器返回值到响应数据的转换。
注意:并不必须通过子类化
DataclassDTO来创建特化 DTO,例如ReadDTO = DataclassDTO[Person]同样是合法的特化 DTO。但子类化允许我们挂载配置对象(config = DTOConfig(...)),同时完成类型特化,因此教程采用子类方式。
再次访问http://localhost:8000/person/peter,响应中不再包含email字段:
至此,我们已经成功隐藏了用户的邮箱地址。
3. 排除嵌套模型的字段:点分路径语法
exclude选项不仅支持顶层字段,还支持通过**点分路径(dotted paths)**定位到嵌套模型中的字段。例如exclude={"a.b"}将排除嵌套在a属性上的实例的b属性。
我们为模型增加一个与Person关联的Address模型。完整代码见 nested_exclude.py:
from __future__ import annotations from dataclasses import dataclass from litestar import Litestar, get from litestar.dto import DataclassDTO, DTOConfig from litestar.params import FromPath @dataclass class Address: street: str city: str country: str @dataclass class Person: name: str age: int email: str address: Address class ReadDTO(DataclassDTO[Person]): config = DTOConfig(exclude={"email", "address.street"}) @get("/person/{name:str}", return_dto=ReadDTO, sync_to_thread=False) def get_person(name: FromPath[str]) -> Person: # Your logic to retrieve the person goes here # For demonstration purposes, a placeholder Person instance is returned address = Address(street="123 Main St", city="Cityville", country="Countryland") return Person(name=name, age=30, email=f"email_of_{name}@example.com", address=address) app = Litestar(route_handlers=[get_person])Address模型有三个属性:street、city、country;Person模型新增了address属性。ReadDTO使用点分路径"address.street"排除了嵌套Address模型中的street字段。
调用处理器后可以看到,响应中address对象保留了city与country,但street不再出现:
4. 排除集合中嵌套模型的字段:类型参数索引语法
在 Python 中,泛型类型可以接受一个或多个类型参数(方括号中的类型),典型场景是表示某种类型的集合,例如List[Person]:List是泛型容器类型,Person特化了集合中元素的类型。
对于一个拥有任意数量类型参数的泛型类型,例如GenericType[Type0, Type1, ..., TypeN],我们使用类型参数的索引来指明排除操作针对的是哪个类型:
a.0.b:排除a的第一个类型参数(Type0)中实例的b字段;a.1.b:排除a的第二个类型参数(Type1)中实例的b字段;- 依此类推。
下面我们为Person模型增加一个自引用(self-referencing)的children关系。完整代码见 nested_collection_exclude.py:
from __future__ import annotations from dataclasses import dataclass from litestar import Litestar, get from litestar.dto import DataclassDTO, DTOConfig from litestar.params import FromPath @dataclass class Address: street: str city: str country: str @dataclass class Person: name: str age: int email: str address: Address children: list[Person] class ReadDTO(DataclassDTO[Person]): config = DTOConfig(exclude={"email", "address.street", "children.0.email", "children.0.address"}) @get("/person/{name:str}", return_dto=ReadDTO, sync_to_thread=False) def get_person(name: FromPath[str]) -> Person: # Your logic to retrieve the person goes here # For demonstration purposes, a placeholder Person instance is returned address = Address(street="123 Main St", city="Cityville", country="Countryland") child1 = Person(name="Child1", age=10, email="child1@example.com", address=address, children=[]) child2 = Person(name="Child2", age=8, email="child2@example.com", address=address, children=[]) return Person( name=name, age=30, email=f"email_of_{name}@example.com", address=address, children=[child1, child2], ) app = Litestar(route_handlers=[get_person])现在一个Person可以有多个children,每个 child 又可以有多个 children,层层嵌套。我们通过"children.0.email"和"children.0.address"显式排除了所有子Person的email与address字段(children是list[Person],其第 0 个类型参数即Person)。
处理器中为Person增加了两个 child,且每个 child 自身没有 children。输出如下:
子对象成功出现在响应中,且它们的 email 与 address 都被排除。细心的读者可能会注意到:我们并没有显式排除Person.children的children字段(例如children.0.children),但该字段并未出现在输出中。要理解原因,我们来看下一节:max_nested_depth配置。
5. max_nested_depth:控制嵌套深度
上一节的现象是:即便没有显式排除children.0.children,每个嵌套Person的children集合也没有出现在响应里。按照"未排除即应输出"的直觉,children集合中的每个Person应该有一个空的children集合——但事实并非如此。原因正是 DTOConfig.max_nested_depth 及其默认值1。
max_nested_depth用于限制响应中包含的嵌套对象深度。在本例中:
Person有一个children集合,集合元素是嵌套的Person对象——这算作 1 层嵌套深度;Person.children中每个元素的children集合则处于第 2 层嵌套,因此被max_nested_depth的默认值1排除掉了。
下面修改脚本,把max_nested_depth提升到2,让孙级 children 也出现在响应中。完整代码见 max_nested_depth.py,核心改动只有一处——DTO 配置:
class ReadDTO(DataclassDTO[Person]): config = DTOConfig( exclude={"email", "address.street", "children.0.email", "children.0.address"}, max_nested_depth=2, )现在输出中可以看到那些空集合("children": []):
本教程后续章节将恢复使用默认值
1。
从源码看,max_nested_depth的默认值定义在 config.py 的DTOConfig数据类中(max_nested_depth: int = 1),它的作用是在 DTO 后端生成传输模型时限制递归展开的层数,既避免无限自引用模型导致递归失控,也天然防止深层嵌套对象被意外暴露。
6. 字段重命名:显式声明与重命名策略
字段在序列化时的名称可以通过两种方式改变:显式声明新名称,或声明重命名策略。
6.1 显式重命名:rename_fields
我们可以通过DTOConfig.rename_fields属性显式重命名字段。它是一个字典,键为原始字段名,值为新字段名。
下面的例子把address字段重命名为location(完整代码见 explicit_field_renaming.py):
class ReadDTO(DataclassDTO[Person]): config = DTOConfig( exclude={"email", "address.street", "children.0.email", "children.0.address"}, rename_fields={"address": "location"}, )响应中的address字段被重命名为location:
6.2 重命名策略:rename_strategy
除了逐字段显式重命名,还可以使用字段重命名策略。策略通过DTOConfig.rename_strategy配置指定。
Litestar 内置支持以下策略:
| 策略 | 说明 |
|---|---|
lower | 将字段名转换为小写 |
upper | 将字段名转换为大写 |
camel | 将字段名转换为驼峰式(camel case) |
pascal | 将字段名转换为帕斯卡式(pascal case) |
提示:也可以自定义策略——向
rename_strategy传入一个"接收字段名并返回新字段名"的可调用对象即可。
把示例改为使用upper策略(完整代码见 field_renaming_strategy.py):
class ReadDTO(DataclassDTO[Person]): config = DTOConfig( exclude={"email", "address.street", "children.0.email", "children.0.address"}, rename_strategy="upper", )结果中所有字段名都被转换为大写:
从 config.py 的源码注释可以确认:rename_fields中显式声明的字段不受rename_strategy影响("Fields defined inrename_fieldsare ignored"),两者可以同时使用、各司其职。
7. 接收数据:从客户端控制入站数据
到目前为止,我们只处理了"返回数据"这一半。另一半是:控制从客户端接收的数据。
为了简化演示,我们把数据模型缩减回只有name、age、email三个属性的Person。完整代码见 simple_receiving_data.py:
from __future__ import annotations from dataclasses import dataclass from litestar import Litestar, post from litestar.dto import DataclassDTO, DTOConfig @dataclass class Person: name: str age: int email: str class ReadDTO(DataclassDTO[Person]): config = DTOConfig(exclude={"email"}) @post("/person", return_dto=ReadDTO, sync_to_thread=False) def create_person(data: Person) -> Person: # Logic for persisting the person goes here return data app = Litestar(route_handlers=[create_person])这里的要点:
- 与之前一样,
ReadDTO通过return_dto配置给处理器,负责排除返回负载中的email字段; - 处理器变成了 @post() 处理器,其函数签名同时声明了接受和返回
Person实例; - Litestar 原生支持将请求负载解码为 Python dataclass,所以本例即使不为入站数据定义 DTO 也能正常工作——入站方向的 DTO 是可选的。
现在需要向服务器发送数据来测试程序,可以使用 Postman 之类的工具,或 Posting。下面是一个请求/响应负载示例:
8. 只读字段:客户端永远不该提交的字段
有些字段永远不应由客户端指定。例如创建新资源实例时,模型的id字段应该由服务端生成,而不是由客户端提交。
下面我们给Person模型加上id字段,并新建一个忽略id的WriteDTO。完整代码见 read_only_fields_error.py:
from __future__ import annotations from dataclasses import dataclass from litestar import Litestar, post from litestar.dto import DataclassDTO, DTOConfig @dataclass class Person: name: str age: int email: str id: int class ReadDTO(DataclassDTO[Person]): config = DTOConfig(exclude={"email"}) class WriteDTO(DataclassDTO[Person]): config = DTOConfig(exclude={"id"}) @post("/person", dto=WriteDTO, return_dto=ReadDTO, sync_to_thread=False) def create_person(data: Person) -> Person: # Logic for persisting the person goes here return data app = Litestar(route_handlers=[create_person])关键点:
WriteDTO被指示忽略id属性;WriteDTO通过dto=WriteDTO关键字参数分配给处理器,这意味着创建新Person实例时,从客户端接收的任何数据中的id字段都会被忽略。
当我们试图携带id字段创建新Person实例时,会得到一个错误:
发生了什么?DTO 试图构造Person模型实例,但我们已经把id字段从接受的客户端数据中排除了。而id是Person模型必填字段,模型构造函数因此抛错。
解决这个问题有不止一种方式,例如:
- 给
id字段一个默认值,并在处理器中覆盖默认值; - 创建一个完全没有
id字段的独立模型,在处理器中把数据从该模型转移到Person模型。
不过,Litestar 内置了更优雅的方案:DTOData。
9. DTOData:延迟实例化与数据访问
有些时候,数据不应当被立即解析成目标类的实例。上一节正是这样的例子:当必填字段被客户端数据排除或缺失时,立即实例化类必然报错。解决方案就是DTOData类型。
完整代码见 dto_data.py:
from __future__ import annotations from dataclasses import dataclass from litestar import Litestar, post from litestar.dto import DataclassDTO, DTOConfig, DTOData @dataclass class Person: name: str age: int email: str id: int class ReadDTO(DataclassDTO[Person]): config = DTOConfig(exclude={"email"}) class WriteDTO(DataclassDTO[Person]): config = DTOConfig(exclude={"id"}) @post("/person", dto=WriteDTO, return_dto=ReadDTO, sync_to_thread=False) def create_person(data: DTOData[Person]) -> Person: # Logic for persisting the person goes here return data.create_instance(id=1) app = Litestar(route_handlers=[create_person])要点拆解:
- DTOData是一个数据容器,既可以用于创建目标类实例,也可以访问底层已解析、已校验的数据。本例从
litestar.dto导入它; - 处理器的数据参数类型从
Person改为DTOData[Person],相应地,注入到函数中的"入站客户端数据"将是一个DTOData实例; - 在处理器内部,我们为
id字段生成一个值,然后通过DTOData.create_instance()方法创建Person实例。从源码(data_structures.py)可见,create_instance(**kwargs)会先把 DTO 校验后的数据(self._data_as_builtins)拷贝为字典,再用传入的 kwargs 覆盖对应键,最后交给 DTO 后端转换为目标类型实例——kwargs 的优先级高于 DTO 校验数据。
应用恢复到正常工作状态:
技巧:要为嵌套属性提供值,可以使用"双下划线"语法作为
create_instance()的关键字参数。例如address__id=1会设置所创建实例的address属性的id。这一机制在源码中由_set_nested_dict_value()实现——它按__切分键名并递归写入嵌套字典。
DTOData还有其他实用的方法,我们将在下一节(更新实例)中看到。
10. 更新实例:PUT 与 PATCH
本节展示如何使用DTOData更新已存在的实例。
10.1 PUT 处理器:全量替换语义
PUT 请求的特征是:要求提交完整的数据模型才能进行更新。
完整代码见 put_handlers.py:
from __future__ import annotations from dataclasses import dataclass from litestar import Litestar, put from litestar.dto import DataclassDTO, DTOConfig, DTOData from litestar.params import FromPath @dataclass class Person: name: str age: int email: str id: int class ReadDTO(DataclassDTO[Person]): config = DTOConfig(exclude={"email"}) class WriteDTO(DataclassDTO[Person]): config = DTOConfig(exclude={"id"}) @put("/person/{person_id:int}", dto=WriteDTO, return_dto=ReadDTO, sync_to_thread=False) def update_person(person_id: FromPath[int], data: DTOData[Person]) -> Person: # Usually the Person would be retrieved from a database person = Person(id=person_id, name="John", age=50, email="email_of_john@example.com") return data.update_instance(person) app = Litestar(route_handlers=[update_person])要点:
- 脚本定义了一个路径为
/person/{person_id:int}的 PUT 处理器,路由参数person_id指明要更新哪个 person; - 处理器中先创建一个
Person实例(模拟数据库查询结果),然后把它传给DTOData.update_instance()方法;该方法返回被修改后的同一个实例。
从源码(data_structures.py)看,update_instance(instance, **kwargs)的逻辑是:把 DTO 校验数据与 kwargs 合并,然后对每个键执行setattr(instance, k, v),原地修改并返回实例。
调用效果:
10.2 PATCH 处理器:部分更新语义
与 PUT 要求提交整个数据模型不同,PATCH 请求允许只提交数据模型属性的任意子集来进行更新。
完整代码见 patch_handlers.py:
from __future__ import annotations from dataclasses import dataclass from litestar import Litestar, patch from litestar.dto import DataclassDTO, DTOConfig, DTOData from litestar.params import FromPath @dataclass class Person: name: str age: int email: str id: int class ReadDTO(DataclassDTO[Person]): config = DTOConfig(exclude={"email"}) class PatchDTO(DataclassDTO[Person]): config = DTOConfig(exclude={"id"}, partial=True) @patch("/person/{person_id:int}", dto=PatchDTO, return_dto=ReadDTO, sync_to_thread=False) def update_person(person_id: FromPath[int], data: DTOData[Person]) -> Person: # Usually the Person would be retrieved from a database person = Person(id=person_id, name="John", age=50, email="email_of_john@example.com") return data.update_instance(person) app = Litestar(route_handlers=[update_person])改动要点:
- 处理器从
@put改为 @patch() 处理器; - 引入
PatchDTO类:配置与WriteDTO类似(排除id),但额外设置了partial=True。该设置允许对资源进行部分更新——即客户端提交的字段中缺失的属性将被视为"不更新",而不是"置空或报错"。
演示效果:
11. 在应用分层上声明 DTO:从处理器到 Controller
到目前为止,DTO 都是逐个处理器声明的。真实应用往往有多个处理器,让我们先看一个声明了多个处理器的脚本。完整代码见 multiple_handlers.py:
from __future__ import annotations from dataclasses import dataclass from litestar import Litestar, patch, post, put from litestar.dto import DataclassDTO, DTOConfig, DTOData from litestar.params import FromPath @dataclass class Person: name: str age: int email: str id: int class ReadDTO(DataclassDTO[Person]): config = DTOConfig(exclude={"email"}) class WriteDTO(DataclassDTO[Person]): config = DTOConfig(exclude={"id"}) class PatchDTO(DataclassDTO[Person]): config = DTOConfig(exclude={"id"}, partial=True) @post("/person", dto=WriteDTO, return_dto=ReadDTO, sync_to_thread=False) def create_person(data: DTOData[Person]) -> Person: # Logic for persisting the person goes here return data.create_instance(id=1) @put("/person/{person_id:int}", dto=WriteDTO, return_dto=ReadDTO, sync_to_thread=False) def update_person(person_id: FromPath[int], data: DTOData[Person]) -> Person: # Usually the Person would be retrieved from a database person = Person(id=person_id, name="John", age=50, email="email_of_john@example.com") return data.update_instance(person) @patch("/person/{person_id:int}", dto=PatchDTO, return_dto=ReadDTO, sync_to_thread=False) def patch_person(person_id: FromPath[int], data: DTOData[Person]) -> Person: # Usually the Person would be retrieved from a database person = Person(id=person_id, name="John", age=50, email="email_of_john@example.com") return data.update_instance(person) app = Litestar(route_handlers=[create_person, update_person, patch_person])可以看到,dto=WriteDTO, return_dto=ReadDTO在三个处理器上重复出现。DTO 可以定义在应用的任何分层(layer)上,这给了我们整理代码的机会——把处理器搬进一个 Controller,并在 Controller 层定义 DTO。完整代码见 controller.py:
from __future__ import annotations from dataclasses import dataclass from litestar import Controller, Litestar, patch, post, put from litestar.dto import DataclassDTO, DTOConfig, DTOData from litestar.params import FromPath @dataclass class Person: name: str age: int email: str id: int class ReadDTO(DataclassDTO[Person]): config = DTOConfig(exclude={"email"}) class WriteDTO(DataclassDTO[Person]): config = DTOConfig(exclude={"id"}) class PatchDTO(DataclassDTO[Person]): config = DTOConfig(exclude={"id"}, partial=True) class PersonController(Controller): dto = WriteDTO return_dto = ReadDTO @post("/person", sync_to_thread=False) def create_person(self, data: DTOData[Person]) -> Person: # Logic for persisting the person goes here return data.create_instance(id=1) @put("/person/{person_id:int}", sync_to_thread=False) def update_person(self, person_id: FromPath[int], data: DTOData[Person]) -> Person: # Usually the Person would be retrieved from a database person = Person(id=person_id, name="John", age=50, email="email_of_john@example.com") return data.update_instance(person) @patch("/person/{person_id:int}", dto=PatchDTO, sync_to_thread=False) def patch_person(self, person_id: FromPath[int], data: DTOData[Person]) -> Person: # Usually the Person would be retrieved from a database person = Person(id=person_id, name="John", age=50, email="email_of_john@example.com") return data.update_instance(person) app = Litestar(route_handlers=[PersonController])对比可以看出:
- 之前的脚本为每条路由定义独立的处理器函数,新脚本把这些路由组织进
PersonController类,从而把公共配置上移到 Controller 层; - 在
PersonController类上同时定义了dto = WriteDTO与return_dto = ReadDTO,无需再在每个处理器上重复声明; - 我们仍然在
patch_person处理器上直接定义dto=PatchDTO,用于覆盖 Controller 层级的dto设置——这正是 Litestar 分层配置的覆盖机制:越内层的声明优先级越高。
12. DTOConfig 全参数一览(源码级)
本教程使用的所有配置都来自 DTOConfig。结合源码,完整的可配置项如下:
| 配置项 | 默认值 | 说明 |
|---|---|---|
exclude: set[str] | set() | 显式排除字段。字段名为点分路径(如"address.street")。指定exclude时,未列出的字段默认包含。与include互斥,同时指定会抛出ImproperlyConfiguredException |
include: set[str] | set() | 显式包含字段(白名单模式)。指定include时,未列出的字段默认排除。与exclude互斥 |
rename_fields: dict[str, str] | dict() | 字段名到新名称的映射,用于显式重命名 |
rename_strategy | None | 重命名策略:内置upper、lower、camel、pascal,或传入自定义可调用对象。rename_fields中声明的字段不受其影响 |
max_nested_depth: int | 1 | 允许数据传输的嵌套最大深度,用于防止深层/递归模型被无限展开 |
partial: bool | False | 是否允许传输部分数据(PATCH 语义) |
underscore_fields_private: bool | True | 以下划线开头的字段视为私有字段,默认排除在数据传输之外 |
experimental_codegen_backend: bool \| None | None | 是否启用实验性代码生成后端 |
forbid_unknown_fields: bool | False | 原始数据中出现模型未定义的字段时是否抛出异常 |
exclude与include的互斥校验在DTOConfig.__post_init__中强制执行(见 config.py),这一点从源码可以直接确认。
深入阅读
- 完整示例代码目录:docs/examples/data_transfer_objects/factory/tutorial
- DTO 工厂更多用法(白名单
include、私有字段underscore_fields_private、未知字段处理等):docs/examples/data_transfer_objects/factory - API 参考:DTO 配置、DTO 数据结构、DataclassDTO、DTO 基类
- 核心源码:litestar/dto/config.py、litestar/dto/data_structures.py、litestar/dto/dataclass_dto.py
- 单元测试(可验证
create_instance的双下划线嵌套参数等行为):tests/unit/test_dto/test_factory/test_integration.py、tests/unit/test_dto/test_config.py - DTO 的进阶使用(DTO 与请求/响应集成、自定义类型):docs/usage/dto
【免费下载链接】litestarLight, flexible and extensible ASGI framework | Built to scale项目地址: https://gitcode.com/GitHub_Trending/li/litestar
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考