Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions changes.d/5090.feat.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Distinct initial and final graphs, separated from the main cycling graph,
to make it easier to configure special behaviour at startup and shutdown.
7 changes: 5 additions & 2 deletions cylc/flow/command_validation.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
)

from cylc.flow.cycling.loader import standardise_point_string
from cylc.flow.cycling.nocycle import NOCYCLE_POINTS
from cylc.flow.exceptions import InputError, PointParsingError
from cylc.flow.flow_mgr import (
FLOW_NEW,
Expand All @@ -40,7 +41,6 @@
from cylc.flow.scripts.set import XTRIGGER_PREREQ_PREFIX
from cylc.flow.task_outputs import TASK_OUTPUT_SUCCEEDED


if TYPE_CHECKING:
from cylc.flow.id import TaskTokens

Expand Down Expand Up @@ -359,7 +359,10 @@ def is_tasks(ids: Iterable[str]) -> 'Set[TaskTokens]':
tokens = tokens.duplicate(task='root')

# if the cycle is not a glob or reference, standardise it
if (
if cast('str', tokens['cycle']) in NOCYCLE_POINTS:
# OK: startup or shutdown graph
pass
elif (
# cycle point is a glob
not contains_fnmatch(cast('str', tokens['cycle']))
# cycle point is a reference to the ICP/FCP
Expand Down
62 changes: 48 additions & 14 deletions cylc/flow/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,13 @@
from cylc.flow.c3mro import C3
from cylc.flow.cfgspec.glbl_cfg import glbl_cfg
from cylc.flow.cfgspec.workflow import RawWorkflowConfig

from cylc.flow.cycling.nocycle import (
NocycleSequence,
NOCYCLE_SEQ_STARTUP,
NOCYCLE_SEQ_SHUTDOWN
)
from cylc.flow.id import Tokens
from cylc.flow.cycling.integer import IntegerInterval
from cylc.flow.cycling.iso8601 import (
ISO8601Interval,
Expand Down Expand Up @@ -87,7 +94,6 @@
import cylc.flow.flags
from cylc.flow.graph_parser import GraphParser
from cylc.flow.graphnode import GraphNodeParser
from cylc.flow.id import Tokens
from cylc.flow.listify import listify
from cylc.flow.log_level import verbosity_to_env
from cylc.flow.param_expand import NameExpander
Expand Down Expand Up @@ -311,6 +317,7 @@ def __init__(
self.start_point: 'PointBase'
self.stop_point: Optional['PointBase'] = None
self.final_point: Optional['PointBase'] = None
self.nocycle_sequences: Set['NocycleSequence'] = set()
self.sequences: List['SequenceBase'] = []
self.actual_first_point: Optional['PointBase'] = None
self._start_point_for_actual_first_point: Optional['PointBase'] = None
Expand Down Expand Up @@ -657,8 +664,13 @@ def _warn_if_queues_have_implicit_tasks(
)

def prelim_process_graph(self) -> None:
"""Ensure graph is not empty; set integer cycling mode and icp/fcp = 1
for simplest "R1 = foo" type graphs.
"""Error if graph empty; set integer cycling and icp/fcp = 1,
if those settings are omitted and the graph is acyclic graphs.

Somewhat relevant notes:
- The default (if not set) cycling mode, gregorian, requires an ICP.
- cycling mode is not stored in the DB, so recompute for restarts.

"""
graphdict = self.cfg['scheduling']['graph']
if not any(graphdict.values()):
Expand All @@ -667,9 +679,21 @@ def prelim_process_graph(self) -> None:
if (
'cycling mode' not in self.cfg['scheduling'] and
self.cfg['scheduling'].get('initial cycle point', '1') == '1' and
all(item in ['graph', '1', 'R1'] for item in graphdict)
all(
seq in [
'R1',
str(NOCYCLE_SEQ_STARTUP),
str(NOCYCLE_SEQ_SHUTDOWN),
'graph', # Cylc 7 back-compat
'1' # Cylc 7 back-compat?
]
for seq in graphdict
)
):
# Pure acyclic graph, assume integer cycling mode with '1' cycle
# Non-cycling graph, assume integer cycling mode with '1' cycle.
# Typos in "startup", "shutdown", or "R1" will appear as cycling
# here, but will be fatal later during proper recurrance checking.

self.cfg['scheduling']['cycling mode'] = INTEGER_CYCLING_TYPE
for key in ('initial cycle point', 'final cycle point'):
if key not in self.cfg['scheduling']:
Expand Down Expand Up @@ -2350,15 +2374,25 @@ def load_graph(self):
try:
seq = get_sequence(section, icp, fcp)
except (AttributeError, TypeError, ValueError, CylcError) as exc:
if cylc.flow.flags.verbosity > 1:
traceback.print_exc()
msg = 'Cannot process recurrence %s' % section
msg += ' (initial cycle point=%s)' % icp
msg += ' (final cycle point=%s)' % fcp
if isinstance(exc, CylcError):
msg += ' %s' % exc.args[0]
raise WorkflowConfigError(msg) from None
self.sequences.append(seq)
try:
# is it a startup or shutdown graph?
seq = NocycleSequence(section)
except ValueError:
if cylc.flow.flags.verbosity > 1:
traceback.print_exc()
msg = (
f"Cannot process recurrence {section}"
f" (initial cycle point={icp})"
f" (final cycle point={fcp})"
)
if isinstance(exc, CylcError):
msg += ' %s' % exc.args[0]
raise WorkflowConfigError(msg) from None
else:
self.nocycle_sequences.add(seq)
else:
self.sequences.append(seq)

parser = GraphParser(
family_map,
self.parameters,
Expand Down
6 changes: 5 additions & 1 deletion cylc/flow/cycling/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -349,7 +349,11 @@ def TYPE_SORT_KEY(self) -> int:
@classmethod
@abstractmethod # Note: stacked decorator not strictly enforced in Py2.x
def get_async_expr(cls, start_point=0):
"""Express a one-off sequence at the initial cycle point."""
"""Express a one-off sequence at the initial cycle point.

Note "async" has nothing to do with asyncio. It was a (bad)
name for one-off (non-cycling) graphs in early Cylc versions.
"""
pass

@abstractmethod
Expand Down
7 changes: 6 additions & 1 deletion cylc/flow/cycling/integer.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,12 @@
import re

from cylc.flow.cycling import (
PointBase, IntervalBase, SequenceBase, ExclusionBase, parse_exclusion, cmp
PointBase,
IntervalBase,
SequenceBase,
ExclusionBase,
parse_exclusion,
cmp
)
from cylc.flow.exceptions import (
CylcMissingContextPointError,
Expand Down
4 changes: 3 additions & 1 deletion cylc/flow/cycling/loader.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,14 @@

from typing import Optional, Type, overload

from cylc.flow.cycling import PointBase, integer, iso8601
from cylc.flow.cycling import PointBase, integer, iso8601, nocycle
from metomi.isodatetime.data import Calendar


ISO8601_CYCLING_TYPE = iso8601.CYCLER_TYPE_ISO8601
INTEGER_CYCLING_TYPE = integer.CYCLER_TYPE_INTEGER
NOCYCLE_CYCLING_TYPE = nocycle.CYCLER_TYPE_NOCYCLE


IS_OFFSET_ABSOLUTE_IMPLS = {
INTEGER_CYCLING_TYPE: integer.is_offset_absolute,
Expand Down
Loading
Loading