Pydantic 序列化¶
Pydantic serialisation
Tortoise ORM 具有一个 Pydantic 插件,可以从 Tortoise 模型生成 Pydantic 模型,并提供辅助函数来序列化该模型及其相关对象。
目前我们只支持为序列化生成 Pydantic 对象,尚不支持反序列化。
请参阅 Pydantic 样例。
Tortoise ORM has a Pydantic plugin that will generate Pydantic Models from Tortoise Models, and then provides helper functions to serialise that model and its related objects.
We currently only support generating Pydantic objects for serialisation, and no deserialisation at this stage.
See the Pydantic 样例
教程¶
Tutorial
1: 基本用法¶
Basic usage
在这里,我们介绍:
从 Tortoise 模型创建 Pydantic 模型
使用文档字符串和文档注释
评估生成的架构
使用
.model_dump()
和.model_dump_json()
进行简单序列化
示例源代码: 1: 基本用法
让我们从一个基本的 Tortoise 模型开始:
from tortoise import fields
from tortoise.models import Model
class Tournament(Model):
"""
这是一个比赛的引用
"""
id = fields.IntField(primary_key=True)
name = fields.CharField(max_length=100)
#: 比赛记录创建的日期时间
created_at = fields.DatetimeField(auto_now_add=True)
from tortoise.contrib.pydantic import pydantic_model_creator
Tournament_Pydantic = pydantic_model_creator(Tournament)
现在我们有了一个 Pydantic 模型,可以用于表示架构和序列化。
Tournament_Pydantic
的 JSON 架构现在是:
>>> print(Tournament_Pydantic.schema())
{
'title': 'Tournament',
'description': '这是一个比赛的引用',
'type': 'object',
'properties': {
'id': {
'title': 'Id',
'type': 'integer'
},
'name': {
'title': 'Name',
'type': 'string'
},
'created_at': {
'title': 'Created At',
'description': '比赛记录创建的日期时间',
'type': 'string',
'format': 'date-time'
}
}
}
注意类文档字符串和文档注释 #:
被包含为架构中的描述。
要序列化一个对象,仅需 (在异步上下文中):
tournament = await Tournament.create(name="新比赛")
tourpy = await Tournament_Pydantic.from_tortoise_orm(tournament)
可以使用 常规 Pydantic 对象方法 获取内容,例如 .model_dump()
或 .model_dump_json()
。
Here we introduce:
Creating a Pydantic model from a Tortoise model
Docstrings & doc-comments are used
Evaluating the generated schema
Simple serialisation with both
.model_dump()
and.model_dump_json()
Source to example: 1: 基本用法
Lets start with a basic Tortoise Model:
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)
from tortoise.contrib.pydantic import pydantic_model_creator
Tournament_Pydantic = pydantic_model_creator(Tournament)
And now have a Pydantic Model that can be used for representing schema and serialisation.
The JSON-Schema of Tournament_Pydantic
is now:
>>> 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'
}
}
}
Note how the class docstring and doc-comment #:
is included as descriptions in the Schema.
To serialise an object it is simply (in an async context):
tournament = await Tournament.create(name="New Tournament")
tourpy = await Tournament_Pydantic.from_tortoise_orm(tournament)
And one could get the contents by using regular Pydantic-object methods, such as .model_dump()
or .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"
}
2: Queryset和List¶
Querysets & Lists
在这里,我们介绍:
创建一个列表模型以序列化查询集
默认排序得以保留
示例源代码: 2: Querysets & Lists
from tortoise import fields
from tortoise.models import Model
class Tournament(Model):
"""
这是一个比赛的引用
"""
id = fields.IntField(primary_key=True)
name = fields.CharField(max_length=100)
#: 比赛记录创建的日期时间
created_at = fields.DatetimeField(auto_now_add=True)
class Meta:
# 定义默认排序
# Pydantic 序列化器将使用此排序结果
ordering = ["name"]
from tortoise.contrib.pydantic import pydantic_queryset_creator
Tournament_Pydantic_List = pydantic_queryset_creator(Tournament)
现在我们有了一个 Pydantic 模型,可以用于表示架构和序列化。
Tournament_Pydantic_List
的 JSON 架构现在是:
>>> print(Tournament_Pydantic_List.schema())
{
'title': 'Tournaments',
'description': '这是一个比赛的引用',
'type': 'array',
'items': {
'$ref': '#/definitions/Tournament'
},
'definitions': {
'Tournament': {
'title': 'Tournament',
'description': '这是一个比赛的引用',
'type': 'object',
'properties': {
'id': {
'title': 'Id',
'type': 'integer'
},
'name': {
'title': 'Name',
'type': 'string'
},
'created_at': {
'title': 'Created At',
'description': '比赛记录创建的日期时间',
'type': 'string',
'format': 'date-time'
}
}
}
}
}
注意,Tournament
现在不是根对象。一个简单的列表是根对象。
要序列化一个对象,仅需 (在异步上下文中):
# 创建对象
await Tournament.create(name="新比赛")
await Tournament.create(name="另一个")
await Tournament.create(name="最后一个比赛")
tourpy = await Tournament_Pydantic_List.from_queryset(Tournament.all())
可以使用 常规 Pydantic 对象方法 获取内容,例如 .model_dump()
或 .model_dump_json()
。
>>> print(tourpy.model_dump())
{
'root': [
{
'id': 2,
'name': '另一个',
'created_at': datetime.datetime(2020, 3, 2, 6, 53, 39, 776504)
},
{
'id': 3,
'name': '最后一个比赛',
'created_at': datetime.datetime(2020, 3, 2, 6, 53, 39, 776848)
},
{
'id': 1,
'name': '新比赛',
'created_at': datetime.datetime(2020, 3, 2, 6, 53, 39, 776211)
}
]
}
>>> print(tourpy.model_dump_json())
[
{
"id": 2,
"name": "另一个",
"created_at": "2020-03-02T06:53:39.776504"
},
{
"id": 3,
"name": "最后一个比赛",
"created_at": "2020-03-02T06:53:39.776848"
},
{
"id": 1,
"name": "新比赛",
"created_at": "2020-03-02T06:53:39.776211"
}
]
注意,.model_dump()
有一个 root
元素,包含列表,但 .model_dump_json()
将列表作为根元素。
同时注意结果按 name
字母顺序排序。
Here we introduce:
Creating a list-model to serialise a queryset
Default sorting is honoured
Source to example: 2: Querysets & Lists
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"]
from tortoise.contrib.pydantic import pydantic_queryset_creator
Tournament_Pydantic_List = pydantic_queryset_creator(Tournament)
And now have a Pydantic Model that can be used for representing schema and serialisation.
The JSON-Schema of Tournament_Pydantic_List
is now:
>>> 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'
}
}
}
}
}
Note that the Tournament
is now not the root. A simple list is.
To serialise an object it is simply (in an async context):
# 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())
And one could get the contents by using regular Pydantic-object methods, such as .model_dump()
or .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"
}
]
Note how .model_dump()
has a root
element with the list, but the .model_dump_json()
has the list as root.
Also note how the results are sorted alphabetically by name
.
3: 关系和初始化¶
Relations & Early-init
在这里,我们介绍:
关系
早期模型初始化
Note
本教程关于早期初始化的部分仅在您需要在初始化 Tortoise ORM 之前 生成 Pydantic 模型时才需要。
请查看 基本用法 (在函数 run
中)以了解 *_creator
仅在我们正确初始化 Tortoise ORM 之后被调用,在这种情况下不需要早期初始化。
示例源代码: 3: Relations & Early-init
我们定义带有关系的模型:
from tortoise import fields
from tortoise.models import Model
class Tournament(Model):
"""
这是一个比赛的引用
"""
id = fields.IntField(primary_key=True)
name = fields.CharField(max_length=100)
#: 比赛记录创建的日期时间
created_at = fields.DatetimeField(auto_now_add=True)
class Event(Model):
"""
这是比赛中的一个事件的引用
"""
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="该事件发生的比赛"
)
接下来,我们使用 pydantic_model_creator
创建我们的 Pydantic 模型:
from tortoise.contrib.pydantic import pydantic_model_creator
Tournament_Pydantic = pydantic_model_creator(Tournament)
Tournament_Pydantic
的 JSON 架构现在是:
>>> print(Tournament_Pydantic.schema())
{
'title': 'Tournament',
'description': '这是一个比赛的引用',
'type': 'object',
'properties': {
'id': {
'title': 'Id',
'type': 'integer'
},
'name': {
'title': 'Name',
'type': 'string'
},
'created_at': {
'title': 'Created At',
'description': '比赛记录创建的日期时间',
'type': 'string',
'format': 'date-time'
}
}
}
哦不!关系在哪里?
因为模型尚未完全初始化,所以此时它不知道关系。
我们需要使用 tortoise.Tortoise.init_models()
早期初始化模型关系。
from tortoise import Tortoise
Tortoise.init_models(["__main__"], "models")
# 现在再试一次
Tournament_Pydantic = pydantic_model_creator(Tournament)
Tournament_Pydantic
的 JSON 架构现在是:
>>> print(Tournament_Pydantic.schema())
{
'title': 'Tournament',
'description': '这是一个比赛的引用',
'type': 'object',
'properties': {
'id': {
'title': 'Id',
'type': 'integer'
},
'name': {
'title': 'Name',
'type': 'string'
},
'created_at': {
'title': 'Created At',
'description': '比赛记录创建的日期时间',
'type': 'string',
'format': 'date-time'
},
'events': {
'title': 'Events',
'description': '该事件发生的比赛',
'type': 'array',
'items': {
'$ref': '#/definitions/Event'
}
}
},
'definitions': {
'Event': {
'title': 'Event',
'description': '这是比赛中的一个事件的引用',
'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': '这是比赛中的一个事件的引用',
'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': '该事件发生的比赛',
'allOf': [
{
'$ref': '#/definitions/Tournament'
}
]
}
},
'definitions': {
'Tournament': {
'title': 'Tournament',
'description': '这是一个比赛的引用',
'type': 'object',
'properties': {
'id': {
'title': 'Id',
'type': 'integer'
},
'name': {
'title': 'Name',
'type': 'string'
},
'created_at': {
'title': 'Created At',
'description': '比赛记录创建的日期时间',
'type': 'string',
'format': 'date-time'
}
}
}
}
}
并且也定义了关系!
注意,这两个架构都没有反向跟随关系。这是默认设置,稍后的教程中我们将展示相关选项。
让我们创建并序列化对象,看看它们的样子 (在异步上下文中):
# 创建对象
tournament = await Tournament.create(name="新比赛")
event = await Event.create(name="事件", tournament=tournament)
# 序列化比赛
tourpy = await Tournament_Pydantic.from_tortoise_orm(tournament)
>>> print(tourpy.model_dump_json())
{
"id": 1,
"name": "新比赛",
"created_at": "2020-03-02T07:23:27.731656",
"events": [
{
"id": 1,
"name": "事件",
"created_at": "2020-03-02T07:23:27.732492"
}
]
}
并序列化事件 (在异步上下文中):
eventpy = await Event_Pydantic.from_tortoise_orm(event)
>>> print(eventpy.model_dump_json())
{
"id": 1,
"name": "事件",
"created_at": "2020-03-02T07:23:27.732492",
"tournament": {
"id": 1,
"name": "新比赛",
"created_at": "2020-03-02T07:23:27.731656"
}
}
Here we introduce:
Relationships
Early model init
Note
The part of this tutorial about early-init is only required if you need to generate the pydantic models before you have initialised Tortoise ORM.
Look at 基本用法 (in function run
) to see where the *_creator is only
called after we initialised Tortoise ORM properly, in that case an early init is not needed.
Source to example: 3: Relations & Early-init
We define our models with a relationship:
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"
)
Next we create our Pydantic Model using pydantic_model_creator
:
from tortoise.contrib.pydantic import pydantic_model_creator
Tournament_Pydantic = pydantic_model_creator(Tournament)
The JSON-Schema of Tournament_Pydantic
is now:
>>> 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! Where is the relation?
Because the models have not fully initialised, it doesn’t know about the relations at this stage.
We need to initialise our model relationships early using tortoise.Tortoise.init_models()
from tortoise import Tortoise
Tortoise.init_models(["__main__"], "models")
# Now lets try again
Tournament_Pydantic = pydantic_model_creator(Tournament)
The JSON-Schema of Tournament_Pydantic
is now:
>>> 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'
}
}
}
}
}
Aha! that’s much better.
Note we can also create a model for Event
the same way, and it should just work:
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'
}
}
}
}
}
And that also has the relation defined!
Note how both schema’s don’t follow relations back. This is on by default, and in a later tutorial we will show the options.
Lets create and serialise the objects and see what they look like (in an async context):
# 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"
}
]
}
And serialising the event (in an async context):
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 和 Callables¶
PydanticMeta & Callables
在这里,我们介绍:
通过
PydanticMeta
类配置模型创建器。使用可调用函数注释额外数据。
示例源代码: 4: PydanticMeta & Callables
让我们添加一些计算数据的方法,并告诉创建器使用它们:
class Tournament(Model):
"""
这是一个比赛的引用
"""
id = fields.IntField(primary_key=True)
name = fields.CharField(max_length=100)
created_at = fields.DatetimeField(auto_now_add=True)
# 手动定义反向关系是有用的,这样类型检查
# 和自动完成才能正常工作
events: fields.ReverseRelation["Event"]
def name_length(self) -> int:
"""
计算名称的长度
"""
return len(self.name)
def events_num(self) -> int:
"""
计算事件数量
"""
try:
return len(self.events)
except NoValuesFetched:
return -1
class PydanticMeta:
# 排除创建时间戳
exclude = ("created_at",)
# 包含两个可调用函数作为计算列
computed = ("name_length", "events_num")
class Event(Model):
"""
这是比赛中的一个事件的引用
"""
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="该事件发生的比赛"
)
class Meta:
ordering = ["name"]
class PydanticMeta:
exclude = ("created_at",)
这里有很多内容需要解释。
首先,我们定义了一个 PydanticMeta
块,里面是 Pydantic 模型创建器的配置选项。
请参见 tortoise.contrib.pydantic.creator.PydanticMeta
以了解可用选项。
其次,我们在两个模型中都排除了 created_at
,因为我们认为它没有提供任何好处。
第三,我们添加了两个可调用函数: name_length
和 events_num
。我们希望这些成为结果集的一部分。
请注意,可调用函数/计算字段需要手动指定返回类型,因为如果没有这个,我们无法确定创建有效 Pydantic 架构所需的记录类型。
这对于标准 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 架构现在是:
{
"title": "Tournament",
"description": "这是一个比赛的引用",
"type": "object",
"properties": {
"id": {
"title": "Id",
"type": "integer"
},
"name": {
"title": "Name",
"type": "string"
},
"events": {
"title": "Events",
"description": "该事件发生的比赛",
"type": "array",
"items": {
"$ref": "#/definitions/Event"
}
},
"name_length": {
"title": "Name Length",
"description": "计算名称的长度",
"type": "integer"
},
"events_num": {
"title": "Events Num",
"description": "计算事件数量。",
"type": "integer"
}
},
"definitions": {
"Event": {
"title": "Event",
"description": "这是比赛中的一个事件的引用",
"type": "object",
"properties": {
"id": {
"title": "Id",
"type": "integer"
},
"name": {
"title": "Name",
"type": "string"
}
}
}
}
}
注意 created_at
被移除,name_length
和 events_num
被添加。
让我们创建并序列化对象,看看它们的样子 (在异步上下文中):
# 创建对象
tournament = await Tournament.create(name="新比赛")
await Event.create(name="事件 1", tournament=tournament)
await Event.create(name="事件 2", tournament=tournament)
# 序列化比赛
tourpy = await Tournament_Pydantic.from_tortoise_orm(tournament)
>>> print(tourpy.model_dump_json())
{
"id": 1,
"name": "新比赛",
"events": [
{
"id": 1,
"name": "事件 1"
},
{
"id": 2,
"name": "事件 2"
}
],
"name_length": 14,
"events_num": 2
}
Here we introduce:
Configuring model creator via
PydanticMeta
class.Using callable functions to annotate extra data.
Source to example: 4: PydanticMeta & Callables
Let’s add some methods that calculate data, and tell the creators to use them:
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",)
There is much to unpack here.
Firstly, we defined a PydanticMeta
block, and in there is configuration options for the pydantic model creator.
See tortoise.contrib.pydantic.creator.PydanticMeta
for the available options.
Secondly, we excluded created_at
in both models, as we decided it provided no benefit.
Thirly, we added two callables: name_length
and events_num
. We want these as part of the result set.
Note that callables/computed fields require manual specification of return type, as without this we can’t determine the record type which is needed to create a valid Pydantic schema.
This is not needed for standard Tortoise ORM fields, as the fields already define a valid type.
Note that the Pydantic serializer can’t call async methods, but since the tortoise helpers pre-fetch relational data, it is available before serialization.
So we don’t need to await the relation.
We should however protect against the case where no prefetching was done, hence catching and handling the tortoise.exceptions.NoValuesFetched
exception.
Next we create our Pydantic Model using pydantic_model_creator
:
from tortoise import Tortoise
Tortoise.init_models(["__main__"], "models")
Tournament_Pydantic = pydantic_model_creator(Tournament)
The JSON-Schema of Tournament_Pydantic
is now:
{
"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"
}
}
}
}
}
Note that created_at
is removed, and name_length
& events_num
is added.
Lets create and serialise the objects and see what they look like (in an async context):
# 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(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'
)[source]¶ Function to build Pydantic Model off Tortoise Model.
- Parameters:¶
- _stack=
()
¶ Internal parameter to track recursion
- cls¶
The Tortoise Model
- name=
None
¶ Specify a custom name explicitly, instead of a generated name.
- exclude=
()
¶ Extra fields to exclude from the provided model.
- include=
()
¶ Extra fields to include from the provided model.
- computed=
()
¶ Extra computed fields to include from the provided model.
- optional=
()
¶ Extra optional fields for the provided model.
- allow_cycles=
None
¶ Do we allow any cycles in the generated model? This is only useful for recursive/self-referential models.
A value of
False
(the default) will prevent any and all backtracking.- sort_alphabetically=
None
¶ Sort the parameters alphabetically instead of Field-definition order.
The default order would be:
Field definition order +
order of reverse relations (as discovered) +
order of computed functions (as provided).
- exclude_readonly=
False
¶ Build a subset model that excludes any readonly fields
- meta_override=
None
¶ A PydanticMeta class to override model’s values.
- model_config=
None
¶ A custom config to use as pydantic config.
- validators=
None
¶ A dictionary of methods that validate fields.
- module=
'tortoise.contrib.pydantic.creator'
¶ The name of the module that the model belongs to.
- Note: Created pydantic model uses config_class parameter and PydanticMeta’s
config_class as its Config class’s bases(Only if provided!), but it ignores
fields
config. pydantic_model_creator will generate fields by include/exclude/computed parameters automatically.
- _stack=
- Return type:¶
Type
[PydanticModel
]
-
tortoise.contrib.pydantic.creator.pydantic_queryset_creator(cls, *, name=
None
, exclude=()
, include=()
, computed=()
, allow_cycles=None
, sort_alphabetically=None
)[source]¶ Function to build a Pydantic Model list off Tortoise Model.
- Parameters:¶
- cls¶
The Tortoise Model to put in a list.
- name=
None
¶ Specify a custom name explicitly, instead of a generated name.
The list generated name is currently naive and merely adds a “s” to the end of the singular name.
- exclude=
()
¶ Extra fields to exclude from the provided model.
- include=
()
¶ Extra fields to include from the provided model.
- computed=
()
¶ Extra computed fields to include from the provided model.
- allow_cycles=
None
¶ Do we allow any cycles in the generated model? This is only useful for recursive/self-referential models.
A value of
False
(the default) will prevent any and all backtracking.- sort_alphabetically=
None
¶ Sort the parameters alphabetically instead of Field-definition order.
The default order would be:
Field definition order +
order of reverse relations (as discovered) +
order of computed functions (as provided).
- Return type:¶
Type
[PydanticListModel
]
PydanticMeta¶
- class tortoise.contrib.pydantic.creator.PydanticMeta[source]¶
The
PydanticMeta
class is used to configure metadata for generating the pydantic Model.Usage:
class Foo(Model): ... class PydanticMeta: exclude = ("foo", "baa") computed = ("count_peanuts", )
-
allow_cycles : bool =
False
¶ Allow cycles in recursion - This can result in HUGE data - Be careful! Please use this with
exclude
/include
and sanemax_recursion
-
backward_relations : bool =
True
¶ Use backward relations without annotations - not recommended, it can be huge data without control
-
computed : Tuple[str, ...] =
()
¶ Computed fields can be listed here to use in pydantic model
-
exclude : Tuple[str, ...] =
('Meta',)
¶ Fields listed in this property will be excluded from pydantic model
-
exclude_raw_fields : bool =
True
¶ If we should exclude raw fields (the ones have _id suffixes) of relations
-
include : Tuple[str, ...] =
()
¶ If not empty, only fields this property contains will be in the pydantic model
-
max_recursion : int =
3
¶ Maximum recursion level allowed
-
model_config : ConfigDict | None =
None
¶ Allows user to specify custom config for generated model
-
sort_alphabetically : bool =
False
¶ Sort fields alphabetically. If not set (or
False
) then leave fields in declaration order
-
allow_cycles : bool =
模型类¶
Model classes
-
class tortoise.contrib.pydantic.base.PydanticListModel(root=
PydanticUndefined
, **data)[source]¶ Pydantic BaseModel for List of Tortoise Models
This provides an extra method above the usual Pydantic model properties
- async classmethod from_queryset(queryset)[source]¶
Returns a serializable pydantic model instance that contains a list of models, from the provided queryset.
This will prefetch all the relations automatically.
-
model_computed_fields : ClassVar[Dict[str, ComputedFieldInfo]] =
{}
¶ A dictionary of computed field names and their corresponding ComputedFieldInfo objects.
-
model_config : ClassVar[ConfigDict] =
{}
¶ Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
-
model_fields : ClassVar[Dict[str, FieldInfo]] =
{'root': FieldInfo(annotation=~RootModelRootType, required=True)}
¶ Metadata about the fields defined on the model, mapping of field names to [FieldInfo][pydantic.fields.FieldInfo] objects.
This replaces Model.__fields__ from Pydantic V1.
- class tortoise.contrib.pydantic.base.PydanticModel(**data)[source]¶
Pydantic BaseModel for Tortoise objects.
This provides an extra method above the usual Pydantic model properties
- async classmethod from_queryset(queryset)[source]¶
Returns a serializable pydantic model instance that contains a list of models, from the provided queryset.
This will prefetch all the relations automatically.
- async classmethod from_queryset_single(queryset)[source]¶
Returns a serializable pydantic model instance for a single model from the provided queryset.
This will prefetch all the relations automatically.
- Parameters:¶
- queryset : QuerySetSingle¶
a queryset on the model this PydanticModel is based on.
- Return type:¶
Self
- async classmethod from_tortoise_orm(obj)[source]¶
Returns a serializable pydantic model instance built from the provided model instance.
Note
This will prefetch all the relations automatically. It is probably what you want.
If you don’t want this, or require a
sync
method, look to using.from_orm()
.In that case you’d have to manage prefetching yourself, or exclude relational fields from being part of the model using
tortoise.contrib.pydantic.creator.PydanticMeta
, or you would be gettingOperationalError
exceptions.This is due to how the
asyncio
framework forces I/O to happen in explicitawait
statements. Hence we can only do lazy-fetching during an awaited method.
-
model_computed_fields : ClassVar[Dict[str, ComputedFieldInfo]] =
{}
¶ A dictionary of computed field names and their corresponding ComputedFieldInfo objects.
-
model_config : ClassVar[ConfigDict] =
{'from_attributes': True}
¶ Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
-
model_fields : ClassVar[Dict[str, FieldInfo]] =
{}
¶ Metadata about the fields defined on the model, mapping of field names to [FieldInfo][pydantic.fields.FieldInfo] objects.
This replaces Model.__fields__ from Pydantic V1.