angle-up arrow-clockwise arrow-counterclockwise arrow-down-up arrow-left at calendar card-list chat check envelope folder house info-circle pencil people person person-plus phone plus question-circle search tag trash x

The mysterious Flask Application Context, my questions and answers

30 January 2020 Updated 30 January 2020 by Peter
In Flask

The application context is initialized, pushed and popped, for a first time user things can be confusing. Time to start reading.

post main image
https://unsplash.com/@donovan_valdivia

When you start with Flask you read a bit about the Application Context, TL;DR. Do not know about you but I certainly did not understand it fully. What is app, what is current_app, what is the difference, you just start programming your application. All the time in the background there is this weird buzz: what exactly is the Application Context ... Then at a certain moment when using a class you instantiated in your create_app() factory you hit this error:

RuntimeError: working outside of application context

WTF! Then you start reading but not too much of course, TL;DR again. Ok, the solution is just to put a 'with' before it and you're good:

with app_context(app):
    ...

It becomes annoying

But slowly it becomes more and more annoying. For example, I instantiate an object during app creation in create_app(). This object has methods with logger statements like:

    app.logger.debug(fname + ': message, var = {}'.format(var))

But if call this method from a Blueprint then I get an error message. Now I must use the logger statement with current_app:

    current_app.logger.debug(fname + ': message, var = {}'.format(var))

It is confusing

But this also may be because I am not a very experienced Python Flask programmer. In create_app() I attach the logger to the app object:

    app.logger.debug(fname + ': message, var = {}'.format(var))

but in the same create_app() we also attach events like @before_request. In @before_request we can use both:

    app.logger.debug(fname + ': message, var = {}'.format(var))

and

    current_app.logger.debug(fname + ': message, var = {}'.format(var))

Why? I can understand that both work but will both produce the same result? In @before_request the current_app (probably) is the pushed Application Context. But is this really true? I was also looking for a way to store data in the Application Context. The documentation says:

'Storing Data: The Application Context is a good place to store common data during a request or CLI command. Flask provides the g object for this purpose. It is a simple namespace object that has the same lifetime as an Application Context.'

