"""统一配置:从 .env / 环境变量读 PG、调度器、数据源开关等参数。 2026-06-16 重构:项目只支持 PostgreSQL,MySQL 字段全部移除。 """ from __future__ import annotations import os from pathlib import Path from typing import Optional from pydantic_settings import BaseSettings, SettingsConfigDict try: from dotenv import load_dotenv _HAS_DOTENV = True except ImportError: _HAS_DOTENV = False _PROJECT_ROOT = Path(__file__).resolve().parent.parent.parent # 启动时自动把 .env 加载到 os.environ,使下游 `os.environ.get(...)` 代码 # (如 app/sources/mairui.py / xueqiu.py)能拿到 credentials。 if _HAS_DOTENV: _env_file = _PROJECT_ROOT / ".env" if _env_file.exists(): load_dotenv(_env_file, override=False) class Settings(BaseSettings): """应用配置。所有字段都可以通过环境变量或 .env 文件覆盖。""" # Runtime mode: "docker" | "systemd" # docker : 单容器运行,依赖进程内 scheduler 触发所有同步任务 # systemd : Linux 宿主机运行,由 systemd timer/service 触发任务, # 进程内 scheduler 必须关闭,避免双调度器重叠 runtime_mode: str = "docker" # Scheduler scheduler_tick_seconds: int = 5 scheduler_timezone: str = "Asia/Shanghai" scheduler_auto_seed: bool = True # API api_host: str = "0.0.0.0" api_port: int = 8100 # Logging log_level: str = "INFO" log_dir: str = "./logs" # Datasources ds_baostock_enabled: bool = True ds_sina_enabled: bool = True xueqiu_token: str = "" # PostgreSQL — 完整 URL(覆盖下面 PG_HOST 等) pg_url: str = "" pg_host: str = "127.0.0.1" pg_port: int = 5432 pg_user: str = "market_sync" pg_password: str = "market_sync" pg_db_name: str = "market_data" # QMT Bridge qmt_bridge_url: str = "http://127.0.0.1:8610" # Trading calendar trading_holidays: str = "" model_config = SettingsConfigDict( env_file=str(_PROJECT_ROOT / ".env"), env_file_encoding="utf-8", case_sensitive=False, extra="ignore", ) @property def project_root(self) -> Path: return _PROJECT_ROOT @property def log_path(self) -> Path: p = Path(self.log_dir) if not p.is_absolute(): p = _PROJECT_ROOT / p p.mkdir(parents=True, exist_ok=True) return p @property def holidays_list(self) -> list[str]: if not self.trading_holidays: return [] return [d.strip() for d in self.trading_holidays.split(",") if d.strip()] def pg_sqlalchemy_url(self) -> str: """构造 PG 的 SQLAlchemy URL(自动剥掉 +psycopg2 让 SQLAlchemy 决定 driver)。""" if self.pg_url: return self.pg_url return ( f"postgresql+psycopg2://{self.pg_user}:{self.pg_password}" f"@{self.pg_host}:{self.pg_port}/{self.pg_db_name}" ) _settings: Optional[Settings] = None def get_settings() -> Settings: """全局单例。""" global _settings if _settings is None: _settings = Settings() return _settings settings = get_settings()