春江暮客

春江暮客的个人学习分享网站

Python 任务配置实战:用 tomllib 读取 TOML、校验参数与固定路径

2026-10-13 技术
Python 任务配置实战:用 tomllib 读取 TOML、校验参数与固定路径

自动化脚本换一批输入就要改源码,命令行参数越来越长;到了定时任务,配置里的相对路径又找不到文件。把任务设置放进 TOML,再在入口统一校验,可以让同一份代码服务多个任务。

本文面向初学者到中级读者,用 Python 标准库写一个完整的输入预览工具:读取配置、检查类型、允许命令行覆盖行数,并验证换工作目录后仍能找到数据。示例只读取文件并输出 JSON。

1. 方法:先校验配置,再运行任务

Python 的 tomllib 官方文档说明,该模块从 Python 3.11 开始提供,负责解析 TOML,不能写入 TOML。tomllib.load() 需要以二进制方式打开文件,返回 Python 字典;格式不合法时会抛出 TOMLDecodeError。

解析成功不等于任务参数有效。 本文自行定义以下规则:

  1. 顶层只允许 [task],表内只允许 input 和 limit,拼错字段直接报错。
  2. input 必须是非空字符串。相对路径以配置文件所在目录为基准。
  3. limit 必须是 1–1000 的整数;没写时默认 3。不能用字符串、浮点数或布尔值代替。
  4. 优先级为默认值、配置文件、显式 --limit。先检查整份配置,即使命令行覆盖了 limit,配置中的非法值也会报错。

输入预览按文本行读取,空行也算一行。它适合检查任务入口;真正的 CSV 处理可以接入流式处理大型 CSV中的记录解析逻辑。

2. 准备环境和测试数据

需要已有的 Python 3.11 或更新版本,不安装第三方包。本文在 Linux、Python 3.11.4 上验证。

  1. 在 bash 或 zsh 中运行以下命令。重复实验时用新的目录名,后续命令保持在同一个终端:
python3 --version
python3 -c 'import tomllib; print("tomllib ready")'
mkdir tomllib-demo
cd tomllib-demo
task_demo_root="$PWD"
mkdir configs data
printf 'alpha\nbeta\ngamma\n' > data/items.txt
cat > configs/task.toml <<'EOF'
[task]
input = "../data/items.txt"
limit = 2
EOF
  1. TOML 的 [task] 会形成一个嵌套字典。这里的 ../data/items.txt 从 configs/ 目录计算,因此指向演示数据;limit = 2 是整数,limit = "2" 是字符串。
  2. 把下一节代码保存成当前目录的 preview_task.py,也可以下载 preview_task.py。

如果需要把带第三方依赖的工具发给别人,可继续参考 uv 单文件脚本教程。本文脚本无需依赖声明。

3. 完整实现:读取、校验和覆盖

"""Preview UTF-8 input using validated TOML settings. Python 3.11+."""
import argparse
from itertools import islice
import json
from pathlib import Path
import sys
import tomllib


def valid_limit(value):
    if type(value) is not int or not 1 <= value <= 1000:
        raise ValueError("limit must be an integer from 1 to 1000")
    return value


def cli_limit(value):
    try:
        return valid_limit(int(value))
    except ValueError as exc:
        raise argparse.ArgumentTypeError(str(exc)) from exc


def load_settings(config):
    config = config.expanduser().resolve(strict=True)
    with config.open("rb") as file:
        data = tomllib.load(file)
    if set(data) != {"task"}:
        raise ValueError("top level must contain only [task]")
    task = data["task"]
    if not isinstance(task, dict):
        raise ValueError("task must be a table")
    unknown = set(task) - {"input", "limit"}
    if unknown:
        raise ValueError("unknown task keys: " + ", ".join(sorted(unknown)))
    raw_input = task.get("input")
    if not isinstance(raw_input, str) or not raw_input.strip():
        raise ValueError("task.input must be a nonempty string")
    limit = valid_limit(task.get("limit", 3))
    input_path = Path(raw_input).expanduser()
    if not input_path.is_absolute():
        input_path = config.parent / input_path
    return config, input_path.resolve(strict=True), limit


def main():
    parser = argparse.ArgumentParser(description=__doc__, allow_abbrev=False)
    parser.add_argument("--config", required=True, type=Path, help="TOML file")
    parser.add_argument("--limit", type=cli_limit, help="override preview line limit")
    args = parser.parse_args()
    try:
        config, input_path, limit = load_settings(args.config)
        if args.limit is not None:
            limit = args.limit
        with input_path.open(encoding="utf-8") as file:
            lines = [line.rstrip("\r\n") for line in islice(file, limit)]
        print(json.dumps({
            "config": str(config),
            "input": str(input_path),
            "limit": limit,
            "lines": lines,
        }, ensure_ascii=False, indent=2))
    except (OSError, ValueError, RuntimeError) as exc:
        print(f"ERROR: {exc}", file=sys.stderr)
        return 1
    return 0


if __name__ == "__main__":
    raise SystemExit(main())

valid_limit() 用 type(value) is int 严格检查。Python 中 bool 是 int 的子类,所以只用 isinstance(value, int) 会把 true 当成整数接受。

argparse 的 type 参数让命令行字符串经过 cli_limit() 校验。这里把未提供的 --limit 保留为 None,避免一个命令行默认值意外盖掉文件中的设置。

路径处理使用 Path.resolve()。它会解析符号链接,因此基准目录是配置文件的实际位置;符号链接旁边的相对目录不会作为基准。代码支持 ~,不替换 TOML 字符串里的 $变量。

