Skip to content

fix: reject a table or array of tables inside an array - #618

Open
feiiiiii5 wants to merge 1 commit into
python-poetry:masterfrom
feiiiiii5:fix/inline-aot-truncation
Open

feiiiiii5 wants to merge 1 commit into
python-poetry:masterfrom
feiiiiii5:fix/inline-aot-truncation

Conversation

@feiiiiii5

Copy link
Copy Markdown

Summary

Putting a Table or an AoT into an Array or an InlineTable is accepted, and the object is then silently lost on rendering — unwrap() still reports it, but the text is no longer valid TOML:

>>> from tomlkit import parse, api
>>> doc = parse("[[a]]\nx = 1\n\n[[a]]\nx = 2\n")
>>> aot = doc["a"]
>>> arr = api.array()
>>> arr.append(aot)
>>> arr.unwrap()
[[{'x': 1}, {'x': 2}]]
>>> print(arr.as_string())
[x = 1

x = 2

]
>>> tomlkit.parse(arr.as_string())
tomlkit.exceptions.UnexpectedCharError: Unexpected character: '=' at line 1 col 3

Two tables collapse into what reads as one element's body and the array brackets are all that is left. The same happens in an inline table ({k = x = 1\n\nx = 2\n\n}), and for a plain Table the header is dropped just as silently. Nothing raises, so a caller that checks unwrap() sees exactly what it put in.

A table renders as its own [header] block, so there is no value form of it to place inside [...] or {...}. InlineTable already refuses that: _validate_child rejects a Table with ValueError("Inline tables cannot contain a table"), added in #532. Array had no check at all, and InlineTable._validate_child did not cover AoT, so the rest of the family wrote text nobody can read back. This adds Array._validate_child on every mutation entry point and the missing AoT branch.

I chose rejection over rendering a sub-table because an array element and an inline-table value are single-line value positions: producing valid output would mean silently moving the table out of its enclosing brackets, changing the document structure, and no code path here does that. The alternative would also have put a second, different policy inside the abstraction #532 just settled.

This is a behaviour change — these conversions used to succeed silently and now raise. That is the point, but it is worth saying plainly in case anyone relied on the old result.

Test: pytest tests/test_items.py -k "table_and_aot_in_array or aot_to_inline_table" fails on 8c959b5 with DID NOT RAISE ValueError for both new cases; the suite is 380 passed with --ignore=tests/test_toml_tests.py, that module needing the tests/toml-test submodule which is not checked out in my environment and so errors at collection both before and after. ruff check and ruff format --check (0.15.21, the version .pre-commit-config.yaml pins) are clean on tomlkit and tests; mypy tomlkit reports the same 5 errors before and after, none on the changed lines.

Still working, and covered by the existing tests: a Table in a Table, a Table in an AoT, an AoT in a Table, and item() turning a list of dicts into an Array of InlineTable — item() keys off the parent, so a list under an Array parent never becomes an AoT.

Related to #516 but a different path: that one is _validate_table_candidate in container.py, rejecting a [table] header that redefines an existing table while parsing. This is item-level construction — putting an already-built table into an array — in items.py, with no parser involved.

I have not added a CHANGELOG entry; the file is organised per release and every entry cites a PR number, so tell me if you would like one and I will add it under a new section referencing this PR.

Agent Drafting Metadata

  • Agent: opencode (autonomous OSS contribution agent for this workspace)
  • Model: Space Bunny Free (opencode space-bunny-free)
  • Notes: the agent read Array.as_string, InlineTable._validate_child and the Table/AoT rendering paths, wrote the guards and the two regression tests, and ran the checks quoted above. I re-ran the red-before failure on the unmodified base, the full suite, ruff at the pinned version and the mypy base comparison myself before pushing; a human review is requested before merge.

AI-assisted development. Human review requested before merge.

`Array.as_string` joins its children's `as_string()` with bare
concatenation and has no notion of a child that renders as a `[header]`
block, so a `Table` or an `AoT` placed in an array kept its value in
`unwrap()` and lost its header in the text. The result does not parse
back.

`InlineTable._validate_child` already rejected a `Table` (11e22ae,
python-poetry#532). `Array` had no check at all, and the inline-table guard did not
cover `AoT`, so the rest of the family wrote TOML nobody can read back.
Add `Array._validate_child` to every mutation entry point and the
missing `AoT` branch to the existing one.

Behaviour change: these conversions now raise instead of silently
producing a truncated document.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant