Movatterモバイル変換


[0]ホーム

URL:


Livetoon Inc.Livetoon Inc.
Livetoon Inc.Publicationへの投稿
💽

FastAPIの作者が作った「SQLModel」が革命的すぎるので、全Python使いに教えたい

に公開

この記事はLivetoon Tech Advent Calendar 2025の11日目の記事です。
https://adventar.org/calendars/12157

本日はCTOの私がよく使ってるSQLModelについてお話します。

宣伝

https://kai0.onelink.me/Hogh/AdventCalendar2025

今回のアドベントカレンダーでは、LivetoonのAIキャラクターアプリのkaiwaに関わるエンジニアが、アプリの話からLLM・合成音声・インフラ監視・GPU・OSSまで、幅広くアドベントカレンダーとして書いて行く予定です。
是非、publicationをフォローして、記事を追ってみてください。

SQLModelとは

https://sqlmodel.tiangolo.com/

SQLModelは、PydanticSQLAlchemy のいいとこ取りをしたPython ORMライブラリです。FastAPIの作者(tiangolo)が開発しており、以下の特徴があります:

  • Pydanticの書き心地: バリデーション・型安全性をそのまま活用
  • SQLAlchemyの互換性: 既存のSQLAlchemy知識がそのまま使える
  • 1つのクラスで完結: Pydanticモデルとテーブル定義を統一
  • FastAPIとの親和性: シームレスな統合

PythonのORMではまだまだSQLAlchemyが主流ですが、FastAPIの作者が作っていること、またSQLAlchemyのラッパーであり移行が簡単なので、今からWatchしておくといいかと思います。

実はFastAPIドキュメントのSQLの項目も、既にSQLModelでの実装に書き換わっています。
https://fastapi.tiangolo.com/tutorial/sql-databases/

ちなみに筆者の環境では、シンプルな案件なら割とSQLModelを初手で選んでいます。
一部、極めて複雑なクエリが必要な場合などはSQLAlchemy生書きを選択することもありますが、大抵のユースケースはこれで足ります。

想定するユースケース

以下のようなLLMでの会話アプリケーションなどを想定して解説します。

ChatSession (LLM会話セッション)      └─ Message (会話履歴)
  • ChatSession: LLMとの会話セッション
  • Message: 会話の各メッセージ(user/assistant)

モダンPythonでいこう

「DBのモデル定義とAPIのスキーマ定義、二重管理するのだるすぎない?」

FastAPIを使っているなら、誰もが一度は思うはず。
SQLAlchemyのモデルを書いて、Pydanticのスキーマも書いて……マッピングして……。

SQLModelなら一発です。

まずPythonでBackendを書くとき、Pydanticはほぼ必須になってきています。
データはスキーマで保護しないとカオスになりやすいです。Typescriptの世界でも最近はZod などスキーマの重要性が認識されてきています。

