Files
market_sync/app/core/config.py
T
gao 8f016f25df chore: baseline — ORM migration + systemd deploy + MCP server + daily check
- ORM migration: models/ops 重构
- deploy: 标准化 systemd timer/service 部署体系
- MCP server: 5 个只读工具(任务/状态/数据集)
- daily check: 数据一致性巡检
- watch: task_watch 后台监控
- kline_daily: 麦蕊优先,时间门禁15:00
- 删除: task_industry_sector, task_sector_features
2026-07-21 16:37:00 +08:00

115 lines
3.1 KiB
Python

"""统一配置:从 .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"
# 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()