SQLAlchemy の Session に型ヒントをつけたい。


◯ 結論

sqlalchemy.orm.session.Session を使います。

# sessionmaker は 「クラスオブジェクト」です。
from sqlalchemy.orm import sessionmaker
from sqlalchemy.orm.session import Session as BaseSession

...




# Session は 「関数」です。
#   「インスタンスオブジェクト」であり
#   「クラスオブジェクト」ではありません。
Session = sessionmaker(bind=engine)

# session は
# BaseSession クラスを継承したクラスの「インスタンスオブジェクト」です。
session: BaseSession = Session()

...

session.commit()
session.close()

◯ 根拠

上記の Sessionsqlalchemy.orm.session.Session の子クラスのインスタンスオブジェクトを返すからです。

class sessionmaker(_SessionClassMethods):

    ...

    def __init__(
        self,
        bind=None,
        # vvv class_ (Session) は sqlalchemy.orm.session.Session
        class_=Session,
        autoflush=True,
        autocommit=False,
        expire_on_commit=True,
        info=None,
        **kw
    ):

        ...

        # class_  (Session) を継承したクラスオブジェクトを代入する。
        self.class_ = type(class_.__name__, (class_,), {})

    def __call__(self, **local_kw):

        ...

        # class_  (Session) を継承したクラスオブジェクトを
        # インスタンス化する。
        return self.class_(**local_kw)

◯ 疑問

1. リスコフの置換則

SessionBaseSession が、もしリスコフの置換則を満たしていなかったら、上記のように型付できず、間違っていることになります。

ぱっコードを見たところ問題はなさそうなのと、実際に使っていて問題はないので、大丈夫なのかなと 思っています

2. 命名規則

sessionmaker は、命名規則的にはインスタンスオブジェクトです。しかし、実際にはクラスオブジェクトが代入されています。

3. 振る舞い

sessionmaker がインスタンス化したインスタンスオブジェクト Session は、クラスオブジェクトではないのに、あたかもクラスオブジェクトのように動作していること。

Session を、あるクラスのインスタンスを返す関数と考えればいいだけの話なのですが。Session と先頭を大文字にしてクラスとして扱った方が全体のコードが綺麗になるような気がするので。