Skip to content
Merged
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
8 changes: 8 additions & 0 deletions backend/api/config.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
"""Configuration settings for the FastAPI application."""

from functools import lru_cache
from typing import Any, Dict

from pydantic import Field
Expand Down Expand Up @@ -45,6 +46,13 @@ class Settings(BaseSettings):
)


@lru_cache()
def get_settings() -> Settings:
"""Return a cached instance of :class:`Settings`."""

return Settings()
Comment on lines +49 to +53
Copy link
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Consider making the cache size explicit.

The implementation is correct, but since get_settings() takes no parameters, only one instance will ever be cached. Consider using @lru_cache (no parentheses) or @lru_cache(maxsize=1) to make the singleton intent clearer.

-@lru_cache()
+@lru_cache
 def get_settings() -> Settings:
     """Return a cached instance of :class:`Settings`."""
-
     return Settings()

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In backend/api/config.py around lines 49 to 53 the decorator uses @lru_cache()
which creates a cache with the default maxsize (128) even though get_settings()
has no parameters; change the decorator to either @lru_cache (no parentheses) or
@lru_cache(maxsize=1) so the singleton intent is explicit and only one Settings
instance is ever cached.



def get_fastapi_settings() -> Dict[str, Any]:
"""Get FastAPI application settings.

Expand Down
2 changes: 1 addition & 1 deletion backend/api/db.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
from sqlalchemy import create_engine
from sqlalchemy.orm import DeclarativeBase, sessionmaker

from api.config import Settings
from api.config import get_settings


class Base(DeclarativeBase):
Expand Down
4 changes: 2 additions & 2 deletions backend/api/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
from fastapi.responses import JSONResponse
from starlette.middleware.cors import CORSMiddleware

from api.config import Settings
from api.config import get_settings
from api.routes import router as api_router
from api.routes_materials import router as materials_router
from api.routes_modules import router as modules_router
Expand All @@ -24,7 +24,7 @@
logger = logging.getLogger(__name__)

# Initialize settings
settings = Settings()
settings = get_settings()

# Create the FastAPI app
app = FastAPI(
Expand Down
9 changes: 9 additions & 0 deletions backend/api/routes_sync.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
from sqlalchemy.exc import IntegrityError
from sqlalchemy.orm import Session

from api.config import get_settings
from api.db import get_db
from api.models import SyncEvent
from api.security import require_write_token, validate_hygraph_request
Expand All @@ -32,7 +33,15 @@
logger = logging.getLogger(__name__)
router = APIRouter(prefix="/api/sync", tags=["sync"])

# Using hygraph_token as webhook secret in MVP; adjust if separate secret provided
settings = get_settings()
_hygraph_service = HygraphService(webhook_secret=settings.hygraph_token)
Comment on lines +36 to +38
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

🧩 Analysis chain

Verify initialization safety and confirm lru_cache decorator.

Module-level initialization creates several concerns:

  1. Import-time failure risk: If get_settings() or HygraphService initialization fails (e.g., missing env vars, invalid token), the entire module import fails rather than failing gracefully at request time.

  2. Testability degradation: The singleton is created once at import, making it harder to:

    • Mock the service in tests
    • Reset state between test cases
    • Test with different configurations
  3. Missing cache decorator: The relevant code snippet shows get_settings() without an @lru_cache decorator, which contradicts the PR description stating "lru_cache-backed get_settings helper." This means Settings() is instantiated on every call to get_settings(), negating the caching benefit at this layer.

Run the following script to verify the lru_cache decorator and search for potential test files that might be affected:


🏁 Script executed:

#!/bin/bash
# Verify lru_cache decorator and find test files

echo "=== Checking get_settings implementation for lru_cache decorator ==="
rg -A 3 -B 1 'def get_settings' backend/api/config.py

echo -e "\n=== Finding test files that might mock or use HygraphService ==="
fd -e py test backend/tests --exec rg -l 'HygraphService|routes_sync'

echo -e "\n=== Checking for existing test patterns around settings ==="
rg -n 'Settings|get_settings' backend/tests/ --type py -C 2 2>/dev/null || echo "No tests directory found or no matches"

Length of output: 359


Lazy-initialize HygraphService instead of module-level instantiation
Avoid calling get_settings() and creating _hygraph_service at import time (backend/api/routes_sync.py:17–19). Import-time init can crash the app on missing/invalid env vars and makes tests hard to mock or reset. Move this into a factory or a get_hygraph_service() function (with internal caching) so it fails gracefully at runtime and is mockable in tests.

🤖 Prompt for AI Agents
In backend/api/routes_sync.py around lines 17 to 19, avoid calling
get_settings() and instantiating HygraphService at import time; instead create a
get_hygraph_service() factory that lazy-initializes and caches a module-level
_hygraph_service on first call (calling get_settings() there), so creation
happens at runtime, failures surface gracefully, and tests can monkeypatch or
reset the cached instance; ensure the factory returns the cached instance on
subsequent calls and provide a simple way to reset or replace the cached service
in tests if needed.



def get_hygraph_service() -> HygraphService:
"""Provide the shared Hygraph service instance."""

return _hygraph_service
def _error_envelope(code: str, message: str, details: Optional[dict] = None) -> Dict[str, Any]:
return {"ok": False, "error": {"code": code, "message": message, "details": details or {}}}

Expand Down
Loading