#E:\AI_System\core\config.py
import os # 必须添加此导入
import json
import logging
import configparser
from pathlib import Path
from typing import Dict, Any, Optional, Union
from dotenv import load_dotenv
class CoreConfig:
"""
核心配置管理类 - 提供统一的配置管理接口
支持多来源配置加载:环境变量 > 配置文件 > 默认值
"""
def __init__(self,
config_path: Optional[Union[str, Path]] = None,
env_prefix: str = "APP_",
default_config: Optional[Dict[str, Any]] = None):
"""
初始化配置管理器
:param config_path: 配置文件路径(支持.json, .ini, .env)
:param env_prefix: 环境变量前缀
:param default_config: 默认配置字典
"""
# 初始化日志
self.logger = logging.getLogger('CoreConfig')
self._setup_logger()
# 设置实例变量
self.env_prefix = env_prefix
self.config_path = Path(config_path) if config_path else None
self.config_data = default_config or {}
self.config_modified = False
self.log(f"📋 初始化配置管理器 | 环境前缀: {env_prefix}")
# 加载配置
self.load_configuration()
def _setup_logger(self):
"""配置日志记录器"""
if not self.logger.handlers:
handler = logging.StreamHandler()
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
handler.setFormatter(formatter)
self.logger.addHandler(handler)
self.logger.setLevel(logging.INFO)
self.logger.propagate = False
def log(self, message: str, level: str = "info"):
"""记录日志"""
log_levels = {
"debug": self.logger.debug,
"info": self.logger.info,
"warning": self.logger.warning,
"error": self.logger.error
}
log_func = log_levels.get(level.lower(), self.logger.info)
log_func(message)
def load_configuration(self):
"""加载所有配置源"""
# 1. 加载环境变量
self._load_environment_vars()
# 2. 加载配置文件(如果存在)
if self.config_path and self.config_path.exists():
self._load_config_file()
elif self.config_path:
self.log(f"⚠️ 配置文件不存在: {self.config_path}", "warning")
self.log(f"✅ 配置加载完成 | 条目数: {len(self.config_data)}")
def _load_environment_vars(self):
"""加载环境变量"""
self.log("🔍 加载环境变量...")
load_dotenv() # 加载.env文件(如果存在)
# 获取所有以指定前缀开头的环境变量
for key, value in os.environ.items():
if key.startswith(self.env_prefix):
config_key = key[len(self.env_prefix):].lower()
self.config_data[config_key] = self._parse_value(value)
self.log(f" - 加载环境变量: {config_key} = {self._mask_secret(config_key, value)}")
def _load_config_file(self):
"""根据文件扩展名加载配置文件"""
suffix = self.config_path.suffix.lower()
self.log(f"📄 加载配置文件: {self.config_path} (类型: {suffix[1:]})")
try:
if suffix == '.json':
self._load_json_config()
elif suffix in ('.ini', '.cfg'):
self._load_ini_config()
elif suffix == '.env':
self._load_dotenv_config()
else:
self.log(f"❌ 不支持的配置文件类型: {suffix}", "error")
except Exception as e:
self.log(f"❌ 配置文件加载失败: {str(e)}", "error")
def _load_json_config(self):
"""加载JSON配置文件"""
with open(self.config_path, 'r', encoding='utf-8') as f:
json_data = json.load(f)
self._merge_config(json_data)
def _load_ini_config(self):
"""加载INI配置文件"""
parser = configparser.ConfigParser()
parser.read(self.config_path)
config_dict = {}
for section in parser.sections():
section_dict = {}
for key, value in parser.items(section):
section_dict[key] = self._parse_value(value)
config_dict[section] = section_dict
self._merge_config(config_dict)
def _load_dotenv_config(self):
"""加载.env配置文件"""
# 已由load_dotenv()处理,这里只需记录
self.log(" - .env文件已加载")
def _merge_config(self, new_config: Dict[str, Any]):
"""合并新配置到现有配置"""
for key, value in new_config.items():
# 处理嵌套配置
if isinstance(value, dict) and key in self.config_data and isinstance(self.config_data[key], dict):
self.config_data[key].update(value)
else:
self.config_data[key] = value
self.log(f" - 加载配置项: {key} = {self._mask_secret(key, value)}")
def _parse_value(self, value: str) -> Any:
"""智能解析字符串值为合适的Python类型"""
# 尝试解析为布尔值
if value.lower() in ('true', 'yes', 'on'):
return True
if value.lower() in ('false', 'no', 'off'):
return False
# 尝试解析为数字
try:
return int(value)
except ValueError:
try:
return float(value)
except ValueError:
pass
# 尝试解析为列表(仅当明确使用逗号分隔时)
if isinstance(value, str) and ',' in value and ' ' not in value.strip():
return [self._parse_value(item.strip()) for item in value.split(',')]
# 返回原始字符串
return value
def _mask_secret(self, key: str, value: Any) -> str:
"""对敏感信息进行掩码处理"""
if 'secret' in key or 'password' in key or 'key' in key or 'token' in key:
return '******' if value else '空'
if isinstance(value, list):
return f"[列表, 长度: {len(value)}]"
if isinstance(value, dict):
return f"{{字典, 键数: {len(value)}}}"
return str(value)
def get(self, key: str, default: Any = None) -> Any:
"""
获取配置值,支持点分路径 (如: 'database.host')
:param key: 配置键名
:param default: 默认值(如果键不存在)
:return: 配置值
"""
keys = key.split('.')
current = self.config_data
for k in keys:
if isinstance(current, dict) and k in current:
current = current[k]
else:
return default
return current
def set(self, key: str, value: Any, persist: bool = False):
"""
设置配置值
:param key: 配置键名
:param value: 配置值
:param persist: 是否持久化到配置文件
"""
keys = key.split('.')
current = self.config_data
# 遍历创建嵌套字典
for i, k in enumerate(keys[:-1]):
if k not in current or not isinstance(current[k], dict):
current[k] = {}
current = current[k]
# 设置最终值
last_key = keys[-1]
current[last_key] = value
self.config_modified = True
self.log(f"⚙️ 设置配置: {key} = {self._mask_secret(last_key, value)}")
# 持久化到文件
if persist and self.config_path:
self.save_config()
def save_config(self, path: Optional[Union[str, Path]] = None):
"""
保存配置到文件
:param path: 可选的自定义保存路径
"""
save_path = Path(path) if path else self.config_path
if not save_path:
self.log("❌ 保存失败: 未指定配置文件路径", "error")
return False
suffix = save_path.suffix.lower()
try:
if suffix == '.json':
with open(save_path, 'w', encoding='utf-8') as f:
json.dump(self.config_data, f, indent=2, ensure_ascii=False)
elif suffix in ('.ini', '.cfg'):
self._save_ini_config(save_path)
else:
self.log(f"❌ 不支持的文件格式: {suffix}", "error")
return False
self.log(f"💾 配置已保存到: {save_path}")
self.config_modified = False
return True
except Exception as e:
self.log(f"❌ 配置保存失败: {str(e)}", "error")
return False
def _save_ini_config(self, path: Path):
"""保存为INI格式配置文件"""
parser = configparser.ConfigParser()
# 递归处理嵌套字典
def add_section(data, section_name=None):
if section_name:
parser.add_section(section_name)
for key, value in data.items():
if isinstance(value, dict):
add_section(value, f"{section_name}.{key}" if section_name else key)
else:
if not section_name:
parser.set('DEFAULT', key, str(value))
else:
parser.set(section_name, key, str(value))
add_section(self.config_data)
with open(path, 'w', encoding='utf-8') as f:
parser.write(f)
def reload(self):
"""重新加载所有配置源"""
self.log("🔄 重新加载配置...")
self.config_data = {}
self.load_configuration()
def to_dict(self) -> Dict[str, Any]:
"""返回当前配置的完整字典"""
return self.config_data.copy()
def __getitem__(self, key: str) -> Any:
"""支持字典式访问"""
return self.get(key)
def __setitem__(self, key: str, value: Any):
"""支持字典式设置"""
self.set(key, value)
def __contains__(self, key: str) -> bool:
"""检查配置项是否存在"""
return self.get(key) is not None
# 创建全局系统配置实例
system_config = CoreConfig(
config_path=Path(__file__).parent.parent / "config" / "system_config.json",
env_prefix="AI_SYSTEM_"
)
# 仅在直接运行时执行测试
if __name__ == "__main__":
run_config_tests()
def run_config_tests():
"""运行配置管理器测试"""
# 设置测试日志
logging.basicConfig(
level=logging.DEBUG,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
print("=" * 50)
print("核心配置管理器测试")
print("=" * 50)
# 创建临时配置文件
config_json = """
{
"database": {
"host": "localhost",
"port": 5432,
"username": "admin",
"password": "db_secret"
},
"logging": {
"level": "INFO",
"path": "/var/log/app.log"
},
"features": ["auth", "caching", "analytics"]
}
"""
test_config_path = "test_config.json"
with open(test_config_path, "w") as f:
f.write(config_json)
try:
# 设置环境变量
os.environ["APP_API_KEY"] = "env_secret_key"
os.environ["APP_DATABASE_PORT"] = "6432" # 覆盖配置文件中的端口
# 创建配置管理器实例
config = CoreConfig(
config_path=test_config_path,
env_prefix="APP_",
default_config={
"app_name": "MyApp",
"version": "1.0.0"
}
)
# 测试配置获取
print("\n测试配置获取:")
print(f"数据库主机: {config.get('database.host')}")
print(f"数据库端口: {config.get('database.port')} (应被环境变量覆盖)")
print(f"API密钥: {config.get('api_key')} (来自环境变量)")
print(f"应用名称: {config.get('app_name')} (来自默认配置)")
print(f"日志级别: {config.get('logging.level')}")
print(f"功能列表: {config.get('features')}")
# 测试配置设置
print("\n测试配置设置:")
config.set("new_feature.enabled", True)
config.set("database.password", "new_secret", persist=True)
print(f"新功能状态: {config.get('new_feature.enabled')}")
# 测试配置保存
print("\n测试配置保存:")
config.save_config("test_config_saved.json")
# 测试配置包含检查
print("\n测试配置包含检查:")
print(f"数据库用户名存在: {'database.username' in config}")
print(f"不存在的键: {'nonexistent.key' in config}")
# 测试配置转字典
print("\n完整配置字典:")
print(json.dumps(config.to_dict(), indent=2, ensure_ascii=False))
finally:
# 清理临时文件
if os.path.exists(test_config_path):
os.remove(test_config_path)
if os.path.exists("test_config_saved.json"):
os.remove("test_config_saved.json")
改成#E:\AI_System\core\config.py
import os # 必须添加此导入
import json
import logging
import configparser
from pathlib import Path
from typing import Dict, Any, Optional, Union
from dotenv import load_dotenv
class CoreConfig:
"""
核心配置管理类 - 提供统一的配置管理接口
支持多来源配置加载:环境变量 > 配置文件 > 默认值
"""
def __init__(self,
config_path: Optional[Union[str, Path]] = None,
env_prefix: str = "APP_",
default_config: Optional[Dict[str, Any]] = None):
"""
初始化配置管理器
:param config_path: 配置文件路径(支持.json, .ini, .env)
:param env_prefix: 环境变量前缀
:param default_config: 默认配置字典
"""
# 初始化日志
self.logger = logging.getLogger('CoreConfig')
self._setup_logger()
# 设置实例变量
self.env_prefix = env_prefix
self.config_path = Path(config_path) if config_path else None
self.config_data = default_config or {}
self.config_modified = False
self.log(f"📋 初始化配置管理器 | 环境前缀: {env_prefix}")
# 加载配置
self.load_configuration()
def _setup_logger(self):
"""配置日志记录器"""
if not self.logger.handlers:
handler = logging.StreamHandler()
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
handler.setFormatter(formatter)
self.logger.addHandler(handler)
self.logger.setLevel(logging.INFO)
self.logger.propagate = False
def log(self, message: str, level: str = "info"):
"""记录日志"""
log_levels = {
"debug": self.logger.debug,
"info": self.logger.info,
"warning": self.logger.warning,
"error": self.logger.error
}
log_func = log_levels.get(level.lower(), self.logger.info)
log_func(message)
def load_configuration(self):
"""加载所有配置源"""
# 1. 加载环境变量
self._load_environment_vars()
# 2. 加载配置文件(如果存在)
if self.config_path and self.config_path.exists():
self._load_config_file()
elif self.config_path:
self.log(f"⚠️ 配置文件不存在: {self.config_path}", "warning")
self.log(f"✅ 配置加载完成 | 条目数: {len(self.config_data)}")
def _load_environment_vars(self):
"""加载环境变量"""
self.log("🔍 加载环境变量...")
load_dotenv() # 加载.env文件(如果存在)
# 获取所有以指定前缀开头的环境变量
for key, value in os.environ.items():
if key.startswith(self.env_prefix):
config_key = key[len(self.env_prefix):].lower()
self.config_data[config_key] = self._parse_value(value)
self.log(f" - 加载环境变量: {config_key} = {self._mask_secret(config_key, value)}")
def _load_config_file(self):
"""根据文件扩展名加载配置文件"""
suffix = self.config_path.suffix.lower()
self.log(f"📄 加载配置文件: {self.config_path} (类型: {suffix[1:]})")
try:
if suffix == '.json':
self._load_json_config()
elif suffix in ('.ini', '.cfg'):
self._load_ini_config()
elif suffix == '.env':
self._load_dotenv_config()
else:
self.log(f"❌ 不支持的配置文件类型: {suffix}", "error")
except Exception as e:
self.log(f"❌ 配置文件加载失败: {str(e)}", "error")
def _load_json_config(self):
"""加载JSON配置文件"""
with open(self.config_path, 'r', encoding='utf-8') as f:
json_data = json.load(f)
self._merge_config(json_data)
def _load_ini_config(self):
"""加载INI配置文件"""
parser = configparser.ConfigParser()
parser.read(self.config_path)
config_dict = {}
for section in parser.sections():
section_dict = {}
for key, value in parser.items(section):
section_dict[key] = self._parse_value(value)
config_dict[section] = section_dict
self._merge_config(config_dict)
def _load_dotenv_config(self):
"""加载.env配置文件"""
# 已由load_dotenv()处理,这里只需记录
self.log(" - .env文件已加载")
def _merge_config(self, new_config: Dict[str, Any]):
"""合并新配置到现有配置"""
for key, value in new_config.items():
# 处理嵌套配置
if isinstance(value, dict) and key in self.config_data and isinstance(self.config_data[key], dict):
self.config_data[key].update(value)
else:
self.config_data[key] = value
self.log(f" - 加载配置项: {key} = {self._mask_secret(key, value)}")
def _parse_value(self, value: str) -> Any:
"""智能解析字符串值为合适的Python类型"""
# 尝试解析为布尔值
if value.lower() in ('true', 'yes', 'on'):
return True
if value.lower() in ('false', 'no', 'off'):
return False
# 尝试解析为数字
try:
return int(value)
except ValueError:
try:
return float(value)
except ValueError:
pass
# 尝试解析为列表(仅当明确使用逗号分隔时)
if isinstance(value, str) and ',' in value and ' ' not in value.strip():
return [self._parse_value(item.strip()) for item in value.split(',')]
# 返回原始字符串
return value
def _mask_secret(self, key: str, value: Any) -> str:
"""对敏感信息进行掩码处理"""
if 'secret' in key or 'password' in key or 'key' in key or 'token' in key:
return '******' if value else '空'
if isinstance(value, list):
return f"[列表, 长度: {len(value)}]"
if isinstance(value, dict):
return f"{{字典, 键数: {len(value)}}}"
return str(value)
def get(self, key: str, default: Any = None) -> Any:
"""
获取配置值,支持点分路径 (如: 'database.host')
:param key: 配置键名
:param default: 默认值(如果键不存在)
:return: 配置值
"""
keys = key.split('.')
current = self.config_data
for k in keys:
if isinstance(current, dict) and k in current:
current = current[k]
else:
return default
return current
def set(self, key: str, value: Any, persist: bool = False):
"""
设置配置值
:param key: 配置键名
:param value: 配置值
:param persist: 是否持久化到配置文件
"""
keys = key.split('.')
current = self.config_data
# 遍历创建嵌套字典
for i, k in enumerate(keys[:-1]):
if k not in current or not isinstance(current[k], dict):
current[k] = {}
current = current[k]
# 设置最终值
last_key = keys[-1]
current[last_key] = value
self.config_modified = True
self.log(f"⚙️ 设置配置: {key} = {self._mask_secret(last_key, value)}")
# 持久化到文件
if persist and self.config_path:
self.save_config()
def save_config(self, path: Optional[Union[str, Path]] = None):
"""
保存配置到文件
:param path: 可选的自定义保存路径
"""
save_path = Path(path) if path else self.config_path
if not save_path:
self.log("❌ 保存失败: 未指定配置文件路径", "error")
return False
suffix = save_path.suffix.lower()
try:
if suffix == '.json':
with open(save_path, 'w', encoding='utf-8') as f:
json.dump(self.config_data, f, indent=2, ensure_ascii=False)
elif suffix in ('.ini', '.cfg'):
self._save_ini_config(save_path)
else:
self.log(f"❌ 不支持的文件格式: {suffix}", "error")
return False
self.log(f"💾 配置已保存到: {save_path}")
self.config_modified = False
return True
except Exception as e:
self.log(f"❌ 配置保存失败: {str(e)}", "error")
return False
def _save_ini_config(self, path: Path):
"""保存为INI格式配置文件"""
parser = configparser.ConfigParser()
# 递归处理嵌套字典
def add_section(data, section_name=None):
if section_name:
parser.add_section(section_name)
for key, value in data.items():
if isinstance(value, dict):
add_section(value, f"{section_name}.{key}" if section_name else key)
else:
if not section_name:
parser.set('DEFAULT', key, str(value))
else:
parser.set(section_name, key, str(value))
add_section(self.config_data)
with open(path, 'w', encoding='utf-8') as f:
parser.write(f)
def reload(self):
"""重新加载所有配置源"""
self.log("🔄 重新加载配置...")
self.config_data = {}
self.load_configuration()
def to_dict(self) -> Dict[str, Any]:
"""返回当前配置的完整字典"""
return self.config_data.copy()
def __getitem__(self, key: str) -> Any:
"""支持字典式访问"""
return self.get(key)
def __setitem__(self, key: str, value: Any):
"""支持字典式设置"""
self.set(key, value)
def __contains__(self, key: str) -> bool:
"""检查配置项是否存在"""
return self.get(key) is not None
# 创建全局系统配置实例
system_config = CoreConfig(
config_path=Path(__file__).parent.parent / "config" / "system_config.json",
env_prefix="AI_SYSTEM_"
)
# 仅在直接运行时执行测试
if __name__ == "__main__":
run_config_tests()
def run_config_tests():
"""运行配置管理器测试"""
# 设置测试日志
logging.basicConfig(
level=logging.DEBUG,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
print("=" * 50)
print("核心配置管理器测试")
print("=" * 50)
# 创建临时配置文件
config_json = """
{
"database": {
"host": "localhost",
"port": 5432,
"username": "admin",
"password": "db_secret"
},
"logging": {
"level": "INFO",
"path": "/var/log/app.log"
},
"features": ["auth", "caching", "analytics"]
}
"""
test_config_path = "test_config.json"
with open(test_config_path, "w") as f:
f.write(config_json)
try:
# 设置环境变量
os.environ["APP_API_KEY"] = "env_secret_key"
os.environ["APP_DATABASE_PORT"] = "6432" # 覆盖配置文件中的端口
# 创建配置管理器实例
config = CoreConfig(
config_path=test_config_path,
env_prefix="APP_",
default_config={
"app_name": "MyApp",
"version": "1.0.0"
}
)
# 测试配置获取
print("\n测试配置获取:")
print(f"数据库主机: {config.get('database.host')}")
print(f"数据库端口: {config.get('database.port')} (应被环境变量覆盖)")
print(f"API密钥: {config.get('api_key')} (来自环境变量)")
print(f"应用名称: {config.get('app_name')} (来自默认配置)")
print(f"日志级别: {config.get('logging.level')}")
print(f"功能列表: {config.get('features')}")
# 测试配置设置
print("\n测试配置设置:")
config.set("new_feature.enabled", True)
config.set("database.password", "new_secret", persist=True)
print(f"新功能状态: {config.get('new_feature.enabled')}")
# 测试配置保存
print("\n测试配置保存:")
config.save_config("test_config_saved.json")
# 测试配置包含检查
print("\n测试配置包含检查:")
print(f"数据库用户名存在: {'database.username' in config}")
print(f"不存在的键: {'nonexistent.key' in config}")
# 测试配置转字典
print("\n完整配置字典:")
print(json.dumps(config.to_dict(), indent=2, ensure_ascii=False))
finally:
# 清理临时文件
if os.path.exists(test_config_path):
os.remove(test_config_path)
if os.path.exists("test_config_saved.json"):
os.remove("test_config_saved.json")
吗
最新发布