Python:测试期间已经定义了Sqlalchemy的解决方法。
#python #flask #pytest #sessions

在测试期间,烧瓶课程间歇性地导致异常 sqlalchemy.exc.invalidrequesterror:table's sessions'已定义为此元数据实例。在现有表对象上重新定义选项和列指定“ Extend_existing = true”。

我正在使用 pytest ,由于异常,我一直在经历间歇性测试失败 sqlalchemy.exc.invalidrequesterror:table'sessions'已定义为此元数据实例。在现有表对象上重新定义选项和列。仅当某些测试一起运行,一次单独运行一个测试时才发生,它们通过。直到几天前,我一直忽略了这个问题。目前,我有大约396(三百和九十六个)测试用例,只是偶然地,我一起进行了大约三(3)个测试,两(2)个始终失败,但有一个例外,只有一(1)个通过。<<<<<<<<<<<<<<<<<<< /p>

事实证明,这是Flask-Session 0.4.0的持续问题,并且仍然存在于最新版本 0.4.0 中。最新截至2022年11月24日。

我的研究导致了以下帖子,以及报告相同问题的其他文章:

How do you resolve 'Already defined in this MetaData Instance' Error with Flask Pytest, SqlAlchemy, and Flask Sessions? user Tassaron在01/01/2021上的答案导致以下URL:

只有在未通过条件上尚未存在的情况下,一般共识似乎仅为数据库会话表创建模型

if table not in self.db.metadata:

已经提出了几年前,但由于某些原因,作者尚未实施。

tassaron,她本人在https://github.com/tassaron/muffin-shop/blob/main/src/helpers/main/session_interface.py中实施了这一点:

class TassaronSessionInterface(SessionInterface):
    ...

    def __init__(self, app, db):
        ...

        if table not in self.db.metadata:
            # ^ Only create Session Model if it doesn't already exist
            # Fixes the SQLAlchemy "extend_existing must be true" exception during tests
            class Session(self.db.Model):
                ...
            self.sql_session_model = db.session_ext_session_model = Session
        else:
            self.sql_session_model = db.session_ext_session_model

与原始的Flask-Session 0.4.0相比:

class SqlAlchemySessionInterface(SessionInterface):
    ...

    def __init__(self, app, db, table, key_prefix, use_signer=False,
                 permanent=True):
        ...

        class Session(self.db.Model):
            ...

        self.sql_session_model = Session

in tassaronSessionInterface ,首先创建了 session model 时,它也将分配给 db new Attribute session_ext_session_model 之后使用 db.session_ext_session_model

除了在测试期间间歇性地提出的例外外,Flask-Session 0.4.0的工作正常。我想尽可能坚持下去。以下是我的尝试,感觉就像是解决方法,一个黑客而不是解决方案,我暂时对此表示满意

Content of fixed_session.py
from flask_session.sessions import SqlAlchemySessionInterface
from flask_session import Session

class FixedSqlAlchemySessionInterface( SqlAlchemySessionInterface ):
    def __init__(self, app, db, table, key_prefix, use_signer=False,
                 permanent=True):
        """
        Assumption: the way I use it, db is always a valid instance 
        at this point.
        """
        if table not in db.metadata:
            super().__init__( app, db, table, key_prefix, use_signer, permanent )
            db.session_ext_session_model = self.sql_session_model
        else:
            # print( "`sessions` table already exists..." )

            self.db = db
            self.key_prefix = key_prefix
            self.use_signer = use_signer
            self.permanent = permanent
            self.has_same_site_capability = hasattr(self, "get_cookie_samesite")

            self.sql_session_model = db.session_ext_session_model

class FixedSession( Session ):
    def _get_interface(self, app):
        config = app.config.copy()

        if config[ 'SESSION_TYPE' ] != 'sqlalchemy':
            return super()._get_interface( app )

        else:
            config.setdefault( 'SESSION_PERMANENT', True )
            config.setdefault( 'SESSION_KEY_PREFIX', 'session:' )

            return FixedSqlAlchemySessionInterface(
                app, config['SESSION_SQLALCHEMY'],
                config['SESSION_SQLALCHEMY_TABLE'],
                config['SESSION_KEY_PREFIX'], config['SESSION_USE_SIGNER'],
                config['SESSION_PERMANENT'] )

要使用此实现,请导入 fixedSession at session ,然后按照正常进行:

try:
    from xxx.yyyy.fixed_session import FixedSession as Session
except ImportError:
    from flask_session import Session

flask-session 是固定的,我只需删除 fixed_session.py 而无需更新任何代码 - 但是,我应该更新导入,例外很昂贵。

回到我的尝试中,在 pixedsqlalchemysessessionserface 中,我复制了 tassaronsessionsessioninterface db.session_ext_session_model 的想法。以下行是从原始代码中复制的:

            self.db = db
            self.key_prefix = key_prefix
            self.use_signer = use_signer
            self.permanent = permanent
            self.has_same_site_capability = hasattr(self, "get_cookie_samesite")

这意味着,如果烧瓶式 - 简历在不解决此问题的情况下进行更新,我可能必须更新我的代码!

in 类固定,在“覆盖方法”中的以下行 def _get_interface(self,app):

            config.setdefault( 'SESSION_PERMANENT', True )
            config.setdefault( 'SESSION_KEY_PREFIX', 'session:' )

也从原始代码复制了,我从来没有 session_permanent session_key_prefix 在我的环境变量文件中。

使用或没有 sessionsâ表,我的测试和应用程序没有任何问题。如果我删除sessionsâ表,它将按预期创建。

调查这个问题一直很有趣。我对我的代码不太满意,但暂时对我有用。希望作者将来会在将来进行修复。同时,我真的希望这篇文章可以帮助遇到同样问题的其他人。感谢您的阅读和保持安全。