Repository navigation
Sort parsed documents recursively with sort_keys - #617
Sreekant13 wants to merge 5 commits into
Conversation
dumps(doc, sort_keys=True) sorted only the top-level keys of a parsed document: item() returned an already-parsed Table/InlineTable/AoT unchanged, so only the top-level container reached the sorting rebuild. A plain dict, whose nested values are plain dicts, already sorted recursively. item() now returns a sorted copy of a parsed table-like item. The copy reorders each container's body by key, keeps every key's leading comment and whitespace lines with it, orders a value that renders as its own section after the inline values so a key never folds under a preceding header, keeps inline tables inline, and leaves the input document unmutated. A container holding an out-of-order table is left in place, since its entries cannot be linearly reordered, but its nested tables are still sorted.
The _sort_container_body annotation referenced Container, which items.py otherwise imports lazily inside item(); add it to the TYPE_CHECKING block so ruff no longer flags F821 (undefined name).
|
Verified locally: full suite passes, the new test fails on master as claimed, and I spot-checked the edge cases (comments following keys, out-of-order fragments, no input mutation, deep nesting, top-level AoT). Looks good to me — the (a)/(b) direction call is for the maintainers, but I lean (a) per the comment on #614. |
|
Thanks for the thorough local verification, and agreed on (a): On your two observations, both are pre-existing and I would keep them out of this PR:
Happy to follow up on either separately if the maintainers would like. |
|
Agreed on the scoping — keeping both observations out of this PR is the right call. Nothing further from my side; the rest is up to the maintainers. |
Wkhanex
left a comment
There was a problem hiding this comment.
There is still a nested sorting case inside an ordinary array:
import tomlkit
doc = tomlkit.parse("values = [{z=2, a=1}]\n")
tomlkit.dumps(doc, sort_keys=True)
# 'values = [{z=2, a=1}]\n'The inline table keeps z, a because _sort_item_keys visits Table, InlineTable and AoT but does not descend through Array. Could it recurse into array elements while retaining their element order? The parsed-table/AoT controls preserve data and leave the original document unchanged.
Checked b7c07ea40bf9 on Python 3.12.14: 6 focused assertions passed, with this sorting case still failing. I haven't run the full upstream suite.
…ort-keys # Conflicts: # CHANGELOG.md
_sort_item_keys descended into Table, InlineTable and AoT but not an
ordinary Array, so sort_keys left an inline table inside an array unsorted:
`values = [{z=2, a=1}]` stayed as-is. It now recurses into array elements,
sorting the keys of any table-like element while leaving the array's own
element order untouched (a list is ordered data, not a key set).
Reordering an inline table's keys also dragged the leading separator space
onto the new first key, so a tight `{z=2, a=1}` sorted to `{ a=1, z=2}`. An
inline table's separators are positional whitespace, not trivia owned by the
following key, so _sort_container_body now permutes only the keyed entries
among their slots for an inline table, leaving every separator in place:
`{z=2, a=1}` sorts to `{a=1, z=2}` and a spaced table keeps its spacing.
Addresses the review on python-poetry#617.
for more information, see https://pre-commit.ci
|
Good catch, and thank you for the thorough check. You are right that >>> tomlkit.dumps(tomlkit.parse("values = [{z=2, a=1}]\n"), sort_keys=True)
'values = [{a=1, z=2}]\n'It also fixes a related wrinkle the same path exposed: reordering a tight inline table used to drag the leading separator space onto the new first key, so |
frostming
left a comment
There was a problem hiding this comment.
the comments are unnecessarily long, please keep it concise and leave comments only when necessary
Fixes #614.
Problem
dumps(doc, sort_keys=True)sorts only the top-level keys of a document produced byparse()/loads(). Nested tables, inline tables and arrays of tables keep their original order, even though the same option applied to a plaindictsorts recursively.Cause
item()returns an already-parsedTable/InlineTable/AoTunchanged. For a parsed document the top-level container reaches thedictbranch and is rebuilt sorted, but each nested value is already such an item, so the recursiveitem(value, _sort_keys=True)call returns it as is and never sorts its keys. A plain dict has plain-dict children, which recurse and sort.Change
Under
_sort_keys,item()returns a sorted copy of a parsed table-like item. The copy:A container holding an out-of-order table is left in place, since its entries cannot be linearly reordered, but its nested tables are still sorted.
Tests
Added
test_dumps_with_sorted_keys_recurses_into_a_parsed_document, covering nested tables, an inline table, an array of tables, a scalar kept before a sub-table section, and that the document is not mutated. It fails onmainand passes with this change. The rest of the suite passes (thetoml-testconformance submodule, which this change does not touch, is not exercised here).