From 6e9ec5150f90504e38b52c43ff340ce3e047e30e Mon Sep 17 00:00:00 2001 From: Mats Wichmann Date: Fri, 24 Apr 2020 07:11:27 -0600 Subject: Add keys() and values() to environment A construction environment provides a method items() which gives you back the k, v pairs from the internal _dict, helping an env behave like a dictionary. This adds the keys() and values() methods to SubstitutionEnvironment (which is the "real" consenv) and OverrideEnvironment to further emulate a dictionary. These methods don't need to convert to list here, the caller can do that itself if it doesn't want a Py3 view object. Add tests, and tweak the existing test a bit. Also in a couple of places in Environment.py, don't loop over a view into a dict that is being changed (del) - loop over the key view object from a copy instead. Signed-off-by: Mats Wichmann --- CHANGES.txt | 2 ++ SCons/Environment.py | 35 +++++++++++++++++++++++++----- SCons/EnvironmentTests.py | 54 ++++++++++++++++++++++++++++------------------- 3 files changed, 64 insertions(+), 27 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index 105147b..a510b20 100755 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -189,6 +189,8 @@ RELEASE VERSION/DATE TO BE FILLED IN LATER repository / website (scons-cookbook.readthedocs.io). - Clean up test harness and tests' use of subdir, file_fixture and dir_fixture. + - SubstitutionEnvironment and OverrideEnvironment now have keys() + and values() methods to better emulate a dict (already had items()). From Joseph Brill: - MSVC updates: When there are multiple product installations (e.g, Community and diff --git a/SCons/Environment.py b/SCons/Environment.py index f9af099..3da726a 100644 --- a/SCons/Environment.py +++ b/SCons/Environment.py @@ -123,7 +123,7 @@ future_reserved_construction_var_names = [ def copy_non_reserved_keywords(dict): result = semi_deepcopy(dict) - for k in list(result.keys()): + for k in result.copy().keys(): if k in reserved_construction_var_names: msg = "Ignoring attempt to set reserved variable `$%s'" SCons.Warnings.warn(SCons.Warnings.ReservedVariableWarning, msg % k) @@ -142,7 +142,7 @@ def _set_future_reserved(env, key, value): def _set_BUILDERS(env, key, value): try: bd = env._dict[key] - for k in list(bd.keys()): + for k in bd.copy().keys(): del bd[k] except KeyError: bd = BuilderDict(bd, env) @@ -434,13 +434,23 @@ class SubstitutionEnvironment: return self._dict.get(key, default) def has_key(self, key): + """Emulates the has_key() method of dictionaries.""" return key in self._dict def __contains__(self, key): return self._dict.__contains__(key) + def keys(self): + """Emulates the keys() method of dictionaries.""" + return self._dict.keys() + + def values(self): + """Emulates the values() method of dictionaries.""" + return self._dict.values() + def items(self): - return list(self._dict.items()) + """Emulates the items() method of dictionaries.""" + return self._dict.items() def arg2nodes(self, args, node_factory=_null, lookup_list=_null, **kw): if node_factory is _null: @@ -2338,10 +2348,12 @@ class OverrideEnvironment(Base): return self.__dict__['overrides'][key] except KeyError: return self.__dict__['__subject'].__getitem__(key) + def __setitem__(self, key, value): if not is_valid_construction_var(key): raise UserError("Illegal construction variable `%s'" % key) self.__dict__['overrides'][key] = value + def __delitem__(self, key): try: del self.__dict__['overrides'][key] @@ -2356,30 +2368,43 @@ class OverrideEnvironment(Base): raise result = None return result + def get(self, key, default=None): """Emulates the get() method of dictionaries.""" try: return self.__dict__['overrides'][key] except KeyError: return self.__dict__['__subject'].get(key, default) + def has_key(self, key): + """Emulates the has_key() method of dictionaries.""" try: self.__dict__['overrides'][key] return 1 except KeyError: return key in self.__dict__['__subject'] + def __contains__(self, key): if self.__dict__['overrides'].__contains__(key): return 1 return self.__dict__['__subject'].__contains__(key) + def Dictionary(self): - """Emulates the items() method of dictionaries.""" d = self.__dict__['__subject'].Dictionary().copy() d.update(self.__dict__['overrides']) return d + def items(self): """Emulates the items() method of dictionaries.""" - return list(self.Dictionary().items()) + return self.Dictionary().items() + + def keys(self): + """Emulates the keys() method of dictionaries.""" + return self.Dictionary().keys() + + def values(self): + """Emulates the values() method of dictionaries.""" + return self.Dictionary().values() # Overridden private construction environment methods. def _update(self, dict): diff --git a/SCons/EnvironmentTests.py b/SCons/EnvironmentTests.py index 6dcf3c1..f90e39d 100644 --- a/SCons/EnvironmentTests.py +++ b/SCons/EnvironmentTests.py @@ -174,14 +174,12 @@ class TestEnvironmentFixture: class SubstitutionTestCase(unittest.TestCase): def test___init__(self): - """Test initializing a SubstitutionEnvironment - """ + """Test initializing a SubstitutionEnvironment.""" env = SubstitutionEnvironment() assert '__env__' not in env def test___cmp__(self): - """Test comparing SubstitutionEnvironments - """ + """Test comparing SubstitutionEnvironments.""" env1 = SubstitutionEnvironment(XXX = 'x') env2 = SubstitutionEnvironment(XXX = 'x') @@ -193,16 +191,14 @@ class SubstitutionTestCase(unittest.TestCase): assert env1 != env4 def test___delitem__(self): - """Test deleting a variable from a SubstitutionEnvironment - """ + """Test deleting a variable from a SubstitutionEnvironment.""" env1 = SubstitutionEnvironment(XXX = 'x', YYY = 'y') env2 = SubstitutionEnvironment(XXX = 'x') del env1['YYY'] assert env1 == env2 def test___getitem__(self): - """Test fetching a variable from a SubstitutionEnvironment - """ + """Test fetching a variable from a SubstitutionEnvironment.""" env = SubstitutionEnvironment(XXX = 'x') assert env['XXX'] == 'x', env['XXX'] @@ -215,38 +211,52 @@ class SubstitutionTestCase(unittest.TestCase): assert env1 == env2 def test_get(self): - """Test the SubstitutionEnvironment get() method - """ + """Test the SubstitutionEnvironment get() method.""" env = SubstitutionEnvironment(XXX = 'x') assert env.get('XXX') == 'x', env.get('XXX') assert env.get('YYY') is None, env.get('YYY') def test_has_key(self): - """Test the SubstitutionEnvironment has_key() method - """ + """Test the SubstitutionEnvironment has_key() method.""" env = SubstitutionEnvironment(XXX = 'x') assert 'XXX' in env assert 'YYY' not in env def test_contains(self): - """Test the SubstitutionEnvironment __contains__() method - """ + """Test the SubstitutionEnvironment __contains__() method.""" env = SubstitutionEnvironment(XXX = 'x') assert 'XXX' in env assert 'YYY' not in env + def test_keys(self): + """Test the SubstitutionEnvironment keys() method.""" + testdata = {'XXX': 'x', 'YYY': 'y'} + env = SubstitutionEnvironment(**testdata) + keys = list(env.keys()) + assert len(keys) == 2, keys + for k in testdata.keys(): + assert k in keys, keys + + def test_values(self): + """Test the SubstitutionEnvironment values() method.""" + testdata = {'XXX': 'x', 'YYY': 'y'} + env = SubstitutionEnvironment(**testdata) + values = list(env.values()) + assert len(values) == 2, values + for v in testdata.values(): + assert v in values, values + def test_items(self): - """Test the SubstitutionEnvironment items() method - """ - env = SubstitutionEnvironment(XXX = 'x', YYY = 'y') + """Test the SubstitutionEnvironment items() method.""" + testdata = {'XXX': 'x', 'YYY': 'y'} + env = SubstitutionEnvironment(**testdata) items = list(env.items()) - assert len(items) == 2 and ('XXX','x') in items and ('YYY','y') in items, items - # Was. This fails under py3 as order changes - # assert items == [('XXX','x'), ('YYY','y')], items + assert len(items) == 2, items + for k, v in testdata.items(): + assert (k, v) in items, items def test_arg2nodes(self): - """Test the arg2nodes method - """ + """Test the arg2nodes method.""" env = SubstitutionEnvironment() dict = {} class X(SCons.Node.Node): -- cgit v0.12