Reputation: 375
So, I'm trying to rollback the database session in case of an HTTP error like a bad_request, unauthorized, forbidden, or not_found happens.
it is a serverless application with wsgi and flask.
The scenario is: I create an entry to be saved in the database, but if something wrong happens, I want it to roll_back the session.
If, I raise an exception, the rollback happens, but if I use abort(make_response(jsonify(message=message, **kwargs), 400))
an HTTPException is raised, but the teardown_appcontext kind of ignores it.
I also tried application.config['PRESERVE_CONTEXT_ON_EXCEPTION'] = True #and false too
but it didn't solve my problem.
In my app:
def database(application, engine=None):
sqlalchemy_url = os.environ.get('SQLALCHEMY_URL')
set_session(sqlalchemy_url, engine=engine)
@application.teardown_appcontext
def finish_session(exception=None):
commit_session(exception)
def commit_session(exception=None):
if exception:
_dbsession.rollback()
else:
_dbsession.commit()
_dbsession.remove()
if hasattr(_engine, 'dispose'):
_engine.dispose()
And here, the function that is called if I want to return an bad_request response. The abort function raises an HTTPException that is ignored by the teardown function
def badrequest(message='bad request.', **kwargs):
abort(make_response(jsonify(message=message, **kwargs), 400))
I want the teardown_appcontext to recognize the HTTPException too, not only an Exception. In this way, if the abort function is called, the rollback will be done.
Upvotes: 0
Views: 610
Reputation: 11242
I think this is because teardown_appcontext
called when the request context is popped. An exception
was init in context of request
. You can rollback session using errorhandler() or register_error_handler(). Here is an example:
from flask import Flask, abort, jsonify
from flask_sqlalchemy import SQLAlchemy
from werkzeug.exceptions import BadRequest
app = Flask(__name__)
app.config.update(dict(SQLALCHEMY_DATABASE_URI='...'))
db = SQLAlchemy(app)
class Node(db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(100), nullable=False)
@app.errorhandler(BadRequest)
def handle_bad_request(e):
db.session.rollback()
return 'session has been rolled back!', 400
@app.teardown_appcontext
def finish_session(exception=None):
if not exception:
db.session.commit()
@app.route('/bad-node')
def bad():
# add into session without commit and abort(see: handle_bad_request)
db.session.add(Node(name='bad node'))
abort(400)
@app.route('/good-node')
def good():
# without exceptions - see: finish_session
db.session.add(Node(name='good node'))
return '<good node> was saved'
@app.route('/nodes')
def all_nodes():
# just list of items from db
return jsonify([i.name for i in Node.query.all()])
if __name__ == '__main__':
db.create_all()
db.session.commit()
app.run(debug=True)
Open /good-node
and /bad-node
a few times. After that open /nodes
you will see that 'bad nodes' were not saved(was rollback).
Hope this helps.
Upvotes: 2