【问题标题】:Flask and sqlalchemy: handling sessionsFlask 和 sqlalchemy:处理会话
【发布时间】:2016-02-27 01:03:41
【问题描述】:

我最近开始在我的项目中使用 Flask + Sqlalchemy,并且在关闭服务器一天后发现了 500 个错误。我认为这是由于数据库会话超时,但我不确定。我们应该为每个请求创建一个新会话,还是 Flask 应用程序启动一个新会话? 我的 app.py 顶部有这个

from sqlalchemy import Column, ForeignKey, Integer, String, create_engine, func, cast, Float 
from sqlalchemy.ext.declarative import declarative_base 
from sqlalchemy.orm import relationship,scoped_session,sessionmaker,aliased
engine = createengine(DB_PATH) 
Session = sessionmaker(bind=engine) 
session = Session() 
app = Flask(name_)

然后对于视图中的所有查询,我执行以下操作:“session.query(Table)...” 这是错的吗,我应该为每个端点调用创建一个会话吗?

【问题讨论】:

    标签: flask sqlalchemy


    【解决方案1】:

    在某些情况下使用Flask-SQLAlchemy Extension 可能不合适。例如,如果您在一个完全不同的 Python 模块中管理您的模型类和数据库连接详细信息,以便在 Flask 之外的软件中重复使用,那么您不希望/不需要扩展来为您管理这些事情。

    假设您有自己的代码来连接数据库并通过类似的方式创建Session 类(还假设提供了engine):

    Session = scoped_session(sessionmaker(bind=engine))
    

    对于需要数据库连接的页面,您可以使用该对象创建会话实例:

    # import the "Session" object created above from wherever you put it
    
    def my_page():
        session = Session() # creates a new, thread-local session
        ...
    

    响应完成后,我们需要删除已创建的会话。这需要在my_page 函数结束之后完成(所以我们不能在那里关闭它),但在响应结束之前。要在适当的时候将其删除,请在创建 Flask 应用程序时添加以下代码:

    # import the "Session" object created above from wherever you put it
    
    # despite the name, this is called when the app is
    # torn down _and_ when the request context is closed
    
    @app.teardown_appcontext
    def shutdown_session(exception=None):
        ''' Enable Flask to automatically remove database sessions at the
        end of the request or when the application shuts down.
        Ref: http://flask.pocoo.org/docs/patterns/sqlalchemy/
        '''
        Session.remove()
    

    请注意,在后一种情况下,remove() 是在 Session(大写 S)上调用的,而不是 session(小写 s,线程本地实例)。 SQLAlchemy 知道哪个session 在哪个线程中,并将关闭为当前线程创建的会话。

    可能还有其他方法可以做到这一点,但这就是想法。请注意,SQLAlchemy 为您提供连接池。

    【讨论】:

    • 谁能澄清一件事? flask.pocoo.org/docs/0.12/patterns/sqlalchemy 的“Declarative”部分下的手册建议直接使用从 scoped_session 返回的对象,而无需每次都创建会话实例,也不需要使用 g 对象。哪种方式正确?
    • 我认为您不需要使用 g 对象。 scoped_session 将为您返回当前会话,仅在必要时创建一个新会话。见docs.sqlalchemy.org/en/latest/orm/…
    • 我在示例中删除了对全局“g”对象的使用,因为 SQLAlchemy 内部维护了会话对象的全局注册表,因此这是不必要的。
    【解决方案2】:

    公认的答案有一些问题,尽管它应该工作。

    1. 它隐含地依赖于threading.local()。虽然对于大多数应用程序来说都很好,但它忽略了安装 greenlet 的可能性,在这种情况下本地线程 ID 是不够的。
    2. 它不必要地使用了g。如评论中所述,scoped_session 已经处理了这部分。

    Flask 本身不管理线程,这是 WSGI 服务器的职责。适当地,per the documentation,不推荐使用线程范围来存储数据库会话,尽管它应该可以正常工作,因为请求可能直接与线程相关联。

    特别是,虽然使用本地线程很方便,但最好将Session直接与请求相关联,而不是与当前线程相关联。 因此,最好根据文档使用自定义范围,以便我们可以将会话直接与请求上下文相关联。这可以使用custom created scope 来完成。

    SQLAlchemy 文档中的伪代码

    from my_web_framework import get_current_request, on_request_end
    from sqlalchemy.orm import scoped_session, sessionmaker
    
    Session = scoped_session(sessionmaker(bind=some_engine), scopefunc=get_current_request)
    
    @on_request_end
    def remove_session(req):
        Session.remove()
    

    对于 SQLAlchemy,将会话附加到的最干净的对象似乎是 Application Context,因为这是与请求直接关联的最高级别变量。这是the flask documentation 关于 Flask 上下文如何工作的内容。您可以通过_app_ctx_stack 使用AppContext 实例访问内部LocalStackThis stackoverlow answer 指向相同的解决方案。 _app_ctx_stack.__ident_func__ 函数很有帮助,因为它要么返回线程 ID,要么调用 greenlet 函数来提供可用的标识符(如果已安装)。也就是说,烧瓶does appear to use the thread local itself 用于许多事情。我搜索并搜索,但找不到任何保证 WSGI 服务器(如 gunicorn 或 uwsgi)会为每个请求创建线程的东西。如果有人对此有消息来源,我很乐意看到它。无论如何,推荐的方法是使用应用程序上下文,这在语义上比依赖与请求具有相同生命周期的线程更清晰。

    最后,另一条评论提到使用 Flask-SQLAlchemy。虽然这对于大多数项目来说都是一个好主意,但我认为这并不总是有意义的。就个人而言,我希望我的模型定义使用 SQLAlchemy 定义,而不是通过 Flask-SQLAlchemy。我认为(就我而言)这些模型很可能在不久的将来在 Flask 之外使用。我也不想使用与 SQLAlchemy 不同的 API。时期。虽然我认为它们可能非常相似,如果不完全相同的话,它并没有使用我不喜欢的 SQLAlchemy 本身。我追溯找到了一个blog from towardsdatascience,得出了相同的结论。

    综上所述,我的解决方案看起来与面向数据科学的人所做的几乎相同。我正在添加相关部分 from a repo they published 这样做。

    ma​​in.py

    from flask import Flask, _app_ctx_stack
    from sqlalchemy.orm import scoped_session
    from .database import SessionLocal, engine
    
    app = Flask(__name__)
    app.session = scoped_session(SessionLocal, scopefunc=_app_ctx_stack.__ident_func__)
    
    @app.teardown_appcontext
    def remove_session(*args, **kwargs):
        app.session.remove()
    

    database.py

      
    from sqlalchemy import create_engine
    from sqlalchemy.ext.declarative import declarative_base
    from sqlalchemy.orm import sessionmaker
    
    SQLALCHEMY_DATABASE_URL = "sqlite:///./test.db"
    # SQLALCHEMY_DATABASE_URL = "postgresql://user:password@postgresserver/db"
    
    engine = create_engine(
        SQLALCHEMY_DATABASE_URL, connect_args={"check_same_thread": False}
    )
    SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
    
    Base = declarative_base()
    
    

    这个主题相当复杂,所以我欢迎 cmets,我会更新答案,但希望这项研究对其他人有所帮助。

    【讨论】:

    • 嗨 Nic,我发布了一个关于跨请求 here 的会话管理的问题。你的解决方案能解决这个问题吗?
    • 不,在本例中,我将 db 会话绑定到应用程序上下文,每个请求都是唯一的,然后我将其拆除。尝试在 REQUEST(共享会话)之间执行此操作对我来说很难闻。当然,您可以使用我定义的方法来执行此操作,但最好使用 Redis。我真的会考虑您的设计要求是否确实需要这样做。我很难找到必要的情况。
    • 这是一个很好的回应,并且比我自己的更详细,适用于超出常见用例的人们。
    【解决方案3】:

    我建议使用处理会话管理和连接池的出色 Flask SQLAlchemy Extension。此外,它还根据请求等处理会话的打开和关闭。

    您可以查看相关的 SQLAlchemy 文档了解更多详细信息:http://docs.sqlalchemy.org/en/latest/orm/session_basics.html#session-frequently-asked-questions

    来自文档:

    一些网络框架包括基础设施,以协助将会话的生命周期与网络请求的生命周期保持一致。这包括 Flask-SQLAlchemy 等产品,用于与 Flask Web 框架结合使用,以及 Zope-SQLAlchemy,通常与 Pyramid 框架一起使用。 SQLAlchemy 建议尽可能使用这些产品。

    【讨论】:

      【解决方案4】:

      仔细阅读Nick's answerlib/python3.8/site-pakages/fkask_sqlalchemy/__init__.py中SQLAlchemy的类定义,

      我做到了,

      from flask import request, current_app
      from flask import _app_ctx_stack
      from app import db
      
      SessionLocal = db.create_scoped_session(options={'autocommit': False,
                                                            'autoflush': False,
                                                            'bind': db.engine,
                                                            'scopefunc':_app_ctx_stack.__ident_func__
                                                            })
      
      with SessionLocal() as session:
          try:
              session.begin()        
              session.add(some_data_from_request)
              session.commit()
          except Exception as ex:
              session.rollback()
          finally:
              session.close()
      

      “db”对象的创建位置如下。 (与SQLAlchey类定义注释中的第二个启动示例相同。)

      db = SQLAlchemy()
      def create_app():
          app = Flask(__name__)
          db.init_app(app)
          return app
      

      【讨论】:

      • 你真的不需要这样做 - Flask-SQLAlchemy already uses the app context by default.
      • @snakecharmerb 感谢您的评论!那么,这意味着,我不必为事务管理创建新的会话对象吗?我的情况只做session = Session() 就足够了吗?
      • 如果您使用的是flask-sqlalchemy,请酌情使用db.sessionMyModel.query。 Flask-SQLAlchemy 抽象了一些您将使用普通 SQLAlchemy 进行的会话管理。如果您没有做任何奇特的事情,请按照 docs 中的示例进行操作。
      • @snakecharmerb 再次感谢!当我执行db.session.begin() 时,我收到“此会话上的事务已开始”错误。如果我删除了db.session.begin(),数据将正确存储在数据库中(db.session.commit() 和 db.sessoin.rollback() 工作正常)。但是,由于我对此感到非常不舒服并想在我的代码中清楚地描述保存点,所以我做了with db.session.begin_nested():,如this question所示。
      猜你喜欢
      • 2018-02-08
      • 1970-01-01
      • 2016-10-17
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-05-27
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多