Tortoise ORM / FastAPI 整合快速筆記
因為以往被 Active Record 慣壞了,導致在 Python 圈一直找不到相同順手的 ORM,又因為用的不是像 Django 那樣的大禮包框架,導致我在 ORM 的路上始終尋尋覓覓,今天又又又又又要來介紹另一款 Tortoise ORM。
先吹一波特性:
- 支援 SQLite、PostgreSQL、MariaDB、MySQL,以及透過 ODBC 支援 SQL Server、Oracle。
- 異步。
- 有 migration 機制。
- 支援多個套件、框架整合,包括 UnitTest、FastAPI、Quart、Sanic、Starlette、aiohttp、BlackSheep、Pydantic,以上有得熱門有得冷門,但奇怪的是竟然沒有 Flask。即便不在上述清單內,只要搞懂 Tortoise 的初始化機制也可以自幹整合。
- 支援多資料庫。
- 支援讀寫分離。
- 但沒有 seeding 機制,得自幹。
下面是 Tortoise 和 FastAPI / pydantic 整合的快速筆記。
定義 Model
這裡的 model 指的是 ORM model,而 pydantic model 則由 ORM model 衍生而來,例如下面這個 models.py:
from tortoise import fields, models from tortoise.contrib.pydantic import pydantic_model_creator class Word(models.Model): id = fields.IntField(pk=True) #: `word` may duplicate, do not have to be unique. word = fields.CharField(max_length=255, null=False) accent_notation = fields.CharField(max_length=255, null=False) class Meta: table = 'words' # Always name with snake_case WordRead = pydantic_model_creator( cls=Word, name='WordRead' ) WordCreate = pydantic_model_creator( cls=Word, name='WordCreate', exclude_readonly=True # Exclude `id` on creating )
這裡我們定義了一個 Word model,旗下欄位由 fields
系列函式定義,應該是可以望文生義。
除欄位定義外,裡面還有一些值得一提的聲明:
word
上面有一行以#:
開頭的註解,這種格式的註解會變成 pydantic 的 field description,因此也會變成 OpenAPI 的 propertie description。- 子類
Meta
的table
屬性聲明了該 model 的資料表名稱,因為個人習慣在資料表用 snake case,因此就得額外聲明此項囉。
有了 ORM model,後續我們用 pydantic_model_creator()
建立兩個衍生的 pydantic model,習慣上他們的變數名和 model 名會取相同。
最終這份 models.py 有三個成員:
- 一個 Word ORM model。
- 一個 WordRead pydantic model。
- 一個 WordCreate pydantic model,不帶
id
欄位,因為 ID 是資料庫自動產生的,無須用戶提供。
後續我們會在 FastAPI 端點函式中使用到他們。
Tortoise ORM 整合 FastAPI
Tortoise 提供了傻瓜整合機制,自動在 FastAPI 啟動時帶起 Tortoise,結束時關閉 Tortoise 和資料庫的連線。
在 FastAPI 主程式 main.py 如此這般:
from fastapi import FastAPI from tortoise.contrib.fastapi import register_tortoise from app.models import WordCreate, Word, WordRead app: FastAPI = FastAPI() register_tortoise( app=app, db_url='sqlite://db.sqlite', modules={'models': ['app.models']}, generate_schemas=True, add_exception_handlers=True, )
那句 register_tortoise()
一行就搞定,它傻瓜你聰明。
在 FastAPI 調用 Tortoise Model
前面定義的三個 model,其中的 Word model 用於實操資料庫,其餘的兩個 model 則用於在 FastAPI 函式中聲明參數或回傳值型態,例如下面這個函式:
@app.post( path="/", response_model=WordRead, ) async def create_word( word: WordCreate, ): new_word: WordRead = await Word.create( **word.dict( exclude_unset=True, ) ) return new_word
WordRead 被聲明成回傳的型態、WordCreate 被聲明成參數 word
的型態,而真正實操資料庫的語句則是由 Word.create()
構成,他們之間的轉換由 Tortoise ORM 處理到好。
下面是 CRUD 的另一個例子:
@app.get( path='/{word}', response_model=list[WordRead], ) async def get_word( word: str ): response = await Word.filter( word=word ) return response
沿前例類推,WordRead 同樣被聲明成回傳型態,而根據 Word model 的設計,欄位 word
不一定唯一,因此此處調用 Word.filter()
下查詢,回傳的應該是陣列,因此我們將回傳 model 聲明為 list[WordRead]
,如果確定查詢只會有單筆紀錄那可以下 Word.get()
做查詢。
結語
本篇是 Tortoise ORM 在 FastAPI 的快速筆記,沒有提到某些也很重要的特性,例如各種花式欄位定義、關聯性、quary API、migration 等等,或許以後有碰到再寫吧,欽此。
喜欢我的作品吗?别忘了给予支持与赞赏,让我知道在创作的路上有你陪伴,一起延续这份热忱!