Skip to content

Commit 01b1bfd

Browse files
authored
Merge pull request #4735 from Flamefire/resolve-context-manager
Add context manager for allowing unresolved templates and make the state members private + remove support for directly setting `enable_templating` and `expect_resolved_template_values`
2 parents 1429bd0 + d243c79 commit 01b1bfd

File tree

2 files changed

+75
-36
lines changed

2 files changed

+75
-36
lines changed

easybuild/framework/easyconfig/easyconfig.py

Lines changed: 55 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -442,10 +442,11 @@ def __init__(self, path, extra_options=None, build_specs=None, validate=True, hi
442442
:param local_var_naming_check: mode to use when checking if local variables use the recommended naming scheme
443443
"""
444444
self.template_values = None
445-
# a boolean to control templating, can be (temporarily) disabled in easyblocks
446-
self.enable_templating = True
447-
# boolean to control whether all template values must be resolvable, can be (temporarily) disabled in easyblocks
448-
self.expect_resolved_template_values = True
445+
# a boolean to control templating, can be (temporarily) disabled via disable_templating context manager
446+
self._templating_enabled = True
447+
# boolean to control whether all template values must be resolvable on access,
448+
# can be (temporarily) disabled via allow_unresolved_templates context manager
449+
self._expect_resolved_template_values = True
449450

450451
self.log = fancylogger.getLogger(self.__class__.__name__, fname=False)
451452

@@ -565,12 +566,48 @@ def disable_templating(self):
565566
# Do what you want without templating
566567
# Templating set to previous value
567568
"""
568-
old_enable_templating = self.enable_templating
569-
self.enable_templating = False
569+
old_templating_enabled = self._templating_enabled
570+
self._templating_enabled = False
570571
try:
571-
yield old_enable_templating
572+
yield old_templating_enabled
572573
finally:
573-
self.enable_templating = old_enable_templating
574+
self._templating_enabled = old_templating_enabled
575+
576+
@property
577+
def templating_enabled(self):
578+
"""Check whether templating is enabled on this EasyConfig"""
579+
return self._templating_enabled
580+
581+
def _enable_templating(self, *_):
582+
self.log.nosupport("self.enable_templating is replaced by self.templating_enabled. "
583+
"To disable it use the self.disable_templating context manager", '5.0')
584+
enable_templating = property(_enable_templating, _enable_templating)
585+
586+
@contextmanager
587+
def allow_unresolved_templates(self):
588+
"""Temporarily allow templates to be not (fully) resolved.
589+
590+
This should only be used when it is intended to use partially resolved templates.
591+
Otherwise `ec.get(key, resolve=False)` should be used.
592+
See also @ref disable_templating.
593+
594+
Usage:
595+
with ec.allow_unresolved_templates():
596+
value = ec.get('key') # This will not raise an error
597+
print(value % {'extra_key': exta_value})
598+
# Resolving is enforced again if it was before
599+
"""
600+
old_expect_resolved_template_values = self._expect_resolved_template_values
601+
self._expect_resolved_template_values = False
602+
try:
603+
yield old_expect_resolved_template_values
604+
finally:
605+
self._expect_resolved_template_values = old_expect_resolved_template_values
606+
607+
@property
608+
def expect_resolved_template_values(self):
609+
"""Check whether resolving all template values on access is enforced."""
610+
return self._expect_resolved_template_values
574611

575612
def __str__(self):
576613
"""Return a string representation of this EasyConfig instance"""
@@ -1839,13 +1876,12 @@ def __contains__(self, key):
18391876
@handle_deprecated_or_replaced_easyconfig_parameters
18401877
def __getitem__(self, key):
18411878
"""Return value of specified easyconfig parameter (without help text, etc.)"""
1842-
value = None
1843-
if key in self._config:
1879+
try:
18441880
value = self._config[key][0]
1845-
else:
1881+
except KeyError:
18461882
raise EasyBuildError("Use of unknown easyconfig parameter '%s' when getting parameter value", key)
18471883

1848-
if self.enable_templating:
1884+
if self.templating_enabled:
18491885
value = self.resolve_template(value)
18501886

18511887
return value
@@ -1923,14 +1959,13 @@ def asdict(self):
19231959
Return dict representation of this EasyConfig instance.
19241960
"""
19251961
res = {}
1926-
for key, tup in self._config.items():
1927-
value = tup[0]
1928-
if self.enable_templating:
1929-
if not self.template_values:
1930-
self.generate_template_values()
1931-
# Not all values can be resolved, e.g. %(installdir)s
1932-
value = resolve_template(value, self.template_values, expect_resolved=False)
1933-
res[key] = value
1962+
# Not all values can be resolved, e.g. %(installdir)s
1963+
with self.allow_unresolved_templates():
1964+
for key, tup in self._config.items():
1965+
value = tup[0]
1966+
if self.templating_enabled:
1967+
value = self.resolve_template(value)
1968+
res[key] = value
19341969
return res
19351970

19361971
def get_cuda_cc_template_value(self, key):

test/framework/easyconfig.py

Lines changed: 20 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1215,9 +1215,11 @@ def test_templating_constants(self):
12151215
ec.validate()
12161216

12171217
# temporarily disable templating, just so we can check later whether it's *still* disabled
1218+
self.assertTrue(ec.templating_enabled)
12181219
with ec.disable_templating():
12191220
ec.generate_template_values()
1220-
self.assertFalse(ec.enable_templating)
1221+
self.assertFalse(ec.templating_enabled)
1222+
self.assertTrue(ec.templating_enabled)
12211223

12221224
self.assertEqual(ec['description'], "test easyconfig PI")
12231225
self.assertEqual(ec['sources'][0], 'PI-3.04.tar.gz')
@@ -1316,12 +1318,14 @@ def test_ec_method_resolve_template(self):
13161318
self.assertErrorRegex(EasyBuildError, error_pattern, ec.resolve_template, val)
13171319
self.assertErrorRegex(EasyBuildError, error_pattern, ec.get, 'installopts')
13181320

1319-
# this can be (temporarily) disabled via expect_resolved_template_values in EasyConfig instance
1320-
ec.expect_resolved_template_values = False
1321-
self.assertEqual(ec.resolve_template(val), val)
1322-
self.assertEqual(ec['installopts'], val)
1321+
# this can be (temporarily) disabled
1322+
with ec.allow_unresolved_templates():
1323+
self.assertFalse(ec.expect_resolved_template_values)
1324+
self.assertEqual(ec.resolve_template(val), val)
1325+
self.assertEqual(ec['installopts'], val)
13231326

1324-
ec.expect_resolved_template_values = True
1327+
# Enforced again
1328+
self.assertTrue(ec.expect_resolved_template_values)
13251329
self.assertErrorRegex(EasyBuildError, error_pattern, ec.resolve_template, val)
13261330
self.assertErrorRegex(EasyBuildError, error_pattern, ec.get, 'installopts')
13271331

@@ -2542,11 +2546,11 @@ def test_dump(self):
25422546
test_ec = os.path.join(self.test_prefix, 'test.eb')
25432547

25442548
ec = EasyConfig(os.path.join(test_ecs_dir, ecfile))
2545-
ec.enable_templating = False
2546-
ecdict = ec.asdict()
2547-
ec.dump(test_ec)
2548-
# dict representation of EasyConfig instance should not change after dump
2549-
self.assertEqual(ecdict, ec.asdict())
2549+
with ec.disable_templating():
2550+
ecdict = ec.asdict()
2551+
ec.dump(test_ec)
2552+
# dict representation of EasyConfig instance should not change after dump
2553+
self.assertEqual(ecdict, ec.asdict())
25502554
ectxt = read_file(test_ec)
25512555

25522556
patterns = [
@@ -2561,7 +2565,6 @@ def test_dump(self):
25612565

25622566
# parse result again
25632567
dumped_ec = EasyConfig(test_ec)
2564-
dumped_ec.enable_templating = False
25652568

25662569
# check that selected parameters still have the same value
25672570
params = [
@@ -2570,9 +2573,10 @@ def test_dump(self):
25702573
'dependencies', # checking this is important w.r.t. filtered hidden dependencies being restored in dump
25712574
'exts_list', # exts_lists (in Python easyconfig) use another layer of templating so shouldn't change
25722575
]
2573-
for param in params:
2574-
if param in ec:
2575-
self.assertEqual(ec[param], dumped_ec[param])
2576+
with ec.disable_templating(), dumped_ec.disable_templating():
2577+
for param in params:
2578+
if param in ec:
2579+
self.assertEqual(ec[param], dumped_ec[param])
25762580

25772581
ec_txt = textwrap.dedent("""
25782582
easyblock = 'EB_toy'
@@ -2823,7 +2827,7 @@ def test_dump_template(self):
28232827
ec.dump(testec)
28242828
ectxt = read_file(testec)
28252829

2826-
self.assertTrue(ec.enable_templating) # templating should still be enabled after calling dump()
2830+
self.assertTrue(ec.templating_enabled) # templating should still be enabled after calling dump()
28272831

28282832
patterns = [
28292833
r"easyblock = 'EB_foo'",

0 commit comments

Comments
 (0)