4. 运行并检查输出

  1. 查看帮助、使用文件设置,再临时覆盖行数:
python3 preview_task.py --help
python3 preview_task.py --config configs/task.toml
python3 preview_task.py --config configs/task.toml --limit 1

第二条命令输出的 limit 是 2,lines 是 ["alpha", "beta"];第三条是 1 和 ["alpha"]。config 与 input 都显示绝对路径,方便检查任务实际使用了哪个文件。

  1. 用断言检查结果:
python3 preview_task.py --config configs/task.toml > preview.json
python3 - <<'PY_CHECK'
import json
from pathlib import Path
result = json.loads(Path("preview.json").read_text(encoding="utf-8"))
assert result["limit"] == 2
assert result["lines"] == ["alpha", "beta"]
assert Path(result["input"]) == Path("data/items.txt").resolve()
print("OK: settings and input path")
PY_CHECK

预期输出 OK: settings and input path。如果输入少于指定行数,返回已有行;空文件返回空列表。limit 表示请求上限,实际读取行数是 len(lines)。

程序只限制行数,会把选中的行保存在内存中;单行特别长时仍会占用较多内存。把它作为小型输入预览,处理大型记录时改用逐条解析。

5. 验证换目录后仍可运行

在同一个终端执行:

(
  cd /
  python3 "$task_demo_root/preview_task.py" \
    --config "$task_demo_root/configs/task.toml" --limit 1
)

虽然子 shell 的工作目录变成 /,输出仍应是 limit = 1,lines = ["alpha"]。--config 的绝对路径负责找到配置,配置内的相对路径负责找到数据。

接入定时任务时,使用已有项目解释器、脚本和配置的绝对路径。如果任务还依赖其他文件或环境变量,按 Linux 定时任务路径排查逐项确认。本文实验无需修改 crontab。

6. 验证错误退出

创建一份 TOML 语法正确、业务类型错误的配置,再测试非法命令行参数:

cat > configs/bad.toml <<'EOF'
[task]
input = "../data/items.txt"
limit = true
EOF
python3 preview_task.py --config configs/bad.toml
printf 'exit=%s\n' "$?"
python3 preview_task.py --config configs/task.toml --limit 0
printf 'exit=%s\n' "$?"

第一条任务命令应输出 ERROR: limit must be an integer from 1 to 1000,随后显示 exit=1。第二条显示 argparse 的用法提示和参数错误,随后是 exit=2;正常任务返回 0。

验证时还检查了默认值、未知字段、缺少输入、非法 TOML、浮点数和字符串行数、缺失文件、Unicode 输入以及配置文件符号链接。代码先校验配置,再读取任务输入;没有通过校验就不会产生成功 JSON。

7. 常见问题与修复

现象 具体修复
No module named 'tomllib' 用 python3 --version 检查,改用已有的 Python 3.11+ 解释器;不要执行 pip install tomllib。
TOML 解析失败 检查引号、重复字段和表声明,修复错误提示所指的配置内容。
unknown task keys 将拼错的字段改成 input 或 limit,新增参数时同步修改允许列表和校验。
limit must be an integer 在文件里写 limit = 2,在命令行写 --limit 2;不要写 true、"2" 或 0。
No such file or directory 先检查传入的配置路径,再按配置实际所在目录计算 input。
Permission denied 或 UTF-8 解码失败 使用当前用户可读的普通 UTF-8 文本文件,并检查文件和父目录权限。

总结

把任务设置放到 TOML,代码负责类型、范围和字段校验,再允许少量命令行覆盖。固定相对路径的基准目录,并用成功与失败两类实验确认行为,这套入口就能用于日常自动化脚本。

标签

1024 12306 ablang adsense agents.md ai ai-agent ai-agents ai-seo algorithm amp antibodies apparmor argparse automation backup bash batch-processing binarycif bioinformatics biopython blockchain boltz bootstrapping boxes bubblewrap c-index cca cdn chatgpt checkpoint cli cloudflare codex cofoldarena copy cpu监控 cron csv cuda curl data-leakage data-processing data-quality data-validation datascience datavisualization debugging deployment desktop-app devtools disk-space disown docker dovecot download electron esm esm2 esm3 esmc esmfold2 faceswap fasta fastmcp faulthandler ffmpeg file-io flashppi flask folium frontend game generator git github-actions google google-research grep gzip harness hls html http hugo indexnow javascript jev json just k-means kaggle langfuse leecode linux list litellm llm llms.txt logging logs lollipop lsof m3u8 machine-learning macos manacher matplotlib mcp mirror mmcif model-evaluation mp3 mp4 mpnn multiomics mutation mysql nanobert nanobodies networkx nginx normalize numpy ollama omegatherm pandas password pdb pep-723 phaser pillow pip postfix preprocessing print protein-alignment protein-design protein-embeddings protein-interactions protein-language-models protein-stability protein-structure proxy pydantic pyecharts pyqt python python3 r raincloud reproducibility requests reservoir-sampling rfoptimization rg ripgrep rosettafold3 roundcube rrsi rsi rsync s-tui sampling scale scikit-learn scrapy screen seaborn security selenium seo sequence-identity sha256 signals sklearn solana somaticsignatures spl sqlite ssh standardize static-site subprocess sysadmin system-one tensorflow tkinter toml tron tronpy troubleshooting turtle typesafe-ai ubuntu usdt uv vhhbert vite webp wordcloud wordpress workflow yaml 后台 寓言 概率 经济 贸易 迅雷解析 钱包

友情链接

其它