Skip to content

Commit a7988c0

Browse files
authored
Merge pull request #6838 from MetRonnie/events-validation
Validate workflow and task handler/mail event names
2 parents 970df19 + a2f74d9 commit a7988c0

File tree

12 files changed

+416
-148
lines changed

12 files changed

+416
-148
lines changed

changes.d/6838.fix.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Workflow and task `handler events` and `mail events` names are now validated. Outdated Cylc 7 workflow event names are automatically upgraded.

cylc/flow/cfgspec/globalcfg.py

Lines changed: 52 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -15,39 +15,55 @@
1515
# along with this program. If not, see <http://www.gnu.org/licenses/>.
1616
"""Cylc site and user configuration file spec."""
1717

18+
from contextlib import suppress
1819
import os
1920
from pathlib import Path
2021
from sys import stderr
21-
from textwrap import dedent, indent
22-
from typing import List, Optional, Tuple, Any, Union
22+
from textwrap import (
23+
dedent,
24+
indent,
25+
)
26+
from typing import (
27+
Any,
28+
Dict,
29+
List,
30+
Optional,
31+
Tuple,
32+
Union,
33+
)
2334

24-
from contextlib import suppress
2535
from packaging.version import Version
2636

27-
from cylc.flow import LOG
28-
from cylc.flow import __version__ as CYLC_VERSION
29-
from cylc.flow.platforms import validate_platforms
37+
from cylc.flow import (
38+
LOG,
39+
__version__ as CYLC_VERSION,
40+
)
3041
from cylc.flow.exceptions import GlobalConfigError
3142
from cylc.flow.hostuserutil import get_user_home
3243
from cylc.flow.network.client_factory import CommsMeth
33-
from cylc.flow.pathutil import SYMLINKABLE_LOCATIONS
3444
from cylc.flow.parsec.config import (
3545
ConfigNode as Conf,
3646
ParsecConfig,
3747
)
3848
from cylc.flow.parsec.exceptions import (
39-
ParsecError,
4049
ItemNotFoundError,
50+
ParsecError,
4151
ValidationError,
4252
)
4353
from cylc.flow.parsec.upgrade import upgrader
44-
from cylc.flow.parsec.util import printcfg, expand_many_section
54+
from cylc.flow.parsec.util import (
55+
expand_many_section,
56+
printcfg,
57+
)
4558
from cylc.flow.parsec.validate import (
4659
CylcConfigValidator as VDR,
4760
DurationFloat,
4861
Range,
4962
cylc_config_validate,
5063
)
64+
from cylc.flow.pathutil import SYMLINKABLE_LOCATIONS
65+
from cylc.flow.platforms import validate_platforms
66+
from cylc.flow.workflow_events import WorkflowEventHandler
5167

5268

5369
PLATFORM_REGEX_TEXT = '''
@@ -150,7 +166,7 @@
150166
Configure the workflow event handling system.
151167
'''
152168

