Pydantic serialisation 中文文档(完整版)
Pydantic serialisation 中文文档(完整版)
前言
初衷:在学习的时候发现 Tortoise-ORM FastAPI integration 官方文档缺中文版,翻阅英文文档效率低,萌生翻译想法。
本系列旨在原汁原味的翻译 Tortoise-ORM FastAPI integration 官方文档,帮助英语不好的小伙伴快速学习使用方法。
翻译不易,禁止商业用途,转载请标明出处,具体情况或反馈请联系作者`。
如果觉得翻译不错,欢迎在文章底部请博主喝杯奶茶,您的鼓励就是俺最大的动力!😄
简介
Tortoise ORM
提供 Pydantic
插件,可以从 Tortoise Models
生成 Pydantic
模型,然后提供辅助函数来序列化该模型及其相关对象。
我们目前仅支持为序列化生成 Pydantic
对象,目前尚不支持反序列化。
教程
基础用法
这里我们将介绍:
- 从 Tortoise 模型创建一个 Pydantic 模型
- 使用文档字符串和文档注释
- 评估生成的 schema
- 使用
.model_dump()
和.model_dump_json()
函数进行简单序列化
让我们从一个基础的 Tortoise
模型开始:
from tortoise import fields
from tortoise.models import Model
class Tournament(Model):
"""
This references a Tournament
"""
id = fields.IntField(primary_key=True)
name = fields.CharField(max_length=100)
#: The date-time the Tournament record was created at
created_at = fields.DatetimeField(auto_now_add=True)
要从中创建一个 Pydantic
模型,就需要调用该函数:tortoise.contrib.pydantic.creator.pydantic_model_creator()
from tortoise.contrib.pydantic import pydantic_model_creator
Tournament_Pydantic = pydantic_model_creator(Tournament)
现在我们有了一个 Pydantic
模型,可以用来定义 schema
数据模式并实现序列化功能。
如下代码是 Tournament_Pydantic
模型的 JSON-Schema :
>>> print(Tournament_Pydantic.schema())
{
'title': 'Tournament',
'description': 'This references a Tournament',
'type': 'object',
'properties': {
'id': {
'title': 'Id',
'type': 'integer'
},
'name': {
'title': 'Name',
'type': 'string'
},
'created_at': {
'title': 'Created At',
'description': 'The date-time the Tournament record was created at',
'type': 'string',
'format': 'date-time'
}
}
}
注意现在关于 Schema
的描述内包含了类的文档字符串和文档注释 #:
。
序列化对象,只需(在异步环境中)进行以下操作:
tournament = await Tournament.create(name="New Tournament")
tourpy = await Tournament_Pydantic.from_tortoise_orm(tournament)
现在我们获取数据库内容只需要使用常规的 Pydantic-object 方法, 例如 .model_dump() 或 .model_dump_json()
>>> print(tourpy.model_dump())
{
'id': 1,
'name': 'New Tournament',
'created_at': datetime.datetime(2020, 3, 1, 20, 28, 9, 346808)
}
>>> print(tourpy.model_dump_json())
{
"id": 1,
"name": "New Tournament",
"created_at": "2020-03-01T20:28:09.346808"
}
查询集与列表
这里我们将介绍:
- 创建一个列表模型来序列化一个查询集
- 遵循默认的排序方式
from tortoise import fields
from tortoise.models import Model
class Tournament(Model):
"""
This references a Tournament
"""
id = fields.IntField(primary_key=True)
name = fields.CharField(max_length=100)
#: The date-time the Tournament record was created at
created_at = fields.DatetimeField(auto_now_add=True)
class Meta:
# Define the default ordering
# the pydantic serialiser will use this to order the results
ordering = ["name"]
要从中创建一个 Pydantic
列表模型,就需要调用该函数:tortoise.contrib.pydantic.creator.pydantic_queryset_creator()
from tortoise.contrib.pydantic import pydantic_queryset_creator
Tournament_Pydantic_List = pydantic_queryset_creator(Tournament)
现在我们有一个 Pydantic
模型,可以用来定义 schema
数据模式并实现序列化功能。
如下代码是 Tournament_Pydantic_List
模型的 JSON-Schema :
>>> print(Tournament_Pydantic_List.schema())
{
'title': 'Tournaments',
'description': 'This references a Tournament',
'type': 'array',
'items': {
'$ref': '#/definitions/Tournament'
},
'definitions': {
'Tournament': {
'title': 'Tournament',
'description': 'This references a Tournament',
'type': 'object',
'properties': {
'id': {
'title': 'Id',
'type': 'integer'
},
'name': {
'title': 'Name',
'type': 'string'
},
'created_at': {
'title': 'Created At',
'description': 'The date-time the Tournament record was created at',
'type': 'string',
'format': 'date-time'
}
}
}
}
}
注意现在 Tournament
不再是根节点了。取而代之的是一个简单的列表,即 Tournament_Pydantic_List
。
序列化对象,只需(在异步环境中)进行以下操作:
# Create objects
await Tournament.create(name="New Tournament")
await Tournament.create(name="Another")
await Tournament.create(name="Last Tournament")
tourpy = await Tournament_Pydantic_List.from_queryset(Tournament.all())
现在我们获取数据库内容只需要使用常规的 Pydantic-object 方法, 例如 .model_dump() 或 .model_dump_json()
>>> print(tourpy.model_dump())
{
'root': [
{
'id': 2,
'name': 'Another',
'created_at': datetime.datetime(2020, 3, 2, 6, 53, 39, 776504)
},
{
'id': 3,
'name': 'Last Tournament',
'created_at': datetime.datetime(2020, 3, 2, 6, 53, 39, 776848)
},
{
'id': 1,
'name': 'New Tournament',
'created_at': datetime.datetime(2020, 3, 2, 6, 53, 39, 776211)
}
]
}
>>> print(tourpy.model_dump_json())
[
{
"id": 2,
"name": "Another",
"created_at": "2020-03-02T06:53:39.776504"
},
{
"id": 3,
"name": "Last Tournament",
"created_at": "2020-03-02T06:53:39.776848"
},
{
"id": 1,
"name": "New Tournament",
"created_at": "2020-03-02T06:53:39.776211"
}
]
请注意,.model_dump()
方法具有一个包含列表的根元素,而 .model_dump_json()
方法将列表作为根节点。同时还要注意结果(名称)按字母顺序排序。
表之间的关系与早期模型初始化
这里我们将介绍:
- 表之间的关系
- 早期模型初始化
关于早期初始化(early-init)的这一部分操作仅在你需要在初始化 Tortoise ORM
之前创建 Pydantic
模型时才需要。
请仔细看 Basic Pydantic
(在函数 run
中),可以看到 *_creator
是在我们正确初始化 Tortoise ORM
之后调用的,在这种情况下,就可以避免不必要的操作,即不需要提前初始化。
简单来说:如果你在使用
Tortoise ORM
之前就需要创建一些pydantic
数据模型,就需要进行早期初始化;但如果你可以在初始化完Tortoise ORM
之后再创建这些模型,那么就不需要进行早期初始化了。
如下我们使用 一对多
的关系来沟通两个模型:
from tortoise import fields
from tortoise.models import Model
class Tournament(Model):
"""
This references a Tournament
"""
id = fields.IntField(primary_key=True)
name = fields.CharField(max_length=100)
#: The date-time the Tournament record was created at
created_at = fields.DatetimeField(auto_now_add=True)
class Event(Model):
"""
This references an Event in a Tournament
"""
id = fields.IntField(primary_key=True)
name = fields.CharField(max_length=100)
created_at = fields.DatetimeField(auto_now_add=True)
tournament = fields.ForeignKeyField(
"models.Tournament", related_name="events", description="The Tournament this happens in"
)
接下来我们使用 pydantic_model_creator
函数来创建 Pydantic
模型:
from tortoise.contrib.pydantic import pydantic_model_creator
Tournament_Pydantic = pydantic_model_creator(Tournament)
如下代码是 Tournament_Pydantic
模型的 JSON-Schema :
>>> print(Tournament_Pydantic.schema())
{
'title': 'Tournament',
'description': 'This references a Tournament',
'type': 'object',
'properties': {
'id': {
'title': 'Id',
'type': 'integer'
},
'name': {
'title': 'Name',
'type': 'string'
},
'created_at': {
'title': 'Created At',
'description': 'The date-time the Tournament record was created at',
'type': 'string',
'format': 'date-time'
}
}
}
Oh no! 我们 一对多
的关系跑哪儿去了!?
因为模型尚未完全初始化,所以在这个阶段它不知道有 一对多
关系的任何信息。
现在我们需要通过使用 tortoise.Tortoise.init_models()
早期初始化我们的模型关系。
from tortoise import Tortoise
Tortoise.init_models(["__main__"], "models")
# Now lets try again
Tournament_Pydantic = pydantic_model_creator(Tournament)
现在的 Tournament_Pydantic
模型的 JSON-Schema 如下 :
>>> print(Tournament_Pydantic.schema())
{
'title': 'Tournament',
'description': 'This references a Tournament',
'type': 'object',
'properties': {
'id': {
'title': 'Id',
'type': 'integer'
},
'name': {
'title': 'Name',
'type': 'string'
},
'created_at': {
'title': 'Created At',
'description': 'The date-time the Tournament record was created at',
'type': 'string',
'format': 'date-time'
},
'events': {
'title': 'Events',
'description': 'The Tournament this happens in',
'type': 'array',
'items': {
'$ref': '#/definitions/Event'
}
}
},
'definitions': {
'Event': {
'title': 'Event',
'description': 'This references an Event in a Tournament',
'type': 'object',
'properties': {
'id': {
'title': 'Id',
'type': 'integer'
},
'name': {
'title': 'Name',
'type': 'string'
},
'created_at': {
'title': 'Created At',
'type': 'string',
'format': 'date-time'
}
}
}
}
}
现在我们可以看到问题迎刃而解了。
请注意,我们也可以以同样的方式为事件(Event)
创建一个模型,它同样能正常运行。
Event_Pydantic = pydantic_model_creator(Event)
>>> print(Event_Pydantic.schema())
{
'title': 'Event',
'description': 'This references an Event in a Tournament',
'type': 'object',
'properties': {
'id': {
'title': 'Id',
'type': 'integer'
},
'name': {
'title': 'Name',
'type': 'string'
},
'created_at': {
'title': 'Created At',
'type': 'string',
'format': 'date-time'
},
'tournament': {
'title': 'Tournament',
'description': 'The Tournament this happens in',
'allOf': [
{
'$ref': '#/definitions/Tournament'
}
]
}
},
'definitions': {
'Tournament': {
'title': 'Tournament',
'description': 'This references a Tournament',
'type': 'object',
'properties': {
'id': {
'title': 'Id',
'type': 'integer'
},
'name': {
'title': 'Name',
'type': 'string'
},
'created_at': {
'title': 'Created At',
'description': 'The date-time the Tournament record was created at',
'type': 'string',
'format': 'date-time'
}
}
}
}
}
并且此处 一对多
关系也被定义了!
这两个模型的 schema
中都没有反向关系。这是默认的情况,而在稍后的教程中,我们将展示其相关选项。
让我们来创建并序列化这些对象,看看它们在异步环境中会如何:
# Create objects
tournament = await Tournament.create(name="New Tournament")
event = await Event.create(name="The Event", tournament=tournament)
# Serialise Tournament
tourpy = await Tournament_Pydantic.from_tortoise_orm(tournament)
>>> print(tourpy.model_dump_json())
{
"id": 1,
"name": "New Tournament",
"created_at": "2020-03-02T07:23:27.731656",
"events": [
{
"id": 1,
"name": "The Event",
"created_at": "2020-03-02T07:23:27.732492"
}
]
}
在异步环境中对事件进行序列化:
eventpy = await Event_Pydantic.from_tortoise_orm(event)
>>> print(eventpy.model_dump_json())
{
"id": 1,
"name": "The Event",
"created_at": "2020-03-02T07:23:27.732492",
"tournament": {
"id": 1,
"name": "New Tournament",
"created_at": "2020-03-02T07:23:27.731656"
}
}
4: PydanticMeta
和可调用对象
这里我们将介绍:
- 通过
PydanticMeta
类配置模型创建器 - 使用可调用函数注解额外数据
让我们添加一些计算数据的方法,并告诉创建器(creator)
如何使用它们:
class Tournament(Model):
"""
This references a Tournament
"""
id = fields.IntField(primary_key=True)
name = fields.CharField(max_length=100)
created_at = fields.DatetimeField(auto_now_add=True)
# It is useful to define the reverse relations manually so that type checking
# and auto completion work
events: fields.ReverseRelation["Event"]
def name_length(self) -> int:
"""
Computed length of name
"""
return len(self.name)
def events_num(self) -> int:
"""
Computed team size
"""
try:
return len(self.events)
except NoValuesFetched:
return -1
class PydanticMeta:
# Let's exclude the created timestamp
exclude = ("created_at",)
# Let's include two callables as computed columns
computed = ("name_length", "events_num")
class Event(Model):
"""
This references an Event in a Tournament
"""
id = fields.IntField(primary_key=True)
name = fields.CharField(max_length=100)
created_at = fields.DatetimeField(auto_now_add=True)
tournament = fields.ForeignKeyField(
"models.Tournament", related_name="events", description="The Tournament this happens in"
)
class Meta:
ordering = ["name"]
class PydanticMeta:
exclude = ("created_at",)
这里涉及了很多内容需要解释。
首先,我们定义了一个 PydanticMeta
区块,其中包含了用于创建 Pydantic 模型的可选配置选项。可查看 tortoise.contrib.pydantic.creator.PydanticMeta
获取更多的可用选项信息。
其次,我们在两个模型中都排除了 created_at
字段,因为我们认为它没有任何必要。
第三,我们添加了两个可调用函数:name_length
和 events_num
。我们希望这些函数作为结果集的一部分。需要注意的是,可调用函数/计算字段需要手动指定返回类型,因为如果没有指定,我们无法确定记录返回类型,而这是创建有效 Pydantic schema
所必需的。对于标准的 Tortoise ORM 字段,并不需要这样做,因为这些字段已经定义了有效的返回类型。
值得注意的是,Pydantic 序列化器无法调用异步方法,但由于 Tortoise 辅助函数预取了关联的数据,因此在序列化之前可以使用这些数据。因此,我们不需要等待关联的数据返回。但是,我们还是需要防范没有预取数据的情况,因此需要捕获并处理 tortoise.exceptions.NoValuesFetched
异常。
现在我们使用 pydantic_model_creator
方法创建我们的 Pydantic
模型:
from tortoise import Tortoise
Tortoise.init_models(["__main__"], "models")
Tournament_Pydantic = pydantic_model_creator(Tournament)
如下代码是 Tournament_Pydantic
模型的 JSON-Schema :
{
"title": "Tournament",
"description": "This references a Tournament",
"type": "object",
"properties": {
"id": {
"title": "Id",
"type": "integer"
},
"name": {
"title": "Name",
"type": "string"
},
"events": {
"title": "Events",
"description": "The Tournament this happens in",
"type": "array",
"items": {
"$ref": "#/definitions/Event"
}
},
"name_length": {
"title": "Name Length",
"description": "Computes length of name",
"type": "integer"
},
"events_num": {
"title": "Events Num",
"description": "Computes team size.",
"type": "integer"
}
},
"definitions": {
"Event": {
"title": "Event",
"description": "This references an Event in a Tournament",
"type": "object",
"properties": {
"id": {
"title": "Id",
"type": "integer"
},
"name": {
"title": "Name",
"type": "string"
}
}
}
}
}
注意,created_at
字段被移除,并且添加了name_length
和 events_num
字段。
让我们在异步环境中创建并序列化对象,看看将会如何:
# Create objects
tournament = await Tournament.create(name="New Tournament")
await Event.create(name="Event 1", tournament=tournament)
await Event.create(name="Event 2", tournament=tournament)
# Serialise Tournament
tourpy = await Tournament_Pydantic.from_tortoise_orm(tournament)
>>> print(tourpy.model_dump_json())
{
"id": 1,
"name": "New Tournament",
"events": [
{
"id": 1,
"name": "Event 1"
},
{
"id": 2,
"name": "Event 2"
}
],
"name_length": 14,
"events_num": 2
}
创建器(Creators)
tortoise.contrib.pydantic.creator.pydantic_model_creator
tortoise.contrib.pydantic.creator.pydantic_model_creator(
cls,
*,
name=None,
exclude=(),
include=(),
computed=(),
optional=(),
allow_cycles=None,
sort_alphabetically=None,
_stack=(),
exclude_readonly=False,
meta_override=None,
model_config=None,
validators=None,
module='tortoise.contrib.pydantic.creator'
)
该函数基于 Tortoise
模型,用于创建 Pydantic
模型。
参数
属性名 | 描述 | 默认值 |
---|---|---|
_stack | 用于跟踪递归的内部参数 | () |
cls | Tortoise 源模型类 | - |
name | 显式指定自定义名称,而不是使用一个自动生成的名称。 | None |
exclude | 从指定的模型中排除额外的字段。 | () |
include | 从指定的模型中获取额外的字段。 | () |
computed | 从指定的模型中获取额外的计算字段。 | () |
optional | 为指定的模型添加额外的可选字段。 | () |
allow_cycles | 在生成模型时,允许存在的循环引用控制选项。这对于涉及递归或自引用模型非常有用。 | None (默认 False ) |
sort_alphabetically | 按照字母顺序对参数进行排序,而不是按照字段定义的顺序进行。排序时会按照字段定义的顺序、发现的反向关系的顺序、提供的计算函数的顺序进行。 | None (默认 False ) |
exclude_readonly | 构建一个子模型,其中不包含任何只读字段。 | False |
meta_override | 用于重写模型的数值的 PydanticMeta 类。 | None |
model_config | 一个用作 Pydantic 配置的自定义配置。 | None |
validators | 一个验证字段的方法字典。 | None |
module | 模型所属的模块名称。注意:创建 Pydantic 模型时使用 config_class 参数,并将 PydanticMeta 的 config_class 作为其 Config 类的基类(仅在有提供此参数时)!但它忽略了 字段(config) 配置。此时 pydantic_model_creator 将通过 include/exclude/computed 参数自动生成字段。 | 'tortoise.contrib.pydantic.creator' |
返回类型 | Type[PydanticModel] | - |
tortoise.contrib.pydantic.creator.pydantic_queryset_creator()
tortoise.contrib.pydantic.creator.pydantic_queryset_creator(
cls,
*,
name=None,
exclude=(),
include=(),
computed=(),
allow_cycles=None,
sort_alphabetically=None
)
该函数基于 Tortoise
模型,用于创建 Pydantic
模型列表。
属性名 | 描述 | 默认值 |
---|---|---|
cls | 将 Tortoise 源模型类放入一个列表中。 | - |
name | 显式指定自定义名称,而不是使用一个自动生成的名称。当前生成的列表名称很简单,只是在单数名词的末尾添加了一个“s”。 | None |
exclude | 从指定的模型中排除额外的字段。 | () |
include | 从指定的模型中获取额外的字段。 | () |
computed | 从指定的模型中获取额外的计算字段。 | () |
allow_cycles | 在生成模型时,允许存在的循环引用控制选项。这对于涉及递归或自引用模型非常有用。默认情况下,该选项取值为 False(即默认不允许循环引用),这将阻止所有形式的回溯。 | None (默认 False ) |
sort_alphabetically | 按照字母顺序对参数进行排序,而不是按照字段定义的顺序进行。排序时会按照字段定义的顺序、发现的反向关系的顺序、提供的计算函数的顺序进行。 | None (默认 False ) |
返回类型 | Type[PydanticListModel] | - |
PydanticMeta
tortoise.contrib.pydantic.creator.PydanticMeta
class tortoise.contrib.pydantic.creator.PydanticMeta
PydanticMeta
类用于配置生成 Pydantic 模型的元数据。
用法:
class Foo(Model):
...
class PydanticMeta:
exclude = ("foo", "baa")
computed = ("count_peanuts", )
属性名 | 类型 | 默认值 | 描述 |
---|---|---|---|
allow_cycles | bool | False | 允许递归中存在循环 - 这可能导致数据量极大 - 请谨慎使用!请结合 exclude/include 功能和适当的 max_recursion 使用 |
backward_relations | bool | True | 使用没有注释的反向关系 - 不建议,可能会导致数据量巨大且无法控制。 |
computed | tuple[str, ...] | () | 在这里可以列出计算字段,以在 Pydantic 模型中使用。 |
exclude | tuple[str, ...] | ('Meta',) | 在该属性中列出的字段将被从 Pydantic 模型中排除。 |
exclude_raw_fields | bool | True | 排除关系字段中带有 _id 后缀的原始字段。 |
include | tuple[str, ...] | () | 如果此属性不为空,那么生成的 Pydantic 模型将仅包含指定的字段,而不是模型中所有的字段。 |
max_recursion | int | 3 | 递归深度的最大允许值。 |
model_config | ConfigDict | None | None |
sort_alphabetically | bool | False | 对字段按字母顺序排序;如果未设置(或设置为 False),则保持字段声明顺序不变。 |
Model Classes
tortoise.contrib.pydantic.base.PydanticListModel
class tortoise.contrib.pydantic.base.PydanticListModel(root=PydanticUndefined, **data)
用于 Tortoise Models 列表的 Pydantic BaseModel
这提供了一个额外的方法,超出了常用的 Pydantic 模型属性 (model properties)
async classmethod from_queryset(queryset)
返回一个可序列化的 Pydantic 模型实例,其中包含来自提供的查询集的模型列表。
这个操作将自动获取并加载所有相关的数据关联。
参数 | 描述 |
---|---|
queryset : QuerySet | 在 PydanticListModel 所对应的数据模型上的一个查询集 |
返回类型 | typing_extensions.Self |
model_computed_fields : ClassVar[dict[str, ComputedFieldInfo]] = {}
计算字段名称及其对应的 ComputedFieldInfo 对象的字典。
model_config : ClassVar[ConfigDict] = {}
模型的配置应该是符合 [ConfigDict][pydantic.config.ConfigDict]
规范的字典。
model_fields : ClassVar[dict[str, FieldInfo]] = {'root': FieldInfo(annotation=~RootModelRootType, required=True)}
(定义在模型上的)字段的元数据会将字段名映射到 [FieldInfo][pydantic.fields.FieldInfo]
。
这个方法取代了 Pydantic V1 中的 Model.__fields__
方法。
class tortoise.contrib.pydantic.base.PydanticModel(**data)
Pydantic BaseModel 用于 Tortoise 对象。
这提供了比常用的 Pydantic 模型属性(model properties)更多的方法。
async classmethod from_queryset(queryset)
返回一个可序列化的 Pydantic 模型实例,其中包含来自提供的查询集的模型列表。
这个操作将自动获取并加载所有相关的数据关联。
参数 | 描述 |
---|---|
queryset : QuerySet | 在 PydanticModel 所对应的数据模型上的一个查询集 |
返回类型 | List[typing_extensions.Self] |
async classmethod from_queryset(queryset)
从给定的一组查询结果中返回一个用于单个数据模型的可序列化 Pydantic 模型实例。
这个操作将自动获取并加载所有相关的数据关联。
参数 | 描述 |
---|---|
queryset : QuerySetSingle | 在 PydanticModel 所对应的数据模型上的一个查询集 |
返回类型 | typing_extensions.Self |
async classmethod from_tortoise_orm(obj)
从提供的模型实例构建一个可序列化的 Pydantic 模型实例并返回。
当你执行查询时,系统会自动获取所有相关数据。这很可能就是你想要实现的功能。
如果你不希望自动获取数据,或者需要同步(aync)
方法,请考虑使用.from_orm()
方法。
在这种情况下,你需要自己管理预取操作,或者通过使用tortoise.contrib.pydantic.creator.PydanticMeta
排除关联字段,否则会出现OperationalError
异常。
这是由于asyncio
框架强制 I/O 操作在明确的await
语句中进行。因此,我们只能在等待的方法中进行延迟获取操作。
参数 | 描述 |
---|---|
obj | 你想要序列化的模型实例 |
返回类型 | typing_extensions.Self |
model_computed_fields : ClassVar[dict[str, ComputedFieldInfo]] = {}
一个包含计算字段名称及其对应的 ComputedFieldInfo
对象的字典。
model_config : ClassVar[ConfigDict] = {'from_attributes': True}
模型的配置应该是符合 [ConfigDict][pydantic.config.ConfigDict]
规范的字典。
model_fields : ClassVar[dict[str, FieldInfo]] = {}
(定义在模型上的)字段的元数据会将字段名映射到 [FieldInfo][pydantic.fields.FieldInfo]
。
这个方法取代了 Pydantic V1 中的 Model.__fields__
方法。
- 感谢你赐予我前进的力量