備忘録:SQLAlchemyのConnectionとTransactionの区別について

SQLAlchemy 1.4系と2.0系の両方において、データベース接続オブジェクトとトランザクションオブジェクト (sqlalchey.Transaction) を区別できるか調べてみた。結果、isinstance() を使ったプログラム実行時の型チェックで両者を区別することも、mypy および pyright のいずれを使った場合でも両者の型アノテーションを区別することも可能だった。

データベースのトランザクション中でのみ呼び出してほしい内部処理関数を作る機会があったのだけれど、そういう場合にはTransactionを引数で受け取るようにして、SQL実行時はそのconnectionプロパティで接続オブジェクトを取得すると良さそうだ。

なお、ここでのデータベース接続オブジェクトはConnectionクラスのインスタンス、トランザクションオブジェクトはTransactionクラスのインスタンスを指している。いずれも、 2.0系であればsqlalchemyから、1.4系であればsqlalchemy.engineパッケージからimportできる。

動作確認環境

  • SQLAlchemy 1.4.50 および 2.0.23
  • mypy 1.6.1
  • pyright 1.1.335

SQLAlchemy 1.4系使用時のみ sqlalchemy2-stubs 0.0.2a36もインストールした。

プログラム実行時の型チェック

プログラム実行時に isinstance() を使って Connection オブジェクトと Transaction オブジェクトを区別できることを、以下のコードで確認できた:

engine = create_engine("sqlite://")
conn = engine.connect()
tx = conn.begin()

assert isinstance(conn, Connection)
assert not isinstance(conn, Transaction)
assert not isinstance(tx, Connection)
assert isinstance(tx, Transaction)

静的コード解析における型アノテーションの区別

以下のような関数を定義したとき:

def f(conn: Connection):
    print(conn)

def g(tx: Transaction):
    print(tx)

engine = create_engine("sqlite://")
conn = engine.connect()
tx = conn.begin()

f(conn)
f(tx)  # mypy, pyrightともにエラーを出力する
g(conn)  # mypy, pyrightともにエラーを出力する
g(tx)

f に Transaction オブジェクトを渡すコードと g に Connection オブジェクトを渡すコードの両方に対して、 mypy も pyright もエラーを出力する。 なお当然、f にConnectionオブジェクトを渡すコードと g にTransactionオブジェクトを渡すコードに対してはエラーを出力しない。

以上