This is a weird bug I've stumbled upon, and I am not sure why is it happening, whether it's a bug in SQLAlchemy, in Flask-SQLAlchemy, or any feature of Python I'm not yet aware of.
We are using Flask 0.11.1, with Flask-SQLAlchemy 2.1 using a PostgreSQL as DBMS.
Examples use the following code to update data from the database:
entry = Entry.query.get(1)
entry.name = 'New name'
db.session.commit()
This works totally fine when executing from the Flask shell, so the database is correctly configured. Now, our controller for updating entries, slightly simplified (without validation and other boilerplate), looks like this:
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'])
When I submit the form in details.html, I can see perfectly fine the changes, meaning the form has been submitted properly, is valid and that the model object has been updated. However, when I reload the page, the changes are gone, as if it had been rolled back by the DBMS.
I have enabled app.config['SQLALCHEMY_ECHO'] = True
and I can see a "ROLLBACK" before my own manual commit.
If I change the line:
entry = Entry.query.get(id)
To:
entry = db.session.query(Entry).get(id)
As explained in https://stackoverflow.com/a/21806294/4454028, it does work as expected, so my guess what there was some kind of error in Flask-SQLAlchemy's Model.query
implementation.
However, as I prefer the first construction, I did a quick modification to Flask-SQLAlchemy, and redefined the query
@property
from the original:
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
To:
class _QueryProperty(object):
def __init__(self, sa):
self.sa = sa
def __get__(self, obj, type):
return self.sa.session.query(type)
Where sa
is the Flask-SQLAlchemy object (ie db
in the controller).
Now, this is where things got weird: it still doesn't save the changes. Code is exactly the same, yet the DBMS is still rolling back my changes.
I read that Flask-SQLAlchemy can execute a commit on teardown, and tried adding this:
app.config['SQLALCHEMY_COMMIT_ON_TEARDOWN'] = True
Suddenly, everything works. Question is: why?
Isn't teardown supposed to happen only when the view has finished rendering? Why is the modified Entry.query
behaving different to db.session.query(Entry)
, even if the code is the same?