Nathan Barreto
Nathan Barreto

Reputation: 375

Does teardown_appcontext ignore HTTPExceptions?

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

Answers (1)

Danila Ganchar
Danila Ganchar

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

Related Questions