tomlkit 报 Key already exists:乱序子表不一定重复定义
tomlkit 0.15.1 的乱序父子表可能误报 KeyAlreadyPresent。按层级整理完整表段,核数组归属与读回语义,区分只读 tomllib 和保留格式编辑。
同一份 TOML,标准库 tomllib 能读,tomlkit 却报 Key "lint" already exists。如果文件先声明子表、之后声明父表,中间再穿插另一张表,报错不一定意味着真的重复定义了 lint。
tomlkit 0.15.1 有这条明确回归;0.15.2 正式源码仍保留相关的代理类型判定分支,不能把其他表合并修正一概当成这条问题已经解决。
下面这组表头能体现触发结构:
[tool.ruff]
[tool.ruff.lint.a]
[tool.ruff.lint]
[[tool.poetry.source]]
[tool.ruff.lint.b]
tool.ruff.lint 先以隐式父表的形式存在,之后才出现具体表头。被其他表隔开后再加入子表,内部可能通过 OutOfOrderTableProxy 表示已有内容;校验分支却把它与普通 Table、AoT 区分开,误报类型冲突。中间的表并不必须是 Poetry source 数组,换成另一张不相关的表也能涉及这一结构。
可调整配置时,把父表放到子表前面
对上述空表结构,按层级组织可以避开这条乱序合并路径:
[tool.ruff]
[tool.ruff.lint]
[tool.ruff.lint.a]
[tool.ruff.lint.b]
[[tool.poetry.source]]
这只是表头结构示例,不是一份完整可用的 Ruff 或 Poetry 业务配置。实际文件带有键值、注释和数组元素时,移动的是整段所属内容,不能只拖动表头,把原来的键留到另一张表里。
尤其是 [[...]] 数组表,后续子表可能属于某一个特定元素。盲目按字母排序整份文件会改变关联关系,不能拿“排序后不报错”代替语义等价。
先保存原文件,在有明确预期的副本上整理顺序。修订后用原消费工具读回,比较完整的嵌套键、类型、值和数组顺序;如果仍需保留排版,再检查写回文本和注释位置。
只读任务可另用解析器,写回任务不能随意替换
Python 3.11 及以上提供 tomllib,只需要读取 TOML 的路径可以评估使用它。但它不是 tomlkit 的保留格式编辑接口,不能用一个普通 dict 的重写过程代替原来的注释、引号和格式保留要求。
如果配置由依赖自动生成、调用方无法安全调整表顺序,应选择确实处理这一结构的正式修正后再切换;不要仅凭某个开发分支有补丁,就把它写成已发布版本的能力。也不要在安装目录里删除重复键检查,真正的重定义仍应被拒绝。
验收保留原先会失败的乱序样本、整理后的等价样本,以及一份真正重复定义表或键的非法样本。前两者应按所选路径正常读取,非法输入仍失败。还要让最终使用这份配置的工具启动到原功能,不能只看一次 loads() 没抛异常。


