Skip to content

Commit

Permalink
logic: introduce the ability to choose what sorting takes place
Browse files Browse the repository at this point in the history
  • Loading branch information
delfick committed Feb 13, 2024
1 parent 9390fa1 commit 1a3b6d0
Show file tree
Hide file tree
Showing 3 changed files with 96 additions and 3 deletions.
25 changes: 22 additions & 3 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -68,14 +68,33 @@ methods
* indent: Amount to prefix each line for each level of nesting
* newlines: Whether or not to use newlines

``dict2xml.Converter.build(data, iterables_repeat_wrap=True, closed_tags_for=None)``
``dict2xml.Converter.build(data, iterables_repeat_wrap=True, closed_tags_for=None, data_sorter=None)``
Instance method on Converter that takes in the data and creates the xml string

* iterables_repeat_wrap - when false the key the array is in will be repeated
* closed_tags_for - an array of values that will produce self closing tags
* data_sorter - an object as explained below for sorting keys in maps

Note that to ensure the build is deterministic, keys in a mapping will be
sorted unless that mapping is an instance of ``collections.OrderedDict``.
``dict2xml.DataSorter``
An object used to determine the sorting of keys for a map of data.

By default an ``OrderedDict`` object will not have it's keys sorted, but any
other type of mapping will.

It can be made so even ``OrderedDict`` will get sorted by passing in
``data_sorter=DataSorter.always()``.

Or it can be made so that keys are produced from the sorting determined by
the mapping with ``data_sorter=DataSorter.never()``.

.. note:: When this library was first created python did not have deterministic
sorting for normal dictionaries which is why default everything gets sorted but
``OrderedDict`` do not.

To create custom sorting logic requires an object that has a single ``keys_from``
method on it that accepts a map of data and returns a list of strings, where only
the keys that appear in the list will go into the output and those keys must exist
in the original mapping.

Self closing tags
-----------------
Expand Down
8 changes: 8 additions & 0 deletions dict2xml/logic.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,14 @@ def keys_from(self, data):
sorted_data = sorted(data)
return sorted_data

class always:
def keys_from(self, data):
return sorted(data)

class never:
def keys_from(self, data):
return data


class Node(object):
"""
Expand Down
66 changes: 66 additions & 0 deletions tests/node_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,72 @@ def N(*args, **kwargs):
),
]

it "can be told to also sort OrderedDict":
called = []

nodes = [mock.Mock(name="n{0}".format(i)) for i in range(3)]

def N(*args, **kwargs):
called.append(1)
return nodes[len(called) - 1]

ds = DataSorter.always()
irw = mock.Mock("irw")
ctf = mock.Mock("ctf")
FakeNode = mock.Mock(name="Node", side_effect=N)

with mock.patch("dict2xml.logic.Node", FakeNode):
data = collections.OrderedDict([("b", 2), ("c", 3), ("a", 1)])
result = Node(
data=data, iterables_repeat_wrap=irw, closed_tags_for=ctf, data_sorter=ds
).convert()
assert result == ("", nodes)

assert FakeNode.mock_calls == [
mock.call(
"a", "", 1, iterables_repeat_wrap=irw, closed_tags_for=ctf, data_sorter=ds
),
mock.call(
"b", "", 2, iterables_repeat_wrap=irw, closed_tags_for=ctf, data_sorter=ds
),
mock.call(
"c", "", 3, iterables_repeat_wrap=irw, closed_tags_for=ctf, data_sorter=ds
),
]

it "can be told to never sort":
called = []

nodes = [mock.Mock(name="n{0}".format(i)) for i in range(3)]

def N(*args, **kwargs):
called.append(1)
return nodes[len(called) - 1]

ds = DataSorter.never()
irw = mock.Mock("irw")
ctf = mock.Mock("ctf")
FakeNode = mock.Mock(name="Node", side_effect=N)

with mock.patch("dict2xml.logic.Node", FakeNode):
data = {"c": 3, "a": 1, "b": 2}
result = Node(
data=data, iterables_repeat_wrap=irw, closed_tags_for=ctf, data_sorter=ds
).convert()
assert result == ("", nodes)

assert FakeNode.mock_calls == [
mock.call(
"c", "", 3, iterables_repeat_wrap=irw, closed_tags_for=ctf, data_sorter=ds
),
mock.call(
"a", "", 1, iterables_repeat_wrap=irw, closed_tags_for=ctf, data_sorter=ds
),
mock.call(
"b", "", 2, iterables_repeat_wrap=irw, closed_tags_for=ctf, data_sorter=ds
),
]

it "returns list of Nodes with wrap as tag and item as data if type is iterable":
called = []

Expand Down

0 comments on commit 1a3b6d0

Please sign in to comment.