Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
67 changes: 57 additions & 10 deletions src/cachekit/backends/provider.py
Original file line number Diff line number Diff line change
Expand Up @@ -102,32 +102,79 @@ async def get_async_client(self):


class DefaultBackendProvider(BackendProviderInterface):
"""Default backend provider using Redis backend.
"""Auto-detecting backend provider based on environment variables.

Creates RedisBackendProvider singleton with connection pooling.
Delegates to RedisBackendProvider.get_backend() for per-request wrappers.
Resolution priority (first match wins):
1. CACHEKIT_API_KEY → CachekitIOBackend
2. CACHEKIT_REDIS_URL → RedisBackend
3. CACHEKIT_MEMCACHED_SERVERS → MemcachedBackend
4. CACHEKIT_FILE_CACHE_DIR → FileBackend
5. REDIS_URL (fallback) → RedisBackend
Comment on lines +107 to +112
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

REDIS_URL priority does not match documentation.

The docstring states REDIS_URL should be priority 5 (checked after Memcached and File), but line 147 checks redis_url or redis_url_fallback together, giving REDIS_URL the same priority (2) as CACHEKIT_REDIS_URL.

This means if a user sets both REDIS_URL and CACHEKIT_MEMCACHED_SERVERS, Redis will be selected instead of Memcached, contradicting the documented fallback behavior.

Proposed fix to implement documented priority
-        if redis_url or redis_url_fallback:
+        if redis_url:
             from cachekit.backends.redis.config import RedisBackendConfig
             from cachekit.backends.redis.provider import RedisBackendProvider, tenant_context

             redis_config = RedisBackendConfig.from_env()
             provider = RedisBackendProvider(redis_url=redis_config.redis_url)
             if tenant_context.get() is None:
                 tenant_context.set("default")
             return provider.get_backend()

         if memcached_servers:
             from cachekit.backends.memcached import MemcachedBackend

             return MemcachedBackend()  # reads from env via pydantic-settings

         if file_cache_dir:
             from cachekit.backends.file import FileBackend, FileBackendConfig

             config = FileBackendConfig.from_env()
             return FileBackend(config)

+        if redis_url_fallback:
+            from cachekit.backends.redis.config import RedisBackendConfig
+            from cachekit.backends.redis.provider import RedisBackendProvider, tenant_context
+
+            redis_config = RedisBackendConfig.from_env()
+            provider = RedisBackendProvider(redis_url=redis_config.redis_url)
+            if tenant_context.get() is None:
+                tenant_context.set("default")
+            return provider.get_backend()
+
         # No backend env vars found — fall back to Redis (will fail at connection time
         # with a clear error if no Redis is available)

Also applies to: 147-155

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/cachekit/backends/provider.py` around lines 107 - 112, The selection
logic currently treats REDIS_URL the same as CACHEKIT_REDIS_URL by combining
redis_url and redis_url_fallback, so REDIS_URL can preempt Memcached/File
backends; change the decision order to match the docstring: first check
CACHEKIT_API_KEY (CachekitIOBackend), then CACHEKIT_REDIS_URL (RedisBackend),
then CACHEKIT_MEMCACHED_SERVERS (MemcachedBackend), then CACHEKIT_FILE_CACHE_DIR
(FileBackend), and only if none of those are present use REDIS_URL as a
fallback. Concretely, adjust the code that references redis_url and
redis_url_fallback (the variables used to pick RedisBackend) to ensure
redis_url_fallback (REDIS_URL) is evaluated only after checking
CACHEKIT_MEMCACHED_SERVERS and CACHEKIT_FILE_CACHE_DIR, leaving the existing
constructors CachekitIOBackend, RedisBackend, MemcachedBackend, and FileBackend
intact.


For single-tenant deployments (default), sets tenant_context to "default".
For multi-tenant deployments, tenant_context must be set externally.
"""

def __init__(self):
self._provider = None
self._backend = None

def get_backend(self):
"""Get per-request backend instance from singleton provider."""
if self._provider is None:
"""Get backend instance, auto-detected from environment on first call."""
if self._backend is None:
self._backend = self._resolve_backend()
return self._backend

def _resolve_backend(self):
"""Resolve backend from environment variables (priority order)."""
import logging
import os

logger = logging.getLogger(__name__)

api_key = os.environ.get("CACHEKIT_API_KEY")
redis_url = os.environ.get("CACHEKIT_REDIS_URL")
memcached_servers = os.environ.get("CACHEKIT_MEMCACHED_SERVERS")
file_cache_dir = os.environ.get("CACHEKIT_FILE_CACHE_DIR")
redis_url_fallback = os.environ.get("REDIS_URL")

if api_key:
if redis_url or redis_url_fallback:
logger.warning("Both CACHEKIT_API_KEY and Redis URL configured; using CachekitIO (higher priority)")
from cachekit.backends.cachekitio import CachekitIOBackend

return CachekitIOBackend() # reads from env via pydantic-settings

if redis_url or redis_url_fallback:
from cachekit.backends.redis.config import RedisBackendConfig
from cachekit.backends.redis.provider import RedisBackendProvider, tenant_context

redis_config = RedisBackendConfig.from_env()
self._provider = RedisBackendProvider(redis_url=redis_config.redis_url)

# Set default tenant for single-tenant mode (if not already set)
provider = RedisBackendProvider(redis_url=redis_config.redis_url)
if tenant_context.get() is None:
tenant_context.set("default")
return provider.get_backend()

if memcached_servers:
from cachekit.backends.memcached import MemcachedBackend

return MemcachedBackend() # reads from env via pydantic-settings

if file_cache_dir:
from cachekit.backends.file import FileBackend, FileBackendConfig

config = FileBackendConfig.from_env()
return FileBackend(config)

# No backend env vars found — fall back to Redis (will fail at connection time
# with a clear error if no Redis is available)
from cachekit.backends.redis.config import RedisBackendConfig
from cachekit.backends.redis.provider import RedisBackendProvider, tenant_context

return self._provider.get_backend()
redis_config = RedisBackendConfig.from_env()
provider = RedisBackendProvider(redis_url=redis_config.redis_url)
if tenant_context.get() is None:
tenant_context.set("default")
return provider.get_backend()


__all__ = [
Expand Down
Loading
Loading