| """ |
| shared/config.py |
| ββββββββββββββββ |
| Application configuration loaded from environment variables. |
| Uses Pydantic Settings so every variable is validated + type-safe. |
| |
| Supabase support: |
| β’ DATABASE_URL should point to Supabase PostgreSQL (port 5432 direct, or |
| port 6543 for the Connection Pooler in Transaction mode). |
| β’ SUPABASE_URL / SUPABASE_ANON_KEY are optional β only needed if you use |
| the Supabase Python client SDK for storage, auth, or realtime features. |
| """ |
| from __future__ import annotations |
|
|
| from functools import lru_cache |
| import os |
|
|
| |
| if "KAGGLE_KERNEL_RUN_TYPE" in os.environ or os.path.exists("/kaggle"): |
| try: |
| from kaggle_secrets import UserSecretsClient |
| user_secrets = UserSecretsClient() |
| for key in ["DATABASE_URL", "RABBITMQ_URL", "USE_MOCK_MODEL"]: |
| try: |
| val = user_secrets.get_secret(key) |
| if val: |
| os.environ[key] = val |
| except Exception: |
| pass |
| except ImportError: |
| pass |
|
|
| from pathlib import Path |
| from pydantic import Field, field_validator |
| from pydantic_settings import BaseSettings, SettingsConfigDict |
|
|
| |
| _ROOT_DIR = Path(__file__).resolve().parent.parent.parent |
| _ENV_FILE = _ROOT_DIR / ".env" |
|
|
|
|
| class Settings(BaseSettings): |
| """ |
| Central configuration object. |
| |
| Priority order (highest β lowest): |
| 1. OS environment variables |
| 2. .env file in the project root |
| 3. Default values defined here |
| """ |
|
|
| model_config = SettingsConfigDict( |
| env_file=str(_ENV_FILE), |
| env_file_encoding="utf-8", |
| case_sensitive=False, |
| extra="ignore", |
| ) |
|
|
| |
| database_url: str = Field( |
| default="sqlite+aiosqlite:///./bp_monitoring.db", |
| description=( |
| "Async database connection string. " |
| "β’ postgresql+asyncpg://postgres.[ref]:[pw]@[host]:5432/postgres (Supabase direct) " |
| "β’ postgresql+asyncpg://postgres.[ref]:[pw]@[host]:6543/postgres (Supabase pooler) " |
| "β’ sqlite+aiosqlite:///./bp_monitoring.db (local dev)" |
| ), |
| ) |
|
|
| |
| db_pool_size: int = Field( |
| default=5, |
| description=( |
| "SQLAlchemy connection pool size. " |
| "Supabase free tier allows up to 60 connections; keep this β€ 10." |
| ), |
| ) |
| db_max_overflow: int = Field( |
| default=10, |
| description="Extra connections allowed above pool_size during peak load.", |
| ) |
| db_pool_recycle: int = Field( |
| default=1800, |
| description="Recycle idle connections after N seconds (30 min default).", |
| ) |
|
|
| |
| supabase_url: str = Field( |
| default="", |
| description="Supabase project URL (https://[project-ref].supabase.co). Optional.", |
| ) |
| supabase_anon_key: str = Field( |
| default="", |
| description="Supabase anonymous/public API key. Optional.", |
| ) |
|
|
| |
| rabbitmq_url: str = Field( |
| default="amqp://guest:guest@localhost:5672/", |
| description="RabbitMQ connection URL. Use amqps:// for CloudAMQP (SSL).", |
| ) |
|
|
| |
| app_host: str = Field(default="0.0.0.0", description="Bind host.") |
| app_port: int = Field(default=7860, description="Bind port (7860 for HF Spaces).") |
|
|
| |
| debug: bool = Field(default=False, description="Enable debug mode.") |
| log_level: str = Field(default="INFO", description="Logging level.") |
|
|
| |
| gan_checkpoint_path: str = Field( |
| default="./models/gan_checkpoint.pt", |
| description="Path to GAN model checkpoint.", |
| ) |
| vgtlnet_checkpoint_path: str = Field( |
| default="./models/vgtlnet_checkpoint.pt", |
| description="Path to VGTL-Net model checkpoint.", |
| ) |
| use_mock_model: bool = Field( |
| default=True, |
| description=( |
| "Use MockModelService instead of real GAN+VGTL-Net. " |
| "Set to false only when checkpoints are available." |
| ), |
| ) |
|
|
| |
| @field_validator("log_level") |
| @classmethod |
| def validate_log_level(cls, v: str) -> str: |
| valid = {"DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"} |
| upper = v.upper() |
| if upper not in valid: |
| raise ValueError(f"log_level must be one of {valid}") |
| return upper |
|
|
| @field_validator("app_port") |
| @classmethod |
| def validate_port(cls, v: int) -> int: |
| if not (1 <= v <= 65535): |
| raise ValueError("app_port must be between 1 and 65535") |
| return v |
|
|
| @field_validator("db_pool_size") |
| @classmethod |
| def validate_pool_size(cls, v: int) -> int: |
| if v < 1: |
| raise ValueError("db_pool_size must be at least 1") |
| return v |
|
|
| |
| @property |
| def is_supabase(self) -> bool: |
| """True when DATABASE_URL points to Supabase (supabase.co host).""" |
| return "supabase.co" in self.database_url |
|
|
| @property |
| def is_sqlite(self) -> bool: |
| """True when DATABASE_URL uses SQLite (local dev).""" |
| return self.database_url.startswith("sqlite") |
|
|
| @property |
| def uses_pooler(self) -> bool: |
| """True when connecting via Supabase's pgBouncer pooler (port 6543).""" |
| return ":6543/" in self.database_url |
|
|
|
|
| @lru_cache(maxsize=1) |
| def get_settings() -> Settings: |
| """ |
| Return a cached singleton Settings instance. |
| """ |
| return Settings() |
|
|