Transactions using Flask-SQLAlchemy
SQLAlchemy is a popular ORM framework in python and the Flask-SQLAlchemy is an extension for the Flask framework that adds support for SQLAlchemy.
Among other things, it provides a sqlalchemy session that is request scoped. In other words, a new session gets created during the creation of the flask application context and the session is removed during the teardown of the application context ( see https://github.com/pallets/flask-sqlalchemy/blob/f465142ea1643b4f0fe50540780ef9a6bf2c7e53/flask_sqlalchemy/__init__.py#L807 )
If you look closely at the function above, it is configured to commit the session if the option “SQLALCHEMY_COMMIT_ON_TEARDOWN” is set true. In other works, during teardown of the app context ( roughly end of the request lifecycle), the current changes in the session are persisted on to the database. This property is generally desirable in a system because it frees the business logic from having to explicitly persist changes to the database and to deal with inconsistencies in case of failures. However the configuration “SQLALCHEMY_COMMIT_ON_TEARDOWN” is not safe to use and is set to be deprecated because as it cannot deal with exceptions arising from the commit.
So how to have the nice property of persisting database changes only at the end of a request, and yet still be able to safely commit and handle errors if case of exceptions? The obvious option seems to write a custom teardown_appcontext hook which can handle exceptions from commit. But it has a disadvantage — we will not be able to use the Flask error handlers to deal with the exception at this stage in the request lifecycle. We need to be able to commit before the error handlers are invoked.
Flask provides a “after_request” hook which seemed to be promising. However on looking closely at the code (https://github.com/pallets/flask/blob/master/src/flask/app.py#L1950) it showed that error handlers are invoked before the after_request functions are called.
So the only option was to write a custom transaction decorator with which we can decorate the view functions which persist changes to the database.
from contextlib import contextmanager@contextmanager
def transaction():
try:
yield
db.session.commit()
except Exception:
db.session.rollback()
raise
It deals with both exceptions from the business logic and database commit. In both cases, the underlying error is raised so that it can be handled appropriately by the error handlers.