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)
要从中创建 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': '这是一个比赛的引用',
    '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)
To create a Pydantic model from that one would call:
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"]
要从中创建 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': '这是一个比赛的引用',
    '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"]
To create a Pydantic list-model from that one would call:
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_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": "这是一个比赛的引用",
    "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_lengthevents_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.

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 sane max_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

模型类

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.

Parameters:
queryset : QuerySet

a queryset on the model this PydanticListModel is based on.

Return type:

Self

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.

Parameters:
queryset : QuerySet

a queryset on the model this PydanticModel is based on.

Return type:

List[Self]

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 getting OperationalError exceptions.

This is due to how the asyncio framework forces I/O to happen in explicit await statements. Hence we can only do lazy-fetching during an awaited method.

Parameters:
obj

The Model instance you want serialized.

Return type:

Self

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.