英文:
SQLAlchemy 2.0 version of User.query.get(1) in Flask-SQLAlchemy?
问题
问题
在SQLAlchemy 2.0中,Query.get()
方法已被弃用。因此,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中,User.query.get(1)
的SQLAlchemy 2.0兼容版本是什么?更具体地说,为什么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>
根据我所了解,这种带有session.get()
的方法在Flask-SQLAlchemy 3.0.x文档中似乎没有提到,除非在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 vs. 方法#2 vs. 遗留方法
方法#1(db.session.get(User, 1)
)似乎最类似于遗留方法(User.query.get(1)
),因为它在首次运行时将结果缓存到session
中,不会不必要地向数据库发出额外的调用。可以在REPL中通过打开echo,即db.engine.echo = True
来查看这一点。相反,方法#2(session.execute(select(...))
)每次都会访问数据库,这是预期的行为。
我的设置/环境
-
版本:Flask 2.2.2,Flask-SQLAlchemy 3.0.3,以及SQLAlchemy 2.0.1,运行在Python 3.11的虚拟环境中。
-
我使用Flask Mega-Tutorial中定义的项目结构,具体来说是Part IV Database。
英文:
The Problem
The Query.get()
method is deprecated in SQLAlchemy 2.0. Accordingly, the Flask-SQLAlchemy query interface is considered legacy. Thus, running User.query.get(1)
in my Flask-SQLAlchemy project gives the legacy warning shown below:
>>> 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>
My Question
What is the new, SQLAlchemy 2.0-compatible version of User.query.get(1)
in Flask-SQLAlchemy? More specifically, why does the Flask-SQLAlchemy documentation recommend Approach #2 below, even though Approach #1 appears to be the new version based on my reading of the SQLAlchemy 2.0 migration guide?
Approach #1: db.session.get(User, 1)
This first approach comes from the SQLAlchemy docs, specifically the SQLAlchemy 2.0 Migration - ORM Usage guide. Translating the "2.0 style" example in that guide to my Flask-SQLAlchemy project yields the following code, which works fine:
>>> db.session.get(User, 1)
<User spongebob>
This approach with session.get()
isn't mentioned in the Flask-SQLAlchemy 3.0.x documentation as far as I can tell, except briefly in the API reference section on get_or_404
.
Approach #2: db.session.execute(db.select(User).filter_by(id=1)).scalar()
This approach comes from the Flask-SQLAlchemy documentation, which suggests using session.execute(select(...))
as a replacement for the legacy Model.query
and session.query
. This works fine, too:
>>> db.session.execute(db.select(User).filter_by(id=1)).scalar()
<User spongebob>
Approach #1 vs. Approach #2 vs. Legacy Approach
Approach #1 (db.session.get(User, 1)
) appears to be most similar to the Legacy Approach (User.query.get(1)
) because it caches the result in the session
the first time it runs and won't emit additional calls to the database unnecessarily. This can be seen in the REPL with the echo turned on, i.e. db.engine.echo = True
. In contrast, Approach #2 (session.execute(select(...))
) goes to the database each time, as expected.
My Set Up / Environment
-
Versions: Flask 2.2.2, Flask-SQLAlchemy 3.0.3, and SQLAlchemy 2.0.1 in a virtual environment with Python 3.11.
-
I'm using the project structure defined in the Flask Mega-Tutorial, specifically Part IV Database.
答案1
得分: 10
替换所有 Model.query.get(id)
为 db.session.get(Model, id)
令人担忧且不优雅:丧失了清晰的可读性。
我们需要更好的解决方案!
英文:
replacing all Model.query.get(id)
by db.session.get(Model, id)
is scary and not elegant: clear loss of readability.
We need a better solution !
答案2
得分: 5
根据https://docs.sqlalchemy.org/en/14/changelog/migration_20.html#orm-query-get-method-moves-to-session
Query.get() 方法仍保留供遗留目的,但现在的主要接口是 Session.get() 方法:
# 旧用法
user_obj = session.query(User).get(5)
迁移到 2.0 版本
在 1.4 / 2.0 版本中,Session 对象添加了一个新的 Session.get() 方法:
# 1.4 / 2.0 跨版本兼容用法
user_obj = session.get(User, 5)
在与 flask_sqlalchemy 兼容的情况下,这应该很好地工作:
user_obj = db.session.get(User, 5)
英文:
According to https://docs.sqlalchemy.org/en/14/changelog/migration_20.html#orm-query-get-method-moves-to-session
The Query.get() method remains for legacy purposes, but the primary interface is now the Session.get() method:
# legacy usage
user_obj = session.query(User).get(5)
Migration to 2.0
In 1.4 / 2.0, the Session object adds a new Session.get() method:
# 1.4 / 2.0 cross-compatible use
user_obj = session.get(User, 5)
In which compatible with flask_sqlalchemy, this should work well
user_obj = db.session.get(User, 5)
答案3
得分: 2
我找到了一种方法,可以避免使用不推荐的方法,同时允许您在模型上调用方法,而无需访问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))
所以我在这个混合类中添加了一个类方法:
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>
这需要一些自定义代码,但我对结果感到满意。
英文:
I found an approach that avoids using the deprecation method while still letting you call a method on the model rather than having to access db.session
.
First I noted that you can access the session from the model:
>>> MyModel.query.session
<sqlalchemy.orm.session.Session object at 0x7f1d9fe86c10>
So you could use the following verbose code:
>>> MyModel.query.session.get(MyModel, value)
<MyModel 1>
But that's not particularly elegant. Instead, in my case, I already made all my models inherit from a custom class I called 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))
So I added a classmethod to that 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_)
Now I can retrieve individual objects as follows:
>>> MyModel.get_by_id(1)
<MyModel 1>
It took a bit of custom code but I'm happy with the result.
答案4
得分: 0
在使用 Flask 应用程序进行用户注册时,我遇到了这个问题。Flask 登录 文档中有一个示例存在一个小错误:
@login_manager.user_loader
def load_user(user_id):
return User.get(user_id)
^^^ 不正确
所以,我将它更改为:
@login_manager.user_loader
def load_user(user_id):
return User.query.get(int(user_id))
^^^^^^^^^ 已弃用
最后,我使用了方法#1,一切都运行顺利:
@login_manager.user_loader
def load_user(user_id):
return db.session.get(User, int(user_id))
英文:
Working with a Flask App with user registration, I came across this problem. The Flask login documentation has an example that has a small error:
@login_manager.user_loader
def load_user(user_id):
return User.get(user_id)
^^^ not correct
So, I changed it to:
@login_manager.user_loader
def load_user(user_id):
return User.query.get(int(user_id))
^^^^^^^^^ deprecated
Finally, I used Approach #1 and everything worked smoothly:
@login_manager.user_loader
def load_user(user_id):
return db.session.get(User, int(user_id))
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论