Files
pesco-ncr/backend/app/config.py

89 lines
3.5 KiB
Python
Raw Normal View History

"""Application configuration, sourced from environment variables (see .env.example)."""
from functools import lru_cache
from typing import Literal
from urllib.parse import quote_plus
from pydantic_settings import BaseSettings, SettingsConfigDict
class Settings(BaseSettings):
model_config = SettingsConfigDict(env_file=".env", extra="ignore")
app_name: str = "PESCO NCR"
app_base_url: str = "http://localhost:8080"
log_level: str = "INFO"
# ── Auth ────────────────────────────────────────────────────────────────
# "entra" validates Entra ID JWTs; "dev" trusts an X-Dev-User header and
# must never be used outside local development.
auth_mode: Literal["entra", "dev"] = "entra"
entra_tenant_id: str = ""
entra_client_id: str = ""
entra_client_secret: str = ""
entra_allowed_group_id: str = ""
entra_api_audience: str = ""
initial_admin_emails: str = ""
# ── Database ────────────────────────────────────────────────────────────
# Full SQLAlchemy URL override (used by tests); otherwise assembled from
# the MYSQL_* parts below.
database_url: str = ""
mysql_host: str = "mysql"
mysql_port: int = 3306
mysql_database: str = "pesco_ncr"
mysql_user: str = "ncr_app"
mysql_password: str = ""
# ── Attachments ─────────────────────────────────────────────────────────
attachments_dir: str = "/data/attachments"
max_upload_mb: int = 25
# ── Notifications ───────────────────────────────────────────────────────
notifications_enabled_default: bool = True
# ── Job lookup (future VISUAL integration) ──────────────────────────────
job_lookup_provider: Literal["null", "visual"] = "null"
visual_db_host: str = ""
visual_db_port: int = 1433
visual_db_name: str = ""
visual_db_user: str = ""
visual_db_password: str = ""
visual_site_id: str = ""
seed_demo_data: bool = False
@property
def effective_database_url(self) -> str:
if self.database_url:
return self.database_url
return (
f"mysql+aiomysql://{quote_plus(self.mysql_user)}:{quote_plus(self.mysql_password)}"
f"@{self.mysql_host}:{self.mysql_port}/{self.mysql_database}?charset=utf8mb4"
)
@property
def sync_database_url(self) -> str:
"""Synchronous-driver URL for Alembic."""
return self.effective_database_url.replace("+aiomysql", "+pymysql").replace(
"+aiosqlite", ""
)
@property
def api_audiences(self) -> list[str]:
if self.entra_api_audience:
return [self.entra_api_audience]
return [f"api://{self.entra_client_id}", self.entra_client_id]
@property
def initial_admin_email_set(self) -> set[str]:
return {e.strip().lower() for e in self.initial_admin_emails.split(",") if e.strip()}
@property
def max_upload_bytes(self) -> int:
return self.max_upload_mb * 1024 * 1024
@lru_cache
def get_settings() -> Settings:
return Settings()