diff --git a/CHANGELOG.md b/CHANGELOG.md index 0af52c54..8b5fd0f9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ ### Fixed +- Fix an `AttributeError` when a table is defined out of order under an indented header nested four or more levels deep, e.g. ` [a.b.c.d]` then `[a.b.c]`. ([#663](https://github.com/python-poetry/tomlkit/pull/663)) - Fix valid TOML being rejected when an out-of-order table reopens an implicit super-table with a dotted key: `[a.b.c]` then `[a]` with `b.z = 1` extends the implicit `a.b` super-table rather than redefining it (stdlib `tomllib` accepts it), so it now parses and round-trips instead of raising `Redefinition of an existing table`. The concrete-table case (`[a.b]` then `[a]` `b.z = 1`) stays rejected. ([#565](https://github.com/python-poetry/tomlkit/pull/565)) - Fix appending a key to a parsed inline table that uses `", "` separators emitting a bare `","` for the new entry, leaving the table inconsistently spaced (e.g. `{x = 1, y = 2,z = 3}`). ([#595](https://github.com/python-poetry/tomlkit/pull/595)) diff --git a/tests/test_toml_document.py b/tests/test_toml_document.py index 84b114b9..09d1398a 100644 --- a/tests/test_toml_document.py +++ b/tests/test_toml_document.py @@ -675,6 +675,25 @@ def test_valid_out_of_order_super_table_extended_by_dotted_key() -> None: assert doc.as_string() == "[a.b.c]\n[a]\nb.z = 1\n" +@pytest.mark.parametrize( + ("content", "expected"), + [ + (" [a.b.c.d]\n[a.b.c]\n", {"a": {"b": {"c": {"d": {}}}}}), + (" [[a.b.c.d]]\n[a.b.c]\n", {"a": {"b": {"c": {"d": [{}]}}}}), + (" [a.b.c.d]\n[x]\n[a.b.c]\n", {"a": {"b": {"c": {"d": {}}}}, "x": {}}), + ], +) +def test_out_of_order_table_after_indented_header( + content: str, expected: dict[str, Any] +) -> None: + # https://github.com/python-poetry/tomlkit/issues/263 covered `[a.b]` after + # an indented `[[a.b.c]]`. One level deeper, the out-of-order table lands + # under an intermediate table that carries the header's indent. + doc = parse(content) + assert doc.unwrap() == expected + assert doc.as_string() == content + + def test_set_value_on_out_of_order_table_with_empty_concrete_part() -> None: # A super table defined after its sub-table (the "defining a super-table # afterward is ok" spec example) leaves an empty concrete `[x]` part. diff --git a/tomlkit/items.py b/tomlkit/items.py index 26b01097..cc496c2e 100644 --- a/tomlkit/items.py +++ b/tomlkit/items.py @@ -1959,7 +1959,9 @@ def append(self, key: Key | str | None, _item: Any) -> Table: indent = m.group(1) - if not isinstance(_item, Whitespace): + # A table split out of order is looked up as an OutOfOrderTableProxy, + # which has no trivia; its parts keep their own indentation. + if isinstance(_item, Item) and not isinstance(_item, Whitespace): m = re.match("(?s)^([^ ]*)(.*)$", _item.trivia.indent) if not m: _item.trivia.indent = indent