diff --git a/CHANGELOG.md b/CHANGELOG.md index b213c935..054bedf3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ ### Fixed - Arithmetic and `replace()` results for `Integer`, `Float`, `String`, `DateTime`, `Date` and `Time` no longer share the original item's `Trivia`, so changing the result's comment or indentation no longer rewrites the item still stored in the document. +- Fix a dotted key made with `key()` and added to an inline table (e.g. `table.add(key(["c", "d"]), 2)`) being written as invalid TOML such as `{c. = d = 2}`; it is now written as `{c.d = 2}`. ## [0.15.2] - 2026-10-16 diff --git a/tests/test_api.py b/tests/test_api.py index 14d245b6..d9a07b47 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -445,6 +445,21 @@ def test_add_dotted_key() -> None: assert table.as_string() == "foo.bar = 1\n" +def test_add_dotted_key_to_inline_table() -> None: + table = tomlkit.inline_table() + table.add(tomlkit.key(["foo", "bar"]), 1) + table[tomlkit.key(["foo", "baz"])] = 2 + assert table.as_string() == "{foo.bar = 1, foo.baz = 2}" + + +def test_add_dotted_key_to_parsed_inline_table() -> None: + doc = parse("a = {b.c = 1}\n") + doc["a"].add(tomlkit.key(["b", "d"]), 2) + doc["a"].append(tomlkit.key(["e", "f", "g"]), 3) + assert doc.as_string() == "a = {b.c = 1, b.d = 2, e.f.g = 3}\n" + assert doc == {"a": {"b": {"c": 1, "d": 2}, "e": {"f": {"g": 3}}}} + + def test_key_with_single_element_list_is_a_single_key() -> None: # Regression test for # https://github.com/python-poetry/tomlkit/issues/430: a one-element key diff --git a/tomlkit/container.py b/tomlkit/container.py index 9265c740..02d7e564 100644 --- a/tomlkit/container.py +++ b/tomlkit/container.py @@ -210,10 +210,14 @@ def _handle_dotted_key(self, key: Key, value: Item) -> None: if isinstance(value, (Table, AoT)): raise TOMLKitError("Can't add a table to a dotted key") name, *mid, last = key + # The parts before the last one are followed by a dot, not by the + # separator, so leave theirs empty as the parser does name._dotted = True + name.sep = "" table = current = Table(Container(True), Trivia(), False, is_super_table=True) for _name in mid: _name._dotted = True + _name.sep = "" new_table = Table(Container(True), Trivia(), False, is_super_table=True) current.append(_name, new_table) current = new_table diff --git a/tomlkit/items.py b/tomlkit/items.py index e84a40e4..c4dcb578 100644 --- a/tomlkit/items.py +++ b/tomlkit/items.py @@ -2088,13 +2088,24 @@ def append(self, key: Key | str | None, _item: Any) -> InlineTable: self._validate_child(_item) + needs_indent = False if not isinstance(_item, (Whitespace, Comment)): - if not _item.trivia.indent and len(self._value) > 0 and not self._new: - _item.trivia.indent = " " + needs_indent = ( + not _item.trivia.indent and len(self._value) > 0 and not self._new + ) if _item.trivia.comment: _item.trivia.comment = "" - self._value.append(key, _item) + if isinstance(key, Key) and key.is_multi(): + # The value of a dotted key is rendered after the key (``a.b = 1``), + # so the space goes on the table that the key adds as a new entry + self._value.append(key, _item) + if needs_indent: + self._value.body[-1][1].trivia.indent = " " + else: + if needs_indent: + _item.trivia.indent = " " + self._value.append(key, _item) if isinstance(key, Key): key = key.key