- 新增 smart_read_excel 工具函数,统一 Excel 读取逻辑并自动选择引擎 - 重构 ConfigManager.get_path 方法,使用 pathlib 提升路径处理可靠性 - 将 GUI 日志处理改为异步队列模式,避免 UI 阻塞 - 优化 ExcelProcessor 的表头识别逻辑,避免重复读取文件 - 更新配置文件中的版本号
145 lines
5.6 KiB
Python
145 lines
5.6 KiB
Python
"""
|
||
配置管理模块
|
||
-----------
|
||
提供统一的配置加载、访问和保存功能。
|
||
"""
|
||
|
||
import os
|
||
import configparser
|
||
import logging
|
||
from typing import Dict, List, Optional, Any
|
||
|
||
from .defaults import DEFAULT_CONFIG
|
||
|
||
logger = logging.getLogger(__name__)
|
||
|
||
class ConfigManager:
|
||
"""
|
||
配置管理类,负责加载和保存配置
|
||
单例模式确保全局只有一个配置实例
|
||
"""
|
||
_instance = None
|
||
|
||
def __new__(cls, config_file=None):
|
||
"""单例模式实现"""
|
||
if cls._instance is None:
|
||
cls._instance = super(ConfigManager, cls).__new__(cls)
|
||
cls._instance._init(config_file)
|
||
return cls._instance
|
||
|
||
def _init(self, config_file):
|
||
"""初始化配置管理器"""
|
||
self.config_file = config_file or 'config.ini'
|
||
self.config = configparser.ConfigParser()
|
||
self.load_config()
|
||
|
||
def load_config(self) -> None:
|
||
"""
|
||
加载配置文件,如果不存在则创建默认配置
|
||
"""
|
||
if not os.path.exists(self.config_file):
|
||
self.create_default_config()
|
||
else:
|
||
try:
|
||
# 先读取现有配置
|
||
self.config.read(self.config_file, encoding='utf-8')
|
||
|
||
# 检查是否有缺失的配置项,只添加缺失的项
|
||
for section, options in DEFAULT_CONFIG.items():
|
||
if not self.config.has_section(section):
|
||
self.config.add_section(section)
|
||
|
||
for option, value in options.items():
|
||
if not self.config.has_option(section, option):
|
||
self.config.set(section, option, value)
|
||
|
||
# 保存更新后的配置
|
||
self.save_config()
|
||
logger.info(f"已加载并更新配置文件: {self.config_file}")
|
||
except Exception as e:
|
||
logger.error(f"加载配置文件时出错: {e}")
|
||
logger.info("使用默认配置")
|
||
self.create_default_config(save=False)
|
||
|
||
def create_default_config(self, save: bool = True) -> None:
|
||
"""创建默认配置"""
|
||
for section, options in DEFAULT_CONFIG.items():
|
||
if not self.config.has_section(section):
|
||
self.config.add_section(section)
|
||
|
||
for option, value in options.items():
|
||
self.config.set(section, option, value)
|
||
|
||
if save:
|
||
self.save_config()
|
||
logger.info(f"已创建默认配置文件: {self.config_file}")
|
||
|
||
def save_config(self) -> None:
|
||
"""保存配置到文件"""
|
||
try:
|
||
with open(self.config_file, 'w', encoding='utf-8') as f:
|
||
self.config.write(f)
|
||
logger.info(f"配置已保存到: {self.config_file}")
|
||
except Exception as e:
|
||
logger.error(f"保存配置文件时出错: {e}")
|
||
|
||
def get(self, section: str, option: str, fallback: Any = None) -> Any:
|
||
"""获取配置值"""
|
||
return self.config.get(section, option, fallback=fallback)
|
||
|
||
def getint(self, section: str, option: str, fallback: int = 0) -> int:
|
||
"""获取整数配置值"""
|
||
return self.config.getint(section, option, fallback=fallback)
|
||
|
||
def getfloat(self, section: str, option: str, fallback: float = 0.0) -> float:
|
||
"""获取浮点数配置值"""
|
||
return self.config.getfloat(section, option, fallback=fallback)
|
||
|
||
def getboolean(self, section: str, option: str, fallback: bool = False) -> bool:
|
||
"""获取布尔配置值"""
|
||
return self.config.getboolean(section, option, fallback=fallback)
|
||
|
||
def get_list(self, section: str, option: str, fallback: str = "", delimiter: str = ",") -> List[str]:
|
||
"""获取列表配置值(逗号分隔的字符串转为列表)"""
|
||
value = self.get(section, option, fallback)
|
||
return [item.strip() for item in value.split(delimiter) if item.strip()]
|
||
|
||
def update(self, section: str, option: str, value: Any) -> None:
|
||
"""更新配置选项"""
|
||
if not self.config.has_section(section):
|
||
self.config.add_section(section)
|
||
|
||
self.config.set(section, option, str(value))
|
||
logger.debug(f"更新配置: [{section}] {option} = {value}")
|
||
|
||
def get_path(self, section: str, option: str, fallback: str = "", create: bool = False) -> str:
|
||
"""
|
||
获取路径配置并确保它是一个有效的绝对路径
|
||
如果create为True,则自动创建该目录
|
||
"""
|
||
from pathlib import Path
|
||
path_str = self.get(section, option, fallback)
|
||
path = Path(path_str)
|
||
|
||
if not path.is_absolute():
|
||
# 相对路径,转为绝对路径(相对于项目根目录)
|
||
path = Path(os.getcwd()) / path
|
||
|
||
if create:
|
||
try:
|
||
# 智能判断是文件还是目录
|
||
# 如果有后缀名则认为是文件,创建其父目录
|
||
if path.suffix:
|
||
directory = path.parent
|
||
if not directory.exists():
|
||
directory.mkdir(parents=True, exist_ok=True)
|
||
logger.info(f"已创建父目录: {directory}")
|
||
else:
|
||
# 否则认为是目录路径
|
||
if not path.exists():
|
||
path.mkdir(parents=True, exist_ok=True)
|
||
logger.info(f"已创建目录: {path}")
|
||
except Exception as e:
|
||
logger.error(f"创建目录失败: {path}, 错误: {e}")
|
||
|
||
return str(path.absolute()) |