153-
EVENTS_SETTINGS = { # workflow events
169+
EVENTS_SETTINGS: Dict[str, Union[str, Dict[str, Any]]] = { # workflow events
154170
'handlers': '''
155171
Configure :term:`event handlers` that run when certain workflow
156172
events occur.
@@ -171,17 +187,26 @@
171187
172188
:ref:`user_guide.scheduler.workflow_events`
173189
''',
174-
'handler events': '''
175-
Specify the events for which workflow event handlers should be invoked.
190+
'handler events': {
191+
'desc': '''
192+
Specify the events for which workflow event handlers should be
193+
invoked.
176194
177-
.. seealso::
195+
.. seealso::
178196
179-
:ref:`user_guide.scheduler.workflow_events`
180-
''',
181-
'mail events': '''
182-
Specify the workflow events for which notification emails should
183-
be sent.
184-
''',
197+
:ref:`user_guide.scheduler.workflow_events`
198+
''',
199+
'options': WorkflowEventHandler.EVENTS.copy(),
200+
'depr_options': WorkflowEventHandler.EVENTS_DEPRECATED.copy(),
201+
},
202+
'mail events': {
203+
'desc': '''
204+
Specify the workflow events for which notification emails should
205+
be sent.
206+
''',
207+
'options': WorkflowEventHandler.EVENTS.copy(),
208+
'depr_options': WorkflowEventHandler.EVENTS_DEPRECATED.copy(),
209+
},
185210
'startup handlers': f'''
186211
Handlers to run at scheduler startup.
187212
@@ -1038,7 +1063,13 @@ def default_for(
10381063

10391064
with Conf('events',
10401065
desc=default_for(EVENTS_DESCR, '[scheduler][events]')):
1041-
for item, desc in EVENTS_SETTINGS.items():
1066+
for item, val in EVENTS_SETTINGS.items():
1067+
if isinstance(val, dict):
1068+
val = val.copy()
1069+
desc: str = val.pop('desc')
1070+
else:
1071+
desc = val
1072+
val = {}
10421073
desc = default_for(desc, f"[scheduler][events]{item}")
10431074
vdr_type = VDR.V_STRING_LIST
10441075
default: Any = Conf.UNSET
@@ -1058,7 +1089,7 @@ def default_for(
10581089
default = DurationFloat(300)
10591090
else:
10601091
default = None
1061-
Conf(item, vdr_type, default, desc=desc)
1092+
Conf(item, vdr_type, default, desc=desc, **val)
10621093

10631094
with Conf('mail', desc=(
10641095
default_for(MAIL_DESCR, "[scheduler][mail]", section=True)

cylc/flow/cfgspec/workflow.py

Lines changed: 49 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -18,18 +18,23 @@
1818
import contextlib
1919
import re
2020
from textwrap import dedent
21-
from typing import Any, Dict, Optional, Set
21+
from typing import (
22+
Any,
23+
Dict,
24+
Optional,
25+
Set,
26+
)
2227

2328
from metomi.isodatetime.data import Calendar
2429

2530
from cylc.flow import LOG
2631
from cylc.flow.cfgspec.globalcfg import (
2732
DIRECTIVES_DESCR,
2833
DIRECTIVES_ITEM_DESCR,
29-
LOG_RETR_SETTINGS,
3034
EVENTS_DESCR,
3135
EVENTS_SETTINGS,
3236
EXECUTION_POLL_DESCR,
37+
LOG_RETR_SETTINGS,
3338
MAIL_DESCR,
3439
MAIL_FOOTER_DESCR,
3540
MAIL_FROM_DESCR,
@@ -47,18 +52,31 @@
4752
UTC_MODE_DESCR,
4853
)
4954
import cylc.flow.flags
50-
from cylc.flow.parsec.exceptions import UpgradeError
51-
from cylc.flow.parsec.config import ParsecConfig, ConfigNode as Conf
5255
from cylc.flow.parsec.OrderedDict import OrderedDictWithDefaults
53-
from cylc.flow.parsec.upgrade import upgrader, converter
56+
from cylc.flow.parsec.config import (
57+
ConfigNode as Conf,
58+
ParsecConfig,
59+
)
60+
from cylc.flow.parsec.exceptions import UpgradeError
61+
from cylc.flow.parsec.upgrade import (
62+
converter,
63+
upgrader,
64+
)
5465
from cylc.flow.parsec.validate import (
55-
DurationFloat, CylcConfigValidator as VDR, cylc_config_validate)
66+
CylcConfigValidator as VDR,
67+
DurationFloat,
68+
cylc_config_validate,
69+
)
5670
from cylc.flow.platforms import (
57-
fail_if_platform_and_host_conflict, get_platform_deprecated_settings,
58-
is_platform_definition_subshell)
59-
from cylc.flow.run_modes import RunMode
71+
fail_if_platform_and_host_conflict,
72+
get_platform_deprecated_settings,
73+
is_platform_definition_subshell,
74+
)
75+
from cylc.flow.run_modes import (
76+
TASK_CONFIG_RUN_MODES,
77+
RunMode,
78+
)
6079
from cylc.flow.task_events_mgr import EventData
61-
from cylc.flow.run_modes import TASK_CONFIG_RUN_MODES
6280

6381

6482
# Regex to check whether a string is a command
@@ -393,9 +411,16 @@ def get_script_common_text(this: str, example: Optional[str] = None):
393411
)
394412
))
395413

396-
with Conf('events',
397-
desc=global_default(EVENTS_DESCR, '[scheduler][events]')):
398-
for item, desc in EVENTS_SETTINGS.items():
414+
with Conf(
415+
'events', desc=global_default(EVENTS_DESCR, '[scheduler][events]')
416+
):
417+
for item, val in EVENTS_SETTINGS.items():
418+
if isinstance(val, dict):
419+
val = val.copy()
420+
desc: str = val.pop('desc')
421+
else:
422+
desc = val
423+
val = {}
399424
desc = global_default(desc, f"[scheduler][events]{item}")
400425
vdr_type = VDR.V_STRING_LIST
401426
default: Any = Conf.UNSET
@@ -426,7 +451,7 @@ def get_script_common_text(this: str, example: Optional[str] = None):
426451
vdr_type = VDR.V_BOOLEAN
427452
elif item.endswith("timeout"):
428453
vdr_type = VDR.V_INTERVAL
429-
Conf(item, vdr_type, default, desc=desc)
454+
Conf(item, vdr_type, default, desc=desc, **val)
430455

431456
Conf('expected task failures', VDR.V_STRING_LIST, desc='''
432457
(For Cylc developers writing a functional tests only)
@@ -2190,13 +2215,15 @@ def upg(cfg, descr):
21902215
silent=cylc.flow.flags.cylc7_back_compat,
21912216
)
21922217

2193-
u.obsolete('8.0.0', ['cylc', 'events', 'abort on stalled'])
2194-
u.obsolete('8.0.0', ['cylc', 'events', 'abort if startup handler fails'])
2195-
u.obsolete('8.0.0', ['cylc', 'events', 'abort if shutdown handler fails'])
2196-
u.obsolete('8.0.0', ['cylc', 'events', 'abort if timeout handler fails'])
2197-
u.obsolete('8.0.0', ['cylc', 'events',
2198-
'abort if inactivity handler fails'])
2199-
u.obsolete('8.0.0', ['cylc', 'events', 'abort if stalled handler fails'])
2218+
for old in [
2219+
'abort on stalled',
2220+
'abort if startup handler fails',
2221+
'abort if shutdown handler fails',
2222+
'abort if timeout handler fails',
2223+
'abort if inactivity handler fails',
2224+
'abort if stalled handler fails',
2225+
]:
2226+
u.obsolete('8.0.0', ['cylc', 'events', old])
22002227

22012228
u.deprecate(
22022229
'8.0.0',
@@ -2279,10 +2306,7 @@ def upgrade_param_env_templates(cfg, descr):
22792306
continue
22802307
if not cylc.flow.flags.cylc7_back_compat:
22812308
if first_warn:
2282-
LOG.warning(
2283-
'deprecated items automatically upgraded in '
2284-
f'"{descr}":'
2285-
)
2309+
LOG.warning(upgrader.DEPR_MSG)
22862310
first_warn = False
22872311
LOG.warning(
22882312
f' * (8.0.0) {dep % task_name} contents prepended to '

0 commit comments

Comments
 (0)