🐍

SQLAlchemyでrefreshがうまくいかなければexpungeを使ってみる

2025/02/20に公開

始めに

特定の環境では再現したのですが、自宅では再現できませんでした。そのため、事象として発生したことのレポートです。

環境

※ 再現しなかったためなし。

まとめ

基本的にはインスタンスの再更新はrefresh()で問題ないはず。しかし、問題発生したときは一度expunge()でインスタンスを切り離してから再検索する方法もあり。

発生したこと

  1. 事前にSQLAlchemyから検索してインスタンスに格納する
  2. 同一セッション内で子テーブルのインスタンスを作成する
  3. 子テーブルも含めて更新した結果をフロントに返却するために再検索する
  4. 1.で取得したインスタンスが返却された
    1. 子テーブルが含まれていないインスタンスが返却された

対応したこと

1.のインスタンスをSQLAlchemyから切り離すexpunge()を使用しました。また、切り離す前に```flush()``を行うことで更新を確実に行いました。

この一連の流れでSQLAlchemyのキャッシュによるインスタンス再利用を行わないようにして、再検索したら最新インスタンスを取得するようにしました。

# 外部からパラメータをもらう
session: AsyncSession

# 更新と切り離し
await sesion.flush()
session.expunge(instance_1)
# 再検索を行う

できなかったこと

本来であれば、インスタンスを再検索するというrefresh()メソッドがあるので、そちらを利用すれば問題ありません。

await session.refresh(instance_1)

ただ、今回に限っては関連オブジェクトのアクセスが同期的に行われてしまい、MissingGreenletが発生してしまいました。おそらく、モデルには定義しているが、今回の処理で使用していない関連オブジェクトに対してもアクセスが発生したのではないかと推測しています。

そのため、refresh()メソッドのほうが便利ではあるのですが、expunge()等でインスタンスの再利用を明示的に行わないようにする方法を今回はとっております。

ソースコード

※ 再現しなかったためなし。

終わりに

自宅で検証したときは子テーブルの数が足りないのか、それともSQLModelを使用しているからか、はたまたSQLAlchemyのバージョンが異なるからか再現しませんでした。

とはいえ、解決方法が提示されているだけでも価値があるかと思うので、ブログに残しておくことにします。

類似情報

Discussion