13

I am working on a small rest api in flask. Api has route that registers a request and spawn separate thread to run in background. Here is the code:

def dostuff(scriptname):
    new_thread = threading.Thread(target=executescript,args=(scriptname,))
    new_thread.start()

Thread starts but it errors out when I try to insert into db from executescript function. It complains about db object not registered with the app.

I am dynamically creating my app (with api as Blueprint).

Here is the structure of the app

-run.py ## runner script
-config
   -development.py
   -prod.py
-app
  -__init__.py
  - auth.py
  - api_v1
     - __init__.py
     - routes.py
     - models.py

here is my runner script run.py :

from app import create_app, db

if __name__ == '__main__':
    app = create_app(os.environ.get('FLASK_CONFIG', 'development'))
    with app.app_context():
        db.create_all()
    app.run()

Here is the code from app/__init__.py which creates the app:

from flask import Flask, jsonify, g
from flask.ext.sqlalchemy import SQLAlchemy

db = SQLAlchemy()

def create_app(config_name):
    """Create an application instance."""
    app = Flask(__name__)

    # apply configuration
    cfg = os.path.join(os.getcwd(), 'config', config_name + '.py')
    app.config.from_pyfile(cfg)

    # initialize extensions
    db.init_app(app)
    # register blueprints
    from .api_v1 import api as api_blueprint
    app.register_blueprint(api_blueprint, url_prefix='/api/')
    return app 

All I need to know is how do I extend app context in routes.py. I can not import app there directly and if I do the following, I get RuntimeError: working outside of application context

def executescript(scriptname):
    with current_app.app_context():
        test_run = Testrun(pid=989, exit_status=988,comments="Test TestStarted")
        db.session.add(test_run)
        db.session.commit()
Abgo80
  • 233
  • 1
  • 3
  • 9

1 Answers1

19

You're running a background task in a different thread that doesn't have the application context. You should pass the app object to the background worker. Miguel Grinberg gives an example of this here:

from threading import Thread
from app import app

def send_async_email(app, msg):
    with app.app_context():
        mail.send(msg)

def send_email(subject, sender, recipients, text_body, html_body):
    msg = Message(subject, sender=sender, recipients=recipients)
    msg.body = text_body
    msg.html = html_body
    thr = Thread(target=send_async_email, args=[app, msg])
    thr.start()

Alternatively (and probably the best solution) would be to actually set up a thread-local scoped SQLAlchemy session instead of relying on Flask-SQLAlchemy's request context.

>>> from sqlalchemy.orm import scoped_session
>>> from sqlalchemy.orm import sessionmaker

>>> session_factory = sessionmaker(bind=some_engine)
>>> Session = scoped_session(session_factory)
Stephen Rauch
  • 47,830
  • 31
  • 106
  • 135
jumbopap
  • 3,969
  • 5
  • 27
  • 47
  • I like the thread local sqlalchmey session. Will work on it. Thanks! – Abgo80 Jan 08 '16 at 13:22
  • 1
    Feel free to accept the answer if it worked for you :) – jumbopap Jan 08 '16 at 16:18
  • I am using flask application factory to set up my app. One of the drawbacks of doing so is you can not import app in api. So, sqlalchemy thread local session was the only option and works so far. Thank you Jumbopap! – Abgo80 Jan 08 '16 at 23:07
  • 1
    You can pass `flask.current_app._get_current_object()` instead of `app`, if you're using the application factory pattern. See [docs](http://flask.pocoo.org/docs/0.12/reqcontext/#notes-on-proxies), [discussion](https://www.reddit.com/r/flask/comments/3hd8j2/current_app_vs_current_app_get_current_object/). – Jeremy Field Jan 01 '17 at 12:20