fastapi之tortoise-orm

概述

fastapi是一個很優秀的框架,但是缺少一個合適的orm,官方代碼裏面使用的是sqlalchemy,異步也是使用的這個。但是我這邊看到有tortoise-orm這個異步orm框架,不知道效率如何,這裏先學習,之後做一個性能測試比較一下。
整個框架非常接近django,如果我沒寫的地方,要麼是和django差不多,要麼是沒這功能。

fastapi引入

在main.py文件裏面引入如下代碼:

from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
from models import User_Pydantic, UserIn_Pydantic, Users
from tortoise.contrib.fastapi import HTTPNotFoundError, register_tortoise

app = FastAPI(title="Tortoise ORM FastAPI example")

...
register_tortoise(#這裏是啓動app的,之後會考慮和使用uvicorn啓動的性能差別
    app,
    db_url="sqlite://:memory:",#數據庫信息
    modules={"models": ["models"]},#models列表
    generate_schemas=True,#如果數據庫爲空,則自動生成對應表單,生產環境不要開
    add_exception_handlers=True,#生產環境不要開,會泄露調試信息
)

引入tortoise-orm的方法:register_tortoise

def register_tortoise(
    app: FastAPI,
    config: Optional[dict] = None,
    config_file: Optional[str] = None,
    db_url: Optional[str] = None,
    modules: Optional[Dict[str, List[str]]] = None,
    generate_schemas: bool = False,
    add_exception_handlers: bool = False,
) -> None:
    """
    在fastapi註冊startup和shutdown

    使用 ``config``, ``config_file``或 ``(db_url, modules)``三者之一來配置
    示例
    ----------
    config:
        Dict containing config:
        Example
        -------
            {
                'connections': {
                    # Dict format for connection
                    'default': {
                        'engine': 'tortoise.backends.asyncpg',
                        'credentials': {
                            'host': 'localhost',
                            'port': '5432',
                            'user': 'tortoise',
                            'password': 'qwerty123',
                            'database': 'test',
                        }
                    },
                    # Using a DB_URL string
                    'default': 'postgres://postgres:qwerty123@localhost:5432/events'
                },
                'apps': {
                    'models': {
                        'models': ['__main__'],
                        # If no default_connection specified, defaults to 'default'
                        'default_connection': 'default',
                    }
                }
            }
    config_file:
        Path to .json or .yml (if PyYAML installed) file containing config with
        same format as above.
    db_url:
        Use a DB_URL string. See :ref:`db_url`
    modules:
        Dictionary of ``key``: [``list_of_modules``] that defined "apps" and modules that
        should be discovered for models.
    generate_schemas:
        True立即生成模式。僅適用於開發環境或SQLite ' ':memory: ' '數據庫,生產環境數據庫一定手動生成。
    add_exception_handlers:
        爲' ' DoesNotExist ' ' & ' ' IntegrityError ' '添加一些自動異常處理程序。
不建議用於生產系統,因爲它可能會泄漏數據。
    """
    pass

創建對應數據模型

tortoise-orm能使用的數據類型還挺豐富,滿足了日常使用的需求。

創建Model

from tortoise import fields
from tortoise.models import Model
class User(Model):
	id=fields.IntField(pk=True)#主鍵必不可少

通過繼承的方式創建Model

from tortoise import fields
from tortoise.models import Model

class TimestampMixin():
    created_at = fields.DatetimeField(null=True, auto_now_add=True)
    modified_at = fields.DatetimeField(null=True, auto_now=True)

class NameMixin():
    name = fields.CharField(40, unique=True)

class MyAbstractBaseModel(Model):
    id = fields.IntField(pk=True)
    class Meta:
        abstract = True
#注意數據庫裏面對應表順序可能會比較亂。。
class UserModel(TimestampMixin, MyAbstractBaseModel):
    # 覆蓋繼承的字段
    id = fields.UUIDField(pk=True)
    # 新增一些字段
    first_name = fields.CharField(20, null=True)
    class Meta:
        table = "user"

class RoleModel(TimestampMixin, NameMixin, MyAbstractBaseModel):
    class Meta:
        table = "role"

設置數據庫字段field

主要有如下字段(這些字段都沒啥好說的,):

