# 配置管理使用说明 MyBoot 使用 [Dynaconf](https://www.dynaconf.com/) 管理配置,支持 YAML 文件、多文件合并、环境变量覆盖与远程配置文件。实现见 `myboot/core/config.py`。 ## 1. 快速开始 在项目根目录或 `conf/` 目录放置 `config.yaml`,应用启动时自动加载: ```yaml # conf/config.yaml app: name: "MyBoot App" version: "0.1.0" server: port: 8000 reload: false logging: level: "INFO" ``` 代码中读取: ```python from myboot.core.config import get_settings, get_config, get_config_bool # 点号路径(推荐) port = get_config("server.port", 8000) debug = get_config_bool("app.debug", False) # Dynaconf 对象 settings = get_settings() name = settings.app.name # 属性访问 port = settings.get("server.port") # 与 get_config 等价 ``` 通过 `create_app` 创建应用时,配置挂在 `app.config` 上,与全局 `get_settings()` 为同一实例(首次初始化后): ```python from myboot import create_app app = create_app(name="我的应用", config_file="conf/config.yaml") port = app.config.get("server.port", 8000) ``` ## 2. 配置文件位置与合并 ### 2.1 自动发现的文件(按加载顺序) Dynaconf **按列表顺序加载**,**后加载的文件覆盖先加载的同名字段**: | 顺序 | 路径 | 说明 | | ---- | -------------------------------------------------------------------------- | ------------------------ | | 1 | `{项目根}/config.yaml` 或 `config.yml` | 基底配置 | | 2 | `{项目根}/conf/config.yaml` 或 `conf/config.yml` | 覆盖根目录同名键 | | 3 | `create_app(config_file=...)` / `get_settings(config_file=...)` 传入的路径 | 覆盖上述文件 | | 4 | 环境变量 `CONFIG_FILE` 指向的路径或 URL | **文件来源中优先级最高** | 示例:根目录 `server.port: 8000`,`conf/config.yaml` 中 `server.port: 9000`,最终以 **9000** 为准(若未再被 `CONFIG_FILE` 或环境变量键覆盖)。 同一嵌套对象(如 `server.cors`)在多个文件中出现时,还会受全局 `merge_enabled` 与 `dynaconf_merge` 影响,见下文 **第 3 节**。 ### 2.2 指定配置文件 ```bash # 本地文件 export CONFIG_FILE=/etc/myboot/production.yaml # 远程 URL(会下载到系统临时目录缓存,失败时尝试使用缓存) export CONFIG_FILE=https://example.com/config.yaml ``` ```python from myboot import create_app app = create_app(config_file="conf/config.prod.yaml") ``` 远程配置下载逻辑见 `_download_config`:网络失败且存在缓存时回退到缓存文件。 ### 2.3 内置默认值 未在任何 YAML 或环境变量中声明时,使用 `config.py` 中 `default_settings`(节选): | 键 | 默认值 | | -------------------------------- | -------------- | | `app.name` | `"MyBoot App"` | | `app.version` | `"0.1.0"` | | `server.host` | `"0.0.0.0"` | | `server.port` | `8000` | | `server.reload` | `true` | | `server.workers` | `1` | | `server.response_format.enabled` | `true` | | `logging.level` | `"INFO"` | | `scheduler.enabled` | `true` | | `scheduler.timezone` | `"UTC"` | | `scheduler.max_workers` | `10` | ## 3. 字典/列表合并与 `dynaconf_merge` MyBoot 在 `create_settings()` 中启用了 Dynaconf 全局合并: ```python # myboot/core/config.py merge_enabled=True, ``` 因此,**多个配置文件**(或带 `@merge` 的环境变量)加载到**同一嵌套字典/列表**时,默认会**深合并**,而不是简单地把后一个文件里的整块 YAML 盖掉前一个。 ### 3.1 默认合并行为(`merge_enabled=True`) | 类型 | 后加载文件中的同路径值 | 结果 | | --------------------------------- | ---------------------- | ---------------------------------------- | | 标量(`str` / `int` / `bool` 等) | 新值 | **覆盖**旧值 | | 字典 | 新键与旧键 | **递归合并**(保留旧文件中未出现的子键) | | 列表 | 新元素 | **拼接合并**(可能产生重复项) | **示例**:根目录与 `conf/` 均定义 `server.cors` 时,若后加载文件只改 `allow_origins`,未写 `allow_methods`,合并后仍会保留先前的 `allow_methods`、`allow_headers`。 ```yaml # config.yaml(先加载) server: cors: allow_origins: ["*"] allow_methods: ["*"] allow_headers: ["*"] # conf/config.yaml(后加载,未使用 dynaconf_merge: false) server: cors: allow_origins: ["http://localhost:3000"] ``` 合并结果等价于: ```yaml server: cors: allow_origins: ["*", "http://localhost:3000"] # 列表被拼接 allow_methods: ["*"] allow_headers: ["*"] ``` ### 3.2 `dynaconf_merge: false` 的作用 在需要**整块替换**某个字典(或列表)而不是与旧值合并时,在该字典(或列表)内加上 **`dynaconf_merge: false`**。 该标记是 Dynaconf 的**合并控制元数据**,不会作为业务配置键出现在 `get_config()` 结果中。 | 写法 | 作用范围 | | ----------------------------- | ---------------------------------------------------- | | 写在某个 **dict / list 内部** | 仅该节点:后加载内容**整体替换**先加载的同级对象 | | 写在 **YAML 文件顶层** | 整个后加载文件相对先前来源按「替换」策略处理(慎用) | **示例:用 `false` 完全替换 `server.cors`** ```yaml # config.yaml server: cors: allow_origins: ["*"] allow_methods: ["*"] allow_headers: ["*"] # conf/config.prod.yaml(后加载) server: cors: allow_origins: ["https://api.example.com"] allow_credentials: true dynaconf_merge: false # 整块替换 cors,不保留 allow_methods / allow_headers ``` 最终 `server.cors` 仅包含: ```yaml allow_origins: ["https://api.example.com"] allow_credentials: true ``` **对比**:若去掉 `dynaconf_merge: false`,后加载文件里未写的 `allow_methods`、`allow_headers` 会**继续保留**,且 `allow_origins` 可能与旧列表**拼接**。 ### 3.3 `dynaconf_merge: true` 显式声明「与已有字典/列表合并」。在 `merge_enabled=True` 时,多数嵌套 dict/list **已默认合并**,一般仅在需要强调或配合 `@merge` 环境变量时使用: ```yaml server: cors: allow_origins: ["http://localhost:3000"] dynaconf_merge: true ``` 环境变量也可使用 Dynaconf 的 `@merge` 标记(TOML/JSON 形式),例如: ```bash export SERVER__CORS='@merge {"allow_origins": ["http://localhost:3000"]}' ``` ### 3.4 何时用 `false`、何时用环境变量 | 场景 | 建议 | | ------------------------------------------------------------ | ----------------------------------------------------------------------------- | | 生产配置要**换一整段** `server.cors` / `logging.third_party` | 后加载 YAML 中对应该段加 `dynaconf_merge: false` | | 只改**一两个嵌套字段** | 直接写子键,或 `SERVER__CORS__ALLOW_ORIGINS=...` | | 列表不想与旧值拼接 | 对该 list 使用 `dynaconf_merge: false`,或写完整列表并 `false` | | 不确定合并结果 | 用 `get_settings().server.cors` 或 `get_config("server.cors")` 在本地打印验证 | ### 3.5 注意 1. **`dynaconf_merge` 只影响合并策略**,不改变「后加载文件优先」的顺序(见第 2 节)。 2. **标量字段**始终是覆盖,与 `dynaconf_merge` 无关。 3. 对**深层嵌套**的精细控制,除 `dynaconf_merge: false` 外,也可用环境变量 `__` 逐键覆盖(见第 5 节环境变量)。 4. 更多语法见 [Dynaconf 合并文档](https://www.dynaconf.com/merging/)。 ## 4. 优先级总览 从高到低(后者覆盖前者): ``` 环境变量键(如 SERVER__PORT) > CONFIG_FILE 指向的文件 > config_file 参数 > conf/config.yaml > 根目录 config.yaml > default_settings ``` ```mermaid flowchart LR A[default_settings] --> B[根目录 config.yaml] B --> C[conf/config.yaml] C --> D[config_file 参数] D --> E[CONFIG_FILE] E --> F[环境变量 SERVER__PORT 等] ``` ## 5. 环境变量 ### 5.1 规则 | 项 | 说明 | | ------------ | ----------------------------------------------------------- | | 前缀 | **无**(`envvar_prefix=False`),变量名即配置路径的大写形式 | | 嵌套分隔符 | **双下划线 `__`**,对应 YAML 中的层级 | | 单下划线 `_` | 仅作为**同一层键名**的一部分(如 `keep_alive_timeout`) | | 类型 | `env_parse_values=True`,自动尝试解析布尔、数字等 | | 未知变量 | `ignore_unknown_envvars=True`,**仅当该键已在配置树中存在时**才接受环境变量覆盖(见下) | #### 已知键限制(`ignore_unknown_envvars=True`) MyBoot 在 `config.py` 中启用了 `ignore_unknown_envvars=True`。环境变量(含 `.env` 加载进 `os.environ` 的项)**不会**凭空新增配置项,只能**覆盖**下列来源里**已经出现过**的键路径: 1. 已加载的 YAML(`config.yaml`、`conf/config.yaml`、`CONFIG_FILE` 等) 2. `config.py` 中 `default_settings` 内置项(如 `app.name`、`server.port`、`logging.level`、`scheduler.timezone` 等) 因此: - ✅ `SERVER__PORT=9000` 有效(`server.port` 在默认配置或 YAML 中已有) - ❌ `DATABASE__URL=...` **无效**,若 YAML / 默认配置里**没有** `database` 段——变量会进入 `os.environ`,但 **MyBoot 不会读入** `get_config("database.url")` - ❌ 任意「只在 `.env` 里出现、YAML 从未声明」的键都会被**静默忽略** **正确做法**:需要靠 `.env` 注入的新配置,先在 YAML 中**声明结构**(值可写占位符),再用环境变量覆盖: ```yaml # conf/config.yaml — 先声明键结构 database: url: "" # 占位,真实值放在 .env / .local.env app: secret_key: "" # 敏感项同理 jobs: cleanup_task: enabled: true ``` ```bash # .local.env — 再覆盖 DATABASE__URL=postgresql://user:pass@127.0.0.1:5432/mydb APP__SECRET_KEY=your-local-secret-key JOBS__CLEANUP_TASK__ENABLED=false ``` 若希望环境变量**无需**在 YAML 预声明即可生效,需修改 `myboot/core/config.py` 将 `ignore_unknown_envvars` 设为 `False`(当前框架默认未开启)。 ### 5.2 示例 | YAML 路径 | 环境变量 | | --------------------------- | --------------------------------------------------------- | | `app.name` | `APP__NAME=MyApp` | | `server.port` | `SERVER__PORT=9000` | | `logging.level` | `LOGGING__LEVEL=DEBUG` | | `server.keep_alive_timeout` | `SERVER__KEEP_ALIVE_TIMEOUT=60` | | `server.cors.allow_origins` | `SERVER__CORS__ALLOW_ORIGINS='["http://localhost:3000"]'` | ```bash export APP__NAME="生产应用" export SERVER__PORT=8080 export LOGGING__LEVEL=WARNING export SCHEDULER__TIMEZONE=Asia/Shanghai ``` **不要**用单下划线表示嵌套层级,例如 `SERVER_PORT` **不会**映射到 `server.port`(除非你在 YAML 里真有名为 `server_port` 的顶层键)。 ### 5.3 布尔值 `get_config_bool` 将字符串视为真:`true`、`1`、`yes`、`on`(不区分大小写)。 ### 5.4 MyBoot 与 `.env` 的关系(0.2.0 起自动加载) **0.2.0 起 MyBoot 自动加载项目根目录的 `.env` 文件**(通过 Dynaconf 的 `load_dotenv`),`main.py` 无需再手动调用 `load_dotenv()`。 加载语义: - 路径:**项目根目录**(含 `pyproject.toml` 的目录)下的 `.env`; - 优先级:**真实环境变量 > `.env` > YAML 配置 > 内置默认值** (`dotenv_override=False`:`.env` 不覆盖已存在的真实环境变量,与容器部署习惯一致); - 仍受 **`ignore_unknown_envvars=True`** 约束:`.env` 里的变量名必须对应 **YAML 或内置默认配置中已声明过的键路径**,否则不会进入 `get_config()` (见上文 **5.1 已知键限制**)。 ```mermaid flowchart LR A[".env(项目根)"] -->|"框架自动加载"| C["os.environ"] B["真实环境变量"] -->|"优先"| C C --> D["Dynaconf / MyBoot"] E["config.yaml"] --> D ``` **注意:** | 事项 | 说明 | | ------------------- | -------------------------------------------------------------------- | | 修改 `.env` 后 | 需**重启进程**;不会热更新 | | 版本控制 | `.env` 加入 `.gitignore`;可提交 `.env.example` 作模板 | | 键必须已声明 | `.env` 中每一项都须在 YAML(或内置默认值)中有对应路径,见 5.1 | | 多个 env 文件 | 框架只自动加载根目录 `.env`;需要 `.local.env` 分层时仍可在 main.py 顶部自行 `load_dotenv(..., override=True)` | ### 5.5 旧版本(≤0.1.x)手动加载方式 0.1.x 不自动加载 `.env`,需在 `create_app()` 之前手动调用: ```python from pathlib import Path from dotenv import load_dotenv load_dotenv(Path(__file__).resolve().parent / ".env") from myboot import create_app # 必须在 load_dotenv 之后 app = create_app(name="我的应用") ``` 升级到 0.2.0 后这段代码可以删除(保留也无害——重复加载是幂等的)。 ### 5.6 `.env` 文件如何书写 `.env` 使用 **dotenv 格式**(不是 YAML):每行 `键=值`,`#` 开头为注释。**不要**写 `export`(那是 shell 脚本写法)。 > **重要**:`.env` 只用于**覆盖**已有配置键,不能代替 YAML 做「首次声明」。未在 YAML / `default_settings` 中出现的键(如仅写 `DATABASE__URL` 而 `config.yaml` 里没有 `database.url`)将被忽略。 #### 命名规则(与 MyBoot 一致) - 嵌套配置用 **`__`(双下划线)** 对应 YAML 层级。 - 键名一般**大写**(与常见约定一致;写入环境后 Dynaconf 会映射到配置树)。 - 单下划线 `_` 只表示键名的一部分,例如 `KEEP_ALIVE_TIMEOUT`。 #### 示例 `.env`(非敏感、可提交 `.env.example`) ```bash # 应用 APP__NAME=MyBoot App APP__VERSION=0.1.0 APP__DEBUG=false # 服务 SERVER__HOST=0.0.0.0 SERVER__PORT=8000 SERVER__RELOAD=true # 日志 LOGGING__LEVEL=INFO # 调度器 SCHEDULER__ENABLED=true SCHEDULER__TIMEZONE=Asia/Shanghai ``` #### 示例 `.local.env`(敏感信息,勿提交 Git) 以下变量均要求 `conf/config.yaml`(或根目录 `config.yaml`)中**已有同名路径**;示例见上一节 YAML 占位声明。 ```bash # 覆盖 .env 中的端口(server.port 已在 YAML/默认配置中存在) SERVER__PORT=9000 # 以下键须先在 YAML 中声明 database.url、app.secret_key DATABASE__URL=postgresql://user:password@127.0.0.1:5432/mydb APP__SECRET_KEY=your-local-secret-key ``` #### 与 YAML 的对应关系 | `.env` 中的写法 | 等价 YAML 路径 | 代码读取 | | ------------------------------------- | --------------------------- | ----------------------------------------- | | `SERVER__PORT=9000` | `server.port` | `get_config("server.port")` | | `LOGGING__LEVEL=DEBUG` | `logging.level` | `get_config("logging.level")` | | `DATABASE__URL=...` | `database.url`(**须先在 YAML 声明**) | `get_config("database.url")` | | `SERVER__CORS__ALLOW_ORIGINS='["*"]'` | `server.cors.allow_origins` | `get_config("server.cors.allow_origins")` | #### 值的书写建议 ```bash # 字符串含空格或特殊字符时用引号 APP__NAME="My Boot App" # 布尔(会被 env_parse_values 解析) APP__DEBUG=true SERVER__RELOAD=false # 数字 SERVER__PORT=8080 SCHEDULER__MAX_WORKERS=20 # 列表 / 字典建议用 JSON 字符串(尤其嵌套较深时) SERVER__CORS__ALLOW_ORIGINS=["http://localhost:3000","http://127.0.0.1:3000"] ``` **错误示例(不会映射到预期配置):** ```bash # 错误:单下划线不能表示 server.port SERVER_PORT=8000 # 错误:dotenv 行内不要用 export export SERVER__PORT=8000 # 错误:YAML 缩进语法不能用在 .env server: port: 8000 # 错误:YAML 中未声明 database 段时,此行会被 ignore_unknown_envvars 忽略 DATABASE__URL=postgresql://... ``` **内置默认已存在、可直接用 `.env` 覆盖的键(无需 YAML)** 包括:`app.name`、`app.version`、`server.host`、`server.port`、`server.reload`、`server.workers`、`logging.level`、`scheduler.enabled`、`scheduler.timezone`、`scheduler.max_workers` 等,完整列表见 `myboot/core/config.py` 中 `default_settings`。 #### 验证是否加载成功 ```python # 在 load_dotenv 且 create_app 之后执行 from myboot.core.config import get_config # server.port 在 YAML/默认配置中存在 → 能读到 .env 覆盖值 print(get_config("server.port")) # database.url 仅当 YAML 中已声明 database.url 时才能读到 .env 值 print(get_config("database.url", "(YAML 未声明或 .env 未覆盖)")) ``` `echo $DATABASE__URL` 能看到 shell 环境变量,但 **`get_config` 仍可能取不到**——说明键未进入 MyBoot 配置树,请检查 YAML 是否已声明该路径。 或在 shell 中确认环境变量已写入(Windows Git Bash): ```bash echo $SERVER__PORT ``` ## 6. 读取 API | 函数 / 对象 | 用途 | | ------------------------------------- | ---------------------------------------- | | `get_settings(config_file=None)` | 获取全局 `Dynaconf` 单例 | | `get_config(key, default=None)` | 按点号路径取值 | | `get_config_str(key, default="")` | 转为字符串 | | `get_config_int(key, default=0)` | 转为整数,失败返回 default | | `get_config_bool(key, default=False)` | 转为布尔 | | `reload_config()` | 清空单例,下次 `get_settings()` 重新加载 | | `app.config` | 应用持有的同一配置对象 | ```python # 运行时修改(仅当前进程内存,不写回文件) app.config.set("server.port", 9001) ``` ### 6.1 单例注意 `get_settings()` 使用模块级单例,**首次调用**时确定加载了哪些文件;之后传入不同的 `config_file` 不会生效,除非先调用 `reload_config()`。 ```python from myboot.core.config import get_settings, reload_config settings = get_settings() # 已固定加载结果 reload_config() settings = get_settings("other.yaml") # 重新加载 ``` ## 7. 常用配置项 与框架行为直接相关的键(完整示例可参考 `conf/config.yaml` 与 README): ### 7.1 `app` | 键 | 说明 | | ------------- | -------------------- | | `app.name` | 应用名称 | | `app.version` | 版本号 | | `app.debug` | 调试开关(按需使用) | ### 7.2 `server` | 键 | 说明 | | -------------------------------------- | ----------------------------------- | | `server.host` | 监听地址 | | `server.port` | 端口 | | `server.reload` | 热重载 | | `server.workers` | Worker 数量 | | `server.keep_alive_timeout` | Keep-Alive 超时 | | `server.graceful_timeout` | 优雅关闭超时 | | `server.cors` | CORS 子对象,存在时启用 CORS 中间件 | | `server.response_format.enabled` | 是否统一响应格式 | | `server.response_format.exclude_paths` | 排除路径列表 | ### 7.3 `logging` | 键 | 说明 | | --------------------- | -------------------- | | `logging.level` | 日志级别 | | `logging.format` | 日志格式 | | `logging.file` | 日志文件路径(可选) | | `logging.third_party` | 第三方库日志级别映射 | ### 7.4 `scheduler` | 键 | 说明 | | -------------------------- | ---------------------------------- | | `scheduler.enabled` | 是否启用调度器 | | `scheduler.timezone` | 时区(建议安装 `pytz`) | | `scheduler.max_workers` | 调度线程池大小 | | `scheduler.on_all_workers` | 多进程时是否在每个 worker 运行调度 | Cron 与任务装饰器详见 [scheduler.md](./scheduler.md)。 ### 7.5 `jobs`(可选) 用于在 `@cron` / `@interval` / `@once` 的 `enabled` 参数中按任务开关: ```yaml jobs: cleanup_task: enabled: false ``` ```python from myboot.core.config import get_config @cron("0 2 * * *", enabled=get_config("jobs.cleanup_task.enabled", True)) def cleanup(self): ... ``` ## 8. 配置与装饰器、组件 - **定时任务**:`enabled=get_config('jobs.xxx.enabled', True)` 在类定义时求值,修改环境变量后需**重启进程**才生效。 - **日志**:`Application` 构造时调用 `setup_logging(self.config)`,之后改 `logging.*` 需自行处理或重启。 - **依赖注入**:`get_config` 可在模块级或组件方法内使用;与 `@component` 无冲突。 ## 9. 最佳实践 1. **开发**用 `conf/config.yaml`,**生产**用 `CONFIG_FILE` 或环境变量注入敏感项,避免密钥进仓库。 2. **嵌套键**统一用 `__` 环境变量,避免与 `snake_case` 字段混淆。 3. **显式设置** `logging.level`、`scheduler.timezone`,减少环境差异。 4. 列表、字典类配置在环境变量中优先使用 **JSON 字符串**(如 CORS origins)。 5. 需要切换配置源时调用 `reload_config()`,或保证在任意 `get_settings()` 之前设置好 `CONFIG_FILE`。 6. 多文件拆分时保持「基底 → 环境专用」的加载顺序意识:后加载覆盖先加载。 7. 多文件共用同一嵌套段(如 `server.cors`)且不想**拼接列表、保留旧子键**时,在后加载文件中对应该段添加 **`dynaconf_merge: false`**(见第 3 节)。 8. 本地敏感项用 **`.local.env`** + `main.py` 中 `load_dotenv`(见第 5.5、5.6 节);敏感值放 `.env`,但**键名须先在 YAML 声明**(`ignore_unknown_envvars=True`),结构可进 `config.yaml`,秘密不进 Git。 ## 10. 相关文档与代码 | 资源 | 说明 | | ---------------------------------------------------- | ---------------------------- | | `myboot/core/config.py` | 配置加载与便捷函数 | | `conf/config.yaml` | 项目默认配置示例 | | [scheduler.md](./scheduler.md) | 调度器配置与 Cron | | [dependency-injection.md](./dependency-injection.md) | 组件与 `get_config` 结合示例 |