feat: add redis cache for copyright label lookups (#566)
* feat: add redis cache for copyright label lookups
the moderation service call was causing 2-3s latency spikes on GET /tracks/
in production. since we have multiple fly.io instances, in-memory caching
wouldn't work - added distributed redis cache (reusing docket's redis).
- add backend/utilities/redis.py for async client from docket URL
- cache active label status with 5min TTL (matches queue cache)
- use mget/pipeline for efficient batch operations
- invalidate cache when labels are emitted
- fail closed on errors (treat as active)
๐ค Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
* refactor: move cache settings to config, add async context manager
- add label_cache_prefix and label_cache_ttl_seconds to ModerationSettings
- add async_redis_client() context manager for isolated connections
- add clear_client_cache() for test cleanup
- remove hardcoded cache constants from moderation.py
- fix tests to properly clear client cache between runs
๐ค Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
* test: add redis isolation for xdist parallel tests
- add redis_database fixture that assigns different DB per worker
- use unique URIs in cache tests to avoid cross-test pollution
- document redis test isolation pattern in docs/testing/README.md
๐ค Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
* fix: handle missing redis gracefully in test fixture
the redis_database fixture now catches ConnectionError and skips
silently when redis is unavailable. tests that don't need redis
will pass, and tests that do need it will fail with specific errors.
๐ค Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
* fix: mock settings in moderation cache tests
tests that mock httpx must also mock settings.moderation to avoid
early return on auth_token check. without this, tests pass locally
(where MODERATION_AUTH_TOKEN may be set) but fail in CI.
๐ค Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
* fix: use D: prefix for DOCKET_URL to allow CI override
pytest-env's D: prefix means "default" - only set if not already
set by the environment. this allows CI's DOCKET_URL=redis://localhost:6379
to take precedence over pyproject.toml's 6380 (for local docker-compose).
๐ค Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
* fix: move deferred imports to top of file, add type hints
- move redis imports to module level in moderation.py
- move asyncio import to module level in redis.py
- add type hint for kwargs dict in redis.py
- move imports to module level in conftest.py
- add rule to AGENTS.md: DO NOT UNNECESSARILY DEFER IMPORTS
๐ค Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
---------
Co-authored-by: Claude <noreply@anthropic.com>
authored by
zzstoatzz.io
Claude
and committed by
GitHub
1d8942ad
3fb7e953