From 2b73002044dcc2f94bbdd032d413453d6ce9091d Mon Sep 17 00:00:00 2001 From: Marius Gedminas Date: Fri, 11 Sep 2015 09:37:44 +0300 Subject: [PATCH 1/2] Python 3: make ansible.template.safe_eval() work Two things changed in Python 3.4: - 'basestring' is no longer defined, so use six.string_types - True/False are now special AST node types (NamedConstant) rather than just names (Good thing we had tests, or I wouldn't have noticed the 2nd thing!) I found only one place where safe_eval() is called inside the ansible codebase: in lib/template/__init__.py. The call to safe_eval(result, ...) is protected by result.startswith('...'), which means result cannot possibly be a byte string on Python 3 (or startswith() would raise, so six.string_types (which excludes byte strings on Python 3) is fine here. --- lib/ansible/template/safe_eval.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/lib/ansible/template/safe_eval.py b/lib/ansible/template/safe_eval.py index 300e634592..8147dc575f 100644 --- a/lib/ansible/template/safe_eval.py +++ b/lib/ansible/template/safe_eval.py @@ -20,6 +20,7 @@ __metaclass__ = type import ast import sys +from six import string_types from six.moves import builtins from ansible import constants as C @@ -73,6 +74,14 @@ def safe_eval(expr, locals={}, include_exceptions=False): ) ) + # And in Python 3.4 too + if sys.version_info[:2] >= (3, 4): + SAFE_NODES.update( + set( + (ast.NameConstant,) + ) + ) + filter_list = [] for filter in filter_loader.all(): filter_list.extend(filter.filters().keys()) @@ -96,7 +105,7 @@ def safe_eval(expr, locals={}, include_exceptions=False): for child_node in ast.iter_child_nodes(node): self.generic_visit(child_node, inside_call) - if not isinstance(expr, basestring): + if not isinstance(expr, string_types): # already templated to a datastructure, perhaps? if include_exceptions: return (expr, None) From f91b28ef23eefb682ed7f616179c159cf6da857f Mon Sep 17 00:00:00 2001 From: Marius Gedminas Date: Fri, 11 Sep 2015 09:41:05 +0300 Subject: [PATCH 2/2] Fix safe_eval() of set literals There was code to support set literals (on Python 2.7 and newer), but it was buggy: SAFE_NODES.union() doesn't modify SAFE_NODES in place, instead it returns a new set object that is then silently discarded. I added a unit test and fixed the code. I also changed the version check to use sys.version_tuple instead of a string comparison, for consistency with the subsequent Python 3.4 version check that I added in the previous commit. --- lib/ansible/template/safe_eval.py | 4 ++-- test/units/template/test_safe_eval.py | 4 ++++ 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/lib/ansible/template/safe_eval.py b/lib/ansible/template/safe_eval.py index 8147dc575f..8e06512d12 100644 --- a/lib/ansible/template/safe_eval.py +++ b/lib/ansible/template/safe_eval.py @@ -67,8 +67,8 @@ def safe_eval(expr, locals={}, include_exceptions=False): ) # AST node types were expanded after 2.6 - if not sys.version.startswith('2.6'): - SAFE_NODES.union( + if sys.version_info[:2] >= (2, 7): + SAFE_NODES.update( set( (ast.Set,) ) diff --git a/test/units/template/test_safe_eval.py b/test/units/template/test_safe_eval.py index 610d471443..531244d15a 100644 --- a/test/units/template/test_safe_eval.py +++ b/test/units/template/test_safe_eval.py @@ -19,6 +19,7 @@ from __future__ import (absolute_import, division, print_function) __metaclass__ = type +import sys from collections import defaultdict from ansible.compat.tests import unittest @@ -45,3 +46,6 @@ class TestSafeEval(unittest.TestCase): self.assertEqual(safe_eval('[]', locals=locals_vars), []) self.assertEqual(safe_eval('{}', locals=locals_vars), {}) + @unittest.skipUnless(sys.version_info[:2] >= (2, 7), "Python 2.6 has no set literals") + def test_set_literals(self): + self.assertEqual(safe_eval('{0}'), set([0]))