From 0a2e083c9b80a3a62e03b04b6293ae5452e46665 Mon Sep 17 00:00:00 2001 From: stoned Date: Thu, 17 Jun 2021 09:26:13 +0200 Subject: [PATCH] Make to_paths handles empty list and mapping (#58) Make to_paths handles empty list and mapping Reviewed-by: https://github.com/apps/ansible-zuul --- changelogs/fragments/empty_list_mapping.yml | 4 ++ plugins/module_utils/common/to_paths.py | 41 ++++++++++++------- .../to_paths/tasks/include/empty_members.yaml | 40 ++++++++++++++++++ tests/unit/module_utils/test_to_paths.py | 36 ++++++++++++++++ 4 files changed, 107 insertions(+), 14 deletions(-) create mode 100644 changelogs/fragments/empty_list_mapping.yml create mode 100644 tests/integration/targets/to_paths/tasks/include/empty_members.yaml diff --git a/changelogs/fragments/empty_list_mapping.yml b/changelogs/fragments/empty_list_mapping.yml new file mode 100644 index 0000000..71f4d7a --- /dev/null +++ b/changelogs/fragments/empty_list_mapping.yml @@ -0,0 +1,4 @@ +--- +bugfixes: + - Also include empty lists and mappings into the output dictionary + (https://github.com/ansible-collections/ansible.utils/pull/58). diff --git a/plugins/module_utils/common/to_paths.py b/plugins/module_utils/common/to_paths.py index e15eea4..5f970ec 100644 --- a/plugins/module_utils/common/to_paths.py +++ b/plugins/module_utils/common/to_paths.py @@ -22,26 +22,39 @@ def to_paths(var, prepend, wantlist): if prepend: var = {prepend: var} - out = {} - - def flatten(data, name=""): + def flatten(data, name="", out=None): + if out is None: + out = {} if isinstance(data, (dict, Mapping, MutableMapping)): - for key, val in data.items(): - if name: - if re.match("^[a-zA-Z_][a-zA-Z0-9_]*$", key): - nname = name + ".{key}".format(key=key) + if data: + for key, val in data.items(): + if name: + if re.match("^[a-zA-Z_][a-zA-Z0-9_]*$", key): + nname = name + ".{key}".format(key=key) + else: + nname = name + "['{key}']".format(key=key) else: - nname = name + "['{key}']".format(key=key) - else: - nname = key - flatten(val, nname) + nname = key + flatten(val, nname, out) + elif name: + out[name] = {} + else: + out = {} elif isinstance(data, list): - for idx, val in enumerate(data): - flatten(val, "{name}[{idx}]".format(name=name, idx=idx)) + if data: + for idx, val in enumerate(data): + flatten( + val, "{name}[{idx}]".format(name=name, idx=idx), out + ) + elif name: + out[name] = [] + else: + out = [] else: out[name] = data + return out - flatten(var) + out = flatten(var) if wantlist: return [out] return out diff --git a/tests/integration/targets/to_paths/tasks/include/empty_members.yaml b/tests/integration/targets/to_paths/tasks/include/empty_members.yaml new file mode 100644 index 0000000..2a8d420 --- /dev/null +++ b/tests/integration/targets/to_paths/tasks/include/empty_members.yaml @@ -0,0 +1,40 @@ +--- +- ansible.builtin.set_fact: + a: + b: [] + c: {} + d: + e: [{}, {}] + f: + g: [[], []] + empty_list: [] + empty_mapping: {} + +- name: Test filter and lookup plugin with empty list and mapping + assert: + that: "{{ item.result == item.expected }}" + loop: + - result: "{{ a|ansible.utils.to_paths }}" + expected: + b: [] + c: {} + d.e[0]: {} + d.e[1]: {} + f.g[0]: [] + f.g[1]: [] + - result: "{{ lookup('ansible.utils.to_paths', a) }}" + expected: + b: [] + c: {} + d.e[0]: {} + d.e[1]: {} + f.g[0]: [] + f.g[1]: [] + - result: "{{ empty_list|ansible.utils.to_paths }}" + expected: [] + - result: "{{ lookup('ansible.utils.to_paths', empty_list) }}" + expected: [] + - result: "{{ empty_mapping|ansible.utils.to_paths }}" + expected: {} + - result: "{{ lookup('ansible.utils.to_paths', empty_mapping) }}" + expected: {} diff --git a/tests/unit/module_utils/test_to_paths.py b/tests/unit/module_utils/test_to_paths.py index e600d32..958e494 100644 --- a/tests/unit/module_utils/test_to_paths.py +++ b/tests/unit/module_utils/test_to_paths.py @@ -65,3 +65,39 @@ class TestToPaths(unittest.TestCase): var, to_test, environment=self._environment, wantlist=False ) self.assertEqual(gotten, paths[to_test]) + + def test_to_paths_empty_list(self): + var = {"a": []} + expected = {"a": []} + result = to_paths(var, prepend=None, wantlist=None) + self.assertEqual(result, expected) + + def test_to_paths_list_of_empty_list(self): + var = {"a": [[], []]} + expected = {"a[0]": [], "a[1]": []} + result = to_paths(var, prepend=None, wantlist=None) + self.assertEqual(result, expected) + + def test_to_paths_empty_mapping(self): + var = {"a": {}} + expected = {"a": {}} + result = to_paths(var, prepend=None, wantlist=None) + self.assertEqual(result, expected) + + def test_to_paths_list_of_empty_mapping(self): + var = [{}, {}] + expected = {"[0]": {}, "[1]": {}} + result = to_paths(var, prepend=None, wantlist=None) + self.assertEqual(result, expected) + + def test_to_paths_only_empty_list(self): + var = [] + expected = [] + result = to_paths(var, prepend=None, wantlist=None) + self.assertEqual(result, expected) + + def test_to_paths_only_empty_mapping(self): + var = {} + expected = {} + result = to_paths(var, prepend=None, wantlist=None) + self.assertEqual(result, expected)