feat(config): isolate config and database via HAOCODE_CONFIG_FILE override
core/config_paths.py resolves a single override so tests and multiple instances can run against isolated temp config/db without touching user data.
This commit is contained in:
@@ -0,0 +1,77 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""core/config_paths.py —— 统一的配置文件路径解析与容错加载(P0-01 新增)
|
||||
|
||||
所有配置读取入口必须经过本模块,不得在别处直接定位 data/config.json:
|
||||
|
||||
1. 环境变量 HAOCODE_CONFIG_FILE 优先级最高(自动化测试用它指向临时文件);
|
||||
2. 未设置环境变量时才回落到项目内 data/config.json(锚定 core/ 目录,不受 CWD 影响)。
|
||||
|
||||
行为约定(REPAIR_BACKLOG P0-01 硬约束):
|
||||
· 路径解析与加载过程只在出错时输出可见警告(只含路径与错误类型,不含配置内容);
|
||||
· 缺失 / 损坏 / 非 JSON 对象 → 返回安全空 dict 继续,不抛异常、不阻断
|
||||
不需要该配置的源码路径;
|
||||
· import 本模块时不打印、不复制、不迁移任何配置内容。
|
||||
"""
|
||||
import json
|
||||
import os
|
||||
|
||||
ENV_KEY = "HAOCODE_CONFIG_FILE"
|
||||
|
||||
_DEFAULT_CONFIG_PATH = os.path.join(
|
||||
os.path.dirname(os.path.abspath(__file__)), "..", "data", "config.json")
|
||||
|
||||
|
||||
def config_path() -> str:
|
||||
"""返回当前生效的配置文件路径(环境变量优先,调用时解析)。"""
|
||||
p = os.environ.get(ENV_KEY, "").strip()
|
||||
return p if p else _DEFAULT_CONFIG_PATH
|
||||
|
||||
|
||||
def load_config() -> dict:
|
||||
"""容错加载配置。缺失/损坏/非对象 → 可见警告 + 安全空 dict(绝不抛异常)。"""
|
||||
p = config_path()
|
||||
if not os.path.exists(p):
|
||||
print(f"[config] 配置文件缺失: {p} → 使用安全默认值(providers 为空)继续启动")
|
||||
return {}
|
||||
try:
|
||||
with open(p, "r", encoding="utf-8") as f:
|
||||
data = json.load(f)
|
||||
except Exception as e:
|
||||
print(f"[config] 配置文件读取/解析失败: {p}({type(e).__name__})"
|
||||
f" → 使用安全默认值继续启动")
|
||||
return {}
|
||||
if not isinstance(data, dict):
|
||||
print(f"[config] 配置文件内容不是 JSON 对象: {p}"
|
||||
f" → 使用安全默认值继续启动")
|
||||
return {}
|
||||
return data
|
||||
|
||||
|
||||
# ----------------------------------------------------------------------
|
||||
# P1-01:渲染窗口配置解析(render_window_mode / render_window_size)
|
||||
# 规则(与 ui/web/render_window.js 的 JS 侧守卫保持一致):
|
||||
# size:只接受非布尔整数 10..200,缺失/布尔/字符串/小数/零/负数/越界 → 静默回落 40;
|
||||
# mode:只接受 "auto"/"manual",否则回落 "auto"。
|
||||
# ----------------------------------------------------------------------
|
||||
DEFAULT_RENDER_WINDOW_SIZE = 40
|
||||
ALLOWED_RENDER_WINDOW_SIZES = (10, 40, 200)
|
||||
|
||||
|
||||
def render_window_settings(cfg: dict | None = None) -> dict:
|
||||
"""解析渲染窗口配置。cfg 缺省时读取当前生效配置。永不抛异常。"""
|
||||
if cfg is None:
|
||||
cfg = load_config()
|
||||
if not isinstance(cfg, dict):
|
||||
cfg = {}
|
||||
|
||||
raw_size = cfg.get("render_window_size", None)
|
||||
if isinstance(raw_size, bool) or not isinstance(raw_size, int) \
|
||||
or raw_size < 10 or raw_size > 200:
|
||||
size = DEFAULT_RENDER_WINDOW_SIZE
|
||||
else:
|
||||
size = raw_size
|
||||
|
||||
raw_mode = cfg.get("render_window_mode", None)
|
||||
mode = raw_mode if raw_mode in ("auto", "manual") else "auto"
|
||||
|
||||
return {"mode": mode, "size": size}
|
||||
Reference in New Issue
Block a user