Mar*_*Sol 7 python postgresql sqlalchemy flask flask-sqlalchemy
这是我偶然发现的一个奇怪的错误,我不确定它为什么会发生,无论是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()
Run Code Online (Sandbox Code Playgroud)
从Flask shell执行时,这完全正常,因此数据库已正确配置.现在,我们的控制器用于更新条目,略微简化(没有验证和其他样板),如下所示:
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'])
Run Code Online (Sandbox Code Playgroud)
当我在details.html中提交表单时,我可以完全看到更改,这意味着表单已正确提交,有效并且模型对象已更新.但是,当我重新加载页面时,更改已经消失,就好像它已被DBMS回滚一样.
我启用了app.config['SQLALCHEMY_ECHO'] = True
,我可以在自己的手动提交之前看到"ROLLBACK".
如果我换行:
entry = Entry.query.get(id)
Run Code Online (Sandbox Code Playgroud)
至:
entry = db.session.query(Entry).get(id)
Run Code Online (Sandbox Code Playgroud)
如/sf/answers/1526440611/中所述,它确实按预期工作,所以我猜测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
Run Code Online (Sandbox Code Playgroud)
至:
class _QueryProperty(object):
def __init__(self, sa):
self.sa = sa
def __get__(self, obj, type):
return self.sa.session.query(type)
Run Code Online (Sandbox Code Playgroud)
sa
Flask-SQLAlchemy对象在哪里(即db
在控制器中).
现在,这就是事情变得奇怪的地方:它仍然没有保存变化.代码完全相同,但DBMS仍在回滚我的更改.
我读到Flask-SQLAlchemy可以在拆卸时执行提交,并尝试添加以下内容:
app.config['SQLALCHEMY_COMMIT_ON_TEARDOWN'] = True
Run Code Online (Sandbox Code Playgroud)
突然间,一切正常.问题是:为什么?
是否应该只在视图完成渲染时才会发生拆解?为什么修改后的Entry.query
行为会有所不同db.session.query(Entry)
,即使代码相同?
Aks*_*ngh 10
以下是更改模型实例并将其提交到数据库的正确方法:
# 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()
Run Code Online (Sandbox Code Playgroud)
注意:不要使用SQLALCHEMY_COMMIT_ON_TEARDOWN
,因为它被认为是有害的,也从文档中删除.请参阅2.0版的更新日志.
编辑:如果你有两个正常会话对象(使用创建sessionmaker()
)而不是作用域会话,那么在调用db.session.add(entry)
上面的代码时会引发错误sqlalchemy.exc.InvalidRequestError: Object '' is already attached to session '2' (this is '3')
.有关sqlalchemy会话的更多信息,请阅读以下部分
我们主要从sessionmaker()
调用构造并用于与数据库通信的会话对象是正常会话.如果您sessionmaker()
再次呼叫,您将获得一个新的会话对象,其状态独立于上一个会话.例如,假设我们有两个以下列方式构造的会话对象:
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()
Run Code Online (Sandbox Code Playgroud)
然后,我们将无法给同一个用户对象添加到都s1
和s2
在同一时间.换句话说,一个对象最多只能附加一个唯一的会话对象.
>>> 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')
Run Code Online (Sandbox Code Playgroud)
scoped_session
但是,如果从对象检索会话对象,那么我们就没有这样的问题,因为该scoped_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()
Run Code Online (Sandbox Code Playgroud)
请注意,s1
并且s2
它们是相同的会话对象,因为它们都是从scoped_session
维护对同一会话对象的引用的对象中检索的.
因此,尽量避免创建多个普通会话对象.创建会话的一个对象,并在从声明模型到查询的任何地方使用它.