Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 40 additions & 0 deletions tests/test_items.py
Original file line number Diff line number Diff line change
Expand Up @@ -1258,6 +1258,46 @@ def test_adding_to_nested_dotted_key_inside_inline_table() -> None:
assert parse(rendered).as_string() == rendered


def test_deleting_last_key_under_dotted_key_in_inline_table() -> None:
# The emptied dotted table still exists, so it must render as an empty
# inline table; emitting nothing leaves a dangling separator and produces
# invalid TOML like ``{a = 1, , d = 1}``.
doc = parse("t = {a = 1, b.c = 2, d = 3}\n")
del doc["t"]["b"]["c"]
rendered = doc.as_string()
assert rendered == "t = {a = 1, b = {}, d = 3}\n"
assert parse(rendered) == {"t": {"a": 1, "b": {}, "d": 3}}
assert parse(rendered).as_string() == rendered

doc = parse("t = {b.c = 2, a = 1}\n")
del doc["t"]["b"]["c"]
rendered = doc.as_string()
assert rendered == "t = {b = {}, a = 1}\n"
assert parse(rendered) == {"t": {"b": {}, "a": 1}}

doc = parse("t = {a = 1, b.c = 2}\n")
del doc["t"]["b"]["c"]
rendered = doc.as_string()
assert rendered == "t = {a = 1, b = {}}\n"
assert parse(rendered) == {"t": {"a": 1, "b": {}}}

doc = parse("t = {b.c = 2}\n")
del doc["t"]["b"]["c"]
rendered = doc.as_string()
assert rendered == "t = {b = {}}\n"
assert parse(rendered) == {"t": {"b": {}}}


def test_deleting_nested_key_under_dotted_key_in_inline_table() -> None:
doc = parse("t = {x = 1, a.b.c = 2, y = 3}\n")
del doc["t"]["a"]["b"]["c"]

rendered = doc.as_string()
assert rendered == "t = {x = 1, a.b = {}, y = 3}\n"
assert parse(rendered) == {"t": {"x": 1, "a": {"b": {}}, "y": 3}}
assert parse(rendered).as_string() == rendered


def test_appending_to_comma_first_array_does_not_double_separator() -> None:
doc = parse(
"""\
Expand Down
15 changes: 12 additions & 3 deletions tomlkit/items.py
Original file line number Diff line number Diff line change
Expand Up @@ -2181,8 +2181,11 @@ def as_string(self) -> str:
if k.is_dotted() and isinstance(v, Table):
# A table materialized from a dotted key renders one
# `prefix.child = value` pair per child, so that keys added
# after parsing stay attached to the dotted prefix.
rendered = ", ".join(self._render_dotted(k, v))
# after parsing stay attached to the dotted prefix. When all
# children were removed the table itself still exists, so it
# must render as an empty inline table to stay valid TOML.
dotted = self._render_dotted(k, v)
rendered = ", ".join(dotted) if dotted else f"{k.as_string()} = {{}}"
else:
rendered = (
f"{k.as_string() + ('.' if k.is_dotted() else '')}"
Expand Down Expand Up @@ -2220,7 +2223,13 @@ def _render_dotted(self, key: Key, table: Table) -> list[str]:
if k is None:
continue
if isinstance(v, Table):
parts.extend(f"{prefix}{sub}" for sub in self._render_dotted(k, v))
sub = self._render_dotted(k, v)
if sub:
parts.extend(f"{prefix}{s}" for s in sub)
else:
# The nested table exists but renders nothing after its
# last child was removed; emit it as an empty inline table.
parts.append(f"{prefix}{k.as_string()} = {{}}")
else:
trail = v.trivia.trail.replace("\n", "")
parts.append(
Expand Down
Loading