Skip to content

Commit 37e6e0c

Browse files
committed
fix: L1-only mode (backend=None) should not attempt Redis connection
When using @cache(backend=None) for L1-only caching, the decorator was still attempting to connect to Redis on every operation. Root cause: Sentinel problem - couldn't distinguish explicit backend=None from unspecified backend. Changes: - Track explicit backend=None intent via _l1_only_mode flag - Skip backend provider lookup in L1-only mode - Fix invalidate_cache() to honor L1-only mode - Add DEBUG log when L1-only mode ignores available Redis - Clean up ttl or ttl copy-paste cruft Closes #3
1 parent 9de54a5 commit 37e6e0c

File tree

4 files changed

+566
-45
lines changed

4 files changed

+566
-45
lines changed

src/cachekit/decorators/intent.py

Lines changed: 23 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
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

76
from __future__ import annotations
@@ -16,18 +15,22 @@
1615
F = 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

3336
def 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

Comments
 (0)