The Request Context and Sessions
When you access request.args or session['user_id'] in a Flask view, you are interacting with global-looking variables that are actually local to the current execution context. If you try to access these outside of a request—for example, in a background thread or a standalone script—Flask raises a RuntimeError stating you are "Working outside of request context."
Flask manages this data through the Request Context, which ensures that multiple concurrent requests to the same application do not leak data to one another.
The Unified AppContext
Starting with Flask 3.2, the application and request contexts are unified into a single AppContext class found in flask.ctx. This class manages the lifecycle of all data associated with a specific execution flow, whether it's a web request or a CLI command.
When a request arrives, Flask creates an AppContext instance using AppContext.from_environ(app, environ). This object holds:
self.app: The currentFlaskapplication instance.self.g: A temporary storage object (_AppCtxGlobals) for data that should persist during the context.self._request: TheRequestobject representing the current HTTP request.self._session: The session data, which is loaded lazily.
Context Lifecycle
The context is managed using a stack-like behavior (implemented via contextvars). You typically don't interact with the AppContext directly, as Flask handles it during the request cycle:
- Push: At the start of a request,
AppContext.push()is called. This sets the context as the active one for the current thread or task. - Execution: Your view functions and error handlers run.
- Pop: After the response is sent,
AppContext.pop()is called. This triggers teardown functions registered with@app.teardown_requestand@app.teardown_appcontext.
Global Proxies
Flask provides several global proxies in flask.globals that point to the attributes of the currently active AppContext. These proxies allow you to access request-specific data without passing the request object through every function in your app.
request: ARequestProxythat points toapp_ctx.request.session: ASessionMixinProxythat points toapp_ctx.session.g: Points to thegattribute of the current context.current_app: Points to theappattribute of the current context.
These are implemented using werkzeug.local.LocalProxy. For example, the request proxy is defined as:
# src/flask/globals.py
request: RequestProxy = LocalProxy(
_cv_app, "request", unbound_message=_no_req_msg
)
If no context is active, accessing request will raise the _no_req_msg ("Working outside of request context").
Session Management and Lazy Loading
The session object in Flask allows you to store information specific to a user across multiple requests. In Flask 3.2+, the session is loaded lazily—it is only retrieved from the storage (like a signed cookie) the first time you access the session proxy.
Internally, AppContext uses the _get_session method to handle this:
# src/flask/ctx.py
def _get_session(self) -> SessionMixin:
if self._request is None:
raise RuntimeError("There is no request in this context.")
if self._session is None:
si = self.app.session_interface
self._session = si.open_session(self.app, self.request)
if self._session is None:
self._session = si.make_null_session(self.app)
return self._session
When you access session, Flask calls _get_session() and sets session.accessed = True. This flag tells Flask that the session might have been modified and needs to be saved back to the response.
[!IMPORTANT] To use sessions, you must configure a
SECRET_KEYin your Flask app. This key is used to cryptographically sign the session cookie to prevent tampering.
Manual Context Management
Sometimes you need to access request or current_app outside of a standard request cycle, such as in a unit test or a CLI command. You can manually manage the context using a with block.
Testing with a Request Context
If you want to simulate a request to test a function that uses request.args, use test_request_context():
def test_search_logic(app):
with app.test_request_context("/search?q=flask"):
assert flask.request.args["q"] == "flask"
Using the App Context
If you only need current_app or g (for example, to access a database connection or generate a URL), use app_context():
# tests/test_appctx.py
with app.app_context():
rv = flask.url_for("index")
assert rv == "https://localhost/"
Context in Background Tasks
Because Flask uses contextvars, the context is local to the thread or greenlet where it was created. If you spawn a background thread or use a concurrent executor, the new thread will not have access to the request or session.
To solve this, use the copy_current_request_context decorator. It captures the current context and pushes it when the decorated function is called in another thread.
# tests/test_reqctx.py
@app.route("/")
def index():
flask.session["task_id"] = 123
@flask.copy_current_request_context
def background_task():
# This now has access to the same request and session
print(f"Processing task {flask.session['task_id']} for {flask.request.path}")
threading.Thread(target=background_task).start()
return "Task started"
Note that while this allows reading from the session, you should not attempt to modify the session in a background thread, as the response (and its Set-Cookie header) may have already been sent to the client by the time the thread finishes.