Skip to content

Commit f4fc7b9

Browse files
gh-70999: Preserve caller vars in ExtendedInterpolation nested interpolation
ExtendedInterpolation honoured caller-supplied ``vars`` only at the first level of interpolation. When a value referenced another option in the same section, the recursive call rebuilt the lookup map from ``parser.items(section, raw=True)``, discarding the map that carried ``vars``. Mirror BasicInterpolation, which threads the same ``(section, map)`` through recursion, by reusing ``map`` when the reference stays in the current section and only rebuilding it when crossing into another section (so ``vars`` do not leak across sections).
1 parent 1f9d20b commit f4fc7b9

3 files changed

Lines changed: 32 additions & 2 deletions

File tree

Lib/configparser.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -547,9 +547,12 @@ def _interpolate_some(self, parser, option, accum, rest, section, map,
547547
if v is None:
548548
continue
549549
if "$" in v:
550+
if sect == section:
551+
submap = map
552+
else:
553+
submap = dict(parser.items(sect, raw=True))
550554
self._interpolate_some(parser, opt, accum, v, sect,
551-
dict(parser.items(sect, raw=True)),
552-
depth + 1)
555+
submap, depth + 1)
553556
else:
554557
accum.append(v)
555558
else:

Lib/test/test_configparser.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1334,6 +1334,30 @@ def test_other_errors(self):
13341334
with self.assertRaises(ValueError):
13351335
cf['interpolation fail']['case6'] = "BLACK $ABBATH"
13361336

1337+
def test_get_with_vars_nested(self):
1338+
# gh-70999: caller-supplied ``vars`` must be honoured at every level
1339+
# of a same-section interpolation chain, not just the first.
1340+
cf = self.fromstring(textwrap.dedent("""
1341+
[section]
1342+
a = ${b}
1343+
b = ${c}
1344+
c = default
1345+
1346+
[cross]
1347+
via = ${section:c}
1348+
""").strip())
1349+
1350+
eq = self.assertEqual
1351+
# Directly referencing the overridden option already worked.
1352+
eq(cf.get('section', 'b', vars={'c': 'OVERRIDE'}), 'OVERRIDE')
1353+
# Reaching it through another same-section option must too.
1354+
eq(cf.get('section', 'a', vars={'c': 'OVERRIDE'}), 'OVERRIDE')
1355+
# Without an override the configured value is still used.
1356+
eq(cf.get('section', 'a'), 'default')
1357+
# ``vars`` are scoped to the requested section and must not leak
1358+
# into a different section reached via ``${section:option}``.
1359+
eq(cf.get('cross', 'via', vars={'c': 'OVERRIDE'}), 'default')
1360+
13371361

13381362
class ConfigParserTestCaseNoValue(ConfigParserTestCase):
13391363
allow_no_value = True
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Fix :class:`configparser.ExtendedInterpolation` dropping caller-supplied
2+
``vars`` during nested interpolation within a section. Patch by Nikolaus
3+
Schuetz.

0 commit comments

Comments
 (0)