Skip to content

Commit 274a925

Browse files
authored
fix: use AliasChoices for REDIS_URL env var fallback (#35)
REDIS_URL was being ignored because config was read in two places: - decorator.py checked REDIS_URL via os.environ.get() - RedisBackendConfig used pydantic-settings with CACHEKIT_ prefix The connection pool always read from RedisBackendConfig.from_env() which only checked CACHEKIT_REDIS_URL, falling back to localhost:6379. Fix uses pydantic-settings AliasChoices for proper env var priority: CACHEKIT_REDIS_URL > REDIS_URL > localhost:6379 (default) This is the declarative pydantic-settings approach vs manual os.environ.
1 parent 7dac24c commit 274a925

File tree

3 files changed

+66
-8
lines changed

3 files changed

+66
-8
lines changed

src/cachekit/backends/redis/config.py

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77

88
from __future__ import annotations
99

10-
from pydantic import Field
10+
from pydantic import AliasChoices, Field
1111
from pydantic_settings import BaseSettings, SettingsConfigDict
1212

1313

@@ -56,7 +56,8 @@ class RedisBackendConfig(BaseSettings):
5656

5757
redis_url: str = Field(
5858
default="redis://localhost:6379",
59-
description="Redis connection URL",
59+
validation_alias=AliasChoices("CACHEKIT_REDIS_URL", "REDIS_URL"),
60+
description="Redis connection URL (env: CACHEKIT_REDIS_URL or REDIS_URL)",
6061
)
6162
connection_pool_size: int = Field(
6263
default=10,
@@ -76,7 +77,11 @@ class RedisBackendConfig(BaseSettings):
7677
def from_env(cls) -> RedisBackendConfig:
7778
"""Create Redis configuration from environment variables.
7879
79-
Reads CACHEKIT_REDIS_URL, CACHEKIT_CONNECTION_POOL_SIZE, etc.
80+
Priority (via AliasChoices): CACHEKIT_REDIS_URL > REDIS_URL > default
81+
82+
This allows standard 12-factor app conventions (REDIS_URL) while
83+
supporting namespaced configuration (CACHEKIT_REDIS_URL) for
84+
multi-service deployments.
8085
8186
Returns:
8287
RedisBackendConfig instance loaded from environment
@@ -86,8 +91,11 @@ def from_env(cls) -> RedisBackendConfig:
8691
8792
.. code-block:: bash
8893
94+
# Option 1: Namespaced (takes priority)
8995
export CACHEKIT_REDIS_URL="redis://localhost:6379"
90-
export CACHEKIT_CONNECTION_POOL_SIZE=20
96+
97+
# Option 2: Standard 12-factor convention
98+
export REDIS_URL="redis://localhost:6379"
9199
92100
.. code-block:: python
93101

src/cachekit/config/decorator.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -107,9 +107,10 @@ def _resolve_backend(explicit_backend: object = _UNSET) -> BaseBackend | None:
107107
if _default_backend is not None:
108108
return _default_backend
109109

110-
# Tier 3: Auto-create from REDIS_URL env var
111-
redis_url = os.environ.get("REDIS_URL")
112-
if redis_url:
110+
# Tier 3: Auto-create from env var (CACHEKIT_REDIS_URL > REDIS_URL)
111+
# Check if either env var is set as signal to create Redis backend
112+
# Actual URL resolution handled by RedisBackendConfig via AliasChoices
113+
if os.environ.get("CACHEKIT_REDIS_URL") or os.environ.get("REDIS_URL"):
113114
# Lazy import to avoid circular dependency
114115
from cachekit.backends.provider import CacheClientProvider
115116
from cachekit.backends.redis import RedisBackend
@@ -118,7 +119,7 @@ def _resolve_backend(explicit_backend: object = _UNSET) -> BaseBackend | None:
118119
# Inject client_provider explicitly (follows Dependency Injection Principle)
119120
container = DIContainer()
120121
client_provider = container.get(CacheClientProvider)
121-
return RedisBackend(redis_url, client_provider=client_provider)
122+
return RedisBackend(client_provider=client_provider)
122123

123124
# No backend configured - fail fast with helpful message
124125
raise ConfigurationError(

tests/unit/test_config_env_fallback.py

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,3 +93,52 @@ def test_backend_and_cache_configs_independent(self, monkeypatch):
9393
# Verify no cross-contamination
9494
assert not hasattr(cache_config, "redis_url")
9595
assert hasattr(cache_config, "default_ttl")
96+
97+
98+
class TestRedisUrlAliasChoicesPriority:
99+
"""Test AliasChoices priority for redis_url field.
100+
101+
The redis_url field uses AliasChoices to support both:
102+
- CACHEKIT_REDIS_URL (namespaced, takes priority)
103+
- REDIS_URL (standard 12-factor convention, fallback)
104+
"""
105+
106+
def test_only_redis_url_set(self, monkeypatch):
107+
"""REDIS_URL alone should work (12-factor convention)."""
108+
monkeypatch.delenv("CACHEKIT_REDIS_URL", raising=False)
109+
monkeypatch.setenv("REDIS_URL", "redis://generic:6379")
110+
111+
config = RedisBackendConfig()
112+
assert config.redis_url == "redis://generic:6379"
113+
114+
def test_only_redis_url_via_from_env(self, monkeypatch):
115+
"""REDIS_URL works via from_env() factory method."""
116+
monkeypatch.delenv("CACHEKIT_REDIS_URL", raising=False)
117+
monkeypatch.setenv("REDIS_URL", "redis://factory:6379")
118+
119+
config = RedisBackendConfig.from_env()
120+
assert config.redis_url == "redis://factory:6379"
121+
122+
def test_only_cachekit_redis_url_set(self, monkeypatch):
123+
"""CACHEKIT_REDIS_URL alone should work (namespaced)."""
124+
monkeypatch.delenv("REDIS_URL", raising=False)
125+
monkeypatch.setenv("CACHEKIT_REDIS_URL", "redis://cachekit:6379")
126+
127+
config = RedisBackendConfig()
128+
assert config.redis_url == "redis://cachekit:6379"
129+
130+
def test_both_set_cachekit_wins(self, monkeypatch):
131+
"""CACHEKIT_REDIS_URL takes priority over REDIS_URL."""
132+
monkeypatch.setenv("CACHEKIT_REDIS_URL", "redis://cachekit-priority:6379")
133+
monkeypatch.setenv("REDIS_URL", "redis://generic-fallback:6379")
134+
135+
config = RedisBackendConfig()
136+
assert config.redis_url == "redis://cachekit-priority:6379"
137+
138+
def test_neither_set_uses_default(self, monkeypatch):
139+
"""Falls back to localhost:6379 when neither env var is set."""
140+
monkeypatch.delenv("CACHEKIT_REDIS_URL", raising=False)
141+
monkeypatch.delenv("REDIS_URL", raising=False)
142+
143+
config = RedisBackendConfig()
144+
assert config.redis_url == "redis://localhost:6379"

0 commit comments

Comments
 (0)