SQLModel¶
SQLModel は FastAPI の作者が作ってるライブラリなので、 FastAPI で DB を使うときはこれにしておけばコードを書く量も減らせるし、エディターのエラーサインもよく出してくれるはず。 FastAPI と同じでドキュメントも読みやすい。
SQLModel の Learn を読んでいって、 SQLModel で DB を扱うときにどう書けばいいのか、型のようなもの(お決まりの書き方)を見つけたい。ここではデータベースとして SQLite を使う。
-
インストール: cf. 公式ドキュメント
DB とテーブルの作成¶
cf. Create the Database and Table
# app.py
from typing import Optional
from sqlmodel import Field, SQLModel, create_engine
class User(SQLModel, table=True):
id: Optional[int] = Field(default=None, primary_key=True)
name: str
mail: str
age: Optional[int] = None
sqlite_file_name = "database.db" # 使用するDBファイル名
sqlite_url = f"sqlite:///{sqlite_file_name}" # sqliteで接続するよう指定
engine = create_engine(sqlite_url, echo=True) # この"engine"を使ってDBに接続できる; 本番で動かすときは"echo=True"は削除する
SQLModel.metadata.create_all(engine) # これでDBとテーブルが作成される; 2回目以降はDBとテーブルが同じ名前で存在しているので何もしない
SQLModel クラスを定義してテーブルを作る。上のコードだと User クラスの定義のとおり、下の 4 つがテーブルのカラムになる。
id— 数値型もしくはNoneの値を持つ。default=Noneかつprimary_key=Trueとしておけば、 Python コードでidを指定せずに DB に入れたときに自動で採番される。name— 文字列型の値を持つ。mail— 文字列型の値を持つ。age— 数値型もしくはNoneの値を持つ。
DB を作るときは、 sqlite_url を指定 → create_engine → SQLModel.metadata.create_all の順序で、このまま覚えておけばいい。
ユーザーの作成(Create)¶
User クラスの user_1 ~ user_3 インスタンスを作成して、 with Session(engine) as session の中で session.add する。
# app.py
from typing import Optional
from sqlmodel import Field, Session, SQLModel, create_engine, select
class User(SQLModel, table=True):
id: Optional[int] = Field(default=None, primary_key=True)
name: str
mail: str
age: Optional[int] = None
sqlite_file_name = "database.db"
sqlite_url = f"sqlite:///{sqlite_file_name}"
engine = create_engine(sqlite_url, echo=True)
def create_users():
user_1 = User(name="Alice", mail="alice@example.com")
user_2 = User(name="Bob", mail="bob@example.com", age=44)
user_3 = User(name="Ken", mail="ken@example.com", age=25)
with Session(engine) as session:
session.add(user_1)
session.add(user_2)
session.add(user_3)
session.commit()
def main():
create_users()
if __name__ == "__main__":
main()
コードを実行するとデータが入る。 id は Python コードでは指定してないけど、 DB が勝手に 1, 2, 3 と付けてくれてる。
実行ログ
% uv run python app.py
2025-10-06 15:07:15,482 INFO sqlalchemy.engine.Engine BEGIN (implicit)
2025-10-06 15:07:15,482 INFO sqlalchemy.engine.Engine PRAGMA main.table_info("user")
2025-10-06 15:07:15,483 INFO sqlalchemy.engine.Engine [raw sql] ()
2025-10-06 15:07:15,484 INFO sqlalchemy.engine.Engine PRAGMA temp.table_info("user")
2025-10-06 15:07:15,484 INFO sqlalchemy.engine.Engine [raw sql] ()
2025-10-06 15:07:15,484 INFO sqlalchemy.engine.Engine
CREATE TABLE user (
id INTEGER NOT NULL,
name VARCHAR NOT NULL,
mail VARCHAR NOT NULL,
age INTEGER,
PRIMARY KEY (id)
)
2025-10-06 15:07:15,485 INFO sqlalchemy.engine.Engine [no key 0.00004s] ()
2025-10-06 15:07:15,485 INFO sqlalchemy.engine.Engine COMMIT

