SmartEDT/backend/config/settings.py

133 lines
3.9 KiB
Python
Raw Normal View History

"""配置加载与设置模型。
优先级 ->
1. 环境变量 SMARTEDT_CONFIG 指定的配置文件
2. 在若干候选位置寻找 config.ini兼容 PyInstaller 打包运行态
3. 环境变量的 fallback
4. 内置默认值
"""
from __future__ import annotations
import configparser
import os
from dataclasses import dataclass
from pathlib import Path
@dataclass(frozen=True)
class ServerSettings:
"""服务监听配置。"""
host: str = "0.0.0.0"
port: int = 5000
debug: bool = False
@dataclass(frozen=True)
class FileSettings:
"""文件存储相关配置。"""
root_path: Path
@dataclass(frozen=True)
class DatabaseSettings:
"""数据库连接相关配置。"""
url: str
timescaledb: bool = True
@dataclass(frozen=True)
class UnitySettings:
host: str = "127.0.0.1"
port: int = 6000
@dataclass(frozen=True)
class AppSettings:
"""应用聚合配置。"""
server: ServerSettings
files: FileSettings
database: DatabaseSettings
unity: UnitySettings
import sys
def _find_config_file() -> Path | None:
"""尝试从若干候选位置定位 config.ini包含 PyInstaller 运行态)。"""
# Handle PyInstaller frozen state
if getattr(sys, 'frozen', False):
# If onefile, _MEIPASS. If onedir, executable dir or _internal
# With onedir, the exe is in root, internal files in _internal.
# But our spec put config in backend/config
# Check relative to executable
exe_dir = Path(sys.executable).parent
candidates = [
exe_dir / "backend" / "config" / "config.ini",
exe_dir / "_internal" / "backend" / "config" / "config.ini",
exe_dir / "config.ini",
]
for path in candidates:
if path.exists():
return path
candidates = [
Path(__file__).resolve().parent / "config.ini",
Path(__file__).resolve().parents[1] / "config.ini",
Path(__file__).resolve().parents[2] / "config.ini",
]
for path in candidates:
if path.exists():
return path
return None
def load_settings() -> AppSettings:
"""加载并返回应用配置。"""
config = configparser.ConfigParser()
config_path = os.getenv("SMARTEDT_CONFIG")
if config_path:
config.read(config_path, encoding="utf-8")
else:
found = _find_config_file()
if found:
config.read(found, encoding="utf-8")
server = ServerSettings(
host=config.get("SERVER", "host", fallback=os.getenv("SMARTEDT_HOST", "0.0.0.0")),
port=config.getint("SERVER", "port", fallback=int(os.getenv("SMARTEDT_PORT", "5000"))),
debug=config.getboolean("SERVER", "debug", fallback=os.getenv("SMARTEDT_DEBUG", "False").lower() == "true"),
)
default_root = Path(os.getenv("SMARTEDT_FILE_ROOT", "data"))
root_value = config.get("FILEPATH", "path", fallback=str(default_root))
root_path = Path(root_value)
if not root_path.is_absolute():
root_path = (Path(__file__).resolve().parents[1] / root_path).resolve()
database_url = config.get("DATABASE", "url", fallback=os.getenv("SMARTEDT_DATABASE_URL", "")).strip()
if not database_url:
database_url = "postgresql+psycopg://smartedt:CHANGE_ME@127.0.0.1:5432/smartedt"
timescaledb = config.getboolean(
"DATABASE",
"timescaledb",
fallback=os.getenv("SMARTEDT_TIMESCALEDB", "True").lower() == "true",
)
unity = UnitySettings(
host=config.get("UNITY", "host", fallback=os.getenv("SMARTEDT_UNITY_HOST", "127.0.0.1")),
port=config.getint("UNITY", "port", fallback=int(os.getenv("SMARTEDT_UNITY_PORT", "6000"))),
)
return AppSettings(
server=server,
files=FileSettings(root_path=root_path),
database=DatabaseSettings(url=database_url, timescaledb=timescaledb),
unity=unity,
)