Plain Notes技术札记
← 全部笔记
Python

给 YAML 配置加一道校验

配置文件把变化从代码中分离出来,但也把一部分错误留到了运行时。缩进、字段拼写、字符串和数字的区别,都可能让一份看起来正确的 YAML 无法按预期工作。

一个简单而有效的做法,是把“读取配置”和“使用配置”分成两个阶段:先解析,再检查结构与取值。

先区分语法正确与含义正确

下面这份配置语法完全合法,但端口写成了字符串,是否能使用取决于应用的处理方式。

service:
  host: 127.0.0.1
  port: "8080"

不要依靠后续代码在某个时刻报错。加载时就给出明确的信息,通常能减少排查成本。

一个最小的校验函数

以下示例使用 PyYAML 的 safe_load,限制根节点和 service 节点必须是映射,并验证端口类型与范围。

from pathlib import Path
import yaml

def load_config(path: Path) -> dict:
    data = yaml.safe_load(path.read_text(encoding="utf-8"))
    if not isinstance(data, dict):
        raise ValueError("配置根节点必须是映射")

    service = data.get("service")
    if not isinstance(service, dict):
        raise ValueError("service 必须是映射")

    port = service.get("port")
    if type(port) is not int or not 1 <= port <= 65535:
        raise ValueError("service.port 必须是 1–65535 的整数")

    host = service.get("host")
    if not isinstance(host, str) or not host.strip():
        raise ValueError("service.host 必须是非空字符串")
    return data

这里使用 type(port) is int,是为了排除 Python 中属于整数子类的布尔值。一个端口字段不应接受 true。

错误信息要包含字段路径

“配置错误”不足以指导修改。“service.port 必须是整数”则同时提供了位置和约束。对于更深的配置结构,可以沿用点分路径,例如 jobs.backup.interval。

也可以检查未知字段。这样把 timeout 拼成 tiemout 时,不会悄悄退回默认值。是否允许未知字段,应由配置是否需要向前兼容决定。

再加三类小测试

safe_load 限制的是 YAML 对象构造能力,不会自动完成业务字段校验,也不等于完整的配置规范。

当配置规模变大,再考虑使用专门的模式校验工具。对于几个字段的小程序,清晰的普通函数往往已经足够。