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
46 changes: 46 additions & 0 deletions tests/test_toml_document.py
Original file line number Diff line number Diff line change
Expand Up @@ -688,6 +688,52 @@ def test_out_of_order_table_merges_aot_fragments() -> None:
assert 'matcher = "patched"' in doc.as_string()


def test_out_of_order_table_merges_aot_element_extension() -> None:
# https://github.com/python-poetry/tomlkit/issues/577
# `[[y.a.c]]` extends the last element of the existing `y.a` array rather
# than adding one, so the second `a` fragment reaches the proxy as an
# implicit super table and not as another AoT.
content = """\
[[y.a]]
n = 1

[b.d.x]

[[y.a.c]]
m = 2
"""
doc = parse(content)
assert doc.as_string() == content

assert doc.unwrap() == {
"y": {"a": [{"n": 1, "c": [{"m": 2}]}]},
"b": {"d": {"x": {}}},
}
assert doc["y"]["a"][0]["c"][0]["m"] == 2


def test_out_of_order_table_extends_last_of_several_aot_elements() -> None:
content = """\
[[y.a]]
n = 1

[[y.a]]
n = 2

[b.d.x]

[[y.a.c]]
m = 3
"""
doc = parse(content)
assert doc.as_string() == content

assert doc.unwrap() == {
"y": {"a": [{"n": 1}, {"n": 2, "c": [{"m": 3}]}]},
"b": {"d": {"x": {}}},
}


def test_out_of_order_table_merges_three_aot_fragments() -> None:
# An AoT split across more than two out-of-order parts merges into a single
# AoT: each later fragment is appended to the growing element list, so the
Expand Down
20 changes: 18 additions & 2 deletions tomlkit/container.py
Original file line number Diff line number Diff line change
Expand Up @@ -1123,7 +1123,9 @@ def _merge_aot_fragment(self, key: Key | None, item: Item) -> AoT | None:
Returns the merged ``AoT``, or ``None`` if this is not such a fragment.
"""
internal = self._internal_container
if key is None or not isinstance(item, AoT) or key not in internal._map:
if key is None or not isinstance(item, (AoT, Table)):
return None
if key not in internal._map:
return None
idx = internal._map[key]
if isinstance(idx, tuple):
Expand All @@ -1137,7 +1139,21 @@ def _merge_aot_fragment(self, key: Key | None, item: Item) -> AoT | None:
if not isinstance(existing, AoT):
return None

merged = AoT([*existing.body, *item.body], parsed=True)
if isinstance(item, AoT):
elements = [*existing.body, *item.body]
else:
# The later part extends the array's last element instead of adding
# one: `[[y.a]]` followed by `[[y.a.c]]` reaches here with `a` as an
# implicit super table holding `c`. Copy the element before merging
# so the fragment the document renders from is left alone.
if not item.is_super_table() or not existing.body:
return None
last = copy.deepcopy(existing.body[-1])
for k, v in item.value.body:
last.value.append(k, copy.deepcopy(v))
elements = [*existing.body[:-1], last]

merged = AoT(elements, parsed=True)
internal._body[idx] = (internal._body[idx][0], merged)
dict.__setitem__(internal, key.key, merged.value)
return merged
Expand Down