一、Everett库核心概览
Everett是一款专为Python设计的轻量级配置管理库,核心用途是帮助开发者统一管理项目中的配置信息,支持从环境变量、配置文件、命令行参数等多种来源加载配置,同时具备类型转换、验证和文档生成功能。其工作原理基于”配置源优先级”机制,允许开发者定义不同来源的加载顺序,确保最终获取的配置符合预期。
Everett的优点包括无依赖、体积小、API简洁易懂,支持动态配置和类型安全;缺点则是高级功能(如配置热更新)需自行实现,生态相对较小。该库采用Apache License 2.0开源协议,允许商业和个人项目免费使用和修改。
二、Everett库安装与环境准备
2.1 基础安装方式
Everett支持通过pip工具快速安装,适用于所有主流Python版本(Python 3.6及以上)。打开终端或命令提示符,执行以下命令:
# 安装最新稳定版
pip install everett
# 安装指定版本(如3.1.0)
pip install everett==3.1.0
# 安装包含所有可选功能的版本(支持配置文件解析等)
pip install everett[ini,toml,yaml]
安装完成后,可通过以下代码验证是否安装成功:
# 验证Everett安装
import everett
# 打印版本号,确认安装成功
print(f"Everett版本:{everett.__version__}")
# 输出示例:Everett版本:3.1.0
2.2 开发环境配置
对于需要参与Everett开发或使用最新开发版的用户,可通过GitHub仓库克隆源码进行安装:
# 克隆GitHub仓库
git clone https://github.com/willkg/everett.git
# 进入项目目录
cd everett
# 安装开发依赖
pip install -r requirements-dev.txt
# 以可编辑模式安装
pip install -e .
三、Everett核心功能实战
3.1 基础配置加载:从环境变量获取配置
Everett最基础的用法是从环境变量加载配置,适合Docker容器、服务器部署等场景。以下代码演示如何定义配置类并从环境变量获取配置:
from everett.manager import ConfigManager
from everett.field import StringField, IntField, BoolField, FloatField
# 1. 定义配置类,继承自object
class AppConfig:
"""应用配置类,定义所有需要的配置项"""
# 字符串类型配置,默认值为"development",环境变量前缀为"MYAPP_"
env = StringField(
default="development",
doc="应用运行环境,可选值:development(开发)、production(生产)、test(测试)"
)
# 整数类型配置,无默认值,必须通过环境变量设置
port = IntField(
doc="应用监听端口,范围:1024-65535"
)
# 布尔类型配置,默认值为False
debug_mode = BoolField(
default=False,
doc="是否开启调试模式,生产环境需设置为False"
)
# 浮点数类型配置,默认值为0.5
timeout = FloatField(
default=0.5,
doc="请求超时时间(秒)"
)
# 2. 创建配置管理器,指定环境变量前缀
config_manager = ConfigManager(
# 设置环境变量前缀,避免与其他项目冲突
env_prefix="MYAPP_",
# 配置文档生成器(可选)
doc_generator=None
)
# 3. 加载配置到配置类实例
config = config_manager.with_options(AppConfig())
# 4. 使用配置
print("=== 应用配置信息 ===")
print(f"运行环境:{config.env}")
print(f"监听端口:{config.port}")
print(f"调试模式:{config.debug_mode}")
print(f"超时时间:{config.timeout}秒")
使用说明:
- 在运行代码前,需要先设置环境变量(以Linux/macOS为例):
export MYAPP_PORT=8080
export MYAPP_DEBUG_MODE=true
export MYAPP_TIMEOUT=1.2
- Windows系统设置环境变量方式:
set MYAPP_PORT=8080
set MYAPP_DEBUG_MODE=true
set MYAPP_TIMEOUT=1.2
- 运行代码后,将输出以下内容:
=== 应用配置信息 ===
运行环境:development
监听端口:8080
调试模式:True
超时时间:1.2秒
3.2 多配置源加载:环境变量+配置文件
在实际项目中,通常需要结合配置文件和环境变量(环境变量优先级更高,用于覆盖配置文件)。以下代码演示如何同时从TOML配置文件和环境变量加载配置:
首先,创建config.toml
配置文件:
# config.toml 配置文件
[app]
env = “production” port = 8000 debug_mode = false timeout = 0.8
[database]
host = “localhost” port = 5432 username = “dbuser” password = “dbpass” db_name = “mydb”
然后,编写Python代码加载配置:
from everett.manager import ConfigManager, ConfigFileEnv
from everett.field import StringField, IntField, BoolField, FloatField
from pathlib import Path
# 1. 定义数据库配置类
class DatabaseConfig:
"""数据库配置类"""
host = StringField(
default="localhost",
doc="数据库主机地址"
)
port = IntField(
default=5432,
doc="数据库端口"
)
username = StringField(
doc="数据库用户名"
)
password = StringField(
doc="数据库密码"
)
db_name = StringField(
doc="数据库名称"
)
# 2. 定义应用配置类(包含数据库配置)
class AppConfig:
"""应用主配置类"""
env = StringField(
default="development",
doc="应用运行环境"
)
port = IntField(
default=8080,
doc="应用监听端口"
)
debug_mode = BoolField(
default=False,
doc="调试模式开关"
)
# 嵌套配置:数据库配置
db = DatabaseConfig()
# 3. 创建配置文件环境(指定TOML文件路径)
config_file = Path(__file__).parent / "config.toml"
config_file_env = ConfigFileEnv(
# 配置文件路径
config_file=str(config_file),
# 配置文件类型(支持ini、toml、yaml,需安装对应依赖)
config_type="toml"
)
# 4. 创建配置管理器,设置多配置源(优先级:环境变量 > 配置文件)
config_manager = ConfigManager(
environments=[
# 第一个配置源:环境变量(前缀MYAPP_)
"env:MYAPP_",
# 第二个配置源:TOML配置文件
config_file_env
]
)
# 5. 加载配置
config = config_manager.with_options(AppConfig())
# 6. 输出配置信息
print("=== 应用主配置 ===")
print(f"环境:{config.env}")
print(f"端口:{config.port}")
print(f"调试模式:{config.debug_mode}")
print("\n=== 数据库配置 ===")
print(f"数据库主机:{config.db.host}")
print(f"数据库端口:{config.db.port}")
print(f"数据库用户:{config.db.username}")
print(f"数据库名称:{config.db.db_name}")
关键说明:
- 需先安装TOML依赖:
pip install everett[toml]
- 配置源优先级:环境变量(MYAPP_前缀)会覆盖配置文件中的值
- 若需要使用YAML配置文件,需安装
pyyaml
:pip install everett
,并将config_type
设为”yaml”
3.3 配置验证与类型转换
Everett支持对配置值进行验证,确保加载的配置符合业务规则。以下代码演示如何使用自定义验证器和内置验证功能:
from everett.manager import ConfigManager
from everett.field import StringField, IntField, Validator
from everett.validation import ValidValue, MinValue, MaxValue
# 1. 自定义验证器:检查字符串是否为有效的邮箱格式
class EmailValidator(Validator):
def __call__(self, value):
if "@" not in value or "." not in value.split("@")[-1]:
raise ValueError(f"无效的邮箱格式:{value},正确格式如:user@example.com")
return value
# 2. 定义带验证的配置类
class UserServiceConfig:
"""用户服务配置类(带验证)"""
# 验证:只能是"http"或"https"
protocol = StringField(
default="https",
doc="服务协议",
validators=[ValidValue(["http", "https"])]
)
# 验证:端口号在1024-65535之间
port = IntField(
default=443,
doc="服务端口",
validators=[MinValue(1024), MaxValue(65535)]
)
# 验证:使用自定义邮箱验证器
admin_email = StringField(
doc="管理员邮箱",
validators=[EmailValidator()]
)
# 验证:整数必须为偶数
max_retries = IntField(
default=3,
doc="最大重试次数(必须为偶数)",
validators=[
lambda x: x % 2 == 0 or ValueError(f"{x}不是偶数")
]
)
# 3. 创建配置管理器(从环境变量加载)
config_manager = ConfigManager(env_prefix="USER_SERVICE_")
# 4. 加载配置(若验证失败,会抛出ValueError)
try:
config = config_manager.with_options(UserServiceConfig())
print("配置加载成功!")
print(f"服务地址:{config.protocol}://localhost:{config.port}")
print(f"管理员邮箱:{config.admin_email}")
print(f"最大重试次数:{config.max_retries}")
except ValueError as e:
print(f"配置验证失败:{e}")
使用示例:
- 正确配置(环境变量):
export USER_SERVICE_ADMIN_EMAIL=admin@example.com
export USER_SERVICE_MAX_RETRIES=4
运行代码后输出:
配置加载成功!
服务地址:https://localhost:443
管理员邮箱:admin@example.com
最大重试次数:4
- 错误配置(环境变量):
export USER_SERVICE_ADMIN_EMAIL=admin.example.com # 无效邮箱
export USER_SERVICE_MAX_RETRIES=5 # 奇数
运行代码后输出:
配置验证失败:无效的邮箱格式:admin.example.com,正确格式如:user@example.com
3.4 命令行参数集成
Everett可与argparse
(Python标准库)无缝集成,支持从命令行参数加载配置。以下代码演示如何结合命令行参数、环境变量和配置文件:
import argparse
from everett.manager import ConfigManager, ConfigFileEnv
from everett.field import StringField, IntField, BoolField
from pathlib import Path
# 1. 定义配置类
class CLIAppConfig:
"""命令行应用配置类"""
input_file = StringField(
doc="输入文件路径"
)
output_file = StringField(
default="output.txt",
doc="输出文件路径"
)
verbose = BoolField(
default=False,
doc="是否显示详细日志"
)
threshold = IntField(
default=50,
doc="处理阈值"
)
# 2. 创建argparse命令行解析器
parser = argparse.ArgumentParser(description="Everett命令行参数集成示例")
# 添加命令行参数(--config指定配置文件路径)
parser.add_argument(
"--config",
type=str,
default=str(Path(__file__).parent / "app.conf"),
help="配置文件路径(默认:app.conf)"
)
# 添加其他命令行参数(对应配置项)
parser.add_argument(
"--input-file",
type=str,
help="输入文件路径(优先级:命令行 > 环境变量 > 配置文件)"
)
parser.add_argument(
"--output-file",
type=str,
help="输出文件路径"
)
parser.add_argument(
"-v", "--verbose",
action="store_true",
help="显示详细日志"
)
# 3. 解析命令行参数
args = parser.parse_args()
# 4. 创建配置源列表(优先级从高到低)
config_sources = []
# 第一个源:命令行参数(将args转换为配置源)
class ArgparseEnv:
def get(self, key, namespace=None):
# 将配置key转换为命令行参数名(如input_file -> input_file)
arg_name = key
# 从args中获取值,若存在则返回
value = getattr(args, arg_name, None)
return value if value is not None else None
config_sources.append(ArgparseEnv())
# 第二个源:环境变量(前缀CLI_APP_)
config_sources.append("env:CLI_APP_")
# 第三个源:配置文件(ini格式)
config_file_env = ConfigFileEnv(
config_file=args.config,
config_type="ini"
)
config_sources.append(config_file_env)
# 5. 创建配置管理器
config_manager = ConfigManager(environments=config_sources)
# 6. 加载配置
config = config_manager.with_options(CLIAppConfig())
# 7. 应用逻辑
print("=== 命令行应用配置 ===")
print(f"输入文件:{config.input_file}")
print(f"输出文件:{config.output_file}")
print(f"详细日志:{'开启' if config.verbose else '关闭'}")
print(f"处理阈值:{config.threshold}")
# 模拟处理逻辑
if config.verbose:
print(f"\n[详细日志] 开始处理文件:{config.input_file}")
print(f"[详细日志] 处理阈值设置为:{config.threshold}")
print(f"\n处理完成,结果已保存到:{config.output_file}")
使用说明:
- 创建
app.conf
配置文件(ini格式):
[DEFAULT]
input_file = data.txt
output_file = result.txt
threshold = 60
verbose = false
- 运行命令行示例(不同优先级测试):
# 1. 仅使用配置文件(默认)
python cli_app.py
# 2. 使用环境变量覆盖配置文件
export CLI_APP_INPUT_FILE=custom_data.txt
python cli_app.py
# 3. 使用命令行参数覆盖环境变量和配置文件
python cli_app.py --input-file command_data.txt -v --threshold 70
- 命令行运行输出示例:
=== 命令行应用配置 ===
输入文件:command_data.txt
输出文件:result.txt
详细日志:开启
处理阈值:70
[详细日志] 开始处理文件:command_data.txt
[详细日志] 处理阈值设置为:70
处理完成,结果已保存到:result.txt
四、实际项目案例:Flask应用配置管理
4.1 项目结构设计
以下是一个使用Everett管理配置的Flask项目结构:
flask-everett-demo/
├── app/ # 应用主目录
│ ├── __init__.py # 应用初始化
│ ├── config.py # 配置类定义
│ ├── routes.py # 路由定义
│ └── utils.py # 工具函数
├── configs/ # 配置文件目录
│ ├── development.toml # 开发环境配置
│ ├── production.toml # 生产环境配置
│ └── test.toml # 测试环境配置
├── .env # 本地环境变量(不提交到Git)
├── .gitignore # Git忽略文件
├── requirements.txt # 项目依赖
└── run.py # 应用启动入口
4.2 配置类实现(app/config.py)
from everett.manager import ConfigManager, ConfigFileEnv
from everett.field import StringField, IntField, BoolField, SecretField
from pathlib import Path
import os
# 获取配置文件目录路径
CONFIG_DIR = Path(__file__).parent.parent / "configs"
class DatabaseConfig:
"""数据库配置类"""
# 数据库连接URI(优先)
uri = StringField(
default="",
doc="数据库连接URI,格式:dialect+driver://username:password@host:port/database"
)
# 数据库连接参数(当uri未设置时使用)
host = StringField(
default="localhost",
doc="数据库主机地址"
)
port = IntField(
default=5432,
doc="数据库端口"
)
username = StringField(
default="postgres",
doc="数据库用户名"
)
password = SecretField(
default="",
doc="数据库密码(SecretField会隐藏敏感信息)"
)
name = StringField(
default="appdb",
doc="数据库名称"
)
class RedisConfig:
"""Redis配置类"""
host = StringField(
default="localhost",
doc="Redis主机地址"
)
port = IntField(
default=6379,
doc="Redis端口"
)
db = IntField(
default=0,
doc="Redis数据库编号"
)
password = SecretField(
default="",
doc="Redis密码"
)
class AppConfig:
"""应用主配置类"""
# 应用基本配置
env = StringField(
default="development",
doc="应用环境:development(开发)、production(生产)、test(测试)",
validators=[lambda x: x in ["development", "production", "test"] or ValueError("无效环境")]
)
secret_key = SecretField(
doc="Flask应用密钥,用于会话加密等"
)
debug = BoolField(
default=False,
doc="是否开启调试模式"
)
host = StringField(
default="0.0.0.0",
doc="应用绑定主机地址"
)
port = IntField(
default=5000,
doc="应用监听端口"
)
# 跨域配置
cors_allowed_origins = StringField(
default="*",
doc="允许跨域请求的源,多个用逗号分隔"
)
# 嵌套配置
db = DatabaseConfig()
redis = RedisConfig()
def get_config_manager():
"""创建并返回配置管理器"""
# 获取当前环境(优先从环境变量获取)
env = os.getenv("APP_ENV", "development")
# 配置文件路径(根据环境选择)
config_file = CONFIG_DIR / f"{env}.toml"
# 确保配置文件存在
if not config_file.exists():
raise FileNotFoundError(f"配置文件不存在:{config_file}")
# 配置源列表(优先级从高到低)
config_sources = [
# 1. 环境变量(前缀APP_)
"env:APP_",
# 2. 环境对应的配置文件
ConfigFileEnv(config_file=str(config_file), config_type="toml"),
# 3. 全局默认配置文件(如果存在)
ConfigFileEnv(config_file=str(CONFIG_DIR / "default.toml"), config_type="toml", optional=True)
]
# 创建并返回配置管理器
return ConfigManager(environments=config_sources)
# 全局配置实例
config_manager = get_config_manager()
config = config_manager.with_options(AppConfig())
4.3 配置文件示例
configs/development.toml(开发环境配置):
[app]
debug = true
secret_key = "dev_secret_key_change_in_production"
port = 5000
[db]
host = “localhost” port = 5432 username = “dev_user” password = “dev_pass” name = “dev_db”
[redis]
host = “localhost” port = 6379
configs/production.toml(生产环境配置):
[app]
debug = false
port = 8000
cors_allowed_origins = "https://example.com,https://api.example.com"
[db]
# 生产环境推荐使用URI配置 uri = “postgresql://prod_user:${DB_PASSWORD}@db-host:5432/prod_db”
[redis]
host = “redis-host”
4.4 Flask应用初始化(app/init.py)
from flask import Flask
from flask_cors import CORS
from .config import config
from .routes import register_routes
def create_app():
"""创建并配置Flask应用"""
# 初始化Flask应用
app = Flask(__name__)
# 配置Flask应用
app.config["SECRET_KEY"] = config.secret_key
app.config["DEBUG"] = config.debug
# 配置CORS
cors_origins = config.cors_allowed_origins.split(",")
CORS(app, resources={r"/*": {"origins": cors_origins}})
# 注册路由
register_routes(app)
# 打印启动信息
app.logger.info(f"应用启动环境:{config.env}")
app.logger.info(f"数据库配置:{config.db.host}:{config.db.port}/{config.db.name}")
app.logger.info(f"Redis配置:{config.redis.host}:{config.redis.port}")
return app
4.5 路由实现(app/routes.py)
from flask import jsonify, request
from .config import config
def register_routes(app):
"""注册应用路由"""
@app.route("/")
def index():
"""首页路由"""
return jsonify({
"message": "Welcome to Flask-Everett Demo",
"environment": config.env,
"debug_mode": config.debug
})
@app.route("/config")
def show_config():
"""展示部分配置信息(过滤敏感信息)"""
# 注意:实际生产环境不要返回完整配置,这里仅做演示
return jsonify({
"app": {
"env": config.env,
"port": config.port,
"debug": config.debug
},
"db": {
"host": config.db.host,
"port": config.db.port,
"name": config.db.name,
"username": config.db.username,
# 密码使用SecretField,直接打印会显示***
"password": str(config.db.password)
}
})
@app.route("/health")
def health_check():
"""健康检查路由"""
return jsonify({"status": "healthy", "timestamp": request.timestamp})
4.6 应用启动入口(run.py)
from app import create_app
from app.config import config
# 创建Flask应用
app = create_app()
if __name__ == "__main__":
# 从配置读取主机和端口
app.run(
host=config.host,
port=config.port,
debug=config.debug
)
4.7 项目依赖与启动方式
requirements.txt:
flask==2.0.1
flask-cors==3.0.10
everett[toml]==3.1.0
psycopg2-binary==2.9.1 # PostgreSQL驱动
redis==3.5.3
启动命令:
# 安装依赖
pip install -r requirements.txt
# 开发环境启动(默认使用development配置)
python run.py
# 生产环境启动(指定环境变量)
export APP_ENV=production
export APP_SECRET_KEY="your_secure_production_key"
export APP_DB_PASSWORD="your_db_password"
python run.py
# 使用Gunicorn启动生产环境(推荐)
gunicorn -w 4 -b 0.0.0.0:8000 "run:app"
访问方式:
启动后,通过以下URL访问应用:
- 首页:http://localhost:5000/
- 配置信息:http://localhost:5000/config
- 健康检查:http://localhost:5000/health
五、Everett高级特性
5.1 配置文档自动生成
Everett可以自动生成配置文档,方便团队协作和维护。以下是生成Markdown格式配置文档的示例:
from everett.manager import ConfigManager
from everett.doc import generate_md
from app.config import AppConfig
# 创建配置管理器
config_manager = ConfigManager()
# 生成配置文档
docs = generate_md(config_manager, AppConfig())
# 保存文档到文件
with open("CONFIGURATION.md", "w") as f:
f.write(docs)
print("配置文档已生成:CONFIGURATION.md")
生成的文档将包含所有配置项的名称、类型、默认值、描述和验证规则,便于维护和查阅。
5.2 动态配置切换
在某些场景下(如多租户应用),可能需要动态切换配置。Everett支持通过上下文管理器临时切换配置源:
from everett.manager import ConfigManager, ConfigEnv
from app.config import AppConfig
# 基础配置管理器
base_config = ConfigManager(env_prefix="APP_")
# 租户A的配置源
class TenantAEnv(ConfigEnv):
def get(self, key, namespace=None):
tenant_configs = {
"db.name": "tenant_a_db",
"port": 5001
}
return tenant_configs.get(key)
# 租户B的配置源
class TenantBEnv(ConfigEnv):
def get(self, key, namespace=None):
tenant_configs = {
"db.name": "tenant_b_db",
"port": 5002
}
return tenant_configs.get(key)
# 处理租户A请求
with base_config.override_environments([TenantAEnv()]):
config_a = base_config.with_options(AppConfig())
print(f"处理租户A请求,数据库:{config_a.db.name},端口:{config_a.port}")
# 处理租户B请求
with base_config.override_environments([TenantBEnv()]):
config_b = base_config.with_options(AppConfig())
print(f"处理租户B请求,数据库:{config_b.db.name},端口:{config_b.port}")
5.3 与 pytest 集成进行测试
在测试环境中,可以使用Everett方便地覆盖配置,确保测试的独立性:
# tests/conftest.py
import pytest
from everett.manager import ConfigManager, ConfigEnv
from app.config import AppConfig
class TestEnv(ConfigEnv):
"""测试环境配置源"""
def __init__(self, overrides=None):
self.overrides = overrides or {}
def get(self, key, namespace=None):
return self.overrides.get(key)
@pytest.fixture
def test_config():
"""测试配置 fixture"""
# 测试环境默认覆盖配置
test_overrides = {
"env": "test",
"debug": "false",
"db.name": "test_db",
"redis.db": 9
}
# 创建测试配置管理器
config_manager = ConfigManager(
environments=[
TestEnv(test_overrides),
"env:APP_" # 允许通过环境变量覆盖测试配置
]
)
return config_manager.with_options(AppConfig())
# tests/test_app.py
def test_app_config(test_config):
"""测试配置加载是否正确"""
assert test_config.env == "test"
assert test_config.debug is False
assert test_config.db.name == "test_db"
assert test_config.redis.db == 9
六、Everett使用最佳实践
- 配置分层管理:根据环境(开发/测试/生产)和功能(应用/数据库/缓存)对配置进行分层,提高可维护性。
- 敏感信息处理:使用
SecretField
存储密码、密钥等敏感信息,避免在日志或调试信息中泄露。 - 明确的配置优先级:建立清晰的配置源优先级规则(如命令行 > 环境变量 > 配置文件 > 默认值),避免配置冲突。
- 配置验证:对所有配置项添加必要的验证规则,尽早发现配置错误。
- 文档即代码:使用Everett的文档生成功能,确保配置文档与代码保持同步。
- 版本控制:配置文件(除包含敏感信息的文件外)应纳入版本控制,便于追溯配置变更。
- 本地开发配置:使用
.env
文件存储本地开发配置,并将其加入.gitignore
,避免敏感信息提交到代码库。
相关资源
- Pypi地址:https://pypi.org/project/everett/
- Github地址:https://github.com/willkg/everett
- 官方文档地址:https://everett.readthedocs.io/
通过本文的介绍,相信你已经掌握了Everett的核心用法和最佳实践。无论是小型脚本还是大型应用,Everett都能帮助你优雅地管理配置,让你的Python项目更加健壮和可维护。在实际开发中,建议根据项目规模和团队需求,灵活运用Everett的各项功能,构建适合自己的配置管理体系。
关注我,每天分享一个实用的Python自动化工具。