Files
srbhr--resume-matcher/apps/backend/app/config_cache.py
T
wehub-resource-sync 5bdf4cc89a
Publish Docker Image / publish (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:39:36 +08:00

68 lines
2.1 KiB
Python

"""Shared config file cache used by multiple routers.
This module owns the cached read of ``config.json`` so that routers
(resumes, enrichment, config) can share it without importing each other.
"""
import copy
import json
import logging
import time
from typing import Any
from app.config import settings
logger = logging.getLogger(__name__)
# Cache state — accessed without a lock because:
# 1. The app runs single-worker uvicorn (one thread, cooperative async).
# 2. The GIL protects dict/float assignment; worst-case TOCTOU is a
# redundant disk read (benign, same file, same result).
# 3. A threading.Lock would block the event loop; an asyncio.Lock would
# require making load_config async and changing every caller.
_config_cache: dict[str, Any] = {}
_config_cache_time: float = 0.0
_CONFIG_CACHE_TTL: float = 300.0 # 5 minutes
def invalidate_config_cache() -> None:
"""Invalidate the config cache so the next read fetches from disk.
Call this after any write to config.json.
"""
global _config_cache, _config_cache_time
_config_cache = {}
_config_cache_time = 0.0
def load_config() -> dict[str, Any]:
"""Load configuration from config file with 5-minute TTL cache.
Returns a deep copy so callers cannot corrupt the cached data.
"""
global _config_cache, _config_cache_time
now = time.monotonic()
if _config_cache and (now - _config_cache_time) < _CONFIG_CACHE_TTL:
return copy.deepcopy(_config_cache)
config_path = settings.config_path
if not config_path.exists():
_config_cache = {}
_config_cache_time = now
return {}
try:
_config_cache = json.loads(config_path.read_text())
_config_cache_time = now
return copy.deepcopy(_config_cache)
except (json.JSONDecodeError, OSError) as e:
logger.error("Failed to load config: %s", e)
_config_cache = {}
_config_cache_time = now
return {}
def get_content_language() -> str:
"""Get configured content language from cached config."""
config = load_config()
return config.get("content_language", config.get("language", "en"))