烧瓶和SQLAlchemy:处理会话

use*_*351 4 sqlalchemy flask

我最近开始为我的项目使用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_)
Run Code Online (Sandbox Code Playgroud)

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

Nic*_*ady 12

接受的答案有一些问题,尽管不可否认它应该有效。

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

Flask 本身不管理线程,这是 WSGI 服务器的职责。适当地,根据文档,依赖线程范围不是存储数据库会话的推荐方法,尽管它应该可以正常工作,因为请求可能直接与线程关联。

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

来自 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()
Run Code Online (Sandbox Code Playgroud)

对于 SQLAlchemy,附加会话的最干净的对象似乎是应用程序上下文,因为这是与请求直接关联的最高级别变量。这是有关 Flask 上下文如何工作的Flask 文档。您可以通过.AppContext实例访问内部LocalStack这个 stackoverlow 答案指向相同的解决方案。该函数很有用,因为它要么返回线程 id,要么调用 greenlet 函数来提供可用的标识符(如果已安装)。也就是说,flask似乎确实使用本地线程本身来做很多事情。我搜索了又搜索,但找不到任何可以保证 WSGI 服务器(例如gunicorn 或 uwsgi)将为每个请求创建一个线程的内容。如果有人有这方面的资料,我很想看看。无论如何,推荐的方法是使用应用程序上下文,这在语义上比依赖与请求具有相同生命周期的线程更清晰。_app_ctx_stack_app_ctx_stack.__ident_func__

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

综上所述,我的解决方案看起来与数据科学人员所做的几乎相同。我正在添加他们发布的存储库中的相关部分来执行此操作。

主要.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()
Run Code Online (Sandbox Code Playgroud)

数据库.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()

Run Code Online (Sandbox Code Playgroud)

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


Dem*_*tri 7

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

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

Session = scoped_session(sessionmaker(bind=engine))
Run Code Online (Sandbox Code Playgroud)

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

from flask import g

def my_page():
    session = Session()
    g.my_db_session = session # save session in the request context
    ...
Run Code Online (Sandbox Code Playgroud)

我们需要删除创建的会话。这需要在my_page函数结束之后但响应结束之前完成。上面,我们g为此目的将会话保存在线程本地对象中。要在正确的时间删除它,请在创建Flask应用程序时添加以下代码:

    @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/
       '''
       if hasattr(g, 'my_db_session'):
           g.my_db_session.remove()
Run Code Online (Sandbox Code Playgroud)

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

  • 我不相信你需要使用 g 对象。scoped_session 将为您返回当前会话,仅在必要时创建一个新会话。请参阅 https://docs.sqlalchemy.org/en/latest/orm/contextual.html#using-thread-local-scope-with-web-applications (3认同)
  • 有人可以澄清一件事吗?http://flask.pocoo.org/docs/0.12/patterns/sqlalchemy/ 下“Declarative”部分的手册建议直接使用从 scoped_session 返回的对象,而无需每次都创建会话实例,也不需要使用 g目的。哪种方式是正确的? (2认同)

pec*_*rin 4

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

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

来自文档:

一些 Web 框架包括帮助完成将会话的生命周期与 Web 请求的生命周期保持一致的任务的基础设施。这包括 Flask-SQLAlchemy(与 Flask Web 框架结合使用)和 Zope-SQLAlchemy(通常与 Pyramid 框架结合使用)等产品。SQLAlchemy 建议尽可能使用这些产品。