autool/env_loader.py
2026-06-17 19:44:18 +08:00

300 lines
9.3 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""
环境变量加载模块
提供统一的环境变量读取接口,支持:
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."
)