diff --git a/changelogs/fragments/jinja2_native-literal_eval-py310.yml b/changelogs/fragments/jinja2_native-literal_eval-py310.yml new file mode 100644 index 00000000000..70a66af8e8c --- /dev/null +++ b/changelogs/fragments/jinja2_native-literal_eval-py310.yml @@ -0,0 +1,2 @@ +minor_changes: + - jinja2_native - keep same behavior on Python 3.10. diff --git a/lib/ansible/playbook/conditional.py b/lib/ansible/playbook/conditional.py index 7afcb41c06a..53a03fb8574 100644 --- a/lib/ansible/playbook/conditional.py +++ b/lib/ansible/playbook/conditional.py @@ -184,12 +184,7 @@ class Conditional: # NOTE The spaces around True and False are intentional to short-circuit safe_eval and avoid # its expensive calls. presented = "{%% if %s %%} True {%% else %%} False {%% endif %%}" % conditional - # NOTE Convert the result to text to account for both native and non-native jinja. - # NOTE The templated result of `presented` is string on native jinja as well prior to Python 3.10. - # ast.literal_eval on Python 3.10 removes leading whitespaces so " True " becomes bool True - # as opposed to Python 3.9 and lower where the same would result in IndentationError and - # string " True " would be returned by Templar. - val = to_text(templar.template(presented, disable_lookups=disable_lookups)).strip() + val = templar.template(presented, disable_lookups=disable_lookups).strip() if val == "True": return True elif val == "False": diff --git a/lib/ansible/template/native_helpers.py b/lib/ansible/template/native_helpers.py index 8886fc1b0b7..94d0c3bb5c0 100644 --- a/lib/ansible/template/native_helpers.py +++ b/lib/ansible/template/native_helpers.py @@ -6,9 +6,9 @@ from __future__ import (absolute_import, division, print_function) __metaclass__ = type -from ast import literal_eval -from itertools import islice, chain +import ast import types +from itertools import islice, chain from jinja2.runtime import StrictUndefined @@ -83,7 +83,11 @@ def ansible_native_concat(nodes): out = u''.join([to_text(_fail_on_undefined(v)) for v in nodes]) try: - out = literal_eval(out) - return out + return ast.literal_eval( + # In Python 3.10+ ast.literal_eval removes leading spaces/tabs + # from the given string. For backwards compatibility we need to + # parse the string ourselves without removing leading spaces/tabs. + ast.parse(out, mode='eval') + ) except (ValueError, SyntaxError, MemoryError): return out