公认的答案有一些问题,尽管它应该工作。
- 它隐含地依赖于
threading.local()。虽然对于大多数应用程序来说都很好,但它忽略了安装 greenlet 的可能性,在这种情况下本地线程 ID 是不够的。
- 它不必要地使用了
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 实例访问内部LocalStack。 This 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 这样做。
main.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,我会更新答案,但希望这项研究对其他人有所帮助。