""" 环境变量加载模块 提供统一的环境变量读取接口,支持: 1. 从 .env 文件加载环境变量 2. 向后兼容:当 .env 不存在时给出友好提示 3. 类型转换和默认值处理 """ from __future__ import annotations import os import logging from pathlib import Path from typing import Any, Optional, Union logger = logging.getLogger(__name__) # 项目根目录 ROOT_DIR = Path(__file__).resolve().parent ENV_FILE_PATH = ROOT_DIR / ".env" ENV_EXAMPLE_PATH = ROOT_DIR / ".env.example" def load_dotenv(env_path: Optional[Path] = None) -> bool: """ 从 .env 文件加载环境变量到 os.environ Args: env_path: .env 文件路径,默认为项目根目录的 .env Returns: bool: 是否成功加载(文件存在且至少加载了一个变量) 注意: - 已存在的环境变量不会被覆盖 - 支持 # 注释和空行 - 支持格式:KEY=value 或 KEY="value" 或 KEY='value' """ if env_path is None: env_path = ENV_FILE_PATH if not env_path.exists(): logger.debug(f".env file not found at {env_path} (optional)") return False loaded_count = 0 try: with env_path.open("r", encoding="utf-8") as f: for line_num, line in enumerate(f, start=1): # 去除首尾空白 line = line.strip() # 跳过空行和注释 if not line or line.startswith("#"): continue # 解析 KEY=VALUE 格式 if "=" not in line: logger.warning(f"Invalid line in {env_path}:{line_num}: {line}") continue key, _, value = line.partition("=") key = key.strip() value = value.strip() # 去除引号(支持单引号和双引号) if value and value[0] in ('"', "'") and value[0] == value[-1]: value = value[1:-1] # 只设置尚未存在的环境变量(已存在的优先级更高) if key and key not in os.environ: os.environ[key] = value loaded_count += 1 logger.info(f"Loaded {loaded_count} environment variables from {env_path}") return loaded_count > 0 except Exception as e: logger.error(f"Failed to load .env file from {env_path}: {e}") return False def get_env( key: str, default: Optional[str] = None, *, required: bool = False, hint: Optional[str] = None, ) -> Optional[str]: """ 读取环境变量(字符串类型) Args: key: 环境变量名 default: 默认值 required: 是否必需(为 True 且变量不存在时抛出异常) hint: 提示信息(当变量不存在时显示) Returns: 环境变量值或默认值 Raises: ValueError: 当 required=True 且环境变量不存在时 Example: >>> get_env("GEMINI_API_KEY", required=True, hint="See .env.example") >>> get_env("LOG_LEVEL", default="INFO") """ value = os.environ.get(key) if value is None: if required: error_msg = f"Required environment variable '{key}' is not set." if hint: error_msg += f"\nHint: {hint}" elif ENV_EXAMPLE_PATH.exists(): error_msg += f"\nHint: Copy .env.example to .env and configure it." raise ValueError(error_msg) return default return value def get_env_bool( key: str, default: bool = False, *, required: bool = False, ) -> bool: """ 读取环境变量(布尔类型) Args: key: 环境变量名 default: 默认值 required: 是否必需 Returns: 布尔值 认为是 True 的值:true, 1, yes, on(不区分大小写) 认为是 False 的值:false, 0, no, off(不区分大小写) 其他值使用默认值 Example: >>> get_env_bool("DEBUG", default=False) >>> get_env_bool("ENABLE_CACHE", default=True) """ value = get_env(key, required=required) if value is None: return default value_lower = value.lower().strip() if value_lower in ("true", "1", "yes", "on"): return True elif value_lower in ("false", "0", "no", "off"): return False else: logger.warning( f"Invalid boolean value for {key}='{value}', using default={default}" ) return default def get_env_int( key: str, default: Optional[int] = None, *, required: bool = False, ) -> Optional[int]: """ 读取环境变量(整数类型) Args: key: 环境变量名 default: 默认值 required: 是否必需 Returns: 整数值或默认值 Example: >>> get_env_int("TIMEOUT", default=30) >>> get_env_int("MAX_WORKERS", default=4) """ value = get_env(key, required=required) if value is None: return default try: return int(value) except ValueError: logger.warning( f"Invalid integer value for {key}='{value}', using default={default}" ) return default def get_env_float( key: str, default: Optional[float] = None, *, required: bool = False, ) -> Optional[float]: """ 读取环境变量(浮点数类型) Args: key: 环境变量名 default: 默认值 required: 是否必需 Returns: 浮点数值或默认值 Example: >>> get_env_float("THRESHOLD", default=0.5) """ value = get_env(key, required=required) if value is None: return default try: return float(value) except ValueError: logger.warning( f"Invalid float value for {key}='{value}', using default={default}" ) return default def check_required_env_vars(required_keys: list[str]) -> list[str]: """ 检查多个必需的环境变量是否已设置 Args: required_keys: 必需的环境变量名列表 Returns: 未设置的环境变量名列表(空列表表示全部已设置) Example: >>> missing = check_required_env_vars(["GEMINI_API_KEY", "AZURE_API_KEY"]) >>> if missing: >>> print(f"Missing required env vars: {', '.join(missing)}") """ missing = [] for key in required_keys: if key not in os.environ: missing.append(key) return missing def print_env_setup_guide(): """ 打印环境变量配置指南(当检测到缺少必需配置时使用) """ guide = """ ╔════════════════════════════════════════════════════════════════════════════╗ ║ 环境变量配置指南 ║ ╠════════════════════════════════════════════════════════════════════════════╣ ║ 检测到缺少必需的环境变量配置。请按以下步骤配置: ║ ║ ║ ║ 1. 复制模板文件 ║ ║ cp .env.example .env ║ ║ ║ ║ 2. 编辑 .env 文件,填入实际的 API 密钥和配置 ║ ║ vim .env # 或使用其他编辑器 ║ ║ ║ ║ 3. 确保 .env 文件不会提交到版本控制 ║ ║ (已在 .gitignore 中排除) ║ ║ ║ ║ 常用配置项: ║ ║ - GEMINI_API_KEY: Google Gemini API 密钥 ║ ║ - AZURE_API_KEY: Azure OpenAI API 密钥 ║ ║ - QWEN_API_KEY: 阿里云通义千问 API 密钥 ║ ║ - GLM_API_KEY: 智谱 ChatGLM API 密钥 ║ ║ ║ ║ 详细说明请查看 .env.example 文件中的注释 ║ ╚════════════════════════════════════════════════════════════════════════════╝ """ print(guide) # 自动加载 .env 文件(当模块被导入时) _dotenv_loaded = load_dotenv() if not _dotenv_loaded and not ENV_FILE_PATH.exists(): logger.info( f".env file not found at {ENV_FILE_PATH}. " f"This is optional but recommended for managing sensitive configuration. " f"See .env.example for reference." )