Cosnomi

プログラミングをする医学生。物を作ること、自動化すること、今まで知らなかったことを知ることが好き。TypeScript書いたり、Pythonで機械学習したりなど。

Twitter / GitHub / GPG key / Fediverse / My Page
TOP >

FlaskとSQLAlchemyでDetachedInstanceErrorが出たときの解決法

環境

  • Flask
  • SQLAlchemy
  • MariaDB

問題

SQLAlchemyでセッションを作成し、それを用いてDBからデータを取得。そのデータをテンプレートに渡し、テンプレート内でDBクラスのrelationを参照した時にDetachedInstanceErrorが出ました。

def PenaltyList():
    session = Session():
    pens = session.query(Penalty).all()
    session.close()
    return render_template('penalty_list.html', pens=pens)

ここで、penalty_list.html内で Penalty クラスのrelationshipになっているクラスを参照していると、DetachedInstanceErrorが出ます。

原因は?

このエラーの原因は以下のとおりであると考えられます。

  • SQLAlchemyはrelationshipの対象となるインスタンスのためのデータを、遅延して取得する。(例えば、PenaltyクラスがrelationshipとしてUserクラスのインスタンスをメンバーとして持っているとき、PenaltyをDBから取得しインスタンスを生成した時点ではUserクラスのインスタンスのためのデータを取得していない。)
  • (当然ながら)セッションが関連付けられていない、あるいは、関連付けられていたセッションがcloseされてしまうと、relationshipの遅延ロードをするときにエラーが生じる

解決法

まずはコードから。

def PenaltyList():
    try:
        session = Session():
        pens = session.query(Penalty).all()
        return render_template('penalty_list.html', pens=pens)
    except:
        flash('エラー', 'error')
        return redirect('/top')
    finally:
        try:
            session.close()
        except:
            pass

DBアクセス部分をtryで囲ってきちんと例外処理をします。(基本的なことなのにしていなかった。)エラー処理の内容は割とどうでもいいのですが、大事なことは、セッションのcloseはfinally節で行うということです。こうすると、render_templateを処理した後、returnの直前にfinally節が実行されるはずです。

要するに、セッションのcloseは render_template の後に行わなければならないということです。例外処理をせず本質を抜き出すなら、次のようにも書けます。

def PenaltyList():
        session = Session():
        pens = session.query(Penalty).all()
        rendered = render_template('penalty_list.html', pens=pens)
        session.close()
        return rendered

あとがき

多くの人にとって、エラー処理をするんだからこれは当たり前の書き方なのかもしれませんが、私は知らなくて苦労しました。誰かの参考になればいいなと思います。もっと標準的な書き方があれば教えてください。


Comments

記事一覧へ