#常規字段
BigIntField,
BinaryField,
BooleanField,
CharEnumField,
CharField,
DateField,
DatetimeField,
DecimalField,
FloatField,
IntEnumField,#繼承於SmallIntField
IntField,
JSONField,
SmallIntField,
TextField,
TimeDeltaField,
UUIDField,
#關係字段(relation不知道啥用,)
BackwardFKRelation,
BackwardOneToOneRelation,
ForeignKeyField,#外鍵
ForeignKeyNullableRelation,
ForeignKeyRelation,
ManyToManyField,#多對多
ManyToManyRelation,#反向代碼提示的工具
OneToOneField,#一對一
OneToOneNullableRelation,
OneToOneRelation,
ReverseRelation,

關係鍵講解:
ForeignKeyField:

tournament = fields.ForeignKeyField('models.Tournament', related_name='events')#related_name關鍵字參數,用於爲引用的模型定義查詢自己的字段,默認爲名字+set
participants = fields.ManyToManyField('models.Team', related_name='events')
modified = fields.DatetimeField(auto_now=True)
prize = fields.DecimalField(max_digits=10, decimal_places=2, null=True)

反向代碼提示
在使用外鍵的時候,我們需要獲取到反向代碼提示(即被綁定的model查詢綁定model),這個時候需要使用relation字段來提示,(其實你不加也沒啥關係,只是個提示作用,在django裏面是編輯器和插件提前內置了相關字段所以不用手寫
示例代碼如下:

from tortoise.models import Model
from tortoise import fields

class Tournament(Model):
    id = fields.IntField(pk=True)
    name = fields.CharField(max_length=255)
    events: fields.ReverseRelation["Event"]#僅用於代碼提示,注意events必須和Event裏面的外鍵指定的related_name同名

class Event(Model):
    id = fields.IntField(pk=True)
    name = fields.CharField(max_length=255)
    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"
    )#注意多對多,兩個model裏面都要寫,雖然複雜了點,但是有代碼提示還是很合算的。。through在django裏面是指定多對多表的名字和功能,需要手動創建,這裏可能是示例代碼不全吧。。得測試

class Team(Model):
    id = fields.IntField(pk=True)
    name = fields.CharField(max_length=255)
    events: fields.ManyToManyRelation[Event]#反向關係,

字段介紹

基本字段:
source_field :自定義數據庫對應字段名稱
generated :是否映射到數據庫,
pk 是否爲主鍵
null
default 可以爲值或可調用對象
unique
index
description 描述功能,數據庫註釋用

自定義字段

tortoise-orm支持自定義字段,這個功能挺好用的,這裏先不講了,一般用不到。。。請自行查看:link.
可以通過繼承,然後重構相關字段控制字段在數據庫的存儲方式

設置Meta

from tortoise import fields
from tortoise.models import Model
class User(Model):
	id=fields.IntField(pk=True)#主鍵必不可少
	class Meta:
		abstract=True#抽象模型,用於繼承
		table="xxx"#該模型對應的表名稱
		unique_together=(("field_a", "field_b"), )#設置唯一索引,參考django
		table_description = ""#數據庫對該表的註釋
		indexes=(("field_a", "field_b"), )#指定列集爲非唯一索引,類似django在字段上的index
		ordering = ["name", "-score"]#設置默認查詢結果的順序

Model模型方法

#常用查詢方法我就不提了,講一些不常用的
annotate()#使用額外的函數/聚合對結果進行再過濾,,
bulk_create()#批量插入:

User.bulk_create([
    User(name="...", email="..."),
    User(name="...", email="...")
])

check()#檢查model數據是否正確
describe()#序列化model,返回json
exists()#True/False 記錄是否存在篩選器參數模式的model
register_listener()#偵聽器,參數爲信號,注意,更新到最新版纔有。。數據的保存和刪除前後可以被監聽,挺有用的一個東西,使用說明查看鏈接:https://tortoise-orm.readthedocs.io/en/latest/examples/basic.html#model-signals
update_from_dict()#通過dict更新數據,配合schema很有用,主要用於字段更新,schema.dict有一個只獲取填寫結果字段的方法,配合這個可以實現局部更新

查詢

參考django,略。

Q對象查詢

