0

I'm writing a flask app that streams content out to the user, and I'm trying to manipulate the db while this streaming is happening. Here's some example code (simplified):

def work_hard(obj):
    yield 'About to do a lot of work...'
    obj.status = do_a_lot_of_work_very_slowly()
    yield obj.status
    db.session.commit()
    obj.more = more_slow_stuff()
    yield obj.more
    db.session.commit()
    yield 'Hard work is done!'

@app.route('/log/<int:objid>/work_hard', methods=['POST'])
def perform_action(objid):
    obj = MyModel.query.get(objid)
    return Response(work_hard(obj), mimetype='text/html')

This code gives the error Instance <MyModel at 0x7f5555f046a0> is not bound to a Session; attribute refresh operation cannot proceed, but if I call db.session.commit() inside perform_action() instead of work_hard(), it works. Similarly, if I try to access flask's request instance, it works in perform_action() but not work_hard() (it complains that I'm trying to access the request outside of a request context).

I assume that these are both because work_hard() is executing after perform_action() has returned. Is is possible to somehow prolong the request context to include work_hard()? So far I've just been passing individual values from request to work_hard() and that worked to a point, but now I need to commit to the db and I'm not sure how to fix the db session here.

I can't just call db.session.commit() from perform_action(), I really do need to be able to make multiple updates in the db in real-time as the output is streaming to the HTTP client.

If this turns out not to be possible, my backup plan is to stream output from a subprocess, and then in the subprocess I'll connect to the db from there, but I'd prefer to do it all within the same process if possible.

Thanks!

2 Answers 2

2

You can use stream_with_context() to keep the context (and hence the session) around while the generator runs:

return Response(stream_with_context(work_hard(obj)), ...)
Sign up to request clarification or add additional context in comments.

1 Comment

This is it, thank you! Quick note for googlers in the future: My first attempt got me the dreaded sqlalchemy.exc.OperationalError: (sqlite3.OperationalError) no such savepoint: sa_savepoint_1 [SQL: 'RELEASE SAVEPOINT sa_savepoint_1'] and googling this suggested some pretty ugly workarounds, but those workarounds are for older versions of python. in python 3.5 at least, all i had to do was stop calling db.session.begin_nested() which was a failed experiment I'd forgotten to delete anyway. It works!
1

Access the session with application context: with app.app_context

def work_hard(obj):
    yield 'About to do a lot of work...'
    obj.status = "blub"
    yield obj.status
    with app.app_context():
        db.session.commit()
    obj.more = "fish"
    yield obj.more
    with app.app_context():
        db.session.commit()
    yield 'Hard work is done!'

@app.route('/log/<int:objid>/work_hard', methods=['POST'])
def perform_action(objid):
    def perform_action(objid):
    obj = MyModel.query.get(objid)
    return Response(work_hard(obj), mimetype='text/html')

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.