nikkie-ftnextの日記

イベントレポートや読書メモを発信

SQLModel素振りの記:PostgreSQLに同期接続 〜selectして見えた、SQLAlchemyのsessionmakerのclass_引数によるいいとこ取り案〜

はじめに

雛様かわわ〜 nikkieです

SQLModelでのDB接続についてまだまだ素振りです。
selectしようとしたことで学んだことがありました。

※SQLModel・SQLAlchemyどちらも今はまだ初学者レベルですので、考え違いをしていたらやさしく教えてください!

目次

前回のPostgreSQLに同期接続

DBのセッションの作り方を2通り知りました。

  • SQLModelドキュメントにあるSession(engine)
    • または、SQLAlchemyのsessionmaker(engine)()
  • SQLAlchemyのsessionmaker(engine).begin()による厳格なトランザクション

DBに保存したデータにPythonコードからアクセスしようとして学びがありました。

SQLAlchemyのSessionでselect

保存した3人のheroをselectします。

from sqlalchemy.orm import sessionmaker
from sqlmodel import select

Session = sessionmaker(engine)

statement = select(Hero)
with Session() as session:
    results = session.execute(statement)
    for row in results:
        hero = row[0]
        print(hero)
  • sesssionmakerが返すのはsqlalchemy.orm.Sessionなので、execute()メソッドです
    • sqlmodel.Sessionexec()メソッドは生えていません(後述)
  • sqlalchemy.orm.Sessionexecute()メソッドは、複数のRowを返す
    • 1つ1つのRowは1要素のタプル(の見た目)
    • (Hero(age=None, id=1, name='Deadpond', secret_name='Dive Wilson'),)

整数インデックスアクセスする以外のやり方がscalars()

statement = select(Hero)
with Session() as session:
    results = session.execute(statement)
    for hero in results.scalars():
        print(hero)

Calling the Session.scalars() method is the equivalent to calling upon Session.execute() to receive a Result object, then calling upon Result.scalars() to receive a ScalarResult object.(Selecting ORM Entities

SQLModelのselectのドキュメント

exec()メソッド

「Read Data - SELECT」の「Execute the Statement

from sqlmodel import Session, select

with Session(engine) as session:
    statement = select(Hero)
    results = session.exec(statement)

SQLModelのSessionexec()メソッドを持ちます1

SQLModelのセッションのexec()とSQLAlchemyのセッションのexecute()

「Read Data - SELECT」には「SQLModel or SQLAlchemy - Technical Details」という項目があります。

SQLAlchemy's own Session has a method session.execute(). It doesn't have a session.exec() method.

SQLModel's own Session inherits directly from SQLAlchemy's Session, and adds this additional method session.exec(). Underneath, it uses the same session.execute().

Sessionインスタンス execute()メソッド exec()メソッド
SQLAlchemy ある ない
SQLModel ある ある(推奨)
  • (上で見たように)SQLAlchemyのSessionインスタンスexecute()メソッドはscalars()も呼び出す必要がある
heroes = session.execute(select(Hero)).scalars().all()
  • SQLModelでは、同じことをscalars()の呼び出し不要でやれる2
heroes = session.exec(select(Hero)).all()

こうしたらいいのでは? SQLAlchemyのsessionmakerのclass_引数

一連の素振りでSQLAlchemyのドキュメントに入り浸ったのですが、2つのいいとこ取りができそうに思っています(実運用してみてだめだったらごめんなさい🙏)

  • SQLAlchemyのsessionmaker
  • SQLModelのSessionexec()メソッド)
from sqlalchemy.orm import sessionmaker
from sqlmodel import create_engine, select
from sqlmodel import Session as SQLModelSession

Session = sessionmaker(engine, class_=SQLModelSession)

statement = select(Hero)
with Session() as session:
    results = session.exec(statement)
    for hero in results:
        print(hero)

sessionmakerclass_引数3
https://docs.sqlalchemy.org/en/20/orm/session_api.html#sqlalchemy.orm.sessionmaker.params.class_

class_ – class to use in order to create new Session objects. Defaults to Session.

デフォルト値はsqlalchemy.orm.Sessionですが、sqlmodel.Sessionを指定しました。
これによりsessionmaker経由で得るSessionインスタンスsqlmodel.Sessionインスタンスとなり、exec()メソッドが使えます!

issue(sessionmakerを追加してという要望)にも同様のコメントを見つけました。
https://github.com/fastapi/sqlmodel/issues/75#issuecomment-2109911909

SQLAlchemyのsessionmakerとsqlmodelのSessionのいいとこ取りサンプルスクリプト

動作環境は一連の素振り記事で共通です。

実行結果

Before insert: name='Deadpond' secret_name='Dive Wilson' id=None age=None
After commit: 
After refresh: secret_name='Dive Wilson' id=1 name='Deadpond' age=None
After session close: secret_name='Dive Wilson' id=1 name='Deadpond' age=None

Before insert: name='Deadpond2' secret_name='Dive Wilson 2' id=None age=None
After session (transaction) close: 

Before insert: name='Deadpond3' secret_name='Dive Wilson 3' id=None age=None
After session (transaction) close: 

secret_name='Dive Wilson' id=1 name='Deadpond' age=None
secret_name='Dive Wilson 2' id=2 name='Deadpond2' age=None
secret_name='Dive Wilson 3' id=3 name='Deadpond3' age=None

終わりに

SQLModelのSessionをSQLAlchemyのsessionmakerといいとこ取りする方法を1つ見つけられたと思います!

Session = sessionmaker(engine, class_=SQLModelSession)

SQLModelのDBセッションの実装は、tiangolo氏が想定する狭い範囲しかサポートしていない印象です。
SQLAlchemyのDBセッションのプラクティスを反映するのに、それなりの労力がかかりました(なおまだ見落としがあるかもしれません)。
SQLModelが提供する(特にPydanticを使った)素晴らしい機能もあると思いますが、今回の素振りから「ここまで知識を得ないと間違いうる」というのはおすすめしにくいなと思ってしまいます


  1. exec()メソッドを追加する実装 https://github.com/fastapi/sqlmodel/blob/0.0.22/sqlmodel/orm/session.py#L52-L76
  2. tiangolo氏が(作ったexec()メソッドが)呼んでくれているから、ユーザは呼び出し不要ということのようです。 https://github.com/fastapi/sqlmodel/blob/0.0.22/sqlmodel/orm/session.py#L74-L75
  3. 実装を見ていて気づきました https://github.com/sqlalchemy/sqlalchemy/blob/rel_2_0_38/lib/sqlalchemy/orm/session.py#L5052-L5061