diff --git a/cylc/flow/parsec/config.py b/cylc/flow/parsec/config.py index 19f937d8e5..68c7427125 100644 --- a/cylc/flow/parsec/config.py +++ b/cylc/flow/parsec/config.py @@ -35,6 +35,10 @@ from optparse import Values +class DefaultList(list): + """List subclass to indicate unassigned list values in expanded config.""" + + class ParsecConfig: """Object wrapper for parsec functions.""" @@ -112,7 +116,7 @@ def expand(self) -> None: else: if node.default == ConfigNode.UNSET: if node.vdr and node.vdr.endswith('_LIST'): - defs[node.name] = [] + defs[node.name] = DefaultList() else: defs[node.name] = None else: diff --git a/cylc/flow/parsec/util.py b/cylc/flow/parsec/util.py index e26134c62c..785612639a 100644 --- a/cylc/flow/parsec/util.py +++ b/cylc/flow/parsec/util.py @@ -212,7 +212,7 @@ def replicate(target, source): target[key].defaults_ = pdeepcopy(val.defaults_) replicate(target[key], val) elif isinstance(val, list): - target[key] = type(val)(val) + target[key] = val[:] else: target[key] = val @@ -247,7 +247,7 @@ def poverride(target, sparse, prepend=False): # Override in-place in the target ordered dict. setitem = target.__setitem__ if isinstance(val, list): - setitem(key, type(val)(val)) + setitem(key, val[:]) else: setitem(key, val) @@ -290,21 +290,25 @@ def m_override(target, sparse): stack.append( (val, dest[key], keylist + [key], child_many_defaults)) else: - if not ( - key in dest - or '__MANY__' in dest - or key in many_defaults - or '__MANY__' in many_defaults - ): - # TODO - validation prevents this, but handle properly - # for completeness. - raise Exception( - "parsec dict override: no __MANY__ placeholder" + - "%s" % (keylist + [key]) - ) + if key not in dest: + if not ( + '__MANY__' in dest + or key in many_defaults + or '__MANY__' in many_defaults + ): + # TODO - validation prevents this, but handle properly + # for completeness. + raise Exception( + "parsec dict override: no __MANY__ placeholder" + + "%s" % (keylist + [key]) + ) + if isinstance(val, list): + dest[key] = val[:] + else: + dest[key] = val if isinstance(val, list): - dest[key] = type(val)(val) + dest[key] = val[:] else: dest[key] = val for dest_dict, defaults in defaults_list: diff --git a/cylc/flow/parsec/validate.py b/cylc/flow/parsec/validate.py index eb2447d9f2..18c19596a6 100644 --- a/cylc/flow/parsec/validate.py +++ b/cylc/flow/parsec/validate.py @@ -597,7 +597,7 @@ def strip_and_unquote_list(cls, keys, value): # allow trailing commas if values[-1] == '': values = values[0:-1] - return UserList(values) + return values @classmethod def _unquoted_list_parse(cls, keys, value): @@ -647,11 +647,6 @@ def __str__(self): return f'{self[0]} .. {self[1]}' -class UserList(list): - # distinguish user defined, possibly empty list from automatic defaults - pass - - class CylcConfigValidator(ParsecValidator): """Type validator and coercer for Cylc configurations. diff --git a/cylc/flow/workflow_events.py b/cylc/flow/workflow_events.py index 719b7d02f6..4e9f3155a2 100644 --- a/cylc/flow/workflow_events.py +++ b/cylc/flow/workflow_events.py @@ -24,7 +24,7 @@ from cylc.flow.cfgspec.glbl_cfg import glbl_cfg from cylc.flow.hostuserutil import get_host, get_user from cylc.flow.log_diagnosis import run_reftest -from cylc.flow.parsec.validate import UserList +from cylc.flow.parsec.config import DefaultList from cylc.flow.subprocctx import SubProcContext if TYPE_CHECKING: @@ -236,7 +236,7 @@ def get_events_conf( glbl_cfg().get(['scheduler', 'mail']) ): value = getter.get(key) - if value not in (None, []) or isinstance(value, UserList): + if value is not None and not isinstance(value, DefaultList): return value return default diff --git a/tests/unit/test_workflow_events.py b/tests/unit/test_workflow_events.py index 47e366b38b..3aabc6de06 100644 --- a/tests/unit/test_workflow_events.py +++ b/tests/unit/test_workflow_events.py @@ -21,7 +21,6 @@ from types import SimpleNamespace -from cylc.flow.parsec.validate import UserList from cylc.flow.workflow_events import ( WorkflowEventHandler, get_template_variables, @@ -66,7 +65,7 @@ def test_get_events_handler( config = SimpleNamespace() config.cfg = { 'scheduler': { - 'events': {'handlers': ['stall'], 'mail events': UserList()}, + 'events': {'handlers': ['stall'], 'mail events': []}, 'mail': {'from': 'docklands@railway'}, } if workflow_cfg else {'events': {}} }