from dataclasses import dataclass from functools import lru_cache import os from typing import Any try: from pathlib import Path as _Path from dotenv import load_dotenv # 显式指定 backend 目录下的 .env,并强制覆盖已有环境变量, # 确保远程数据库配置生效,不被本地默认值或残留环境变量影响。 _env_path = _Path(__file__).resolve().parent.parent.parent / ".env" load_dotenv(dotenv_path=_env_path, override=True) except ImportError: pass def _int_env(name: str, default: int) -> int: raw = os.getenv(name) if raw is None or raw == "": return default return int(raw) def _list_env(name: str, default: list[str]) -> list[str]: raw = os.getenv(name) if raw is None or raw.strip() == "": return default return [item.strip() for item in raw.split(",") if item.strip()] def _bool_env(name: str, default: bool) -> bool: raw = os.getenv(name) if raw is None or raw.strip() == "": return default return raw.strip().lower() in {"1", "true", "yes", "on"} def docs_kwargs(enabled: bool) -> dict[str, Any]: """Swagger UI / ReDoc / OpenAPI schema 路由开关。 关闭时 FastAPI 不注册 /docs、/redoc、/openapi.json,访问一律返回 404, 避免未授权访问泄露 API 结构。 """ if enabled: return {} return {"docs_url": None, "redoc_url": None, "openapi_url": None} @dataclass(frozen=True) class Settings: app_name: str = os.getenv("APP_NAME", "YG Fine-Tune Platform API") app_env: str = os.getenv("APP_ENV", "local") route_prefix: str = os.getenv("MODELTF_ROUTE_PREFIX", "/modelTF") app_mode: str = os.getenv("APP_MODE", "local") database_url: str = os.getenv("DATABASE_URL", "postgresql+psycopg://yg_ft:change_me@localhost:15432/yg_ft") cors_allow_origins: list[str] = None # type: ignore[assignment] compute_mode: str = os.getenv("COMPUTE_MODE", "real") compute_status_sync_mode: str = os.getenv("COMPUTE_STATUS_SYNC_MODE", "polling") compute_poll_interval_seconds: int = _int_env("COMPUTE_POLL_INTERVAL_SECONDS", 3) compute_request_timeout_seconds: int = _int_env("COMPUTE_REQUEST_TIMEOUT_SECONDS", 5) compute_service_token: str = os.getenv("COMPUTE_SERVICE_TOKEN", "") log_level: str = os.getenv("LOG_LEVEL", "INFO") log_dir: str = os.getenv("LOG_DIR", "./logs") log_file_prefix: str = os.getenv("LOG_FILE_PREFIX", "backend") log_error_file_prefix: str = os.getenv("LOG_ERROR_FILE_PREFIX", "error") log_max_bytes: int = _int_env("LOG_MAX_BYTES", 20 * 1024 * 1024) log_retention_days: int = _int_env("LOG_RETENTION_DAYS", 10) enable_docs: bool = None # type: ignore[assignment] def __post_init__(self) -> None: object.__setattr__( self, "cors_allow_origins", _list_env( "CORS_ALLOW_ORIGINS", [ "http://localhost:16801", "http://127.0.0.1:16801", "http://localhost:17861", "http://127.0.0.1:17861", ], ), ) # Swagger UI / ReDoc / OpenAPI 文档路由开关: # 未显式配置 ENABLE_DOCS 时,仅本地/开发环境开放,生产环境默认关闭, # 避免未授权访问泄露 API 结构。从运行时环境读取 APP_ENV,而非类定义时 # 缓存的默认值,保证生产默认关闭始终生效且便于测试。 object.__setattr__( self, "enable_docs", _bool_env("ENABLE_DOCS", os.getenv("APP_ENV", "local") != "prod"), ) @lru_cache def get_settings() -> Settings: return Settings()