ユーザーの読み取り(Read)¶
上の図のようなテーブルから Bob のデータだけを読み取る。
# app.py
from typing import Optional
from sqlmodel import Field, Session, SQLModel, create_engine, select
class User(SQLModel, table=True):
id: Optional[int] = Field(default=None, primary_key=True)
name: str
mail: str
age: Optional[int] = None
sqlite_file_name = "database.db"
sqlite_url = f"sqlite:///{sqlite_file_name}"
engine = create_engine(sqlite_url, echo=True)
def select_users():
with Session(engine) as session:
statement = select(User).where(User.name == "Bob")
results = session.exec(statement)
user = results.first()
print(user)
def main():
select_users()
if __name__ == "__main__":
main()
コードを実行すると、 Bob のデータだけを取得できる。
実行ログ
% uv run python app.py
2025-10-06 15:29:34,458 INFO sqlalchemy.engine.Engine BEGIN (implicit)
2025-10-06 15:29:34,460 INFO sqlalchemy.engine.Engine SELECT user.id, user.name, user.mail, user.age
FROM user
WHERE user.name = ?
2025-10-06 15:29:34,460 INFO sqlalchemy.engine.Engine [generated in 0.00010s] ('Bob',)
mail='bob@example.com' name='Bob' id=2 age=44
2025-10-06 15:29:34,461 INFO sqlalchemy.engine.Engine ROLLBACK
1 つのデータだけを取得するときの first() と one() の違いは下記: cf. Exactly One
| 条件にマッチ | results.first() |
results.one() |
|---|---|---|
| 1 件マッチ | そのデータを返す | そのデータを返す |
| 複数マッチ | 最初の 1 件だけ返す | エラー |
| マッチなし | None を返す |
エラー |
ユーザーの更新(Update)¶
# app.py
from typing import Optional
from sqlmodel import Field, Session, SQLModel, create_engine, select
class User(SQLModel, table=True):
id: Optional[int] = Field(default=None, primary_key=True)
name: str
mail: str
age: Optional[int] = None
sqlite_file_name = "database.db"
sqlite_url = f"sqlite:///{sqlite_file_name}"
engine = create_engine(sqlite_url, echo=True)
def update_users():
with Session(engine) as session:
statement = select(User).where(User.name == "Alice")
results = session.exec(statement)
user = results.one()
print("User:", user)
user.age = 16
user.name = "Alice-Wonder"
session.add(user)
session.commit()
session.refresh(user)
print("Updated User:", user)
def main():
update_users()
if __name__ == "__main__":
main()
コードを実行すると、 Alice の情報を変更できる。
実行ログ
% uv run python app.py
2025-10-06 16:08:13,643 INFO sqlalchemy.engine.Engine BEGIN (implicit)
2025-10-06 16:08:13,645 INFO sqlalchemy.engine.Engine SELECT user.id, user.name, user.mail, user.age
FROM user
WHERE user.name = ?
2025-10-06 16:08:13,645 INFO sqlalchemy.engine.Engine [generated in 0.00010s] ('Alice',)
User: mail='alice@example.com' name='Alice' id=1 age=None
2025-10-06 16:08:13,646 INFO sqlalchemy.engine.Engine UPDATE user SET name=?, age=? WHERE user.id = ?
2025-10-06 16:08:13,646 INFO sqlalchemy.engine.Engine [generated in 0.00007s] ('Alice-Wonder', 16, 1)
2025-10-06 16:08:13,647 INFO sqlalchemy.engine.Engine COMMIT
2025-10-06 16:08:13,648 INFO sqlalchemy.engine.Engine BEGIN (implicit)
2025-10-06 16:08:13,648 INFO sqlalchemy.engine.Engine SELECT user.id, user.name, user.mail, user.age
FROM user
WHERE user.id = ?
2025-10-06 16:08:13,648 INFO sqlalchemy.engine.Engine [generated in 0.00006s] (1,)
Updated User: mail='alice@example.com' name='Alice-Wonder' id=1 age=16
2025-10-06 16:08:13,649 INFO sqlalchemy.engine.Engine ROLLBACK

ユーザーの削除(Delete)¶
cf. Delete the Hero from the Session
# app.py
from typing import Optional
from sqlmodel import Field, Session, SQLModel, create_engine, select
class User(SQLModel, table=True):
id: Optional[int] = Field(default=None, primary_key=True)
name: str
mail: str
age: Optional[int] = None
sqlite_file_name = "database.db"
sqlite_url = f"sqlite:///{sqlite_file_name}"
engine = create_engine(sqlite_url, echo=True)
def delete_users():
with Session(engine) as session:
statement = select(User).where(User.name == "Alice-Wonder")
results = session.exec(statement)
user = results.one()
print("User: ", user)
session.delete(user)
session.commit()
print("Deleted User:", user)
statement = select(User).where(User.name == "Alice-Wonder")
results = session.exec(statement)
user = results.first()
if user is None:
print("There's no User named Alice-Wonder")
def main():
delete_users()
if __name__ == "__main__":
main()
コードを実行すると、 Alice-Wonder のデータは削除できる。
実行ログ
% uv run python app.py
2025-10-06 16:14:29,484 INFO sqlalchemy.engine.Engine BEGIN (implicit)
2025-10-06 16:14:29,485 INFO sqlalchemy.engine.Engine SELECT user.id, user.name, user.mail, user.age
FROM user
WHERE user.name = ?
2025-10-06 16:14:29,486 INFO sqlalchemy.engine.Engine [generated in 0.00010s] ('Alice-Wonder',)
User: mail='alice@example.com' name='Alice-Wonder' id=1 age=16
2025-10-06 16:14:29,489 INFO sqlalchemy.engine.Engine DELETE FROM user WHERE user.id = ?
2025-10-06 16:14:29,489 INFO sqlalchemy.engine.Engine [generated in 0.00007s] (1,)
2025-10-06 16:14:29,489 INFO sqlalchemy.engine.Engine COMMIT
Deleted User: mail='alice@example.com' name='Alice-Wonder' id=1 age=16
2025-10-06 16:14:29,489 INFO sqlalchemy.engine.Engine BEGIN (implicit)
2025-10-06 16:14:29,489 INFO sqlalchemy.engine.Engine SELECT user.id, user.name, user.mail, user.age
FROM user
WHERE user.name = ?
2025-10-06 16:14:29,490 INFO sqlalchemy.engine.Engine [cached since 0.004089s ago] ('Alice-Wonder',)
There's no User named Alice-Wonder
2025-10-06 16:14:29,490 INFO sqlalchemy.engine.Engine ROLLBACK

インデックス(Indexes)¶
cf. Indexes
読み込みのパフォーマンスを改善するために、インデックスを追加する方法がある。検索されるカラムに対してインデックスをつけるべき。 primary_key は自動でインデックスが作られる。
例えば User クラスを修正する場合は、下記のように index=True を追加するだけ。