這一塊主要針對複雜查詢。
有時,您需要執行比簡單 AND 提供的更復雜的查詢。幸運的是,我們有Q對象來調味的東西,並幫助您找到你需要的。然後,這些 Q 對象可用作參數。.filter().filter()

Q 對象用途極多,例如用例:
創建 OR 篩選器
嵌套篩選器
倒置過濾器
例如,查找名稱或 的事件:Event 1Event 2

found_events = await Event.filter(
    Q(name='Event 1') | Q(name='Event 2')
)
#等效於:
	found_events = await Event.filter(
    Q(Q(name='Event 1'), Q(name='Event 2'), join_type="OR")#如果省略join_type,則爲AND
)

字段過濾

在搜索的時候增加後綴可以實現字段的過濾效果:
比如:

teams = await Team.filter(name__icontains='CON')

相關參數如下:

  • not
  • in- 檢查字段的值是否位於傳遞列表中
  • not_in
  • gte- 大於或等於傳遞的值
  • gt- 大於傳遞的值
  • lte- 低於或等於傳遞的值
  • lt- 低於傳遞值
  • range- 介於和給定兩個值之間
  • isnull- 字段爲空
  • not_isnull- 字段不爲空
  • contains- 字段包含指定的子字符串
  • icontains- 不區分大小寫contains
  • startswith- 如果字段以值開頭
  • istartswith- 不區分大小寫startswith
  • endswith- 如果字段以值結尾
  • iendswith- 不區分大小寫endswith
  • iequals- 區分大小寫等於

預取

通過預取,可以減少數據庫讀取次數,然後提高響應速度
有時只需要獲取某些相關記錄。您可以使用對象實現:Prefetch
示例:

tournament_with_filtered = await Tournament.all().prefetch_related(
    Prefetch('events', queryset=Event.filter(name='First'))
).first()

更多的參考:預取詳情

F表達式

某些時候,我們只是需要將數據進行一次計算或處理然後保存,我們並不在意值是多少,只是想把值進行我們指定的修改,就可以使用F表達式,這樣就可以減少一次數據庫讀取(我感覺好像沒卵用啊。。。)
參考如下:

from tortoise.expressions import F
await User.filter(id=1).update(balance = F('balance') - 10)
await User.filter(id=1).update(balance = F('balance') + F('award'), award = 0)

# or use .save()
user = await User.get(id=1)
user.balance = F('balance') - 10
await user.save(update_fields=['balance'])

功能和聚合


請參考功能和聚合

事務

略 請參考事務

根據Model生成Schema

講道理schema這個東西名字挺奇葩的。。。不過既然官網這麼弄就這麼弄吧。這個可以很方便的生成相關字段
注意,schema不要有相同的類名,會報錯的

User_Pydantic = pydantic_model_creator(Users, name="User")
UserIn_Pydantic = pydantic_model_creator(Users, name="UserIn", exclude_readonly=True)

下面這個文檔裏面還沒講解;
可以通過在model裏面創建一個class PydanticMeta來實現創建schema的控制:

class PydanticMeta:
    """
    The ``PydanticMeta`` class is used to configure metadata for generating the pydantic Model.

    Usage:

    .. code-block:: python3

        class Foo(Model):
            ...

            class PydanticMeta:
                exclude = ("foo", "baa")
                computed = ("count_peanuts", )
    """

    #: If not empty, only fields this property contains will be in the pydantic model
    include: Tuple[str, ...] = ()

    #: Fields listed in this property will be excluded from pydantic model
    exclude: Tuple[str, ...] = ()

    #: Computed fields can be listed here to use in pydantic model
    computed: Tuple[str, ...] = ()

    #: Use backward relations without annotations - not recommended, it can be huge data
    #: without control
    backward_relations: bool = True

    #: Maximum recursion level allowed
    max_recursion: int = 3

    #: Allow cycles in recursion - This can result in HUGE data - Be careful!
    #: Please use this with ``exclude``/``include`` and sane ``max_recursion``
    allow_cycles: bool = False

    #: If we should exclude raw fields (the ones have _id suffixes) of relations
    exclude_raw_fields: bool = True

    #: Sort fields alphabetically.
    #: If not set (or ``False``) then leave fields in declaration order
    sort_alphabetically: bool = False

如果你想跨表搜索或join搜索,在computed裏面定義。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章