1- """Intent-based intelligent cache decorator interface.
1+ """Intent-based cache decorator interface.
22
3- Provides the new @cache decorator with automatic optimization and
4- intent-based variants (@cache.minimal, @cache.production, @cache.secure, @cache.dev, @cache.test).
3+ Provides the @cache decorator with intent-based variants (@cache.minimal, @cache.production, @cache.secure, @cache.dev, @cache.test).
54"""
65
76from __future__ import annotations
1615F = TypeVar ("F" , bound = Callable [..., Any ])
1716
1817
19- def _apply_cache_logic (func : Callable [..., Any ], decorator_config : DecoratorConfig ) -> Callable [..., Any ]:
18+ def _apply_cache_logic (
19+ func : Callable [..., Any ], decorator_config : DecoratorConfig , _l1_only_mode : bool = False
20+ ) -> Callable [..., Any ]:
2021 """Apply resolved configuration using the wrapper factory.
2122
2223 Args:
2324 func: Function to wrap
2425 decorator_config: DecoratorConfig instance with all settings
26+ _l1_only_mode: If True, backend=None was explicitly passed (L1-only mode).
27+ This prevents the wrapper from trying to get a backend from the provider.
2528
2629 Returns:
2730 Wrapped function
2831 """
2932 # Use the wrapper factory with DecoratorConfig
30- return create_cache_wrapper (func , config = decorator_config )
33+ return create_cache_wrapper (func , config = decorator_config , _l1_only_mode = _l1_only_mode )
3134
3235
3336def cache (
@@ -101,7 +104,11 @@ def cache(
101104
102105 def decorator (f : F ) -> F :
103106 # Resolve backend at decorator application time
104- # Only if backend is explicitly provided in manual_overrides
107+ # Track if backend=None was explicitly passed (L1-only mode)
108+ # This is a sentinel problem: we need to distinguish between:
109+ # 1. User passed @cache(backend=None) explicitly -> L1-only mode
110+ # 2. User didn't pass backend at all -> should try provider
111+ _explicit_l1_only = "backend" in manual_overrides and manual_overrides .get ("backend" ) is None
105112 backend = manual_overrides .pop ("backend" , None )
106113
107114 # Backward compatibility: map flattened l1_enabled to nested l1.enabled
@@ -128,6 +135,9 @@ def decorator(f: F) -> F:
128135 if backend is not None :
129136 override_dict ["backend" ] = backend
130137 resolved_config = replace (config , ** override_dict )
138+ # Check if config explicitly specifies backend=None (L1-only mode via config)
139+ if not _explicit_l1_only and resolved_config .backend is None :
140+ _explicit_l1_only = True
131141 # Intent-based presets (renamed per Task 6)
132142 elif _intent == "minimal" : # Renamed from "fast"
133143 resolved_config = DecoratorConfig .minimal (backend = backend , ** manual_overrides )
@@ -150,8 +160,13 @@ def decorator(f: F) -> F:
150160 # No intent specified - use default DecoratorConfig with overrides
151161 resolved_config = DecoratorConfig (backend = backend , ** manual_overrides )
152162
153- # Delegate to wrapper factory
154- return _apply_cache_logic (f , resolved_config ) # type: ignore[return-value]
163+ # Check if resolved config has backend=None (L1-only mode)
164+ # This catches both explicit backend=None in manual_overrides AND backend=None via config presets
165+ if not _explicit_l1_only and resolved_config .backend is None :
166+ _explicit_l1_only = True
167+
168+ # Delegate to wrapper factory with L1-only mode flag
169+ return _apply_cache_logic (f , resolved_config , _l1_only_mode = _explicit_l1_only ) # type: ignore[return-value]
155170
156171 # Handle both @cache and @cache() syntax
157172 if func is None :
0 commit comments