Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[master] implement jq-esque to_entries and from_entries functions #64602

Merged
merged 5 commits into from Aug 14, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions changelog/64600.added.md
@@ -0,0 +1 @@
Add jq-esque to_entries and from_entries functions
51 changes: 51 additions & 0 deletions doc/topics/jinja/index.rst
Expand Up @@ -1682,6 +1682,57 @@ Returns:
.. _`JMESPath language`: https://jmespath.org/
.. _`jmespath`: https://github.com/jmespath/jmespath.py


.. jinja_ref:: to_entries

``to_entries``
--------------

.. versionadded:: 3007.0

A port of the ``to_entries`` function from ``jq``. This function converts between an object and an array of key-value
pairs. If ``to_entries`` is passed an object, then for each ``k: v`` entry in the input, the output array includes
``{"key": k, "value": v}``. The ``from_entries`` function performs the opposite conversion. ``from_entries`` accepts
"key", "Key", "name", "Name", "value", and "Value" as keys.

Example:

.. code-block:: jinja

{{ {"a": 1, "b": 2} | to_entries }}

Returns:

.. code-block:: text

[{"key":"a", "value":1}, {"key":"b", "value":2}]


.. jinja_ref:: from_entries

``from_entries``
----------------

.. versionadded:: 3007.0

A port of the ``from_entries`` function from ``jq``. This function converts between an array of key-value pairs and an
object. If ``from_entries`` is passed an object, then the input is expected to be an array of dictionaries in the format
of ``{"key": k, "value": v}``. The output will be be key-value pairs ``k: v``. ``from_entries`` accepts "key", "Key",
"name", "Name", "value", and "Value" as keys.

Example:

.. code-block:: jinja

{{ [{"key":"a", "value":1}, {"key":"b", "value":2}] | from_entries }}

Returns:

.. code-block:: text

{"a": 1, "b": 2}


.. jinja_ref:: to_snake_case

``to_snake_case``
Expand Down
56 changes: 56 additions & 0 deletions salt/utils/data.py
Expand Up @@ -1690,3 +1690,59 @@ def shuffle(value, seed=None):
Any value which will be hashed as a seed for random.
"""
return sample(value, len(value), seed=seed)


@jinja_filter("to_entries")
def to_entries(data):
"""
Convert a dictionary or list into a list of key-value pairs (entries).

Args:
data (dict, list): The input dictionary or list.

Returns:
list: A list of dictionaries representing the key-value pairs.
Each dictionary has 'key' and 'value' keys.

Example:
data = {'a': 1, 'b': 2}
entries = to_entries(data)
print(entries)
# Output: [{'key': 'a', 'value': 1}, {'key': 'b', 'value': 2}]
"""
if isinstance(data, dict):
ret = [{"key": key, "value": value} for key, value in data.items()]
elif isinstance(data, list):
ret = [{"key": idx, "value": value} for idx, value in enumerate(data)]
else:
raise SaltException("Input data must be a dict or list")
return ret


@jinja_filter("from_entries")
def from_entries(entries):
"""
Convert a list of key-value pairs (entries) into a dictionary.

Args:
entries (list): A list of dictionaries representing the key-value pairs.
Each dictionary must have 'key' and 'value' keys.

Returns:
dict: A dictionary constructed from the key-value pairs.

Example:
entries = [{'key': 'a', 'value': 1}, {'key': 'b', 'value': 2}]
dictionary = from_entries(entries)
print(dictionary)
# Output: {'a': 1, 'b': 2}
"""
ret = {}
for entry in entries:
entry = CaseInsensitiveDict(entry)
for key in ("key", "name"):
keyval = entry.get(key)
if keyval:
ret[keyval] = entry.get("value")
break
return ret
20 changes: 20 additions & 0 deletions tests/pytests/unit/utils/test_data.py
@@ -1,6 +1,7 @@
import pytest

import salt.utils.data
from salt.exceptions import SaltException


def test_get_value_simple_path():
Expand Down Expand Up @@ -87,3 +88,22 @@ def test_shuffle():
"three",
"one",
]


def test_to_entries():
data = {"a": 1, "b": 2}
entries = [{"key": "a", "value": 1}, {"key": "b", "value": 2}]
assert salt.utils.data.to_entries(data) == entries

data = ["monkey", "donkey"]
entries = [{"key": 0, "value": "monkey"}, {"key": 1, "value": "donkey"}]
assert salt.utils.data.to_entries(data) == entries

with pytest.raises(SaltException):
salt.utils.data.to_entries("RAISE ON THIS")


def test_from_entries():
entries = [{"key": "a", "value": 1}, {"key": "b", "value": 2}]
data = {"a": 1, "b": 2}
assert salt.utils.data.from_entries(entries) == data