Skip to content

Commit

Permalink
Merge pull request #4633 from mwichmann/feature/Dictionary-dict
Browse files Browse the repository at this point in the history
Add as_dict flag to env.Dictionary
  • Loading branch information
bdbaddog authored Nov 14, 2024
2 parents b4d72c7 + c8ebf8e commit c1ddbc7
Show file tree
Hide file tree
Showing 5 changed files with 100 additions and 57 deletions.
4 changes: 4 additions & 0 deletions CHANGES.txt
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,10 @@ RELEASE VERSION/DATE TO BE FILLED IN LATER
- Add a tag to each CacheDir to let systems ignore backing it up
(per https://bford.info/cachedir/). Update the way a CacheDir
is created, since it now has to create two files.
- The Dictionary method now has an as_dict flag. If true, Dictionary
always returns a dict. The default remains to return different
types depending on whether zero, one, or multiple construction
variable names are given.


RELEASE 4.8.1 - Tue, 03 Sep 2024 17:22:20 -0700
Expand Down
4 changes: 4 additions & 0 deletions RELEASE.txt
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,10 @@ CHANGED/ENHANCED EXISTING FUNCTIONALITY
(per https://bford.info/cachedir/). Update the way a CacheDir
is created, since it now has to create two files.

- The Dictionary method now has an as_dict flag. If true, Dictionary
always returns a dict. The default remains to return different
types depending on whether zero, one, or multiple construction

FIXES
-----

Expand Down
58 changes: 33 additions & 25 deletions SCons/Environment.py
Original file line number Diff line number Diff line change
Expand Up @@ -537,7 +537,7 @@ class actually becomes useful.)
Special note: methods here and in actual child classes might be called
via proxy from an :class:`OverrideEnvironment`, which isn't in the
Python inheritance chain. Take care that methods called with a *self*
class inheritance chain. Take care that methods called with a *self*
that's really an ``OverrideEnvironment`` don't make bad assumptions.
"""

Expand Down Expand Up @@ -1708,23 +1708,33 @@ def Detect(self, progs):
return None


def Dictionary(self, *args: str):
def Dictionary(self, *args: str, as_dict: bool = False):
"""Return construction variables from an environment.
Args:
args (optional): variable names to look up
args (optional): construction variable names to select.
If omitted, all variables are selected and returned
as a dict.
as_dict: if true, and *args* is supplied, return the
variables and their values in a dict. If false
(the default), return a single value as a scalar,
or multiple values in a list.
Returns:
If *args* omitted, the dictionary of all construction variables.
If one arg, the corresponding value is returned.
If more than one arg, a list of values is returned.
A dictionary of construction variables, or a single value
or list of values.
Raises:
KeyError: if any of *args* is not in the construction environment.
.. versionchanged:: NEXT_RELEASE
Added the *as_dict* keyword arg to specify always returning a dict.
"""
if not args:
return self._dict
dlist = [self._dict[x] for x in args]
if as_dict:
return {key: self._dict[key] for key in args}
dlist = [self._dict[key] for key in args]
if len(dlist) == 1:
return dlist[0]
return dlist
Expand Down Expand Up @@ -1754,13 +1764,10 @@ def Dump(self, *key: str, format: str = 'pretty') -> str:
If *key* is supplied, a formatted dictionary is generated like the
no-arg case - previously a single *key* displayed just the value.
"""
if not key:
cvars = self.Dictionary()
elif len(key) == 1:
dkey = key[0]
cvars = {dkey: self[dkey]}
if len(key):
cvars = self.Dictionary(*key, as_dict=True)
else:
cvars = dict(zip(key, self.Dictionary(*key)))
cvars = self.Dictionary()

fmt = format.lower()

Expand Down Expand Up @@ -2716,28 +2723,29 @@ def __contains__(self, key) -> bool:
return False
return key in self.__dict__['__subject']

def Dictionary(self, *args):
def Dictionary(self, *args, as_dict: bool = False):
"""Return construction variables from an environment.
Returns all the visible variables, or just those in *args* if
specified. Obtains a Dictionary view of the subject env, then layers
the overrrides on top, after which any any deleted items are removed.
Returns:
Like :meth:`Base.Dictionary`, returns a dict if *args* is
omitted; a single value if there is one arg; else a list of values.
Behavior is as described for :class:`SubstitutionEnvironment.Dictionary`
but understanda about the override.
Raises:
KeyError: if any of *args* is not visible in the construction environment.
KeyError: if any of *args* is not in the construction environment.
.. versionchanged: NEXT_RELEASE
Added the *as_dict* keyword arg to always return a dict.
"""
d = self.__dict__['__subject'].Dictionary().copy()
d = {}
d.update(self.__dict__['__subject'])
d.update(self.__dict__['overrides'])
d = {k: v for k, v in d.items() if k not in self.__dict__['__deleted']}
if not args:
return d
dlist = [d[x] for x in args]
if as_dict:
return {key: d[key] for key in args}
dlist = [d[key] for key in args]
if len(dlist) == 1:
dlist = dlist[0]
return dlist[0]
return dlist

def items(self):
Expand Down
71 changes: 41 additions & 30 deletions SCons/Environment.xml
Original file line number Diff line number Diff line change
Expand Up @@ -1624,16 +1624,22 @@ but will not include any such extension in the return value.

<scons_function name="Dictionary">
<arguments signature="env">
([vars])
([var, ...], [as_dict=])
</arguments>
<summary>
<para>
Returns a dictionary object
containing the &consvars; in the &consenv;.
If there are any arguments specified,
the values of the specified &consvars;
are returned as a string (if one
argument) or as a list of strings.
Return an object containing &consvars; from
<parameter>env</parameter>.
If <parameter>var</parameter> is omitted,
all the &consvars; with their values
are returned in a <type>dict</type>.
If <parameter>var</parameter> is specified,
and <parameter>as_dict</parameter> is true,
the specified &consvars; are returned in a <type>dict</type>;
otherwise (the default, for backwards compatibility),
values only are returned,
as a scalar if one <parameter>var</parameter> is given,
or as a list if multiples.
</para>

<para>
Expand All @@ -1648,15 +1654,20 @@ cc_values = env.Dictionary('CC', 'CCFLAGS', 'CCCOM')
<note><para>
The object returned by &f-link-env-Dictionary; should be treated
as a read-only view into the &consvars;.
Some &consvars; have special internal handling,
and making changes through the &f-env-Dictionary; object can bypass
Some &consvars; require special internal handling,
and modifying them through the &f-env-Dictionary; object can bypass
that handling and cause data inconsistencies.
The primary use of &f-env-Dictionary; is for diagnostic purposes -
it is used widely by test cases specifically because
it bypasses the special handling so that behavior
can be verified.
</para></note>

<para>
<emphasis>Changed in NEXT_RELEASE</emphasis>:
<parameter>as_dict</parameter> added.
</para>

</summary>
</scons_function>

Expand Down Expand Up @@ -1702,56 +1713,56 @@ for more information.

<scons_function name="Dump">
<arguments signature="env">
([key, ...], [format=])
([var, ...], [format=TYPE])
</arguments>
<summary>
<para>
Serializes &consvars; from the current &consenv;
to a string.
The method supports the following formats specified by
<parameter>format</parameter>,
which must be used a a keyword argument:
Serialize &consvars; from <parameter>env</parameter> to a string.
If <varname>var</varname> is omitted,
all the &consvars; are serialized.
If one or more <varname>var</varname> values are supplied,
only those variables and their values are serialized.
</para>

<para>
The optional <parameter>format</parameter> string
selects the serialization format:
</para>
<variablelist>
<varlistentry>
<term><literal>pretty</literal></term>
<listitem>
<para>
Returns a pretty-printed representation of the variables
Returns a pretty-printed representation
of the &consvars; - the result will look like a
&Python; <type>dict</type>
(this is the default).
The variables will be presented in &Python; dict form.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term><literal>json</literal></term>
<listitem>
<para>
Returns a JSON-formatted string representation of the variables.
Returns a JSON-formatted representation of the variables.
The variables will be presented as a JSON object literal,
the JSON equivalent of a &Python; dict.
the JSON equivalent of a &Python; <type>dict</type>..
</para>
</listitem>
</varlistentry>
</variablelist>

<para>
If no <varname>key</varname> is supplied,
all the &consvars; are serialized.
If one or more keys are supplied,
only those keys and their values are serialized.
</para>

<para>
<emphasis>Changed in NEXT_RELEASE</emphasis>:
More than one <parameter>key</parameter> can be specified.
The returned string always looks like a dict (or JSON equivalent);
The returned string always looks like a <type>dict</type>
(or equivalent in other formats);
previously a single key serialized only the value,
not the key with the value.
</para>

<para>
This SConstruct:
Examples: this &SConstruct;
</para>

<example_commands>
Expand All @@ -1773,7 +1784,7 @@ will print something like:
</example_commands>

<para>
While this SConstruct:
While this &SConstruct;:
</para>

<example_commands>
Expand Down Expand Up @@ -2022,7 +2033,7 @@ FindSourceFiles('src')
</example_commands>

<para>
As you can see build support files (SConstruct in the above example)
As you can see build support files (&SConstruct; in the above example)
will also be returned by this function.
</para>
</summary>
Expand Down
20 changes: 18 additions & 2 deletions SCons/EnvironmentTests.py
Original file line number Diff line number Diff line change
Expand Up @@ -1832,8 +1832,10 @@ def test_AppendUnique(self) -> None:
assert env['CCC1'] == 'c1', env['CCC1']
assert env['CCC2'] == ['c2'], env['CCC2']
assert env['DDD1'] == ['a', 'b', 'c'], env['DDD1']
assert env['LL1'] == [env.Literal('a literal'), \
env.Literal('b literal')], env['LL1']
assert env['LL1'] == [
env.Literal('a literal'),
env.Literal('b literal'),
], env['LL1']
assert env['LL2'] == [
env.Literal('c literal'),
env.Literal('b literal'),
Expand Down Expand Up @@ -2135,6 +2137,13 @@ def test_Dictionary(self) -> None:
xxx, zzz = env.Dictionary('XXX', 'ZZZ')
assert xxx == 'x'
assert zzz == 'z'
# added in NEXT_RELEASE: as_dict flag
with self.subTest():
expect = {'XXX': 'x'}
self.assertEqual(env.Dictionary('XXX', as_dict=True), expect)
with self.subTest():
expect = {'XXX': 'x', 'YYY': 'y'}
self.assertEqual(env.Dictionary('XXX', 'YYY', as_dict=True), expect)
assert 'BUILDERS' in env.Dictionary()
assert 'CC' in env.Dictionary()
assert 'CCFLAGS' in env.Dictionary()
Expand Down Expand Up @@ -3927,6 +3936,13 @@ def test_Dictionary(self) -> None:
xxx, yyy = env2.Dictionary('XXX', 'YYY')
assert xxx == 'x2', xxx
assert yyy == 'y', yyy
# added in NEXT_VERSION: as_dict flag
with self.subTest():
expect = {'XXX': 'x3'}
self.assertEqual(env3.Dictionary('XXX', as_dict=True), expect)
with self.subTest():
expect = {'XXX': 'x2', 'YYY': 'y'}
self.assertEqual(env2.Dictionary('XXX', 'YYY', as_dict=True), expect)

# test deletion in top override
del env3['XXX']
Expand Down

0 comments on commit c1ddbc7

Please sign in to comment.