当上下文管理器没有提供事务时,SQLAlchemy session.begin() 给出事务错误

roc*_*man 8 python sqlalchemy

简而言之,为什么我收到“sqlalchemy.exc.InvalidRequestError:事务已开始。使用 subtransactions=True 允许子事务”错误?

遵循分离和保持会话外部的最佳实践,我foo(input)使用上下文管理器创建,而不是使用 try / except / else。如果我使用foo(user)它而不是它,我会收到上述错误。我的猜测是,这foo()不是提交并关闭连接。然而,文档另有说明。

Flask 文档使用 a,scoped_sessionSQLAlchemy 文档表示“但是,强烈建议使用 Web 框架本身提供的集成工具(如果可用),而不是 scoped_session。” 也许这scoped_session会导致请求的线程之间出现错误?

这是我的主要代码:

#__init__.py
import os

from flask import Flask, render_template, redirect, request, url_for


def create_app(test_config=None):
    # create and configure the app
    app = Flask(__name__, instance_relative_config=False)
    app.config.from_object('config.DevelopmentConfig')
    
    # set up extensions
    # all flask extensions must support factory pattern
    # can run these two steps from the cli
    from app.database import init_db
    init_db()

    
    @app.route('/')
    def index():
        return render_template('index.html')

    from app.auth import RegistrationForm
    from app.models import User
    from app.database import db_session, foo

    @app.route('/register', methods=['GET', 'POST'])
    def register():
        form = RegistrationForm(request.form)
        if request.method == 'POST' and form.validate():
            user = User(form.name.data, form.email.data,
                        form.password.data)
            foo(user)
            # try:
            #     db_session.add(user)
            # except:
            #     db_session.rollback()
            #     raise
            # else:
            #     db_session.commit()
            return redirect(url_for('login'))
        return render_template('register.html', form=form)

    
    @app.route('/login', methods=['GET'])
    def login():
        return render_template('login.html')
    

    @app.teardown_appcontext
    def shutdown_session(exception=None):
        db_session.remove()
    
    return app
Run Code Online (Sandbox Code Playgroud)

这是我的数据库代码:

#database.py
from sqlalchemy import create_engine 
from sqlalchemy.orm import scoped_session, sessionmaker 
from sqlalchemy.ext.declarative import declarative_base

_database_uri = os.environ['DATABASE_URL'] engine = create_engine(_database_uri)
db_session = scoped_session(sessionmaker(autocommit=False,
                                         autoflush=False,
                                         bind=engine))

Base = declarative_base() Base.query = db_session.query_property()

def init_db():
    # import all modules here that might define models so that
    # they will be registered properly on the metadata.  Otherwise
    # you will have to import them first before calling init_db()
    import app.models
    Base.metadata.create_all(bind=engine)


def foo(input):
    with db_session.begin() as session:
        session.add(input)
Run Code Online (Sandbox Code Playgroud)

yap*_*rdi 8

我不确定这是否能真正回答你的问题,但我认为值得一提。

\n

在文件的最后一行附近database.py,我建议您不要使用别名db_session.begin()session因为这样您就会感到困惑,认为它session是类的对象,而它是SessionTransactionSession类的对象,即:

\n
\n

很大程度上是一个内部对象,在现代使用中为会话事务提供了上下文管理器。会话交易

\n
\n

您可以切换到:

\n
with db_session() as session, session.begin():\n    session.add(input)\n
Run Code Online (Sandbox Code Playgroud)\n

或更短的版本

\n
with db_session.begin():\n    db_session.add(input)\n
Run Code Online (Sandbox Code Playgroud)\n

您还需要将User对象创建包装起来Session.begin()如下所示:

\n
def register():\n    form = RegistrationForm(request.form)\n    if request.method == \'POST\' and form.validate():\n        with db_session.begin():\n            user = User(form.name.data, form.email.data, \n                        form.password.data)\n        foo(user)\n
Run Code Online (Sandbox Code Playgroud)\n

因为User模型只是一个代理对象,它将在幕后实际执行数据库查询。所以A transaction is already begun.在创作过程中。异常本身将在下一次事务查询调用时引发。

\n
\n

使用 Session 时,考虑将其维护的 ORM 映射对象作为数据库行的代理对象非常有用,这些对象对于 Session 所持有的事务来说是本地的。为了保持对象的状态与数据库中实际的\xe2\x80\x99s 相匹配,有多种事件会导致对象重新访问数据库以保持同步。可以从会话中分离 \xe2\x80\x9c\xe2\x80\x9d 对象并继续使用它们,尽管这种做法有其注意事项。它的目的是,通常,当您想要再次使用分离的对象时,您可以将它们与另一个会话重新关联,以便它们可以恢复表示数据库状态的正常任务。
\n会话基本

\n
\n

作为最后一个问题的附加答案:也许scoped_session 导致请求的线程之间出现错误?

\n

不。SQLAlchemyscoped_session实际上是一个辅助函数,充当全局Session对象的注册表。它在多线程应用程序中非常有用,有助于确保Session跨线程使用同一个对象,同时每个线程将自己的数据保存到本地threading.localpython 提供的 api 将自己的数据保存到本地。大多数 Web 框架使用线程策略来同时处理许多 Web 请求,因此大多数框架都提供了一些带有/不带有此帮助器的集成。

\n