正在进入ing...

Tortoise-ORM 深入浅出第一篇

发布时间:2024-01-21 浏览量: 3063 文章分类: python

Tortoise-ORM 学习笔记

关于tortoise-orm,他是一个支持异步的数据库ORM,使用比较简单,在功能上面和DjangoORM也比较类似,所以如果你对DjangoORM本身就比较熟悉,同时也有以后想迁移比如FastAPI或者要使用纯异步编程的需求,那Tortoise-orm都是你一定要学习的一个框架。

官方的定位

Tortoise ORM 是一款易于使用的 asyncio ORM(对象关系映射器),其灵感来自 Django。 Tortoise ORM 在设计时考虑到了关系,并对优秀且流行的 Django ORM 表示钦佩。它的设计理念是,你不仅要处理表格,还要处理关系数据。

环境和版本

Python版本是 3.9.10,理论只要你是3.8以后的版本都是可以支持的。主要官方支持的数据库类型是(SQLiteMySQLPostgreSQLtortoise-orm 0.20.0 aiosqlite 0.17.0 aiomysql 0.2.0

我准备要把这个框架彻底吃透,所以会将官方文档也全部都过一遍,同时也会将例子都逐个尝试,在结合自己的经验来一起来逐个过一遍。因为实际项目中使用sqlite的场景也并不多,所以我的测试都会基于mysql:5.7版本来进行。

基本的使用

先通过简单的引用和使用,来看看最基本的使用,整体还是非常的简单和简洁。如果你对ORM不熟悉,我建议你先熟悉了在来看

下面的代码我加入了一下注释,你可以简单看一下。

from tortoise import Tortoise, fields, run_async
from tortoise.models import Model

# 定义个数据模型
class Event(Model):
    id = fields.IntField(pk=True)
    name = fields.TextField()
    detetime = fields.DatetimeField(null=True)
    class Meta:
        # 数据表的名称
        table = "event"

    def __str__(self):
        return self.name


async def run():
    await Tortoise.init(
        db_url="mysql://root:123456@127.0.0.1:3306/test",
        modules={"models": ["__main__"]},
    )
    await Tortoise.generate_schemas()

    event = await Event.create(name="Test")
    await Event.filter(id=event.id).update(name="Updated name")

    print(await Event.filter(name="Updated name").first())
    # # Updated name

    await Event(name="Test 2").save()

    # 将所有的结果返回,并直接返回一个列表
    # flat为True,则直接返回列表可以遍历,而False也为元组包裹
    print(await Event.all().values_list("id", flat=False))
    # [1, 2, 3, 4, 5, 6, 7, 8, 9]
    print(await Event.all().values_list("id", flat=False))
    # [(1,), (2,), (3,), (4,), (5,), (6,), (7,), (8,), (9,), (10,)]

    # 将所有的结果返回,并以列表包含字典的方式,字典的key则是字段名称
    print(await Event.all().values("id", "name"))
    # [{'id': 1, 'name': 'Updated name'}, {'id': 2, 'name': 'Updated name'}, {'id': 3, 'name': 'Updated name'}, {'id': 4, 'name': 'Test 2'}, {'id': 5, 'name': 'Updated name'}, {'id': 6, 'name': 'Test 2'}, {'id': 7, 'name': 'Updated name'}, {'id': 8, 'name': 'Test 2'}, {'id': 9, 'name': 'Test 2'}]

if __name__ == "__main__":
    run_async(run())

第二个小例子

这里我们主要是在创建数据结构的时候,可以给数据字段通过description添加描述,并在Metatable_description可以给数据表增加描述。

from tortoise import Tortoise, fields, run_async
from tortoise.models import Model


class Event(Model):
    id = fields.IntField(pk=True)
    name = fields.TextField(description="事件名称")
    detetime = fields.DatetimeField(null=True, description="事件时间")

    class Meta:
        table = "event"
        table_description = "数据表的一些描述说明"

    def __str__(self):
        return self.name


async def run():
    await Tortoise.init(
        db_url="mysql://root:123456@127.0.0.1:3306/test",
        modules={"models": ["__main__"]},
    )
    await Tortoise.generate_schemas()

    # 在数据表里面创建一个名字叫Test的事件名称,并将对象返回到 event上
    event = await Event.create(name="Test")
    # 通过filter查询id=上面创建的对象id,并通过 update 把事件名称更改为Updated name
    await Event.filter(id=event.id).update(name="Updated name")
    # 打印查询名字为 Updated name 的第一条数据
    print(await Event.filter(name="Updated name").first())
    # Updated name

    # 再次创建一个名叫 Test2的事件对象,并通过.save()存储
    await Event(name="Test2").save()
    # 查询所有的事件数据,并将id输出列表
    print(await Event.all().values_list("id", flat=True))
    # [1, 2, 3]

    # 查询所有的事件数据,并将id、name以字典的模式输出
    print(await Event.all().values("id", "name"))
    # [{'id': 1, 'name': 'Updated name'}, {'id': 2, 'name': 'Updated name'}, {'id': 3, 'name': 'Test2'}]

if __name__ == "__main__":
    run_async(run())

根据官网的解释是预加载,实际也就是正向查询和反向查询,同时在查询的时候把关联表的数据查询出来。 实际说实话这个官方例子并不好,甚至某种程度来说还有点复杂和难以理解。 先用官方的例子,大致有概念以后在用我们便于理解的例子来重新过一遍。

官方例子

from tortoise import Tortoise, fields, run_async
from tortoise.models import Model
from tortoise.query_utils import Prefetch


class Tournament(Model):
    id = fields.IntField(pk=True)
    name = fields.TextField()
    events: fields.ReverseRelation["Event"]
    def __str__(self):
        return self.name

class Team(Model):
    id = fields.IntField(pk=True)
    name = fields.TextField()
    events: fields.ManyToManyRelation["Event"]

    def __str__(self):
        return self.name


class Event(Model):
    id = fields.IntField(pk=True)
    name = fields.TextField(description="事件名称")
    tournament: fields.ForeignKeyRelation[Tournament] = fields.ForeignKeyField(
        "models.Tournament", related_name="events"
    )

    participants: fields.ManyToManyRelation["Team"] = fields.ManyToManyField(
        "models.Team", related_name="events", through="event_team"
    )

    def __str__(self):
        return self.name

async def run():
    await Tortoise.init(
        db_url="mysql://root:123456@127.0.0.1:3306/test",
        modules={"models": ["__main__"]},
    )
    await Tortoise.generate_schemas()

    # 先在 Tournament 创建 名字是 tournament 的数据,并返回在 tournament 中
    tournament = await Tournament.create(name="tournament")
    # 在创建事件表的数据,名称分别是 First、Second,他们关联的比赛分别是 刚创建的 tournament 对象
    await Event.create(name="First", tournament=tournament)
    await Event.create(name="Second", tournament=tournament)

    # 先查询 Tournament 的所有比赛,然后通过 prefetch_related 预加载Event中的数据,在通过queryset,进行filter过滤取出名字是First的数据,并只取第一条
    tournament_with_filtered = (
        await Tournament.all()
        .prefetch_related(Prefetch("events", queryset=Event.filter(name="First")))
        .first()
    )
    # 这样就可以关联查询,类似赛事下的所有事件
    event = await tournament_with_filtered.events
    print(event)
    # [<Event: 1>, <Event: 2>]

    # 同时也支持对事件在进行过滤查询
    event = await tournament_with_filtered.events.filter(name="First").first()
    print(event)
    # First
    print(tournament_with_filtered, type(tournament_with_filtered))
    # tournament <class '__main__.Tournament'>

    tournament_with_filtered = await Tournament.first().prefetch_related("events")
    print(tournament_with_filtered, type(tournament_with_filtered))
    # tournament <class '__main__.Tournament'>

    # 这个理解比较难一点了,通过查询 Tournament的所有数据,在预加载Tournament对象的events字段
    # Prefetch 预加载配置,它包含了两个查询,每个查询都有一个特定的 queryset 参数,用于过滤 Event 对象。这里有两个 Prefetch 配置,每个都指定了一个不同的 queryset:
    # 第一个 Prefetch 使用 Event.filter(name="First") 来过滤出所有名称为 "First" 的 Event 对象,并将这些对象预加载到 Tournament 的 events 字段中,并且将这些对象的集合命名为 to_attr_events_first。
    # 第二个 Prefetch 使用 Event.filter(name="Second") 来过滤出所有名称为 "Second" 的 Event 对象,并将这些对象预加载到 Tournament 的 events 字段中,并且将这些对象的集合命名为 to_attr_events_second。
    # first():这个方法用于获取查询结果的第一个元素。在 Tortoise-ORM 中,.first() 通常用于获取单个对象,而不是列表。
    tournament_with_filtered_to_attr = (
        await Tournament.all()
        .prefetch_related(
            Prefetch(
                "events",
                queryset=Event.filter(name="First"),
                to_attr="to_attr_events_first",
            ),
            Prefetch(
                "events",
                queryset=Event.filter(name="Second"),
                to_attr="to_attr_events_second",
            ),
        )
        .first()
    )

    print(tournament_with_filtered_to_attr, type(tournament_with_filtered_to_attr))
    # tournament <class '__main__.Tournament'>
    print(tournament_with_filtered_to_attr.to_attr_events_first)
    # [<Event: 1>]
    print(tournament_with_filtered_to_attr.to_attr_events_second)
    # [<Event: 2>]

if __name__ == "__main__":
    run_async(run())

可能你看完上面的例子,和我一样懵逼,学习的复杂度感觉突然一下上来了。 接下来先用一个简单的例子,更好的理解

gushi_dict = {
        "李白": ["将进酒", "古朗月行", "行路难", "蜀道难"],
        "白居易": ["长恨歌", "卖炭翁", "琵琶行"],
        "欧阳修": ["醉翁亭记", "卖油翁", "秋声赋", "画眉鸟"],
        "李商隐": ["锦瑟", "夜雨寄北", "嫦娥", "贾生"],
    }

我们想通过一个作者表和一个 诗词名称表,来进行一对多关系的设定,也就是一个作者可以有多个诗词,每个诗词只有一个作者。 同时要能通过 作者查询所有的古诗,也可以通过其中任意一篇古诗的名称来查询到作者。

from tortoise import Tortoise, fields, run_async
from tortoise.models import Model
from tortoise.query_utils import Prefetch


class Author(Model):
    name = fields.CharField(max_length=60, description="作者")

    class Meta:
        table = "author"
        table_description = "作者表"


class Book(Model):
    book_name = fields.CharField(max_length=60, description="书名")
    author = fields.ForeignKeyField(
        "models.Author", description="作者", related_name="books_list"
    )

    class Meta:
        table = "book"
        table_description = "书籍表"


async def run():
    await Tortoise.init(
        db_url="mysql://root:123456@127.0.0.1:3306/test",
        modules={"models": ["__main__"]},
    )
    await Tortoise.generate_schemas()

    # 我们先创建一些作者进到表里
    author_list = ["李白", "白居易", "欧阳修", "李商隐"]
    for author in author_list:
        await Author.create(name=author)
    # 作者创建完毕

    # 接下来在逐个填写对应的作者和对应的诗词(不推荐)
    gushi_dict = {
        "李白": ["将进酒", "古朗月行", "行路难", "蜀道难"],
        "白居易": ["长恨歌", "卖炭翁", "琵琶行"],
        "欧阳修": ["醉翁亭记", "卖油翁", "秋声赋", "画眉鸟"],
        "李商隐": ["锦瑟", "夜雨寄北", "嫦娥", "贾生"],
    }

    for author_name in gushi_dict:
        author_obj, _ = await Author.get_or_create(name=author_name)
        for gushi_name in gushi_dict.get(author_name):
            await Book.create(book_name=gushi_name, author=author_obj)

    # 上面是第一种创建方式,但是这种方式对于存储来说,实际次数较多,使用Tortoise-orm 可以使用官方推荐的模式来操作。(推荐)
    gushi_dict = {
        "李白": ["将进酒", "古朗月行", "行路难", "蜀道难"],
        "白居易": ["长恨歌", "卖炭翁", "琵琶行"],
        "欧阳修": ["醉翁亭记", "卖油翁", "秋声赋", "画眉鸟"],
        "李商隐": ["锦瑟", "夜雨寄北", "嫦娥", "贾生"],
    }
    # 用下面这个方法,可以更为快速的创建,因为次数少了一次查询,效率也会更高
    for author in gushi_dict:
        author_obj = await Author.create(name=author)
        for gushi_name in gushi_dict.get(author):
            await Book.create(book_name=gushi_name, author=author_obj)


    # 接下来就是如何来进行查询了,比如我们想查询李白都写了那些古诗词(不推荐)
    author_obj = await Author.get(name="李白")
    books_obj = await Book.filter(author=author_obj)
    print(books_obj)

    # 在换成官方推荐的方式来试着改写一下(推荐)
    author_obj = (
        await Author.get(name="李白")
        .prefetch_related(Prefetch("books_list", queryset=Book.all()))
        .first()
    )
    print(await author_obj.books_list)

    # 接下来我们在实现一个 通过书名来查询作者的模型(不推荐)
    book_obj = await Book.get(book_name="卖炭翁")
    author_obj = await book_obj.author
    print(author_obj, author_obj.name)
    # <Author> 白居易

    # 而使用Tortoise-orm则可以这样改写(推荐)
    book_obj = await Book.get(book_name="卖炭翁").prefetch_related("author")
    print(book_obj.author, book_obj.author.name)
    # <Author> 白居易

if __name__ == "__main__":
    run_async(run())