Context Locals
Fenrir provides thread/async-task-safe context locals powered by Python's contextvars. These proxies allow you to access the current request, application, session, and a per-request namespace object (g) from anywhere in your code during a request cycle.
How contextvars Ensure Thread/Async Safety
Fenrir uses Python's built-in contextvars module, which provides ContextVar objects that store values scoped to the current execution context.
- Threads: Each thread gets its own copy of context variables automatically. Two concurrent requests in separate threads never share state.
- Asyncio tasks: When using
asyncio.create_task(), the new task inherits a copy of the current context. Two concurrent requests in the same event loop remain isolated.
Fenrir defines three ContextVar instances in context.py:
ContextVar |
Holds | Used by |
|---|---|---|
_request_ctx_var |
Current Request object |
request proxy |
_g_ctx_var |
Current G namespace |
g proxy |
_app_ctx_var |
Current Fenrir app |
current_app proxy |
These variables are never accessed directly — they are wrapped by LocalProxy subclasses that raise a clear error when accessed outside a valid context.
Core Classes
LocalProxy
LocalProxy wraps a ContextVar (or a callable) and transparently delegates attribute access, item access, and iteration to the underlying object. It is the base for all context-local proxies.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | |
How it works:
- On every attribute/item access,
_get_current_object()reads the current value from theContextVar. - If no value has been set for the current context (e.g. outside a request), a
RuntimeError("Working outside of context.")is raised. - All subsequent operations (
getattr,getitem,iter, etc.) are forwarded to that real object.
This means request.method calls _get_current_object().method under the hood — you always interact with the real request, session, or app instance.
AppProxy
Subclass of LocalProxy that reads from _app_ctx_var. Raises a specific error when no application context is active.
1 2 3 4 5 6 | |
Used for the current_app proxy.
SessionProxy
Subclass of LocalProxy that reads the session from the current request object. Raises a specific error when no request context is active.
1 2 3 4 5 6 7 | |
Used for the session proxy. The session is always accessed through the current request — there is no standalone session context.
G
A plain namespace object for storing per-request data. It has no predefined attributes; any attribute you set on it is available for the duration of the request.
1 2 3 4 | |
A fresh G() instance is created at the start of each RequestContext and reset at the end. It is accessed via the g proxy.
Context Managers
AppContext
Manages the application context. Sets _app_ctx_var on entry and resets it on exit. Calls do_teardown_appcontext during cleanup.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | |
Lifecycle:
__enter__: Stores the app in_app_ctx_var.- During the context:
current_appresolves to this app. __exit__: Runs all@app.teardown_appcontextfunctions (even if an exception occurred), then resets theContextVar.
If an exception is passed to __exit__, it is forwarded to teardown functions as the exc argument.
RequestContext
Manages the request context. Wraps AppContext (an application context is always active during a request). Sets _request_ctx_var and _g_ctx_var on entry. Calls do_teardown_request during cleanup.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | |
Lifecycle:
__enter__: Enters the app context, then sets the request and a freshGin their respectiveContextVars.- During the context:
request,g,current_app, andsessionall resolve correctly. __exit__: Runsdo_teardown_request(blueprint-specific and global teardown functions), resetsrequestandg, then exits the app context (which runs app-context teardowns).
Teardown functions are called even if an exception occurred. If a teardown function itself raises, the exception is silently caught to avoid masking the original error.
The Global Proxies
Defined at module level in context.py:
1 2 3 4 | |
| Proxy | Type | Resolves To | Active During |
|---|---|---|---|
request |
LocalProxy |
The current Request object |
RequestContext |
g |
LocalProxy |
A per-request G namespace |
RequestContext |
current_app |
AppProxy |
The Fenrir app instance |
AppContext or RequestContext |
session |
SessionProxy |
request.session |
RequestContext (when session middleware is active) |
App Methods
app.app_context()
Returns an AppContext context manager. Use this when you need current_app outside of a request — for example in CLI commands, background scripts, or tests.
1 2 3 | |
app.test_request_context(path, **kwargs)
Returns a RequestContext context manager with a synthetic request. Useful for testing code that accesses request, g, session, or current_app outside of a real HTTP server.
1 2 3 4 | |
Internally it:
- Builds an ASGI scope from the provided arguments.
- Creates a
Requestobject from that scope. - Opens a session if a session interface is configured.
- Returns a
RequestContextwrapping the app and request.
Usage Examples
Request context
1 2 3 4 5 6 7 8 9 | |
Per-request data with g
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | |
g is reset to a fresh G() instance at the start of every request. It is not shared between requests.
Accessing the app with current_app
1 2 3 4 5 6 7 8 9 10 11 | |
Session
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | |
Testing with test_request_context
1 2 3 4 5 6 7 8 9 10 11 12 | |
Summary
| Concept | Description |
|---|---|
LocalProxy |
Transparent proxy over a ContextVar; forwards attribute/item access to the real object |
AppProxy |
LocalProxy subclass for the application context (current_app) |
SessionProxy |
LocalProxy subclass for the session (delegates to request.session) |
G |
Plain namespace object for per-request data, accessed via g |
AppContext |
Context manager that sets the app context and runs do_teardown_appcontext on exit |
RequestContext |
Context manager that sets request + g context, wraps AppContext, and runs do_teardown_request on exit |
app.app_context() |
Returns an AppContext for use outside of requests |
app.test_request_context() |
Returns a RequestContext with a synthetic request for testing |
| Thread safety | Each thread gets its own ContextVar values automatically |
| Async safety | Each asyncio.create_task() inherits a copy of the context, keeping concurrent requests isolated |