Skip to content

feat: auto-detect backend from environment variables#120

Open
27Bslash6 wants to merge 1 commit into
mainfrom
feat/auto-detect-backend
Open

feat: auto-detect backend from environment variables#120
27Bslash6 wants to merge 1 commit into
mainfrom
feat/auto-detect-backend

Conversation

@27Bslash6
Copy link
Copy Markdown
Contributor

@27Bslash6 27Bslash6 commented May 16, 2026

Summary

DefaultBackendProvider now resolves the backend from environment variables instead of always defaulting to Redis.

Priority order (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

Warns when multiple backends are configured. Falls back to Redis when no env vars set (existing behavior preserved).

Test plan

  • 10 new unit tests covering each backend, priority, caching, warning, and fallback
  • 1405 total unit tests pass
  • Lint + type check clean

Closes #87

Summary by CodeRabbit

Release Notes

  • New Features

    • Cache backend is now automatically detected based on environment configuration, with a documented priority order: CachekitIO API, Redis, Memcached, then file cache.
    • Warnings are emitted when conflicting backend configurations are detected.
  • Bug Fixes

    • Redis backend now correctly defaults to single-tenant mode when not explicitly configured.

Review Change Stack

DefaultBackendProvider now resolves the backend from env vars with
priority ordering instead of always defaulting to Redis:

  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

Warns when multiple backends are configured (higher priority wins).
Falls back to Redis when no env vars are set (existing behavior).

Closes #87
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 16, 2026

📝 Walkthrough

Walkthrough

DefaultBackendProvider now auto-detects cache backends from environment variables instead of hardcoding Redis. It checks variables in priority order (CachekitIO, Redis, Memcached, file cache), caches the resolved backend, warns when conflicting configs exist, and defaults Redis to single-tenant mode. Tests cover all provider interfaces and logger implementations.

Changes

Backend Auto-Detection and Provider Tests

Layer / File(s) Summary
Backend auto-detection with lazy initialization
src/cachekit/backends/provider.py, tests/unit/backends/test_provider.py
DefaultBackendProvider._resolve_backend() checks environment variables in documented priority order and caches the result. First call to get_backend() triggers resolution; subsequent calls return cached instance. Warns when both CACHEKIT_API_KEY and a Redis URL are set. Ensures tenant_context="default" for Redis-based backends (including fallback). TestDefaultBackendProviderAutoDetect suite validates env-var routing, precedence, caching, fallback behavior, and tenant-context initialization.
Provider interface and cache client tests
tests/unit/backends/test_provider.py
CacheClientProvider and BackendProviderInterface abstract interface tests simplified to instantiate and assert NotImplementedError. DefaultCacheClientProvider tests patched to mock redis sync/async factory functions and verify correct factory invocation and client identity for both sync and async code paths.
Logger implementation and provider tests
tests/unit/backends/test_provider.py
LoggerProvider interface test instantiates before asserting NotImplementedError. SimpleLogger tests refactored to validate each method (debug, info, warning, error, cache hit/miss/stored/invalidated) calls underlying logger with exact message strings for default/custom source and TTL/non-TTL scenarios. DefaultLoggerProvider tests assert get_logger returns SimpleLogger, wrapped _logger is standard logging.Logger with correct name, and different names produce distinct underlying loggers.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

🐇 From the warren, a cheer for the change,
No more Redis locked in—backends can exchange!
With env vars dancing in order so true,
API keys now lead the way through,
Lazy, cached, smart, and zero-config too! 🎯

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 35.29% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title 'feat: auto-detect backend from environment variables' clearly and concisely describes the main feature addition: automatic backend selection based on environment variables.
Description check ✅ Passed The PR description addresses the key aspects: it summarizes the environment variable priority order, explains test coverage, and references the closed issue. While the template wasn't fully followed, the essential information is present.
Linked Issues check ✅ Passed The PR implementation matches issue #87 requirements: auto-detects backend from environment variables with correct priority order (CACHEKIT_API_KEY, CACHEKIT_REDIS_URL, CACHEKIT_MEMCACHED_SERVERS, CACHEKIT_FILE_CACHE_DIR, REDIS_URL fallback), warns when multiple backends configured, and preserves explicit backend parameter precedence.
Out of Scope Changes check ✅ Passed All changes in provider.py and test_provider.py are directly related to implementing the backend auto-detection feature. Provider class modifications and comprehensive test updates align with the scope of issue #87 without introducing unrelated modifications.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/auto-detect-backend

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
tests/unit/backends/test_provider.py (1)

234-253: ⚡ Quick win

Missing test for REDIS_URL fallback priority.

This test verifies REDIS_URL works when no other env vars are set, but doesn't test that REDIS_URL has lower priority than CACHEKIT_MEMCACHED_SERVERS or CACHEKIT_FILE_CACHE_DIR as documented.

Once the priority bug in provider.py is fixed, add a test that sets both REDIS_URL and CACHEKIT_MEMCACHED_SERVERS, asserting MemcachedBackend is selected.

Proposed additional test
def test_memcached_wins_over_redis_url_fallback(self, monkeypatch: pytest.MonkeyPatch) -> None:
    """CACHEKIT_MEMCACHED_SERVERS has priority over REDIS_URL fallback."""
    monkeypatch.setenv("REDIS_URL", "redis://fallback:6379")
    monkeypatch.setenv("CACHEKIT_MEMCACHED_SERVERS", '["127.0.0.1:11211"]')
    monkeypatch.delenv("CACHEKIT_API_KEY", raising=False)
    monkeypatch.delenv("CACHEKIT_REDIS_URL", raising=False)
    monkeypatch.delenv("CACHEKIT_FILE_CACHE_DIR", raising=False)

    with mock.patch("cachekit.backends.memcached.MemcachedBackend") as mock_backend:
        mock_instance = mock.MagicMock()
        mock_backend.return_value = mock_instance

        provider = DefaultBackendProvider()
        backend = provider.get_backend()

        assert backend is mock_instance
        mock_backend.assert_called_once()
🤖 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 `@tests/unit/backends/test_provider.py` around lines 234 - 253, Add a unit test
that verifies REDIS_URL is lower priority than CACHEKIT_MEMCACHED_SERVERS by
setting both env vars, instantiating DefaultBackendProvider and calling
get_backend(), and asserting the MemcachedBackend is chosen; specifically, set
REDIS_URL and CACHEKIT_MEMCACHED_SERVERS via monkeypatch, clear
CACHEKIT_API_KEY/CACHEKIT_REDIS_URL/CACHEKIT_FILE_CACHE_DIR, patch or mock
cachekit.backends.memcached.MemcachedBackend to return a mock instance, call
DefaultBackendProvider().get_backend(), assert the returned backend is the
memcached mock instance and that MemcachedBackend was called once to ensure
memcached wins over the redis fallback.
🤖 Prompt for all review comments with 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.

Inline comments:
In `@src/cachekit/backends/provider.py`:
- Around line 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.

---

Nitpick comments:
In `@tests/unit/backends/test_provider.py`:
- Around line 234-253: Add a unit test that verifies REDIS_URL is lower priority
than CACHEKIT_MEMCACHED_SERVERS by setting both env vars, instantiating
DefaultBackendProvider and calling get_backend(), and asserting the
MemcachedBackend is chosen; specifically, set REDIS_URL and
CACHEKIT_MEMCACHED_SERVERS via monkeypatch, clear
CACHEKIT_API_KEY/CACHEKIT_REDIS_URL/CACHEKIT_FILE_CACHE_DIR, patch or mock
cachekit.backends.memcached.MemcachedBackend to return a mock instance, call
DefaultBackendProvider().get_backend(), assert the returned backend is the
memcached mock instance and that MemcachedBackend was called once to ensure
memcached wins over the redis fallback.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 6f3ff744-2c4b-4d4b-afd0-5b72720d3f5e

📥 Commits

Reviewing files that changed from the base of the PR and between 1fc506b and 04a88cf.

📒 Files selected for processing (2)
  • src/cachekit/backends/provider.py
  • tests/unit/backends/test_provider.py

Comment on lines +107 to +112
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
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.

@codecov
Copy link
Copy Markdown

codecov Bot commented May 16, 2026

Codecov Report

❌ Patch coverage is 97.14286% with 1 line in your changes missing coverage. Please review.
✅ All tests successful. No failed tests found.

Files with missing lines Patch % Lines
src/cachekit/backends/provider.py 97.14% 0 Missing and 1 partial ⚠️

📢 Thoughts on this report? Let us know!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat: auto-detect backend from environment variables in DefaultBackendProvider

1 participant