问题
Query.get()
方法在 SQLAlchemy 2.0 中已被弃用。因此,Flask-SQLAlchemy 查询接口被视为遗留。因此,在我的 Flask-SQLAlchemy 项目中运行 User.query.get(1)
会出现如下所示的遗留警告:
>>> User.query.get(1)
<stdin>:1: LegacyAPIWarning: The Query.get() method
is considered legacy as of the 1.x series of SQLAlchemy
and becomes a legacy construct in 2.0. The method is
now available as Session.get() (deprecated since: 2.0)
(Background on SQLAlchemy 2.0 at: https://sqlalche.me/e/b8d9)
<User spongebob>
我的问题
Flask-SQLAlchemy 中新的、与 SQLAlchemy 2.0 兼容的
User.query.get(1)
版本是什么?更具体地说,为什么 Flask-SQLAlchemy 文档推荐下面的方法 #2,尽管根据我对 SQLAlchemy 2.0 迁移指南的阅读,方法 #1 似乎是新版本?
方法#1:
db.session.get(User, 1)
第一种方法来自 SQLAlchemy 文档,特别是 SQLAlchemy 2.0 迁移 - ORM 使用 指南。将该指南中的“2.0 样式”示例转换为我的 Flask-SQLAlchemy 项目会生成以下代码,该代码运行良好:
>>> db.session.get(User, 1)
<User spongebob>
据我所知,Flask-SQLAlchemy 3.0.x 文档中没有提到这种使用
session.get()
的方法,除了关于 get_or_404
的 API 参考部分中的简要介绍。
方法#2:
db.session.execute(db.select(User).filter_by(id=1)).scalar()
此方法来自 Flask-SQLAlchemy 文档,该文档建议使用
session.execute(select(...))
作为旧版 Model.query
和 session.query
的替代品。这也很好用:
>>> db.session.execute(db.select(User).filter_by(id=1)).scalar()
<User spongebob>
方法 #1 与方法 #2 与传统方法
方法 #1 (
db.session.get(User, 1)
) 似乎与旧方法 (User.query.get(1)
) 最相似,因为它在第一次运行时将结果缓存在 session
中,并且不会对数据库发出不必要的额外调用。这可以在打开回显的 REPL 中看到,即 db.engine.echo = True
。相比之下,方法 #2 (session.execute(select(...))
) 每次都会按预期访问数据库。
我的设置/环境
版本:Python 3.11 虚拟环境中的 Flask 2.2.2、Flask-SQLAlchemy 3.0.3 和 SQLAlchemy 2.0.1。
我正在使用 Flask Mega-Tutorial 中定义的项目结构,特别是第四部分数据库。
用
Model.query.get(id)
替换所有 db.session.get(Model, id)
既可怕又不优雅:明显丧失了可读性。
我们需要更好的解决方案!
根据https://docs.sqlalchemy.org/en/14/changelog/migration_20.html#orm-query-get-method-moves-to-session
Query.get() 方法保留用于遗留目的,但主要接口现在是 Session.get() 方法:
# legacy usage
user_obj = session.query(User).get(5)
迁移到2.0
在1.4 / 2.0中,Session对象添加了新的Session.get()方法:
# 1.4 / 2.0 cross-compatible use
user_obj = session.get(User, 5)
与flask_sqlalchemy兼容,这应该可以很好地工作
user_obj = db.session.get(User, 5)
我找到了一种方法,可以避免使用已弃用的方法,同时仍然允许您调用模型上的方法,而不必访问
db.session
。
首先我注意到您可以从模型访问会话:
>>> MyModel.query.session
<sqlalchemy.orm.session.Session object at 0x7f1d9fe86c10>
所以你可以使用以下详细代码:
>>> MyModel.query.session.get(MyModel, value)
<MyModel 1>
但这并不是特别优雅。相反,就我而言,我已经让所有模型继承自一个名为
IDMixin
: 的自定义类
from flask_sqlalchemy import SQLAlchemy
db = SqlAlchemy()
class IDMixin:
id = db.Column(db.Integer, primary_key=True)
class MyModel(IDMixin, db.Model):
field = db.column(db.String(50))
所以我向该 mixin 添加了一个类方法:
class IDMixin:
id = db.Column(db.Integer, primary_key=True)
@classmethod
def get_by_id(cls, id_):
return cls.query.session.get(cls, id_)
现在我可以按如下方式检索单个对象:
>>> MyModel.get_by_id(1)
<MyModel 1>
它需要一些自定义代码,但我对结果很满意。
使用带有用户注册的 Flask 应用程序时,我遇到了这个问题。 Flask登录文档有一个有小错误的示例:
@login_manager.user_loader
def load_user(user_id):
return User.get(user_id)
^^^ not correct
所以,我将其更改为:
@login_manager.user_loader
def load_user(user_id):
return User.query.get(int(user_id))
^^^^^^^^^ deprecated
最后,我使用了方法#1,一切都很顺利:
@login_manager.user_loader
def load_user(user_id):
return db.session.get(User, int(user_id))