Skip to main content

The Design of Global Proxies

Flask uses a proxy-based architecture to provide global access to request-specific and application-specific data without sacrificing thread safety or requiring developers to pass context objects through every function call. This design relies on werkzeug.local.LocalProxy and Python's contextvars module to ensure that globals like current_app, request, and g always point to the correct object for the current execution context.

The Role of LocalProxy

At the heart of Flask's global variables is werkzeug.local.LocalProxy. A proxy object intercepts all attribute access and method calls, forwarding them to an underlying object that is looked up dynamically at runtime.

In Flask, these proxies are bound to a ContextVar that holds the active AppContext. When you access current_app.config, the proxy looks up the active context in the current thread or task, retrieves the Flask application instance from it, and then accesses the config attribute on that instance.

Type Safety with ProxyMixin

Because LocalProxy is a dynamic wrapper, static type checkers like Mypy or Pyright cannot natively know that current_app behaves like a Flask instance. Flask solves this using the ProxyMixin protocol and specialized proxy subclasses defined in src/flask/globals.py.

# src/flask/globals.py

if t.TYPE_CHECKING:
class ProxyMixin(t.Protocol[T]):
def _get_current_object(self) -> T: ...

class FlaskProxy(ProxyMixin[Flask], Flask): ...
class AppContextProxy(ProxyMixin[AppContext], AppContext): ...
class RequestProxy(ProxyMixin[Request], Request): ...

The ProxyMixin is a typing.Protocol that informs type checkers that the proxy object provides a _get_current_object() method. Subclasses like AppContextProxy inherit from both the mixin and the target class (e.g., AppContext), allowing IDEs to provide accurate autocompletion and type validation for the proxied attributes.

Global Definitions and ContextVars

Flask maintains a single source of truth for the active context using a ContextVar named _cv_app. As of Flask 3.2, the application and request contexts have been merged into a single AppContext class, which simplifies the internal management of these globals.

The globals are initialized in src/flask/globals.py:

_cv_app: ContextVar[AppContext] = ContextVar("flask.app_ctx")

app_ctx: AppContextProxy = LocalProxy(
_cv_app, unbound_message=_no_app_msg
)

current_app: FlaskProxy = LocalProxy(
_cv_app, "app", unbound_message=_no_app_msg
)

g: _AppCtxGlobalsProxy = LocalProxy(
_cv_app, "g", unbound_message=_no_app_msg
)

request: RequestProxy = LocalProxy(
_cv_app, "request", unbound_message=_no_req_msg
)

When LocalProxy is initialized with a ContextVar, it uses that variable to find the target object. If a second argument (a string) is provided, it further drills down into that attribute of the object found in the ContextVar. For example, current_app looks up the AppContext in _cv_app and then accesses its .app attribute.

Bypassing the Proxy

While proxies are convenient, there are scenarios where you need the actual object behind the proxy. This is common when:

  • Passing the object to a library that performs strict type checks or identity comparisons (is).
  • Improving performance in a tight loop where the overhead of proxy lookup is significant.
  • Checking if a context is active without triggering the "unbound" error message.

The _get_current_object() method, defined in the ProxyMixin protocol and implemented by LocalProxy, returns the underlying instance:

# Example from tests/test_appctx.py
def test_app_context_provides_current_app(app):
with app.app_context():
# current_app is a proxy, but _get_current_object() returns the app instance
assert flask.current_app._get_current_object() is app

In src/flask/cli.py, Flask uses this to ensure the active context matches the expected application:

# src/flask/cli.py
if not current_app or current_app._get_current_object() is not app:
ctx.with_resource(app.app_context())

Context Lifecycle and the 3.2 Merger

The lifecycle of these proxies is managed by the AppContext.push() and AppContext.pop() methods in src/flask/ctx.py. When a context is pushed, it sets the value of _cv_app:

# src/flask/ctx.py
def push(self) -> None:
self._push_count += 1
if self._cv_token is not None:
return

self._cv_token = _cv_app.set(self)
# ... signals and request matching ...

Historically, Flask maintained separate AppContext and RequestContext classes. In Flask 3.2, these were merged into a single AppContext that optionally contains request data. This change is reflected in flask.globals via a deprecated __getattr__ hook that redirects request_ctx to app_ctx.

# src/flask/globals.py
def __getattr__(name: str) -> t.Any:
if name == "request_ctx":
warnings.warn(
"'request_ctx' has merged with 'app_ctx'...",
DeprecationWarning,
stacklevel=2,
)
return app_ctx
raise AttributeError(name)

This unified design ensures that all global proxies—whether they refer to the application, the request, or the session—are synchronized to the same underlying context state.