XmLLM / src /app /settings.py
Claude
Wire HF_TOKEN secret into settings and propagate to environment
13a1f4e unverified
"""Application settings — single source of truth for configuration.
Auto-detects whether the app is running locally or on a Hugging Face Space
via the SPACE_ID environment variable.
"""
from __future__ import annotations
import os
from enum import StrEnum
from functools import lru_cache
from pathlib import Path
from pydantic import Field
from pydantic_settings import BaseSettings, SettingsConfigDict
class AppMode(StrEnum):
LOCAL = "local"
SPACE = "space"
def _detect_mode() -> AppMode:
"""Detect execution mode from environment."""
if os.environ.get("SPACE_ID"):
return AppMode.SPACE
return AppMode(os.environ.get("APP_MODE", "local"))
class Settings(BaseSettings):
"""Central settings loaded from environment / .env file."""
model_config = SettingsConfigDict(
env_file=".env",
env_file_encoding="utf-8",
extra="ignore",
)
# -- Execution mode -------------------------------------------------------
app_mode: AppMode = Field(default_factory=_detect_mode)
# -- Storage --------------------------------------------------------------
storage_root: Path = Field(default=Path("./data"))
db_name: str = "app.db"
# -- Server ---------------------------------------------------------------
host: str = "0.0.0.0"
port: int = 7860
log_level: str = "info"
# -- Upload limits --------------------------------------------------------
max_upload_size: int = 52_428_800 # 50 MB
allowed_mime_types: str = "image/png,image/jpeg,image/tiff,image/webp"
# -- Provider defaults ----------------------------------------------------
provider_timeout: int = 120
api_provider_timeout: int = 60
api_provider_max_retries: int = 2
# -- Geometry -------------------------------------------------------------
bbox_containment_tolerance: int = 5
# -- HuggingFace ----------------------------------------------------------
hf_home: Path | None = None
hf_token: str | None = Field(default=None, alias="HF_TOKEN")
# -- Derived properties ---------------------------------------------------
@property
def is_space(self) -> bool:
return self.app_mode == AppMode.SPACE
@property
def db_path(self) -> Path:
return self.storage_root / self.db_name
@property
def jobs_dir(self) -> Path:
return self.storage_root / "jobs"
@property
def providers_dir(self) -> Path:
return self.storage_root / "providers"
@property
def exports_dir(self) -> Path:
return self.storage_root / "exports"
@property
def cache_dir(self) -> Path:
return self.storage_root / "cache"
@property
def allowed_mime_types_set(self) -> set[str]:
return {t.strip() for t in self.allowed_mime_types.split(",")}
def ensure_directories(self) -> None:
"""Create all required storage directories."""
for d in (
self.storage_root,
self.jobs_dir,
self.providers_dir,
self.exports_dir,
self.cache_dir,
):
d.mkdir(parents=True, exist_ok=True)
def model_post_init(self, __context: object) -> None:
"""Apply Space-specific defaults after init."""
# Propagate HF token to environment so huggingface_hub picks it up
if self.hf_token:
os.environ.setdefault("HF_TOKEN", self.hf_token)
if self.is_space:
if self.storage_root == Path("./data"):
object.__setattr__(self, "storage_root", Path("/data"))
if self.hf_home is None:
object.__setattr__(self, "hf_home", Path("/data/.huggingface"))
os.environ.setdefault("HF_HOME", str(self.hf_home))
@lru_cache(maxsize=1)
def get_settings() -> Settings:
"""Factory for dependency injection (FastAPI Depends). Cached singleton."""
return Settings()