2026-01-25 22:34:33 +08:00
|
|
|
|
"""配置加载与设置模型。
|
|
|
|
|
|
|
|
|
|
|
|
优先级(高 -> 低):
|
|
|
|
|
|
1. 环境变量 SMARTEDT_CONFIG 指定的配置文件
|
|
|
|
|
|
2. 在若干候选位置寻找 config.ini(兼容 PyInstaller 打包运行态)
|
|
|
|
|
|
3. 环境变量的 fallback
|
|
|
|
|
|
4. 内置默认值
|
|
|
|
|
|
"""
|
|
|
|
|
|
|
2026-01-19 14:27:41 +08:00
|
|
|
|
from __future__ import annotations
|
|
|
|
|
|
|
|
|
|
|
|
import configparser
|
|
|
|
|
|
import os
|
|
|
|
|
|
from dataclasses import dataclass
|
|
|
|
|
|
from pathlib import Path
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
|
|
|
|
class ServerSettings:
|
2026-01-25 22:34:33 +08:00
|
|
|
|
"""服务监听配置。"""
|
|
|
|
|
|
|
2026-01-19 14:27:41 +08:00
|
|
|
|
host: str = "0.0.0.0"
|
|
|
|
|
|
port: int = 5000
|
|
|
|
|
|
debug: bool = False
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
|
|
|
|
class FileSettings:
|
2026-01-25 22:34:33 +08:00
|
|
|
|
"""文件存储相关配置。"""
|
|
|
|
|
|
|
2026-01-19 14:27:41 +08:00
|
|
|
|
root_path: Path
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
|
|
|
|
class DatabaseSettings:
|
2026-01-25 22:34:33 +08:00
|
|
|
|
"""数据库连接相关配置。"""
|
|
|
|
|
|
|
2026-01-19 14:27:41 +08:00
|
|
|
|
url: str
|
|
|
|
|
|
timescaledb: bool = True
|
|
|
|
|
|
|
|
|
|
|
|
|
2026-01-25 22:34:33 +08:00
|
|
|
|
@dataclass(frozen=True)
|
|
|
|
|
|
class UnitySettings:
|
|
|
|
|
|
host: str = "127.0.0.1"
|
|
|
|
|
|
port: int = 6000
|
|
|
|
|
|
|
|
|
|
|
|
|
2026-01-19 14:27:41 +08:00
|
|
|
|
@dataclass(frozen=True)
|
|
|
|
|
|
class AppSettings:
|
2026-01-25 22:34:33 +08:00
|
|
|
|
"""应用聚合配置。"""
|
|
|
|
|
|
|
2026-01-19 14:27:41 +08:00
|
|
|
|
server: ServerSettings
|
|
|
|
|
|
files: FileSettings
|
|
|
|
|
|
database: DatabaseSettings
|
2026-01-25 22:34:33 +08:00
|
|
|
|
unity: UnitySettings
|
2026-01-19 14:27:41 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
import sys
|
|
|
|
|
|
|
|
|
|
|
|
def _find_config_file() -> Path | None:
|
2026-01-25 22:34:33 +08:00
|
|
|
|
"""尝试从若干候选位置定位 config.ini(包含 PyInstaller 运行态)。"""
|
2026-01-19 14:27:41 +08:00
|
|
|
|
# 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:
|
2026-01-25 22:34:33 +08:00
|
|
|
|
"""加载并返回应用配置。"""
|
2026-01-19 14:27:41 +08:00
|
|
|
|
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",
|
|
|
|
|
|
)
|
|
|
|
|
|
|
2026-01-25 22:34:33 +08:00
|
|
|
|
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"))),
|
|
|
|
|
|
)
|
|
|
|
|
|
|
2026-01-19 14:27:41 +08:00
|
|
|
|
return AppSettings(
|
|
|
|
|
|
server=server,
|
|
|
|
|
|
files=FileSettings(root_path=root_path),
|
|
|
|
|
|
database=DatabaseSettings(url=database_url, timescaledb=timescaledb),
|
2026-01-25 22:34:33 +08:00
|
|
|
|
unity=unity,
|
2026-01-19 14:27:41 +08:00
|
|
|
|
)
|