From a789bd128f9c88022084d82378fa28f3bdf24748 Mon Sep 17 00:00:00 2001 From: Max Gautier Date: Mon, 2 Dec 2024 20:20:13 +0100 Subject: [PATCH] Add the accumulate filter (#9133) * Add the accumulate filter - Add myself as a maintainer for it. - Some integration tests. * accumulate: fix documentation and add test aliases The aliases file was copied over from tests/integrations/targets/filter_dict/aliases as the documentation[1] suggests to use the same group as existing similar tests. [1]: https://docs.ansible.com/ansible/latest/dev_guide/testing/sanity/integration-aliases.html Suggested-by: Felix Fontein * accumulate: documentation: markup consistency with other plugins Suggested-by: Felix Fontein Suggested-by: Alexei Znamensky <103110+russoz@users.noreply.github.com> * filter/accumulate: Validate input is a Sequence Accepting arbitrary iterables might lead to surprising behavior so we are stricter on what we accept in the filter. Relaxing those requirements is easier than retrofitting them, in terms of backwards compatibility. Suggested-by: Felix Fontein Signed-off-by: Max Gautier * filter/accumulate: Document the behavior with a string Signed-off-by: Max Gautier --------- Signed-off-by: Max Gautier --- .github/BOTMETA.yml | 2 + plugins/filter/accumulate.py | 62 +++++++++++++++++++ .../targets/filter_accumulate/aliases | 5 ++ .../targets/filter_accumulate/tasks/main.yml | 35 +++++++++++ 4 files changed, 104 insertions(+) create mode 100644 plugins/filter/accumulate.py create mode 100644 tests/integration/targets/filter_accumulate/aliases create mode 100644 tests/integration/targets/filter_accumulate/tasks/main.yml diff --git a/.github/BOTMETA.yml b/.github/BOTMETA.yml index 9650fd0ef3..ec9b9b7ddc 100644 --- a/.github/BOTMETA.yml +++ b/.github/BOTMETA.yml @@ -135,6 +135,8 @@ files: $doc_fragments/xenserver.py: labels: xenserver maintainers: bvitnik + $filters/accumulate.py: + maintainers: VannTen $filters/counter.py: maintainers: keilr $filters/crc32.py: diff --git a/plugins/filter/accumulate.py b/plugins/filter/accumulate.py new file mode 100644 index 0000000000..9400936e1d --- /dev/null +++ b/plugins/filter/accumulate.py @@ -0,0 +1,62 @@ +# Copyright (c) Max Gautier +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +DOCUMENTATION = ''' + name: accumulate + short_description: Produce a list of accumulated sums of the input list contents + version_added: 10.1.0 + author: Max Gautier (@VannTen) + description: + - Passthrough to the L(Python itertools.accumulate function,https://docs.python.org/3/library/itertools.html#itertools.accumulate). + - Transforms an input list into the cumulative list of results from applying addition to the elements of the input list. + - Addition means the default Python implementation of C(+) for input list elements type. + options: + _input: + description: A list. + type: list + elements: any + required: true +''' + +RETURN = ''' + _value: + description: A list of cumulated sums of the elements of the input list. + type: list + elements: any +''' + +EXAMPLES = ''' +- name: Enumerate parent directories of some path + ansible.builtin.debug: + var: > + "/some/path/to/my/file" + | split('/') | map('split', '/') + | community.general.accumulate | map('join', '/') + # Produces: ['', '/some', '/some/path', '/some/path/to', '/some/path/to/my', '/some/path/to/my/file'] +- name: Growing string + ansible.builtin.debug: + var: "'abc' | community.general.accumulate" + # Produces ['a', 'ab', 'abc'] +''' + +from itertools import accumulate +from collections.abc import Sequence + +from ansible.errors import AnsibleFilterError + + +def list_accumulate(sequence): + if not isinstance(sequence, Sequence): + raise AnsibleFilterError('Invalid value type (%s) for accumulate (%r)' % + (type(sequence), sequence)) + + return accumulate(sequence) + + +class FilterModule(object): + + def filters(self): + return { + 'accumulate': list_accumulate, + } diff --git a/tests/integration/targets/filter_accumulate/aliases b/tests/integration/targets/filter_accumulate/aliases new file mode 100644 index 0000000000..343f119da8 --- /dev/null +++ b/tests/integration/targets/filter_accumulate/aliases @@ -0,0 +1,5 @@ +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +azp/posix/3 diff --git a/tests/integration/targets/filter_accumulate/tasks/main.yml b/tests/integration/targets/filter_accumulate/tasks/main.yml new file mode 100644 index 0000000000..8fe854228a --- /dev/null +++ b/tests/integration/targets/filter_accumulate/tasks/main.yml @@ -0,0 +1,35 @@ +--- +# Copyright (c), Max Gautier +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +- name: Filter | Accumulate | Test valid values + assert: + that: + - "'abc' | community.general.accumulate == ['a', 'ab', 'abc']" + - "['a', 'b'] | community.general.accumulate == ['a', 'ab']" + - "[1, 2, 3] | community.general.accumulate == [1, 3, 6]" + - "[['foo'],['bar'],['foobar']] | community.general.accumulate == [['foo'], ['foo', 'bar'], ['foo', 'bar', 'foobar']]" + - "'path/to/file' | split('/') | map('split', '/') | community.general.accumulate | map('join', '/') == ['path', 'path/to', 'path/to/file']" + - "[{'foo':1}, {'bar':2}] | map('dict2items') | community.general.accumulate | map('items2dict') == [{'foo':1}, {'foo':1, 'bar':2}]" + + +- name: Filter | Accumulate | Test invalid values | Integer + debug: + var: "1 | community.general.accumulate" + register: integer_result + ignore_errors: true + +- name: Filter | Accumulate | Test invalid values | Non uniform list + debug: + var: "['aa', 1] | community.general.accumulate" + register: non_uniform_list_result + ignore_errors: true + +- name: Filter | Accumulate | Test invalid values | Check errors + assert: + that: + - integer_result is failed + - integer_result.msg is match('Invalid value type.*') + - non_uniform_list_result is failed + - non_uniform_list_result.msg is match('Unexpected templating type error.*can only concatenate str.*')