sqlalchemy.orm.exc.DetachedInstanceError: InstDetachedInstanceError:

计划结合flask-admin 和casbin 来开发一个权限管理系统, 遇到一个比较tricky的错误.
project/app/admin/base.py:

class ModelView(_ModelView):
page_size = 50
column_type_formatters = MY_DEFAULT_FORMATTERS

def is_accessible(self):
    endpoint = self.endpoint
    self.can_create = current_user.can(endpoint, "post")
    self.can_edit = current_user.can(endpoint, "put")
    self.can_delete = current_user.can(endpoint, "delete")
    return current_user.can(endpoint, "get")
    # return current_user.is_authenticated

project/app/models/auth.py:

class User(UserMixin, TimestampMixin, ActiveMixin, db.Model):
__tablename__ = "users"
id = Column(Integer, primary_key=True)
email = Column(String(32), unique=True, nullable=False)
username = Column(String(32), unique=True, nullable=False)
auth = Column(SmallInteger, default=1)
password_hash = Column(String(200))
name = Column(String(100))
flag_id = Column(Integer, ForeignKey("flags.id"))
.........
    @property
    def is_active(self):
        return self.active

    @property
    def is_authenticated(self):
        return self.is_active
    def can(self, obj, act):
        return self.active
    from app.lib.casbin import enforcer
    return enforcer.enforce(self.username, obj, act)

project/app/templates/admin/master.html:

{% extends "admin/base.html" %}
{% block access_control %}
{% include "admin/_access_control.html" %}
{% endblock %}

project/app/templates/admin/_access_control.html:

{% if current_user.is_authenticated %}
    <p> {{current_user.username }}</p>
{% else %} 
    <p>Nobody</p>
{% endif %}

程序第一次启动如果之前已经登陆而浏览器没有重启的情况下,第一次访问 http://127.0.0.1:5000/admin 的时候会出现:

sqlalchemy.orm.exc.DetachedInstanceError: Instance <User at 0x7f5ed33218a0> is not bound to a 
Session; attribute refresh operation cannot proceed (Background on this error at: 
https://sqlalche.me/e/14/bhk3)

检查 error trackback,在本次执行到 project/app/admin/base.py, 对应的 dump()如下:

__name__	   'app.admin.base'
__doc__	 None
__package__ 	'app.admin'
__loader__	       <werkzeug.debug.console._ConsoleLoader object at 0x7f62d47718a0>
__spec__	    ModuleSpec(name='app.admin.base', loader= 
<_frozen_importlib_external.SourceFileLoader object at 0x7f62da488ac0>, 
    origin='/home/eros/iclass/app/admin/base.py')
__file__	'/home/eros/iclass/app/admin/base.py'
__cached__	'/home/eros/iclass/app/admin/__pycache__/base.cpython-310.pyc'
__builtins__	    {'__name__': 'builtins', '__doc__': "Built-in functions, exceptions, and other 
    objects.\n\nNoteworthy: Non  , '__package__': '', '__loader__': <class 
    '_frozen_importlib.BuiltinImporter'>, }
Enum	<enum 'Enum'>
request	<LocalProxy unbound>
redirect	<function redirect at 0x7f62db5496c0>
url_for	 <function url_for at 0x7f62db221bd0>
render_template	<function render_template at 0x7f62db0e5bd0>
current_user	None
login_user	<function login_user at 0x7f62db145120>
logout_user	<function logout_user at 0x7f62db1451b0>
expose	<function expose at 0x7f62d9516ef0>
_AdminIndexView	<class 'flask_admin.base.AdminIndexView'>
_ModelView	<class 'flask_admin.contrib.sqla.view.ModelView'>
typefmt	<module 'flask_admin.model.typefmt' from '/home/eros/venv/lib/python3.10/site- 
packages/flask_admin/model/typefmt.py'>
LoginForm	<class 'app.forms.auth.LoginForm'>
enum_formatter	<function enum_formatter at 0x7f62d953c5e0>
   MY_DEFAULT_FORMATTERS	 <class 'NoneType'>: <function empty_formatter at 
  0x7f62d90fbac0>, <class 'bool'>: <function bool_formatter at 0x7f62d90fbb50>, <class 'list'> 
  <function list_formatter at 0x7f62d90fbbe0>, <class 'dict'>: <function dict_formatter at 
 0x7f62d90fbd00>, }
ModelView	<class 'app.admin.base.ModelView'>
AdminIndexView	<class 'app.admin.base.AdminIndexView'>
self	<app.admin.views.CasbinView object at 0x7f62d4bfe770>
endpoint	'casbin_rules'
dump	<function dump at 0x7f62d46b53f0>
help	Type help(object) for help about object.

不能理解是什么原因current_user没有正确绑定到对应的对象上面。 不知道哪位大神能指点一二?错误只在第一个request的时候出现。后续request一切正常。

哈哈,经过两天不休止的探索,终于发现问题所在了。原来是casbin代码里面提供的 contextmanager 惹的祸。

class Adapter(persist.Adapter, persist.adapters.UpdateAdapter):
    """the interface for Casbin adapters."""

    def __init__(self, engine, db_class=None, filtered=False):
        if isinstance(engine, str):
            self._engine = create_engine(engine)
        else:
            self._engine = engine

        if db_class is None:
            db_class = CasbinRule
        else:
            for attr in (
                "id",
                "ptype",
                "v0",
                "v1",
                "v2",
                "v3",
                "v4",
                "v5",
            ):  # id attr was used by filter
                if not hasattr(db_class, attr):
                    raise Exception(f"{attr} not found in custom DatabaseClass.")
            Base.metadata = db_class.metadata

        self._db_class = db_class
        self.session_local = sessionmaker(bind=self._engine)

        Base.metadata.create_all(self._engine)
        self._filtered = filtered

    @contextmanager
    def _session_scope(self):
        """Provide a transactional scope around a series of operations."""
        session = self.session_local()
        try:
            yield session
            session.commit()
        except Exception as e:
            session.rollback()
            raise e
        finally:
            session.close()

经过适当的代码兼容,再最后两行的代码去掉就好了。

       # finally:
       #     session.close()