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

Update tojson to allow arbitrary args #56022

Merged
merged 9 commits into from Mar 5, 2021
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/56012.added
@@ -0,0 +1 @@
Added arbitrary kwarg support for tojson filter.
10 changes: 5 additions & 5 deletions salt/utils/jinja.py
Expand Up @@ -387,13 +387,13 @@ def indent(s, width=4, first=False, blank=False, indentfirst=None):


@jinja_filter("tojson")
def tojson(val, indent=None):
def tojson(val, indent=None, **options):
"""
Implementation of tojson filter (only present in Jinja 2.9 and later). If
Jinja 2.9 or later is installed, then the upstream version of this filter
will be used.
Implementation of tojson filter (only present in Jinja 2.9 and later).
Unlike the Jinja built-in filter, this allows arbitrary options to be
passed in to the underlying JSON library.
"""
options = {"ensure_ascii": True}
options.setdefault("ensure_ascii", True)
if indent is not None:
options["indent"] = indent
return (
Expand Down
4 changes: 0 additions & 4 deletions salt/utils/templates.py
Expand Up @@ -460,13 +460,9 @@ def opt_jinja_env_helper(opts, optname):
undefined=jinja2.StrictUndefined, **env_args
)

tojson_filter = jinja_env.filters.get("tojson")
indent_filter = jinja_env.filters.get("indent")
jinja_env.tests.update(JinjaTest.salt_jinja_tests)
jinja_env.filters.update(JinjaFilter.salt_jinja_filters)
if tojson_filter is not None:
# Use the existing tojson filter, if present (jinja2 >= 2.9)
jinja_env.filters["tojson"] = tojson_filter
if salt.utils.jinja.JINJA_VERSION >= LooseVersion("2.11"):
# Use the existing indent filter on Jinja versions where it's not broken
jinja_env.filters["indent"] = indent_filter
Expand Down
7 changes: 7 additions & 0 deletions tests/unit/utils/test_jinja.py
Expand Up @@ -74,6 +74,13 @@ def test_indent(self):
expected = Markup('foo:\n "bar"')
assert result == expected, result

def test_tojson_should_ascii_sort_keys_when_told(self):
data = {"z": "zzz", "y": "yyy", "x": "xxx"}
expected = '{"x": "xxx", "y": "yyy", "z": "zzz"}'

actual = tojson(data, sort_keys=True)
assert actual == expected


class MockFileClient:
"""
Expand Down
49 changes: 36 additions & 13 deletions tests/unit/utils/test_templates.py
Expand Up @@ -2,16 +2,18 @@
Unit tests for salt.utils.templates.py
"""


import logging
import os
import sys
from collections import OrderedDict
from pathlib import PurePath, PurePosixPath

import pytest
import salt.utils.files
import salt.utils.templates
from tests.support import mock
from tests.support.helpers import with_tempdir
from tests.support.mock import patch
from tests.support.unit import TestCase, skipIf

try:
Expand Down Expand Up @@ -56,6 +58,31 @@ def test_render_jinja_variable(self):
res = salt.utils.templates.render_jinja_tmpl(tmpl, ctx)
self.assertEqual(res, "OK")

def test_render_jinja_tojson_sorted(self):
templ = """thing: {{ var|tojson(sort_keys=True) }}"""
expected = """thing: {"x": "xxx", "y": "yyy", "z": "zzz"}"""

with patch.dict(self.context, {"var": {"z": "zzz", "y": "yyy", "x": "xxx"}}):
res = salt.utils.templates.render_jinja_tmpl(templ, self.context)

assert res == expected

def test_render_jinja_tojson_unsorted(self):
templ = """thing: {{ var|tojson(sort_keys=False) }}"""
expected = """thing: {"z": "zzz", "x": "xxx", "y": "yyy"}"""

# Values must be added to the dict in the expected order. This is
# only necessary for older Pythons that don't remember dict order.
d = OrderedDict()
d["z"] = "zzz"
d["x"] = "xxx"
d["y"] = "yyy"

with patch.dict(self.context, {"var": d}):
res = salt.utils.templates.render_jinja_tmpl(templ, self.context)

assert res == expected

### Tests for mako template
def test_render_mako_sanity(self):
tmpl = """OK"""
Expand Down Expand Up @@ -234,7 +261,7 @@ def _test_generated_sls_context(self, tmplpath, sls, **expected):
actual = salt.utils.templates._generate_sls_context(tmplpath, sls)
self.assertDictContainsAll(actual, **expected)

@mock.patch("salt.utils.templates.generate_sls_context")
@patch("salt.utils.templates.generate_sls_context")
@with_tempdir()
def test_sls_context_call(self, tempdir, generate_sls_context):
""" Check that generate_sls_context is called with proper parameters"""
Expand All @@ -250,7 +277,7 @@ def test_sls_context_call(self, tempdir, generate_sls_context):
res = wrapped(slsfile, context=context, tmplpath=tmplpath)
generate_sls_context.assert_called_with(tmplpath, sls)

@mock.patch("salt.utils.templates.generate_sls_context")
@patch("salt.utils.templates.generate_sls_context")
@with_tempdir()
def test_sls_context_no_call(self, tempdir, generate_sls_context):
""" Check that generate_sls_context is not called if sls is not set"""
Expand Down Expand Up @@ -398,11 +425,9 @@ def test_generate_sls_context__two_level_repeating(self):
slspath="foo/foo",
)

@mock.patch(
"salt.utils.templates._generate_sls_context_legacy", return_value="legacy"
)
@mock.patch("salt.utils.templates._generate_sls_context", return_value="new")
@mock.patch("salt.utils.templates.features.get", return_value=True)
@patch("salt.utils.templates._generate_sls_context_legacy", return_value="legacy")
@patch("salt.utils.templates._generate_sls_context", return_value="new")
@patch("salt.utils.templates.features.get", return_value=True)
def test_feature_flag_on(self, feature_get, new_impl, legacy_impl):
""" Test feature flag selection with FF on"""
tplpath = "tplpath"
Expand All @@ -411,11 +436,9 @@ def test_feature_flag_on(self, feature_get, new_impl, legacy_impl):
new_impl.assert_called_with(tplpath, sls)
legacy_impl.assert_not_called()

@mock.patch(
"salt.utils.templates._generate_sls_context_legacy", return_value="legacy"
)
@mock.patch("salt.utils.templates._generate_sls_context", return_value="new")
@mock.patch("salt.utils.templates.features.get", return_value=False)
@patch("salt.utils.templates._generate_sls_context_legacy", return_value="legacy")
@patch("salt.utils.templates._generate_sls_context", return_value="new")
@patch("salt.utils.templates.features.get", return_value=False)
def test_feature_flag_off(self, feature_get, new_impl, legacy_impl):
""" Test feature flag selection with FF on"""
tplpath = "tplpath"
Expand Down