Pydanticとほぼ同じ内容をDBモデルに書くのはDRY(Don't Repeat Yourself)の原則から反するだけでなく、メンテナンス性の悪化やスキーマによる保護の効果を著しく減らすことになります。

SQLModelなら、DBモデル自体がPydanticモデルだから、バリデーションもガッツリ効くし、エディタの補完も爆速。PydanticとSQLAlchemyの融合でかつFastAPIとの相性もめっちゃいいので使わない手はないです。

1. なぜSQLModelなのか?

従来のやり方 (SQLAlchemy + Pydantic)

「DB保存用」と「API返却用」で、似たようなクラスを2回書く必要がありました。

# 1. DB用の定義 (SQLAlchemy)classUserDB(Base):    __tablename__="users"id= Column(Integer, primary_key=True)    name= Column(String, nullable=False)    age= Column(Integer)# 2. API用の定義 (Pydantic)classUserSchema(BaseModel):id:int    name:str    age:int

「これ、name の定義変えたら両方修正するの? 正気?」
ってなりません? DRY原則どこいった?

SQLModelのやり方

1つのクラスで両方の役割を果たします。

from sqlmodelimport Field, SQLModel# これだけで、DBテーブル定義 兼 APIスキーマ定義classUser(SQLModel, table=True):id:int|None= Field(default=None, primary_key=True)    name:str    age:int

これが革命。
table=True をつければDBモデルになり、外せばただのPydanticモデルとして振る舞います。
この「継承元を変えなくていい」「二重管理しなくていい」が死ぬほどデカいです。

2. インストール

現代のPythonなら非同期(Async) 一択。
SQLiteで非同期通信するためにaiosqlite も入れておきます。

pipinstall sqlmodel aiosqlite

3. テーブル定義:これがPydanticに見えますか?

見えるよな? でもこれ、DBのテーブル定義なんだぜ。
LLMチャットアプリを想定して、「セッション(親)」と「メッセージ(子)」を定義します。

from datetimeimport datetime, timezonefrom sqlmodelimport Field, SQLModel, Relationship# 親:チャットセッションclassChatSession(SQLModel, table=True):    __tablename__="chat_sessions"id:int|None= Field(default=None, primary_key=True)    title:str= Field(index=True)    model_name:str= Field(default="gpt-5.1")    created_at: datetime= Field(default_factory=lambda: datetime.now(timezone.utc))# リレーション:このセッションに紐づくメッセージ一覧    messages:list["Message"]= Relationship(back_populates="session")# 子:メッセージclassMessage(SQLModel, table=True):    __tablename__="messages"id:int|None= Field(default=None, primary_key=True)    content:str    role:str# user or assistant# 外部キー    session_id:int|None= Field(default=None, foreign_key="chat_sessions.id")# 親への参照    session: ChatSession|None= Relationship(back_populates="messages")

ここが最高:

  • SQLModel を継承するだけでPydanticの機能が全部使えます。
  • Field でDBのカラム制約(PK, Index, Foreign Key)を設定。
  • 型ヒントがそのままDBの型になる(str ->VARCHAR etc)。
  • リレーションもlist["Message"] という型ヒントで定義可能。

4. DB接続と初期化

お決まりのセットアップです。

from sqlmodel.ext.asyncio.sessionimport AsyncSessionfrom sqlalchemy.ext.asyncioimport create_async_engine# SQLiteの非同期接続DATABASE_URL="sqlite+aiosqlite:///./database.db"# echo=Trueで発行されたSQLがログに出る(開発中は必須)engine= create_async_engine(DATABASE_URL, echo=True)# DB初期化(テーブル作成)asyncdefinit_db():asyncwith engine.begin()as conn:await conn.run_sync(SQLModel.metadata.create_all)

5. CRUD操作:超直感的

SQLAlchemyの「あの長い呪文」はもういりません。

Create

Pydanticモデルをインスタンス化してadd するだけ。バリデーションエラーがあればここで落ちます。

asyncdefcreate_session(title:str):asyncwith AsyncSession(engine)as session:# インスタンス化(ここで型チェックが走る!)        new_session= ChatSession(title=title)                session.add(new_session)await session.commit()await session.refresh(new_session)# IDが入った状態を取得return new_session

Read

select 文もモダンで読みやすいです。

from sqlmodelimport selectasyncdefget_session(session_id:int):asyncwith AsyncSession(engine)as session:        statement= select(ChatSession).where(ChatSession.id== session_id)        result=await session.exec(statement)return result.first()

6. 【上級編】セッション作成と同時にメッセージも突っ込む

SQLModel(SQLAlchemy)の便利なところ。
「親(セッション)を作るときに、子(初期プロンプト)もまとめて保存」ができます。

asyncdefcreate_session_with_prompt(title:str, initial_prompt:str):asyncwith AsyncSession(engine)as session:# 1. 子(メッセージ)を作る# ID指定不要!        initial_msg= Message(content=initial_prompt, role="user")# 2. 親を作りつつ、子を持たせる(リストに突っ込むだけ!)        new_session= ChatSession(            title=title,            messages=[initial_msg])                session.add(new_session)await session.commit()# ↑ これだけで sessionsテーブル と messagesテーブル 両方にINSERTが走る!await session.refresh(new_session)return new_session

7. FastAPIとの統合:これが真骨頂

ここがSQLModelを使う最大の理由です。
DBモデルをそのままレスポンスモデルとして使えます。

from typingimport Annotatedfrom fastapiimport FastAPI, Dependsapp= FastAPI()# 依存性注入用asyncdefget_session():asyncwith AsyncSession(engine)as session:yield session# ここがモダン!型定義と依存関係をセットにする(SessionDepパターン)SessionDep= Annotated[AsyncSession, Depends(get_session)]@app.post("/sessions/", response_model=ChatSession)asyncdefcreate_chat_session(    title:str,     session: SessionDep# ← 記述がスッキリ!):    chat_session= ChatSession(title=title)    session.add(chat_session)await session.commit()await session.refresh(chat_session)return chat_session# ↑ ここ!DBモデルをそのまま返しても、FastAPIがPydanticモデルとして処理してくれる!

response_model=ChatSession と書くだけで、Swagger UIのドキュメントも自動生成されるし、不要なフィールドの除外もPydanticの設定で制御できます。

使い勝手はPydantic、でもモデルの定義やバリデーションもできる優れモノです。

まとめ:これからSQLModelがスタンダードになるかも?

  • 二重管理の解消:DBモデル =Pydanticスキーマこの快適さは戻れません。
  • 型安全性: バリデーションと補完が開発速度をブーストさせます。
  • 可読性: コードがスッキリして、何をやっているか一目瞭然。
  • 便利: 親子関係の同時保存など、高度なDB操作も直感的。

ガードレールのない素のSQLや、型定義が曖昧なORMで消耗してる人がいればぜひ検討してほしいです。
SQLModel +Pydantic で、堅牢かつ爆速な開発体験を手に入れましょう。

だいち

趣味はAIモデル開発Livetoon CTO | 元医者、現AIエンジニア | 医療LLM専門家 | 東大病院AIチーム所属 | FastAPIとRAGのオタク | 最高峰アジア人画像生成モデル: longisland3XL | Livetoon TTS: 最高クラスの音声合成AI

Discussion


[8]ページ先頭

©2009-2025 Movatter.jp