mirror of
https://github.com/zebrajr/ansible.git
synced 2025-12-06 12:19:53 +01:00
Fix slicing of tuples in templating (#85608)
* Fix slicing of tuples in templating * Improve lazy container test coverage
This commit is contained in:
parent
4cb5fe44b6
commit
00fe38215c
2
changelogs/fragments/template-tuple-fix.yml
Normal file
2
changelogs/fragments/template-tuple-fix.yml
Normal file
|
|
@ -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
|
__slots__ = _AnsibleLazyTemplateMixin._SLOTS
|
||||||
|
|
||||||
def __init__(self, contents: t.Iterable | _LazyValueSource, /, **kwargs) -> None:
|
def __init__(self, contents: t.Iterable | _LazyValueSource, /, **kwargs) -> None:
|
||||||
_AnsibleLazyTemplateMixin.__init__(self, contents)
|
|
||||||
|
|
||||||
if isinstance(contents, _AnsibleLazyTemplateDict):
|
if isinstance(contents, _AnsibleLazyTemplateDict):
|
||||||
super().__init__(dict.items(contents), **kwargs)
|
super().__init__(dict.items(contents), **kwargs)
|
||||||
elif isinstance(contents, _LazyValueSource):
|
elif isinstance(contents, _LazyValueSource):
|
||||||
|
|
@ -238,6 +236,8 @@ class _AnsibleLazyTemplateDict(_AnsibleTaggedDict, _AnsibleLazyTemplateMixin):
|
||||||
else:
|
else:
|
||||||
raise UnsupportedConstructionMethodError()
|
raise UnsupportedConstructionMethodError()
|
||||||
|
|
||||||
|
_AnsibleLazyTemplateMixin.__init__(self, contents)
|
||||||
|
|
||||||
def get(self, key: t.Any, default: t.Any = None) -> t.Any:
|
def get(self, key: t.Any, default: t.Any = None) -> t.Any:
|
||||||
if (value := super().get(key, _NoKeySentinel)) is _NoKeySentinel:
|
if (value := super().get(key, _NoKeySentinel)) is _NoKeySentinel:
|
||||||
return default
|
return default
|
||||||
|
|
@ -372,8 +372,6 @@ class _AnsibleLazyTemplateList(_AnsibleTaggedList, _AnsibleLazyTemplateMixin):
|
||||||
__slots__ = _AnsibleLazyTemplateMixin._SLOTS
|
__slots__ = _AnsibleLazyTemplateMixin._SLOTS
|
||||||
|
|
||||||
def __init__(self, contents: t.Iterable | _LazyValueSource, /) -> None:
|
def __init__(self, contents: t.Iterable | _LazyValueSource, /) -> None:
|
||||||
_AnsibleLazyTemplateMixin.__init__(self, contents)
|
|
||||||
|
|
||||||
if isinstance(contents, _AnsibleLazyTemplateList):
|
if isinstance(contents, _AnsibleLazyTemplateList):
|
||||||
super().__init__(list.__iter__(contents))
|
super().__init__(list.__iter__(contents))
|
||||||
elif isinstance(contents, _LazyValueSource):
|
elif isinstance(contents, _LazyValueSource):
|
||||||
|
|
@ -381,6 +379,8 @@ class _AnsibleLazyTemplateList(_AnsibleTaggedList, _AnsibleLazyTemplateMixin):
|
||||||
else:
|
else:
|
||||||
raise UnsupportedConstructionMethodError()
|
raise UnsupportedConstructionMethodError()
|
||||||
|
|
||||||
|
_AnsibleLazyTemplateMixin.__init__(self, contents)
|
||||||
|
|
||||||
def __getitem__(self, key: t.SupportsIndex | slice, /) -> t.Any:
|
def __getitem__(self, key: t.SupportsIndex | slice, /) -> t.Any:
|
||||||
if type(key) is slice: # pylint: disable=unidiomatic-typecheck
|
if type(key) is slice: # pylint: disable=unidiomatic-typecheck
|
||||||
return _AnsibleLazyTemplateList(_LazyValueSource(source=super().__getitem__(key), templar=self._templar, lazy_options=self._lazy_options))
|
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:
|
def __getitem__(self, key: t.SupportsIndex | slice, /) -> t.Any:
|
||||||
if type(key) is slice: # pylint: disable=unidiomatic-typecheck
|
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)
|
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._datatag._tags import Origin, TrustedAsTemplate
|
||||||
from ansible._internal._templating._utils import TemplateContext, LazyOptions
|
from ansible._internal._templating._utils import TemplateContext, LazyOptions
|
||||||
from ansible._internal._templating._engine import TemplateEngine, TemplateOptions
|
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 ansible.module_utils._internal._datatag import AnsibleTaggedObject
|
||||||
|
|
||||||
from ...module_utils.datatag.test_datatag import ExampleSingletonTag
|
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
|
('type(d1)(d1)', dict(a=_LazyValue(1), c=_LazyValue(1)), _AnsibleLazyTemplateDict), # _AnsibleLazyTemplateDict.__init__ copy
|
||||||
('l1.copy()', [_LazyValue(1)], _AnsibleLazyTemplateList), # _AnsibleLazyTemplateList.copy
|
('l1.copy()', [_LazyValue(1)], _AnsibleLazyTemplateList), # _AnsibleLazyTemplateList.copy
|
||||||
('type(l1)(l1)', [_LazyValue(1)], _AnsibleLazyTemplateList), # _AnsibleLazyTemplateList.__init__ copy
|
('type(l1)(l1)', [_LazyValue(1)], _AnsibleLazyTemplateList), # _AnsibleLazyTemplateList.__init__ copy
|
||||||
|
('type(t1)(t1)', (1,), _AnsibleLazyAccessTuple),
|
||||||
('copy.copy(l1)', [_LazyValue(1)], _AnsibleLazyTemplateList),
|
('copy.copy(l1)', [_LazyValue(1)], _AnsibleLazyTemplateList),
|
||||||
('copy.copy(d1)', dict(a=_LazyValue(1), c=_LazyValue(1)), _AnsibleLazyTemplateDict),
|
('copy.copy(d1)', dict(a=_LazyValue(1), c=_LazyValue(1)), _AnsibleLazyTemplateDict),
|
||||||
('copy.deepcopy(l1)', [_LazyValue(1)], _AnsibleLazyTemplateList), # __AnsibleLazyTemplateList.__deepcopy__
|
('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(l1))', [1], list), # _AnsibleLazyTemplateList.__reversed__
|
||||||
('list(reversed(d1))', ['c', 'a'], list), # dict.__reversed__ - keys only
|
('list(reversed(d1))', ['c', 'a'], list), # dict.__reversed__ - keys only
|
||||||
('l1[:]', [_LazyValue(1)], _AnsibleLazyTemplateList), # __getitem__ (slice)
|
('l1[:]', [_LazyValue(1)], _AnsibleLazyTemplateList), # __getitem__ (slice)
|
||||||
|
('t1[:]', (1,), _AnsibleLazyAccessTuple), # __getitem__ (slice)
|
||||||
('d1["a"]', 1, int), # __getitem__
|
('d1["a"]', 1, int), # __getitem__
|
||||||
('d1.get("a")', 1, int), # get
|
('d1.get("a")', 1, int), # get
|
||||||
('l1[0]', 1, int), # __getitem__
|
('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() + 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__
|
('tuple() + d1', 'can only concatenate tuple (not "_AnsibleLazyTemplateDict") to tuple', TypeError), # relies on tuple.__add__
|
||||||
('l1.pop(42)', "pop index out of range", IndexError),
|
('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)
|
], ids=str)
|
||||||
def test_lazy_container_operators(expression: str, expected_value: t.Any, expected_type: type) -> None:
|
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 }}')],
|
l1x=[TRUST.tag('{{ one }}')],
|
||||||
l2=[TRUST.tag('{{ two }}')],
|
l2=[TRUST.tag('{{ two }}')],
|
||||||
l2f=l2f,
|
l2f=l2f,
|
||||||
|
t1=(TRUST.tag('{{ one }}'),),
|
||||||
d1=dict(a=TRUST.tag('{{ one }}'), c=TRUST.tag('{{ one }}')),
|
d1=dict(a=TRUST.tag('{{ one }}'), c=TRUST.tag('{{ one }}')),
|
||||||
d1x=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 }}')),
|
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)]
|
actual_list_types: list[type] = [type(value) for value in list.__iter__(result)]
|
||||||
|
|
||||||
assert actual_list_types == expected_list_types
|
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):
|
elif issubclass(expected_type, dict):
|
||||||
assert isinstance(result, dict) # redundant, but assists mypy in understanding the type
|
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 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._templar is original._templar) != deep
|
||||||
assert (copied._lazy_options is original._lazy_options) != 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…
Reference in New Issue
Block a user