gpt4 book ai didi

python - Flask-SQLAlchemy db.session.query(Model) 与 Model.query

转载 作者:太空狗 更新时间:2023-10-29 22:21:22 27 4
gpt4 key购买 nike

这是我偶然发现的一个奇怪的错误,我不确定它为什么会发生,无论它是 SQLAlchemy 中的错误、Flask-SQLAlchemy 中的错误,还是我还不知道的 Python 的任何特性。

我们使用 Flask 0.11.1,Flask-SQLAlchemy 2.1 使用 PostgreSQL 作为 DBMS。

示例使用以下代码更新数据库中的数据:

entry = Entry.query.get(1)
entry.name = 'New name'
db.session.commit()

这在从 Flask shell 执行时完全正常,因此数据库已正确配置。现在,我们用于更新条目的 Controller 稍微简化了(没有验证和其他样板文件),如下所示:

def details(id):
entry = Entry.query.get(id)

if entry:
if request.method == 'POST':
form = request.form
entry.name = form['name']
db.session.commit()
flash('Updated successfully.')
return render_template('/entry/details.html', entry=entry)
else:
flash('Entry not found.')
return redirect(url_for('entry_list'))

# In the application the URLs are built dynamically, hence why this instead of @app.route
app.add_url_rule('/entry/details/<int:id>', 'entry_details', details, methods=['GET', 'POST'])

当我在 details.html 中提交表单时,我可以看到非常好的更改,这意味着表单已正确提交,有效并且模型对象已更新。然而,当我重新加载页面时,更改消失了,就好像它已被 DBMS 回滚一样。

我启用了 app.config['SQLALCHEMY_ECHO'] = True,我可以在自己手动提交之前看到“ROLLBACK”。

如果我改变行:

entry = Entry.query.get(id)

收件人:

entry = db.session.query(Entry).get(id)

https://stackoverflow.com/a/21806294/4454028 中所述,它确实按预期工作,所以我猜测 Flask-SQLAlchemy 的 Model.query 实现中存在某种错误。

然而,由于我更喜欢​​第一种构造,我对 Flask-SQLAlchemy 做了一个快速修改,并重新定义了原始的 query @property:

class _QueryProperty(object):

def __init__(self, sa):
self.sa = sa

def __get__(self, obj, type):
try:
mapper = orm.class_mapper(type)
if mapper:
return type.query_class(mapper, session=self.sa.session())
except UnmappedClassError:
return None

收件人:

class _QueryProperty(object):

def __init__(self, sa):
self.sa = sa

def __get__(self, obj, type):
return self.sa.session.query(type)

其中sa是Flask-SQLAlchemy对象(即controller中的db)。

现在,这就是事情变得奇怪的地方:它仍然没有保存更改。代码完全一样,但 DBMS 仍在回滚我的更改。

我读到 Flask-SQLAlchemy 可以在拆卸时执行提交,并尝试添加这个:

app.config['SQLALCHEMY_COMMIT_ON_TEARDOWN'] = True

突然之间,一切正常。问题是:为什么?

拆解不是应该只在 View 完成渲染时发生吗?为什么修改后的 Entry.query 的行为与 db.session.query(Entry) 不同,即使代码相同?

最佳答案

以下是对模型实例进行更改并将其提交到数据库的正确方法:

# get an instance of the 'Entry' model
entry = Entry.query.get(1)

# change the attribute of the instance; here the 'name' attribute is changed
entry.name = 'New name'

# now, commit your changes to the database; this will flush all changes
# in the current session to the database
db.session.commit()

注意:不要使用 SQLALCHEMY_COMMIT_ON_TEARDOWN,因为它被认为是有害的并且也已从文档中删除。参见 the changelog for version 2.0 .

编辑:如果您有两个普通 session 对象(使用sessionmaker()创建)而不是scoped session ,然后调用 db.session.add(entry) 上面的代码将引发错误 sqlalchemy.exc.InvalidRequestError: Object '' is already attached to session '2' (this is ' 3')。有关 sqlalchemy session 的更多信息,请阅读以下部分

Scoped Session 与普通 Session 的主要区别

我们主要从 sessionmaker() 调用构建并用于与我们的数据库通信的 session 对象是一个普通 session 。如果您第二次调用 sessionmaker(),您将获得一个新的 session 对象,其状态独立于之前的 session 。例如,假设我们有两个按以下方式构造的 session 对象:

from sqlalchemy import Column, String, Integer, ForeignKey
from sqlalchemy.ext.declarative import declarative_base

Base = declarative_base()

class User(Base):
__tablename__ = 'user'
id = Column(Integer, primary_key=True)
name = Column(String)


from sqlalchemy import create_engine
engine = create_engine('sqlite:///')

from sqlalchemy.orm import sessionmaker
session = sessionmaker()
session.configure(bind=engine)
Base.metadata.create_all(engine)

# Construct the first session object
s1 = session()
# Construct the second session object
s2 = session()

那么,我们将无法同时向 s1s2 添加相同的 User 对象。换句话说,一个对象最多只能附加一个唯一的 session 对象。

>>> jessica = User(name='Jessica')
>>> s1.add(jessica)
>>> s2.add(jessica)
Traceback (most recent call last):
......
sqlalchemy.exc.InvalidRequestError: Object '' is already attached to session '2' (this is '3')

但是,如果 session 对象是从 scoped_session 对象中检索到的,那么我们就没有这样的问题,因为 scoped_session 对象为同一 session 维护了一个注册表对象。

>>> session_factory = sessionmaker(bind=engine)
>>> session = scoped_session(session_factory)
>>> s1 = session()
>>> s2 = session()
>>> jessica = User(name='Jessica')
>>> s1.add(jessica)
>>> s2.add(jessica)
>>> s1 is s2
True
>>> s1.commit()
>>> s2.query(User).filter(User.name == 'Jessica').one()

请注意 s1s2 是相同的 session 对象,因为它们都是从维护对相同引用的 scoped_session 对象中检索的 session 对象。

提示

因此,尽量避免创建多个正常 session 对象。创建一个 session 对象,并在从声明模型到查询的任何地方使用它。

关于python - Flask-SQLAlchemy db.session.query(Model) 与 Model.query,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/40020388/

27 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com