Cached Views¶
High-Performance Python Web Apps
Duck’s cached view system provides a high-performance, flexible way to cache the output of both synchronous and asynchronous views. It offers fine-grained control over what contributes to the cache key: request attributes, request callables, or fully custom Python callables.
Unlike traditional caching decorators, Duck’s cached_view is designed for:
Deterministic cache keys
Dynamic argument/callable resolution
Async awareness
Safe component caching
Optional namespace isolation
Per-request debugging skips
Auto-adapted backends (sync/async)
Overview¶
The core concept:
@cached_view(targets=..., expiry=..., namespace=..., cache_backend=...)
def view(request, ...):
...
Where the targets determine what uniquely identifies the cached result.
Duck offers:
In-memory caching (
InMemoryCache) as defaultPluggable backends with
.get()/.set()APIAutomatic wrapping for async ↔ sync compatibility
After the first call, subsequent invocations return the cached result instantly, skipping view execution entirely.
cached_view Decorator¶
The decorator handles:
✔ Request attribute-based caching¶
targets=["path", "method"]
✔ Callable-based caching¶
targets={static_mtime: {'args': ()}}
✔ Request callable targets¶
targets={'get_user_id': {'args': ()}}
✔ External pure functions¶
targets={compute_hash: {'args': ('{request.path}',)}}
Targets are resolved before executing the view.
The resolved values are then normalized into a stable, hashable structure (frozenset + tuple).
Parameters¶
Parameter |
Type |
Description |
|---|---|---|
|
|
Required. Defines which request attributes or callable results contribute to the cache key. |
|
|
TTL in seconds; default is backend-defined. |
|
any object with |
Custom cache backend (sync or async). |
|
|
Optional namespace prefix for cache isolation. Useful for per-user or per-tenant caching. |
|
|
Request attribute name that forces bypass of caching (for debugging). Default |
|
|
Hook executed when a cached value is returned. Useful if the cached result requires reinitialization. |
|
|
Indicates the response is static and safe to cache even if components are returned. Avoids safety warnings. |
How Targets Work¶
✔ Attribute Targets (simple)¶
@cached_view(targets=["path", "method"])
def handler(request):
return HttpResponse("OK")
✔ Callable Targets (complex)¶
@cached_view(targets={static_mtime: {'args': None}})
def serve_static(request, staticfile):
return FileResponse(...)
✔ Request Callable Targets¶
@cached_view(targets={'compute_key': {'args': ('{request.method}',)}})
def handler(request):
...
✔ External Functions¶
@cached_view(targets={my_hash_fn: {'args': ('{request.path}',)}})
def handler(request):
...
Dynamic formatting supported everywhere¶
{request.path}{request.user.id}etc.
Namespace Support¶
Namespaces allow isolated cached values:
@cached_view(
targets=['path'],
namespace=lambda request: request.COOKIES.get('session_id')
)
def dashboard(request):
...
This means:
Each user gets their own cache bucket.
Cache invalidation is trivial: change namespace → old keys ignored.
Debugging with Skip¶
request.skip_cache = True
or with a custom attribute:
@cached_view(targets=['path'], skip_cache_attr='debug_skip')
Allows bypassing cache for that single request.
on_cache_result Hook¶
A callable executed whenever a cached result is retrieved:
def rehydrate(component):
component.rebind_runtime_state()
@cached_view(targets=['path'], on_cache_result=rehydrate)
def handler(request):
...
Useful for UI components, objects with state, etc.
Internal Behavior¶
1. Target Resolution¶
Extract values from:
request attributes
request callables
external Python callables
2. Key Construction¶
A stable structure:
(
namespace_string_or_empty,
frozenset(resolved_targets.items()),
call_args,
frozenset(call_kwargs.items())
)
This guarantees:
Deterministic hashing
Reproducibility
No collision from dict order
3. Backend Lookup¶
Sync wrappers for async backends and vice-versa are created automatically.
4. View Execution or Cache Return¶
5. Cache Update (if allowed)¶
Stored with expiry if provided.
Examples¶
Simple¶
@cached_view(targets=['path'])
def handler(request):
return HttpResponse("OK")
Async Class-Based View¶
class MyView(View):
@cached_view(targets=['fullpath', 'method'])
async def run(self, request, **kwargs):
return HttpResponse("OK")
Callable-based Caching¶
@cached_view(targets={compute_hash: {'args': ('{request.path}',)}})
def handler(request):
return HttpResponse("OK")
Static file caching¶
@cached_view(targets={static_mtime: {'args': None}})
def staticfiles(request, staticfile):
return FileResponse(...)
Best Practices¶
Cache expensive or read-heavy views.
Avoid caching highly personalized output unless namespaced.
Use callable targets for:
mtime-based invalidation
database-derived cache keys
user-scoped session keys
Use
SkipViewCachingto disable caching when prerequisites fail.
Comparison to Other Frameworks¶
Django (cache_page, low-level API)¶
Feature |
Django |
Duck |
|---|---|---|
Async support |
partial |
full & automatic |
Callable targets |
❌ |
✔ full |
Per-request skip |
❌ |
✔ |
Namespaces |
manual prefixing |
built-in |
Deterministic keys |
basic |
strong, structured |
Auto backend sync/async |
❌ |
✔ |
Dynamic format args |
❌ |
✔ |
Flask (flask-caching)¶
Feature |
Flask |
Duck |
|---|---|---|
Async |
❌ |
✔ |
Callable targets |
limited |
✔ arbitrary callables |
Namespaced keys |
manual |
✔ |
Request attribute keys |
partial |
✔ full |
FastAPI (Depends + custom caching)¶
Feature |
FastAPI |
Duck |
|---|---|---|
Decorator-based caching |
❌ (not built-in) |
✔ |
Sync/async backend adaptation |
manual |
✔ |
Dynamic request formatting |
❌ |
✔ |
Next.js (Edge/Static)¶
Feature |
Next.js |
Duck |
|---|---|---|
Static response caching |
strong |
✔ via |
Dynamic callable keys |
❌ |
✔ |
Per-user namespaces |
tricky |
✔ |
Overall:
Duck’s cached_view is more granular, callable-aware, async-adaptive, and flexible than traditional caching decorators found in popular Python frameworks.
Summary¶
Duck’s cached view system delivers:
Deterministic, stable cache keys
Full sync/async compatibility
Advanced callable & dynamic target resolution
Namespace-powered isolation
Instant debugging skip
Safe component caching
Backend abstraction
The result is a caching mechanism that is:
Faster
Safer
More expressive
More adaptive
More predictable
than typical caching decorators in Python frameworks.