尽管已被接受的答案看起来可以工作,但它存在一些问题。
- 它隐式地依赖于
threading.local()
。虽然对于大多数应用程序是可以的,但它忽略了安装greenlet
的可能性,在这种情况下,本地线程ID是不够的。
- 它不必要地使用了
g
。如评论中所述,scoped_session
已经处理了这一部分。
Flask本身不管理线程,这是WSGI服务器的责任。因此,根据文档,依赖于线程范围不是存储db会话的推荐方式,尽管它应该能正常工作,因为请求很可能直接与线程相关联。
特别地,虽然使用线程局部变量可能很方便,但最好将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()
对于SQLAlchemy,最干净的对象来附加session似乎是“应用程序上下文”,因为这是与请求直接关联的最高级别变量。 这里
Flask文档介绍了Flask上下文如何工作。 您可以通过
_app_ctx_stack
访问
AppContext实例的内部
LocalStack。
这个stackoverflow答案指向相同的解决方案。
_app_ctx_stack.__ident_func__
函数非常有用,因为它将返回线程ID,或者调用greenlet函数以提供可用的标识符(如果已安装)。 话虽如此,Flask似乎
确实在许多方面使用线程本地。 我搜索了很久,但找不到任何保证WSGI服务器(例如gunicorn或uwsgi)会为每个请求创建一个线程的内容。 如果有人有这方面的来源,我很想看看。 无论如何,推荐的方法是使用应用程序上下文,这比依赖线程具有相同生命周期更具语义清晰性。
最后,
另一个评论提到使用Flask-SQLAlchemy。 对于大多数项目来说,这是一个不错的主意,但我认为并不总是合理的。 就个人而言,我希望我的模型定义是使用SQLAlchemy定义的,而不是通过Flask-SQLAlchemy定义的。 我认为,在不久的将来,模型很可能会在Flask之外使用。我也不想拥有一个与SQLAlchemy不同的API. 虽然它们可能非常相似,如果不是完全相同,但这并不是我喜欢的使用SQLAlchemy本身。 最后,我找到了一篇
towardsdatascience的博客得出了相同的结论。
说了这么多,我的解决方案与towardsdatascience人员所做的几乎相同。 我正在添加相关部分
从他们发布的存储库中进行此操作。
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"
engine = create_engine(
SQLALCHEMY_DATABASE_URL, connect_args={"check_same_thread": False}
)
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
Base = declarative_base()
这个主题相当复杂,所以我欢迎评论并会更新答案,但希望这项研究能帮助其他人。
scoped_session
返回的对象可以在带有()
和不带()
的情况下互换使用,这没有任何区别。 - LucG