Regression in 0.15.1: valid out-of-order child table raises KeyAlreadyPresent

Open
#571 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Assessment

Difficulty
3/5
Estimated time
1-2 days
Newbie friendliness
68/100
Issue type
Bug
Clarity
Mostly clear
Activity status
Quiet
Tech stack
python
Domain
tooling

Research direction

Start by running the minimal reproduction and inspect _validate_table_candidate() together with the OutOfOrderTableProxy and Table validation described in the report. The fix is done when the document parses successfully without KeyAlreadyPresent, while the listed older-version behavior and expected nested result remain valid.

Written by the indexing model from the issue text.

Description

Summary

Tomlkit 0.15.1 rejects a valid document when a table contains an out-of-order
child, its concrete parent is declared afterward, and another table separates a
later sibling child.

This is a regression: tomllib and Tomlkit 0.13.3, 0.14.0, and 0.15.0 all
accept the document.

Minimal reproduction

import tomlkit

source = """\
[tool.ruff]
[tool.ruff.lint.a]
[tool.ruff.lint]
[[tool.poetry.source]]
[tool.ruff.lint.b]
"""

tomlkit.loads(source)

Actual result

tomlkit.exceptions.ParseError: Key "lint" already exists. at line 5 col 0

Expected result

The document parses successfully, matching tomllib:

{
    "tool": {
        "ruff": {"lint": {"a": {}, "b": {}}},
        "poetry": {"source": [{}]},
    }
}

Each header in the minimized example is necessary to trigger the failure. The
array-of-tables is not special: replacing [[tool.poetry.source]] with any
intervening table header also reproduces it, while removing the intervening
header makes it pass.

Regression boundary

Reproduced on Python 3.13.8 and 3.14.2:

  • stdlib tomllib: passes
  • Tomlkit 0.13.3: passes
  • Tomlkit 0.14.0: passes
  • Tomlkit 0.15.0: passes
  • Tomlkit 0.15.1 and current master: fail

Suspected root cause

The regression appears to come from the concrete+super validation added in
#530 / commit d3c76f0.

When [tool.ruff.lint.b] is appended, the existing lint entry is represented
by OutOfOrderTableProxy, while the candidate is a Table.
_validate_table_candidate() checks:

isinstance(existing, (Table, AoT)) != isinstance(v, (Table, AoT))

The proxy is table-like but fails the left-hand isinstance check, so valid
input is reported as a type conflict and raises KeyAlreadyPresent("lint").

Dominant language
Python
Stars
850
Forks
163
Avg merge
13m
Merged PRs (30d)
2

Contributor guide

No contributing guide indexed for this repository

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

More from python-poetry/tomlkit

All issues in python-poetry/tomlkit

Similar issues

More Python issues

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.