How Global Proxies Work
In Flask, global variables like request, session, g, and current_app are not traditional static globals. Instead, they are context-bound proxies. This design allows Flask to support multiple concurrent requests in a thread-safe and task-safe manner while maintaining a simple, global-like API for developers.
The Global Illusion
In a multi-threaded or asynchronous environment, a true global variable would be shared across all threads, leading to race conditions and data leakage between requests. Flask solves this by using "proxies" that look and act like the objects they represent but dynamically resolve to the correct data for the current execution context (thread, greenlet, or asyncio task).
The implementation relies on two primary components:
contextvars.ContextVar: A standard Python library feature that provides context-local storage.werkzeug.local.LocalProxy: A wrapper that forwards all operations (attribute access, method calls, etc.) to an underlying object resolved at runtime.
The Context Container: AppContext
In src/flask/ctx.py, the AppContext class serves as the single container for all state related to a specific execution context. Since Flask 3.2, the previously separate RequestContext has been merged into AppContext.
class AppContext:
def __init__(
self,
app: Flask,
*,
request: Request | None = None,
session: SessionMixin | None = None,
) -> None:
self.app = app
self.g: _AppCtxGlobals = app.app_ctx_globals_class()
self._request = request
self._session = session
# ...
When a request begins or a CLI command runs, an AppContext is created and "pushed" into a ContextVar named _cv_app (defined in src/flask/globals.py).
How Proxies Resolve
The proxies themselves are defined in src/flask/globals.py. They are instances of LocalProxy configured to look up specific attributes on the AppContext currently stored in _cv_app.
_cv_app: ContextVar[AppContext] = ContextVar("flask.app_ctx")
# current_app proxies to the 'app' attribute of the active AppContext
current_app: FlaskProxy = LocalProxy(
_cv_app, "app", unbound_message=_no_app_msg
)
# request proxies to the 'request' attribute
request: RequestProxy = LocalProxy(
_cv_app, "request", unbound_message=_no_req_msg
)
When you access request.method, the LocalProxy performs the following steps:
- Calls
_cv_app.get()to retrieve the currentAppContext. - If no context is active, it raises a
RuntimeErrorwith theunbound_message. - Accesses the
"request"attribute on that context. - Returns the
methodattribute from that request object.
Type Safety with ProxyMixin
Because these proxies are dynamic, static type checkers (like Mypy or Pyright) would normally struggle to understand that current_app has the same methods as a Flask instance. Flask uses a combination of typing.Protocol and multiple inheritance to solve this.
The ProxyMixin protocol defines the internal _get_current_object method, which is a standard way to bypass the proxy and get the real underlying object.
class ProxyMixin(t.Protocol[T]):
def _get_current_object(self) -> T: ...
# FlaskProxy tells the type checker:
# "I am a Flask object AND I have _get_current_object()"
class FlaskProxy(ProxyMixin[Flask], Flask): ...
This allows developers to use current_app with full IDE autocompletion while still having access to the proxy's internal utility methods.
Accessing the Underlying Object
Sometimes you need the actual object behind the proxy—for example, when passing the application object to a library that doesn't understand proxies, or when performing identity checks in tests.
As seen in tests/test_appctx.py, you can use _get_current_object() to retrieve the real instance:
def test_app_context_provides_current_app(app):
with app.app_context():
# current_app is a proxy, app is the real object
assert current_app._get_current_object() is app
Tradeoffs and Constraints
While proxies provide a clean API, they introduce specific constraints:
- Context Requirement: Accessing a proxy outside of its lifecycle (e.g., accessing
requestin a background thread without manually pushing a context) results in aRuntimeError. - Identity Confusion:
current_app is appwill always beFalsebecausecurrent_appis a proxy object, not theFlaskinstance itself. You must use_get_current_object()for identity comparisons. - Performance: There is a small overhead for every attribute access because the proxy must look up the current context in the
ContextVar. In practice, this is negligible for web applications but relevant for extremely tight loops. - Scope Merging: The decision in Flask 3.2 to merge
RequestContextintoAppContextsimplifies the internal stack but means thatapp_ctxnow carries the weight of request data if it exists, which is reflected in thehas_requestproperty ofAppContext.