Skip to content
Open
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
- Fix invalid serialization with a duplicated comma when appending or inserting into a comma-first formatted array. ([#499](https://github.com/python-poetry/tomlkit/pull/499))
- Fix `ParseError` when a sub-table extends the last element of an array of tables after an unrelated table. ([#261](https://github.com/python-poetry/tomlkit/issues/261))
- Fix unparseable serialization when adding a key to a dotted-key table inside an inline table. ([#500](https://github.com/python-poetry/tomlkit/pull/500))
- Fix `item()` reordering the keys of an inline table (dict-valued keys were forced last) under the default `sort_keys=False`. ([#546](https://github.com/python-poetry/tomlkit/issues/546))
- Fix a table replaced by a plain value being serialized inside the preceding table's body when other tables follow; the value now moves before the first table like other root-level values. ([#504](https://github.com/python-poetry/tomlkit/issues/504))
- Fix assigning a table over a dotted key (e.g. `doc["a"] = {...}` where `a` came from `a.b = ...`): the dotted prefix was duplicated onto the new `[a]` header, and the header then swallowed any sibling that follows it on round-trip. The replacement now renders as a plain table and, when needed, moves before the inline entries (values and dotted keys) it would otherwise capture. ([#513](https://github.com/python-poetry/tomlkit/issues/513), [#524](https://github.com/python-poetry/tomlkit/issues/524))
- Restore `dumps()` rendering mapping-like wrappers around a parsed document (e.g. `dotty_dict`'s `Dotty`) through their delegated `as_string`, preserving the original table order and layout instead of re-encoding through a plain dict — a 0.15.0 regression. ([#482](https://github.com/python-poetry/tomlkit/issues/482))
Expand Down
22 changes: 22 additions & 0 deletions tests/test_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -347,6 +347,28 @@ def test_item_mixed_aray() -> None:
assert dumps({"x": {"y": example}}).strip() == "[x]\ny = " + expected


def test_item_inline_table_preserves_key_order() -> None:
# Inline tables cannot capture following keys, so their key order must be
# preserved under the default sort_keys=False, even when a value is itself
# a dict/table (#546).
assert (
tomlkit.item([[{"a": {"x": 1}, "b": 2}]]).as_string().strip()
== "[[{a = {x = 1}, b = 2}]]"
)

# Arrays of tables still render dict-valued keys last to avoid them
# capturing the following keys.
assert (
tomlkit.item([{"a": {"x": 1}, "b": 2}]).as_string() == "b = 2\n\n[a]\nx = 1\n"
)

# Sorting is still honoured when explicitly requested.
assert (
tomlkit.item([[{"b": 2, "a": {"x": 1}}]], _sort_keys=True).as_string().strip()
== "[[{b = 2, a = {x = 1}}]]"
)


def test_build_super_table() -> None:
doc = tomlkit.document()
table = tomlkit.table(True)
Expand Down
16 changes: 12 additions & 4 deletions tomlkit/items.py
Original file line number Diff line number Diff line change
Expand Up @@ -161,14 +161,22 @@ def item(value: Any, _parent: Item | None = None, _sort_keys: bool = False) -> I
a = Array([], Trivia())
table_constructor = InlineTable

if table_constructor is Table:
# AoT tables render as ``[[table]]`` headers, so dict-valued keys
# must come last to avoid them capturing the following keys.
def _sort_key(i: tuple[Any, Any]) -> Any:
return (isinstance(i[1], dict), i[0] if _sort_keys else 1)
else:
# Inline tables cannot capture, so preserve insertion order unless
# explicitly sorting (matching the dict branch above). See #546.
def _sort_key(i: tuple[Any, Any]) -> Any:
return (isinstance(i[1], dict), i[0]) if _sort_keys else 1

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why are you keeping dictionaries last in this branch?


for v in value:
if isinstance(v, dict):
table = table_constructor(Container(), Trivia(), True)

for k, _v in sorted(
v.items(),
key=lambda i: (isinstance(i[1], dict), i[0] if _sort_keys else 1),
):
for k, _v in sorted(v.items(), key=_sort_key):
i = item(_v, _parent=table, _sort_keys=_sort_keys)
if isinstance(table, InlineTable):
i.trivia.trail = ""
Expand Down
Loading