はじめに
雛様かわわ〜 nikkieです
SQLModelでのDB接続についてまだまだ素振りです。
selectしようとしたことで学んだことがありました。
※SQLModel・SQLAlchemyどちらも今はまだ初学者レベルですので、考え違いをしていたらやさしく教えてください!
目次
- はじめに
- 目次
- 前回のPostgreSQLに同期接続
- SQLAlchemyのSessionでselect
- SQLModelのselectのドキュメント
- こうしたらいいのでは? SQLAlchemyのsessionmakerのclass_引数
- SQLAlchemyのsessionmakerとsqlmodelのSessionのいいとこ取りサンプルスクリプト
- 終わりに
前回のPostgreSQLに同期接続
DBのセッションの作り方を2通り知りました。
- SQLModelドキュメントにある
Session(engine)- または、SQLAlchemyの
sessionmaker(engine)()
- または、SQLAlchemyの
- 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.Sessionのexec()メソッドは生えていません(後述)
sqlalchemy.orm.Sessionのexecute()メソッドは、複数の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のSessionはexec()メソッドを持ちます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の
Session(exec()メソッド)
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)
sessionmakerのclass_引数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
Sessionobjects. Defaults toSession.
デフォルト値は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を使った)素晴らしい機能もあると思いますが、今回の素振りから「ここまで知識を得ないと間違いうる」というのはおすすめしにくいなと思ってしまいます
-
exec()メソッドを追加する実装 https://github.com/fastapi/sqlmodel/blob/0.0.22/sqlmodel/orm/session.py#L52-L76↩ -
tiangolo氏が(作った
exec()メソッドが)呼んでくれているから、ユーザは呼び出し不要ということのようです。 https://github.com/fastapi/sqlmodel/blob/0.0.22/sqlmodel/orm/session.py#L74-L75↩ - 実装を見ていて気づきました https://github.com/sqlalchemy/sqlalchemy/blob/rel_2_0_38/lib/sqlalchemy/orm/session.py#L5052-L5061↩