Pydantic 序列化

Tortoise ORM 具有一个 Pydantic 插件,该插件将从 Tortoise 模型生成 Pydantic 模型,然后提供帮助程序函数来序列化该模型及其相关对象。

我们目前仅支持为序列化生成 Pydantic 对象,而不支持在此阶段进行反序列化。

请参阅 Pydantic 示例

教程

1:基本用法

这里我们介绍

  • 从 Tortoise 模型创建 Pydantic 模型

  • 使用文档字符串和文档注释

  • 评估生成的架构

  • 使用 .model_dump().model_dump_json() 两种方法进行简单序列化

示例源代码:1:基本用法

让我们从一个基本的 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 模型,需要调用
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': '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 = await Tournament.create(name="New Tournament")
tourpy = await Tournament_Pydantic.from_tortoise_orm(tournament)

可以使用 常规 Pydantic 对象方法(例如 .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"
}

2:查询集和列表

这里我们介绍

  • 创建列表模型以序列化查询集

  • 默认排序得到保留

示例源代码:2:查询集和列表

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 列表模型,需要调用
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': '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 现在不是根。一个简单的列表是根。

要序列化一个对象,只需(在异步上下文中)

# 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 对象方法(例如 .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() 有一个包含列表的 root 元素,但 .model_dump_json() 将列表作为根。还要注意,结果如何按 name 按字母顺序排序。

3:关系和早期初始化

这里我们介绍

  • 关系

  • 早期模型初始化

注意

本教程中关于早期初始化的部分仅在需要在初始化 Tortoise ORM 之前生成 Pydantic 模型时才需要。

查看 基本 Pydantic(在函数 run 中)以查看 *_creator is only 仅在正确初始化 Tortoise ORM 之后才被调用,在这种情况下,不需要早期初始化。

示例源代码:3:关系和早期初始化

我们使用关系定义模型

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 架构为

>>> 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'
        }
    }
}

哦,不!关系在哪里?

由于模型尚未完全初始化,因此它在此阶段不知道关系。

我们需要使用 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 架构为

>>> 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'
                }
            }
        }
    }
}

并且它还定义了关系!

请注意,两个模式都不遵循关系。这是默认设置,在后面的教程中,我们将展示这些选项。

让我们创建并序列化对象,看看它们的样子(在异步上下文中)

# 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 类配置模型创建器。

  • 使用可调用函数注释额外数据。

示例来源:4: PydanticMeta 和可调用对象

让我们添加一些计算数据的方法,并告诉创建者使用它们

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_lengthevents_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": "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_lengthevents_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
}

创建器

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]

根据 Tortoise 模型构建 Pydantic 模型 的函数。

参数:
_stack=()

跟踪递归的内部参数

cls

Tortoise 模型

name=None

明确指定自定义名称,而不是生成名称。

exclude=()

从提供的模型中排除的额外字段。

include=()

从提供的模型中包含的额外字段。

computed=()

从提供的模型中包含的额外计算字段。

optional=()

为提供的模型提供的额外可选字段。

allow_cycles=None

我们是否允许在生成的模型中出现任何循环?这仅对递归/自引用模型有用。

False(默认值)的值将阻止任何和所有回溯。

sort_alphabetically=None

按字母顺序对参数进行排序,而不是按字段定义顺序进行排序。

默认顺序为

  • 字段定义顺序 +

  • 反向关系的顺序(按发现的顺序) +

  • 计算函数的顺序(按提供的顺序)。

exclude_readonly=False

构建一个排除任何只读字段的子集模型

meta_override=None

用于覆盖模型值的 PydanticMeta 类。

model_config=None

用作 pydantic 配置的自定义配置。

validators=None

验证字段的方法的字典。

module='tortoise.contrib.pydantic.creator'

模型所属模块的名称。

注意:创建的 pydantic 模型使用 config_class 参数和 PydanticMeta 的

config_class 作为其 Config 类的基础(仅在提供时),但它忽略 fields 配置。pydantic_model_creator 将自动通过 include/exclude/computed 参数生成字段。

返回类型:

Type[PydanticModel]

tortoise.contrib.pydantic.creator.pydantic_queryset_creator(cls, *, name=None, exclude=(), include=(), computed=(), allow_cycles=None, sort_alphabetically=None)[source]

用于构建 Pydantic 模型 Tortoise 模型列表的函数。

参数:
cls

要放入列表中的 Tortoise 模型。

name=None

明确指定自定义名称,而不是生成名称。

当前生成的列表名称很幼稚,只是在单数名称的末尾添加一个“s”。

exclude=()

从提供的模型中排除的额外字段。

include=()

从提供的模型中包含的额外字段。

computed=()

从提供的模型中包含的额外计算字段。

allow_cycles=None

我们是否允许在生成的模型中出现任何循环?这仅对递归/自引用模型有用。

False(默认值)的值将阻止任何和所有回溯。

sort_alphabetically=None

按字母顺序对参数进行排序,而不是按字段定义顺序进行排序。

默认顺序为

  • 字段定义顺序 +

  • 反向关系的顺序(按发现的顺序) +

  • 计算函数的顺序(按提供的顺序)。

返回类型:

Type[PydanticListModel]

PydanticMeta

class tortoise.contrib.pydantic.creator.PydanticMeta[source]

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),则按声明顺序保留字段

模型类

class tortoise.contrib.pydantic.base.PydanticListModel(root=PydanticUndefined, **data)[source]

Tortoise 模型列表的 Pydantic BaseModel

它在通常的 Pydantic 模型属性之上提供了一个额外的方法

async classmethod from_queryset(queryset)[source]

返回一个可序列化的 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)[source]

Tortoise 对象的 Pydantic BaseModel。

这提供了除通常的 Pydantic 模型属性 之外的额外方法

async classmethod from_queryset(queryset)[source]

返回一个可序列化的 pydantic 模型实例,其中包含来自所提供查询集的模型列表。

这会自动预取所有关系。

参数:
queryset : QuerySet

基于此 PydanticModel 的模型上的查询集。

返回类型:

List[typing_extensions.Self]

async classmethod from_queryset_single(queryset)[source]

返回提供的查询集中单个模型的可序列化 pydantic 模型实例。

这会自动预取所有关系。

参数:
queryset : QuerySetSingle

基于此 PydanticModel 的模型上的查询集。

返回类型:

typing_extensions.Self

async classmethod from_tortoise_orm(obj)[source]

返回由提供的模型实例构建的可序列化 pydantic 模型实例。

注意

这将自动预取所有关系。这可能是你想要的。

如果你不想要这个,或者需要一个 sync 方法,请考虑使用 .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__