Skip to content

Conversation

@olindost
Copy link
Collaborator

@olindost olindost commented Nov 27, 2025

Overview: This PR introduces a new module for robust management and rotation of external API keys.

Changes

  • Added app/security/api_keys.py to centralize API key handling.
  • Ensures secure loading of API keys directly from .env files.
  • Implemented get_key(provider_name) for efficient key retrieval.
  • Includes logic for key rotation and fallback keys to mitigate rate limit issues.

Summary by CodeRabbit

  • Chores
    • Enhanced backend API key management to load provider-specific keys from environment settings, ignore invalid entries, and rotate keys automatically with thread-safe access for reliable request routing.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link

coderabbitai bot commented Nov 27, 2025

Walkthrough

Adds a new APIKeyManager utility that loads provider-scoped API keys from environment variables, groups them per provider, creates a rotation cycle for each provider, and exposes get_key(provider_name) to return the next key. A module-level singleton api_key_manager is initialized at import.

Changes

Cohort / File(s) Change Summary
New API Key Manager Utility
backend/app/security/api_keys.py
New file introducing APIKeyManager which scans environment variables for provider-prefixed keys (e.g., PROVIDER_API_KEY_1, PROVIDER_API_KEY_2, ...), filters empty/invalid entries, constructs per-provider itertools.cycle rotations, uses per-provider locks for thread-safe access, and exposes get_key(provider_name); module-level api_key_manager singleton created at import.

Sequence Diagram(s)

sequenceDiagram
    participant Caller
    participant APIKeyManager
    participant Env as "Environment (os.environ)"

    Caller->>APIKeyManager: request get_key(provider_name)
    alt provider cycle exists
        APIKeyManager->>APIKeyManager: acquire provider lock
        APIKeyManager->>APIKeyManager: next(key_cycle)
        APIKeyManager->>APIKeyManager: release provider lock
        APIKeyManager-->>Caller: return key
    else no keys loaded for provider
        APIKeyManager-->>Caller: return null / None
    end

    note over APIKeyManager,Env: On init, _load_keys() scans Env,\ngroups keys by provider and builds cycles
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

  • Inspect environment variable discovery and parsing in _load_keys.
  • Verify per-provider lock handling and correctness of rotation via itertools.cycle.
  • Confirm behavior when providers have zero keys (return value, logging).
  • Check singleton initialization timing and tests (if any).

Poem

🐇🔑 I hop the env for hidden strings,
gather tokens, make them sing,
round the cycle, one by one —
keys trot out till work is done. ✨

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 66.67% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main change: implementing API key management and rotation functionality through the new APIKeyManager class.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/api-key-management

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

Copy link

@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: 2

🧹 Nitpick comments (2)
backend/app/security/api_keys.py (2)

6-8: Type annotation mismatch during construction.

self._keys temporarily holds list values during _load_keys() before being converted to cycle objects. Consider using Union[List[str], cycle] or storing raw lists separately.

-    def __init__(self):
-        self._keys: Dict[str, cycle] = {}
+    def __init__(self):
+        self._raw_keys: Dict[str, list] = {}
+        self._keys: Dict[str, cycle] = {}

Then use _raw_keys during loading and convert to _keys at the end.


36-37: Consider adding a method to refresh keys at runtime.

The singleton is initialized at import time, so environment variable changes require an application restart. For a key rotation feature, you may want to support runtime refresh.

+    def refresh_keys(self):
+        """Reloads API keys from environment variables."""
+        with self._lock:
+            self._keys.clear()
+            self._load_keys()
+
 # Initialize the APIKeyManager
 api_key_manager = APIKeyManager()
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 62ef0dc and 5640105.

📒 Files selected for processing (1)
  • backend/app/security/api_keys.py (1 hunks)
🔇 Additional comments (1)
backend/app/security/api_keys.py (1)

1-3: LGTM!

Standard library imports are appropriate for the module's functionality.

Copy link

@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: 0

🧹 Nitpick comments (3)
backend/app/security/api_keys.py (3)

12-31: Env var parsing now handles earlier edge cases; only minor nits left

The _load_keys logic correctly ignores empty values, handles multiple _API_KEY occurrences via rsplit, and avoids empty/underscore-only provider names, which resolves the earlier parsing issues. One minor nit: the docstring implies only PROVIDER_NAME_API_KEY_1‑style vars, but the code will accept any name containing _API_KEY; if you expect stricter naming, you could tighten the condition or document the broader behavior, and optionally log when entries are skipped to aid debugging misconfigurations.


7-10: Clarify _keys typing by separating raw key collection from rotation

Functionally this works and the per‑provider locks around next(self._keys[provider_name]) give you thread‑safe rotation. The only rough edge is that _keys is annotated as Dict[str, cycle] but temporarily holds lists before being converted, which will trip static analyzers.

You can keep behavior but make typing cleaner by using a local raw_keys dict in _load_keys:

@@
-    def _load_keys(self):
+    def _load_keys(self):
@@
-        for key, value in os.environ.items():
+        raw_keys: Dict[str, list[str]] = {}
+        for key, value in os.environ.items():
@@
-                if provider_name not in self._keys:
-                    self._keys[provider_name] = []
-                self._keys[provider_name].append(value)
+                if provider_name not in raw_keys:
+                    raw_keys[provider_name] = []
+                raw_keys[provider_name].append(value)
@@
-        # Convert lists to cycles for rotation
-        for provider_name, key_list in self._keys.items():
-            self._keys[provider_name] = cycle(key_list)
+        # Convert lists to cycles for rotation
+        for provider_name, key_list in raw_keys.items():
+            self._keys[provider_name] = cycle(key_list)
             self._key_locks[provider_name] = threading.Lock()

This keeps _keys always “cycle‑typed” while leaving the runtime behavior unchanged.

Also applies to: 28-35, 37-45


47-48: Consider lazy or explicit initialization instead of import‑time singleton

Creating api_key_manager at import time tightly couples env evaluation to when this module is first imported, which can make tests or dynamic configuration (e.g., setting env vars in a test before import vs. after) a bit brittle. If you anticipate that need, consider a small factory like get_api_key_manager() with lazy initialization, or exposing a reload() method to re‑scan env vars explicitly.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5640105 and 5b80526.

📒 Files selected for processing (1)
  • backend/app/security/api_keys.py (1 hunks)

@felixjordandev
Copy link
Collaborator

nice, this’ll really simplify key management... merging it now! 🚀

@felixjordandev felixjordandev merged commit d64f9f3 into main Nov 27, 2025
1 check passed
@felixjordandev felixjordandev deleted the feat/api-key-management branch November 27, 2025 07:18
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.

3 participants