Note that it says: DURING a request. It also says to use the g object. Anyway, time to start reading and do some tests. I tried to answer a number of questions I had and thought to write them down so I can never forget. Hope the answers are correct ... :-( By the way, in my application am using the factory pattern:

def create_app(config_name):

    ...

    @app.before_request
    def before_request():
        ...

    return app

Question: is the pushed Application Context request destroyed after a request?

Short answer: Yes.

The more you read about it the more confusing it gets. For example, from the Flask documentation:

'The Application Context is created and destroyed as necessary. When a Flask application begins handling a request, it pushes an Application Context and a request context. When the request ends it pops the request context then the Application Context. Typically, an Application Context will have the same lifetime as a request.'

Let us look at this sentence: 'The Application Context is created and destroyed as necessary.' What does this 'necessary' exactly mean?

The documentation states that the Application Context is destroyed after a request but this is not true, or is it? To check this I added a variable to the pushed Application Context in some view function, for example:

def home():
    fname = 'home'
    ...
    # test to check if config can be accessed and is maintained between requests
    if 'SOME_PARAM_KEY' not in current_app.config:
        current_app.logger.debug(fname + ': SOME_PARAM_KEY not in current_app.config')
        # add it now
        current_app.config['SOME_PARAM_KEY'] = 'some_param_value'
    else:		
        current_app.logger.debug(fname + ': SOME_PARAM_KEY in current_app.config, current_app.config[SOME_PARAM] = {}'.format(current_app.config['SOME_PARAM_KEY']))

On the first request SOME_PARAM_KEY is not in the config, we print this and then we set it. The next requests show that SOME_PARAM_KEY is in the config and the config holds the correct value for SOME_PARAM_KEY. This means the Application Context is not destroyed but instead, it looks like that only a reference to the Application Context is changed.

Here is what is wrong: the above was done with Flask in DEBUG mode. If you do exactly the same test in PRODUCTION mode, you will NEVER find SOME_PARAM_KEY in current_app.config on the next request. So, be warned in which state you are using Flask. In DEBUG mode is on it appears that Flask saves the Application Context, obviously for debugging.

Question: can you attach a database object at app create time and access it later in your Blueprints?

Short answer: no.

With access it later I mean: on subsequent requests. You must be very carefull with what you attach to the Application Context at app create time. If you attach a SQLAlchemy database object at app creation time, e.g.

    app.languages = db.session.query(Language).all()

Then during the first request you will be able to access the languages in a Blueprint like:

    for language in current_app.languages:
        ...

But then remember that the session is destroyed (remove) at teardown time:

    @app.teardown_appcontext
    def teardown_db(resp_or_exc):
        current_app.db.session.remove()

On a next request, if you try to access such an object then you get an error message:

(DetachedInstanceError('Instance <Setting at 0x7f4466739d90> is not bound to a Session; attribute refresh operation cannot proceed'),)

To be expected but I hit it.

Question: does 'pops the Application Context' means the Application Context is still available?

Short answer: no.

From the same text as above: 'When the request ends it pops the request context then the Application Context.' So here we are talking about pop. This can mean two things:

  1. The Application Context is popped into blue sky
  2. When they talk about the Application Context, they in fact mean a pointer to the Application Context

Why is this so important? Because in the first case it means the Application Context is read-only, and in the second case the Application Context between requests is not destroyed. See also the answer in a previous question.

Question: should you keep a global object outside of the app object?

Short answer: depends.

Logger object

First lets look at logging. Here we attach the logger to the app object.

def create_app(config_name):

    ...
    # set app log handler level
    app_log_handler.setLevel(app_log_level)
    # set app logger
    app.logger.setLevel(app_log_level)
    app.logger.addHandler(app_log_handler)
    ...


    return app

And then in the Blueprints we can use the logger as follows:

from flask import current_app

def user_list(page_number):
    fname = 'user_list'
    current_app.logger.debug(fname + '()')
    ...

The logger is initialized during app creation and attached to the app object and works fine when using it in Blueprint view functions.

Database object

For Flask-SQLAlchemy and other extensions another method is suggested. Here we keep the SQLAlchemy object outside of the app object. During app creation we call init_app(app) to configure and prepare the SQLAlchemy object.

from flask_sqlalchemy import SQLAlchemy
db = SQLAlchemy()


def create_app(config_name):

    app = Flask()
    ...
    # set up
    db.init_app(app)
    ...

    return app

Then we can use this in a blueprint view function, something like:

from my_app import db

def list_users:
    users = db.session.query(User).all()

There certainly is some magic going on in the SQLAlchemy object. But here, in contrast to the logger, the database object is outside of the app object.

Question: is the Application Context read-only?

Short answer: depends.

During a request your can attach to it, read it. But the next time all of it is gone.

Question: is the Application Context unique for each thread?

Short answer: yes.

From the documentation:

'When a Flask application begins handling a request, it pushes a request context, which also pushes an The Application Context.'

Does this mean that messing with the Application Context, using current_app, can affect other threads? This is still not clear to me.

What to do?

I made a list of rules for myself about the Application Context:

  1. Never store data / objects in the Application Context during app creation time
  2. Never attach data / objects to the Application Context during a request
  3. Keep global objects outside of the Application Context
  4. Use g instead of current_app to attach data / objects to be used during a request
  5. Only use app in create_app(), but in event handlers attached in create_app() always use current_app
  6. Everywhere else use current_app

This means for example that if you have data that is global for all requests and it is not in the Flask config, you should have this data e.g. somewhere on the file system or in an external cache, and then every time, at the start of a request, you must load this data and attach it to the g object. First moment where you can this is in the @before_request handler. So @before_request is the place to attach your data and other things like a database session, that must available during all requests. This means that you do not have access to the database in the event handler @app.url_value_preprocessor but this is not a big thing.

Summary

Among other things, I was looking for a way to store data e.g. application configuration, in the Application Context and then somewhere modify this, making the changes available to all threads. I thought this could be done but we cannot access the inital Application Context once the app is running. I was tricked by the DEBUG mode. This text is written by a person, me, writing his first Flask application so some statements may not be correct. One day I hope to understand the Application Context better. Until that day I will use the Application Context in a very conservative way.

Links / credits

Demystifying Flask’s Application Context
https://hackingandslacking.com/demystifying-flasks-application-context-c7bd31a53817

Documention for "The Application Context" is confusing #1151
https://github.com/pallets/flask/issues/1151

Making an object persist between requests and available to the whole app
https://www.reddit.com/r/flask/comments/9kenzp/making_an_object_persist_between_requests_and/

number of threads in flask
https://stackoverflow.com/questions/59320537/number-of-threads-in-flask

The Application Context
https://flask.palletsprojects.com/en/1.1.x/appcontext/

Understand Flask (---)
http://songroger.win/understand-flask-s1/

What is the purpose of Flask's context stacks?
https://stackoverflow.com/questions/20036520/what-is-the-purpose-of-flasks-context-stacks

Read more:
Flask

Leave a comment

Comment anonymously or log in to comment.

Comments

Leave a reply

Reply anonymously or log in to reply.