Files
SenSu/utils/config_utils.py
T
AskaEth e6875f0b4b Initial commit: SenSu Alpha 0.2.0
- 13-service async plugin framework
- Textual TUI with CLI fallback
- Plugin hot-reload + permission system
- Web management panel (aiohttp)
- Bridge-based inter-module communication
- 10 regression tests

Fixes applied:
- PBKDF2-SHA256 auth (was plain SHA256)
- Auth bypass removed (was allow-all on fail)
- Bare excepts replaced with logged errors
- CatFramework/DreamSu -> SenSu naming unified
- ServiceManager: health checks + startup_order
- Env var credentials (SENSU_ADMIN_PASSWORD etc)
2026-06-10 12:28:05 +08:00

204 lines
7.8 KiB
Python

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import logging
import yaml
import json
from pathlib import Path
from typing import Dict, Any, Optional
import copy
logger = logging.getLogger(__name__)
class ConfigUtils:
"""配置工具类"""
@staticmethod
def load_yaml_config(file_path: str, default_config: Dict = None) -> Dict:
"""加载YAML配置文件"""
try:
path = Path(file_path)
if not path.exists():
logger.warning(f"YAML配置文件不存在: {file_path}")
if default_config:
ConfigUtils.save_yaml_config(file_path, default_config)
logger.debug(f"已创建默认YAML配置: {file_path}")
return default_config or {}
with open(path, 'r', encoding='utf-8') as f:
config = yaml.safe_load(f)
logger.debug(f"YAML配置加载成功: {file_path}")
return config or {}
except yaml.YAMLError as e:
logger.error(f"YAML配置文件解析错误 {file_path}: {str(e)}", exc_info=True)
return default_config or {}
except Exception as e:
logger.error(f"加载YAML配置时出错 {file_path}: {str(e)}", exc_info=True)
return default_config or {}
@staticmethod
def save_yaml_config(file_path: str, config: Dict) -> bool:
"""保存YAML配置文件"""
try:
path = Path(file_path)
path.parent.mkdir(parents=True, exist_ok=True)
with open(path, 'w', encoding='utf-8') as f:
yaml.dump(config, f, default_flow_style=False, allow_unicode=True, indent=2)
logger.debug(f"YAML配置保存成功: {file_path}")
return True
except Exception as e:
logger.error(f"保存YAML配置时出错 {file_path}: {str(e)}", exc_info=True)
return False
@staticmethod
def load_json_config(file_path: str, default_config: Dict = None) -> Dict:
"""加载JSON配置文件"""
try:
path = Path(file_path)
if not path.exists():
logger.warning(f"JSON配置文件不存在: {file_path}")
if default_config:
ConfigUtils.save_json_config(file_path, default_config)
logger.debug(f"已创建默认JSON配置: {file_path}")
return default_config or {}
with open(path, 'r', encoding='utf-8') as f:
config = json.load(f)
logger.debug(f"JSON配置加载成功: {file_path}")
return config
except json.JSONDecodeError as e:
logger.error(f"JSON配置文件解析错误 {file_path}: {str(e)}", exc_info=True)
return default_config or {}
except Exception as e:
logger.error(f"加载JSON配置时出错 {file_path}: {str(e)}", exc_info=True)
return default_config or {}
@staticmethod
def save_json_config(file_path: str, config: Dict) -> bool:
"""保存JSON配置文件"""
try:
path = Path(file_path)
path.parent.mkdir(parents=True, exist_ok=True)
with open(path, 'w', encoding='utf-8') as f:
json.dump(config, f, ensure_ascii=False, indent=2)
logger.debug(f"JSON配置保存成功: {file_path}")
return True
except Exception as e:
logger.error(f"保存JSON配置时出错 {file_path}: {str(e)}", exc_info=True)
return False
@staticmethod
def get_nested_value(config: Dict, key_path: str, default: Any = None) -> Any:
"""获取嵌套配置值"""
try:
keys = key_path.split('.')
current = config
for key in keys:
if isinstance(current, dict) and key in current:
current = current[key]
else:
logger.debug(f"配置键不存在: {key_path}")
return default
logger.debug(f"获取嵌套配置值: {key_path} -> {current}")
return current
except Exception as e:
logger.error(f"获取嵌套配置值时出错 {key_path}: {str(e)}", exc_info=True)
return default
@staticmethod
def set_nested_value(config: Dict, key_path: str, value: Any) -> bool:
"""设置嵌套配置值"""
try:
keys = key_path.split('.')
current = config
# 遍历到最后一个键的父级
for key in keys[:-1]:
if key not in current or not isinstance(current[key], dict):
current[key] = {}
current = current[key]
# 设置值
current[keys[-1]] = value
logger.debug(f"设置嵌套配置值: {key_path} -> {value}")
return True
except Exception as e:
logger.error(f"设置嵌套配置值时出错 {key_path}: {str(e)}", exc_info=True)
return False
@staticmethod
def merge_configs(base_config: Dict, override_config: Dict) -> Dict:
"""合并配置(深度合并)"""
try:
result = copy.deepcopy(base_config)
for key, value in override_config.items():
if (key in result and
isinstance(result[key], dict) and
isinstance(value, dict)):
# 递归合并字典
result[key] = ConfigUtils.merge_configs(result[key], value)
else:
# 直接覆盖
result[key] = copy.deepcopy(value)
logger.debug("配置合并完成")
return result
except Exception as e:
logger.error(f"合并配置时出错: {str(e)}", exc_info=True)
return base_config
@staticmethod
def validate_config_structure(config: Dict, schema: Dict) -> bool:
"""验证配置结构"""
try:
def _validate(current_config, current_schema, path=""):
for key, expected_type in current_schema.items():
full_path = f"{path}.{key}" if path else key
if key not in current_config:
logger.error(f"配置缺少必要字段: {full_path}")
return False
actual_value = current_config[key]
expected_type_name = expected_type.__name__ if hasattr(expected_type, '__name__') else str(expected_type)
if not isinstance(actual_value, expected_type):
logger.error(f"配置类型错误 {full_path}: 期望 {expected_type_name}, 实际 {type(actual_value).__name__}")
return False
# 如果是字典且schema有嵌套定义,递归验证
if (isinstance(expected_type, dict) and
isinstance(actual_value, dict)):
if not _validate(actual_value, expected_type, full_path):
return False
return True
result = _validate(config, schema)
if result:
logger.debug("配置结构验证通过")
else:
logger.error("配置结构验证失败")
return result
except Exception as e:
logger.error(f"验证配置结构时出错: {str(e)}", exc_info=True)
return False