Ben Davis Ben Davis - 3 months ago 15
Python Question

What is the purpose of Flask's context stacks?

I've been using the request/application context for some time without fully understanding how it works or why it was designed the way it was. What is the purpose of the "stack" when it comes to the request or application context? Are these two separate stacks, or are they both part of one stack? Is the request context pushed onto a stack, or is it a stack itself? Am I able to push/pop multiple contexts on top of eachother? If so, why would I want to do that?

Sorry for all the questions, but I'm still confused after reading the documentation for Request Context and Application Context.

Answer

Multiple Apps

The application context (and its purpose) is indeed confusing until you realize that Flask can have multiple apps. Imagine the situation where you want to have a single WSGI Python interpreter run multiple Flask application. We're not talking Blueprints here, we're talking entirely different Flask applications.

You might set this up similar to the Flask documentation section on "Application Dispatching" example:

from werkzeug.wsgi import DispatcherMiddleware
from frontend_app import application as frontend
from backend_app import application as backend

application = DispatcherMiddleware(frontend, {
    '/backend':     backend
})

Notice that there are two completely different Flask applications being created "frontend" and "backend". In other words, the Flask(...) application constructor has been called twice, creating two instances of a Flask application.

Contexts

When you are working with Flask, you often end up using global variables to access various functionality. For example, you probably have code that reads...

from flask import request

Then, during a view, you might use request to access the information of the current request. Obviously, request is not a normal global variable; in actuality, it is a context local value. In other words, there is some magic behind the scenes that says "when I call request.path, get the path attribute from the request object of the CURRENT request." Two different requests will have a different results for request.path.

In fact, even if you run Flask with multiple threads, Flask is smart enough to keep the request objects isolated. In doing so, it becomes possible for two threads, each handling a different request, to simultaneously call request.path and get the correct information for their respective requests.

Putting it Together

So we've already seen that Flask can handle multiple applications in the same interpreter, and also that because of the way that Flask allows you to use "context local" globals there must be some mechanism to determine what the "current" request is (in order to do things such as request.path).

Putting these ideas together, it should also make sense that Flask must have some way to determine what the "current" application is!

You probably also have code similar to the following:

from flask import url_for

Like our request example, the url_for function has logic that is dependent on the current environment. In this case, however, it is clear to see that the logic is heavily dependent on which app is considered the "current" app. In the frontend/backend example shown above, both the "frontend" and "backend" apps could have a "/login" route, and so url_for('/login') should return something different depending on if the view is handling the request for the frontend or backend app.

To answer your questions...

What is the purpose of the "stack" when it comes to the request or application context?

From the Request Context docs:

Because the request context is internally maintained as a stack you can push and pop multiple times. This is very handy to implement things like internal redirects.

In other words, even though you typically will have 0 or 1 items on these stack of "current" requests or "current" applications, it is possible that you could have more.

The example given is where you would have your request return the results of an "internal redirect". Let's say a user requests A, but you want to return to the user B. In most cases, you issue a redirect to the user, and point the user to resource B, meaning the user will run a second request to fetch B. A slightly different way of handling this would be to do an internal redirect, which means that while processing A, Flask will make a new request to itself for resource B, and use the results of this second request as the results of the user's original request.

Are these two separate stacks, or are they both part of one stack?

They are two separate stacks. However, this is an implementation detail. What's more important is not so much that there is a stack, but the fact that at any time you can get the "current" app or request (top of the stack).

Is the request context pushed onto a stack, or is it a stack itself?

A "request context" is one item of the "request context stack". Similarly with the "app context" and "app context stack".

Am I able to push/pop multiple contexts on top of eachother? If so, why would I want to do that?

In a Flask application, you typically would not do this. One example of where you might want to is for an internal redirect (described above). Even in that case, however, you would probably end up having Flask handle a new request, and so Flask would do all of the pushing/popping for you.

However, there are some cases where you'd want to manipulate the stack yourself.

Running code outside of a request

One typical problem people have is that they use the Flask-SQLAlchemy extension to set up a SQL database and model definition using code something like what is shown below...

app = Flask(__name__)
db = SQLAlchemy() # Initialize the Flask-SQLAlchemy extension object
db.init_app(app)

Then they use the app and db values in a script that should be run from the shell. For example, a "setup_tables.py" script...

from myapp import app, db

# Set up models
db.create_all()

In this case, the Flask-SQLAlchemy extension knows about the app application, but during create_all() it will throw an error complaining about there not being an application context. This error is justified; you never told Flask what application it should be dealing with when running the create_all method.

You might be wondering why you don't end up needing this with app.app_context() call when you run similar functions in your views. The reason is that Flask already handles the management of the application context for you when it is handling actual web requests. The problem really only comes up outside of these view functions (or other such callbacks), such as when using your models in a one-off script.

The resolution is to push the application context yourself, which can be done by doing...

from myapp import app, db

# Set up models
with app.app_context():
    db.create_all()

This will push a new application context (using the application of app, remember there could be more than one application).

Testing

Another case where you would want to manipulate the stack is for testing. You could create a unit test that handles a request and you check the results:

import unittest
from flask import request

class MyTest(unittest.TestCase):
    def test_thing(self):
        with app.test_request_context('/?next=http://example.com/') as ctx:
            # You can now view attributes on request context stack by using `request`.

        # Now the request context stack is empty