"""主 Settings:多继承所有子配置,统一以绝对路径 ``_env_file`` 加载 ``.env``。 子配置见同目录下的 ``app_config`` / ``auth_config`` / ``database_config`` / ``log_config`` / ``runtime_config`` / ``sandbox_config``。本文件聚合字段,并承载 跨子配置的路径解析与计算属性。 配置优先级(自高到低):命令行参数 > 系统环境变量 > ``.env`` > 类属性默认值。 """ from __future__ import annotations from functools import cached_property from pathlib import Path from pydantic_settings import BaseSettings, SettingsConfigDict from app.core.config.app_config import AppConfig from app.core.config.auth_config import AuthConfig from app.core.config.database_config import DatabaseConfig from app.core.config.log_config import LogConfig from app.core.config.runtime_config import RuntimeConfig from app.core.config.sandbox_config import SandboxSettings from app.core.config.time_config import TimeConfig # backend 目录:app/core/config/settings.py → parents[3] = backend/ _BACKEND_DIR = Path(__file__).resolve().parents[3] # .env 永远在项目根目录(backend 父级),以绝对路径定位 _ENV_FILE = _BACKEND_DIR.parent / ".env" class Settings( AppConfig, AuthConfig, DatabaseConfig, LogConfig, RuntimeConfig, SandboxSettings, TimeConfig, BaseSettings, ): """应用配置类。 所有配置项可通过 ``.env`` 文件或环境变量覆盖。路径配置会自动解析为绝对路径。 """ model_config = SettingsConfigDict( env_file=str(_ENV_FILE), # 绝对路径,不使用 python-dotenv env_file_encoding="utf-8", case_sensitive=False, # 环境变量名不区分大小写 extra="ignore", # 忽略未定义的额外字段 ) # ==================== 路径解析 ==================== @cached_property def backend_dir(self) -> Path: """backend 目录路径。""" return _BACKEND_DIR @cached_property def project_root(self) -> Path: """项目根目录路径(backend 的父级)。""" return _BACKEND_DIR.parent def resolve_from_backend(self, value: str) -> Path: """解析相对于 backend 目录的路径为绝对路径。 Args: value: 相对路径或绝对路径字符串 Returns: 解析后的绝对路径 """ path = Path(value) if path.is_absolute(): return path return (self.backend_dir / path).resolve() # ==================== 计算属性 ==================== @property def resolved_database_url(self) -> str: """解析数据库连接 URL。 如果设置了 ``database_url`` 则直接使用,否则根据 ``database_type`` 构建。 """ if self.database_url: return self.database_url if self.database_type == "sqlite": db_path = self.resolve_from_backend(self.sqlite_database_path) return f"sqlite+aiosqlite:///{db_path}" password = self.postgres_password ssl = "?ssl=require" if self.postgres_ssl else "" return ( f"postgresql+asyncpg://{self.postgres_user}:{password}" f"@{self.postgres_host}:{self.postgres_port}/{self.postgres_db}{ssl}" ) @cached_property def resolved_log_dir(self) -> Path: """解析日志目录为绝对路径。""" return self.resolve_from_backend(self.log_dir) @cached_property def resolved_character_templates_path(self) -> Path: """解析人物模板配置文件路径。 如果配置文件不存在,尝试使用示例配置文件。 """ configured = self.resolve_from_backend(self.character_templates_path) if configured.exists(): return configured example = self.project_root / "config" / "character_templates.example.yaml" if example.exists(): return example return configured @cached_property def resolved_avatar_dir(self) -> Path: """解析角色头像存储目录为绝对路径。""" path = self.resolve_from_backend(self.avatar_dir) path.mkdir(parents=True, exist_ok=True) return path # 全局配置实例 settings = Settings()