[stable-2.19] Fix slicing of tuples in templating (#85608) (#85661)

* Fix slicing of tuples in templating

* Improve lazy container test coverage
(cherry picked from commit 00fe38215c)
pull/85675/head
Matt Clay 4 months ago committed by GitHub
parent 0718473815
commit 3c1e46435b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -0,0 +1,2 @@
bugfixes:
- templating - Fix slicing of tuples in templating (https://github.com/ansible/ansible/issues/85606).

@ -229,8 +229,6 @@ class _AnsibleLazyTemplateDict(_AnsibleTaggedDict, _AnsibleLazyTemplateMixin):
__slots__ = _AnsibleLazyTemplateMixin._SLOTS
def __init__(self, contents: t.Iterable | _LazyValueSource, /, **kwargs) -> None:
_AnsibleLazyTemplateMixin.__init__(self, contents)
if isinstance(contents, _AnsibleLazyTemplateDict):
super().__init__(dict.items(contents), **kwargs)
elif isinstance(contents, _LazyValueSource):
@ -238,6 +236,8 @@ class _AnsibleLazyTemplateDict(_AnsibleTaggedDict, _AnsibleLazyTemplateMixin):
else:
raise UnsupportedConstructionMethodError()
_AnsibleLazyTemplateMixin.__init__(self, contents)
def get(self, key: t.Any, default: t.Any = None) -> t.Any:
if (value := super().get(key, _NoKeySentinel)) is _NoKeySentinel:
return default
@ -372,8 +372,6 @@ class _AnsibleLazyTemplateList(_AnsibleTaggedList, _AnsibleLazyTemplateMixin):
__slots__ = _AnsibleLazyTemplateMixin._SLOTS
def __init__(self, contents: t.Iterable | _LazyValueSource, /) -> None:
_AnsibleLazyTemplateMixin.__init__(self, contents)
if isinstance(contents, _AnsibleLazyTemplateList):
super().__init__(list.__iter__(contents))
elif isinstance(contents, _LazyValueSource):
@ -381,6 +379,8 @@ class _AnsibleLazyTemplateList(_AnsibleTaggedList, _AnsibleLazyTemplateMixin):
else:
raise UnsupportedConstructionMethodError()
_AnsibleLazyTemplateMixin.__init__(self, contents)
def __getitem__(self, key: t.SupportsIndex | slice, /) -> t.Any:
if type(key) is slice: # pylint: disable=unidiomatic-typecheck
return _AnsibleLazyTemplateList(_LazyValueSource(source=super().__getitem__(key), templar=self._templar, lazy_options=self._lazy_options))
@ -567,7 +567,7 @@ class _AnsibleLazyAccessTuple(_AnsibleTaggedTuple, _AnsibleLazyTemplateMixin):
def __getitem__(self, key: t.SupportsIndex | slice, /) -> t.Any:
if type(key) is slice: # pylint: disable=unidiomatic-typecheck
return _AnsibleLazyAccessTuple(super().__getitem__(key))
return _AnsibleLazyAccessTuple(_LazyValueSource(source=super().__getitem__(key), templar=self._templar, lazy_options=self._lazy_options))
value = super().__getitem__(key)

@ -16,7 +16,8 @@ from ansible._internal._templating._jinja_common import CapturedExceptionMarker,
from ansible._internal._datatag._tags import Origin, TrustedAsTemplate
from ansible._internal._templating._utils import TemplateContext, LazyOptions
from ansible._internal._templating._engine import TemplateEngine, TemplateOptions
from ansible._internal._templating._lazy_containers import _AnsibleLazyTemplateMixin, _AnsibleLazyTemplateList, _AnsibleLazyTemplateDict, _LazyValue
from ansible._internal._templating._lazy_containers import _AnsibleLazyTemplateMixin, _AnsibleLazyTemplateList, _AnsibleLazyTemplateDict, _LazyValue, \
_AnsibleLazyAccessTuple, UnsupportedConstructionMethodError
from ansible.module_utils._internal._datatag import AnsibleTaggedObject
from ...module_utils.datatag.test_datatag import ExampleSingletonTag
@ -299,6 +300,7 @@ def test_lazy_list_adapter_operators(template, variables, expected) -> None:
('type(d1)(d1)', dict(a=_LazyValue(1), c=_LazyValue(1)), _AnsibleLazyTemplateDict), # _AnsibleLazyTemplateDict.__init__ copy
('l1.copy()', [_LazyValue(1)], _AnsibleLazyTemplateList), # _AnsibleLazyTemplateList.copy
('type(l1)(l1)', [_LazyValue(1)], _AnsibleLazyTemplateList), # _AnsibleLazyTemplateList.__init__ copy
('type(t1)(t1)', (1,), _AnsibleLazyAccessTuple),
('copy.copy(l1)', [_LazyValue(1)], _AnsibleLazyTemplateList),
('copy.copy(d1)', dict(a=_LazyValue(1), c=_LazyValue(1)), _AnsibleLazyTemplateDict),
('copy.deepcopy(l1)', [_LazyValue(1)], _AnsibleLazyTemplateList), # __AnsibleLazyTemplateList.__deepcopy__
@ -308,6 +310,7 @@ def test_lazy_list_adapter_operators(template, variables, expected) -> None:
('list(reversed(l1))', [1], list), # _AnsibleLazyTemplateList.__reversed__
('list(reversed(d1))', ['c', 'a'], list), # dict.__reversed__ - keys only
('l1[:]', [_LazyValue(1)], _AnsibleLazyTemplateList), # __getitem__ (slice)
('t1[:]', (1,), _AnsibleLazyAccessTuple), # __getitem__ (slice)
('d1["a"]', 1, int), # __getitem__
('d1.get("a")', 1, int), # get
('l1[0]', 1, int), # __getitem__
@ -366,6 +369,9 @@ def test_lazy_list_adapter_operators(template, variables, expected) -> None:
('tuple() + l1', 'can only concatenate tuple (not "_AnsibleLazyTemplateList") to tuple', TypeError), # __radd__ (relies on tuple.__add__)
('tuple() + d1', 'can only concatenate tuple (not "_AnsibleLazyTemplateDict") to tuple', TypeError), # relies on tuple.__add__
('l1.pop(42)', "pop index out of range", IndexError),
('type(l1)([])', 'Direct construction of lazy containers is not supported.', UnsupportedConstructionMethodError),
('type(t1)([])', 'Direct construction of lazy containers is not supported.', UnsupportedConstructionMethodError),
('type(d1)({})', 'Direct construction of lazy containers is not supported.', UnsupportedConstructionMethodError),
], ids=str)
def test_lazy_container_operators(expression: str, expected_value: t.Any, expected_type: type) -> None:
"""
@ -387,6 +393,7 @@ def test_lazy_container_operators(expression: str, expected_value: t.Any, expect
l1x=[TRUST.tag('{{ one }}')],
l2=[TRUST.tag('{{ two }}')],
l2f=l2f,
t1=(TRUST.tag('{{ one }}'),),
d1=dict(a=TRUST.tag('{{ one }}'), c=TRUST.tag('{{ one }}')),
d1x=dict(a=TRUST.tag('{{ one }}'), c=TRUST.tag('{{ one }}')),
d2=dict(b=TRUST.tag('{{ two }}'), c=TRUST.tag('{{ two }}')),
@ -436,6 +443,15 @@ def test_lazy_container_operators(expression: str, expected_value: t.Any, expect
actual_list_types: list[type] = [type(value) for value in list.__iter__(result)]
assert actual_list_types == expected_list_types
elif issubclass(expected_type, tuple):
assert isinstance(result, tuple) # redundant, but assists mypy in understanding the type
expected_tuple_types = [type(value) for value in expected_value]
expected_result = expected_value
actual_tuple_types: list[type] = [type(value) for value in tuple.__iter__(result)]
assert actual_tuple_types == expected_tuple_types
elif issubclass(expected_type, dict):
assert isinstance(result, dict) # redundant, but assists mypy in understanding the type
@ -867,3 +883,12 @@ def test_lazy_copies(value: list | dict, deep: bool, template_context: TemplateC
assert all((base_type.__getitem__(copied, key) is base_type.__getitem__(original, key)) != deep for key in keys)
assert (copied._templar is original._templar) != deep
assert (copied._lazy_options is original._lazy_options) != deep
def test_lazy_template_mixin_init() -> None:
"""
Verify `_AnsibleLazyTemplateMixin` checks the __init__ arg type.
This code path is not normally reachable, since types which use it perform the same check before invoking the mixin.
"""
with pytest.raises(UnsupportedConstructionMethodError):
_AnsibleLazyTemplateMixin(t.cast(t.Any, None))

Loading…
Cancel
Save