shockmap-api / backend /app /config.py
Dhruva P Gowda
Deploy ShockMap MVP: High-fidelity UI overhaul, animated graph particles, live GDELT ingestion, and production scaffolding
f59a149
Raw History Blame Contribute Delete
3.48 kB
"""
Configuration management for the PharmaShield backend using Pydantic Settings.
"""
import json
import logging
from pathlib import Path
from typing import List
from pydantic import field_validator
from pydantic_settings import BaseSettings, SettingsConfigDict
def _discover_base_dir() -> Path:
"""
Find the project base directory by looking for bundled seed data.
Local development usually has ``repo_root/data``.
Backend-only serverless deployments use ``backend/data``.
"""
config_path = Path(__file__).resolve()
for candidate in reversed(config_path.parents):
if (candidate / "data" / "seed" / "drugs.json").exists():
return candidate
# Fallback to the backend root if no data bundle is found yet.
return config_path.parent.parent
BASE_DIR = _discover_base_dir()
class Settings(BaseSettings):
"""
Application settings and environment variables.
Defaults are provided where safe; sensitive keys must be provided in .env or environment.
"""
# API Keys & Endpoints
GEMINI_API_KEY: str = ""
QDRANT_URL: str = ""
QDRANT_API_KEY: str = ""
GOOGLE_MAPS_API_KEY: str = ""
# Project Info
FIREBASE_PROJECT_ID: str = ""
BACKEND_URL: str = "http://localhost:8080"
ALLOWED_ORIGINS: List[str] = [
"http://localhost:3000",
"http://localhost:5173",
"http://localhost:8080",
"https://pharmashield.web.app",
]
# Paths
SEED_DATA_DIR: str = str(BASE_DIR / "data" / "seed")
GNN_WEIGHTS_PATH: str = str(BASE_DIR / "ml" / "weights" / "gnn_v1.pt")
ENABLE_GNN: bool = False
DEMO_MODE: bool = True
DEMO_SCENARIOS_PATH: str = str(BASE_DIR / "data" / "seed" / "demo_scenarios.json")
# Model & Database Configurations
QDRANT_COLLECTION: str = "pharmashield_kb"
GEMINI_FLASH_MODEL: str = "gemini-2.0-flash"
GEMINI_PRO_MODEL: str = "gemini-2.5-pro"
EMBEDDING_MODEL: str = "models/gemini-embedding-001"
@field_validator("ALLOWED_ORIGINS", mode="before")
@classmethod
def parse_allowed_origins(cls, value):
if isinstance(value, list):
return value
if isinstance(value, str):
text = value.strip()
if not text:
return []
if text.startswith("["):
try:
parsed = json.loads(text)
except json.JSONDecodeError:
parsed = None
if isinstance(parsed, list):
return [str(item).strip() for item in parsed if str(item).strip()]
return [item.strip() for item in text.split(",") if item.strip()]
return value
model_config = SettingsConfigDict(
env_file=(
BASE_DIR / "backend" / ".env",
BASE_DIR / ".env",
),
env_file_encoding="utf-8",
extra="ignore",
)
try:
settings = Settings()
if not settings.GEMINI_API_KEY or not settings.QDRANT_URL:
missing = []
if not settings.GEMINI_API_KEY:
missing.append("GEMINI_API_KEY")
if not settings.QDRANT_URL:
missing.append("QDRANT_URL")
logging.warning(
"Missing non-critical configuration: %s. Some features may be disabled.",
", ".join(missing),
)
except Exception as e:
if isinstance(e, RuntimeError):
raise e
raise RuntimeError(f"Failed to load application settings: {str(e)}")