133 lines
3.9 KiB
Python
133 lines
3.9 KiB
Python
"""配置加载与设置模型。
|
||
|
||
优先级(高 -> 低):
|
||
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,
|
||
)
|