diff --git a/changes.d/7024.feat.md b/changes.d/7024.feat.md new file mode 100644 index 00000000000..78f790b3ad4 --- /dev/null +++ b/changes.d/7024.feat.md @@ -0,0 +1 @@ +Ensure `next()/previous()` syntax works for the `cylc play --startcp` option. diff --git a/cylc/flow/config.py b/cylc/flow/config.py index df5d7026a8d..f68f0331cc2 100644 --- a/cylc/flow/config.py +++ b/cylc/flow/config.py @@ -42,6 +42,7 @@ Dict, Iterable, List, + Literal, Mapping, Optional, Set, @@ -249,6 +250,17 @@ def interpolate_template(tmpl, params_dict): raise ParamExpandError('bad template syntax') from None +def _parse_iso_cycle_point(value: str) -> str: + """Helper for parsing initial/start cycle point option in + datetime cycling mode.""" + if value == 'now': + return get_current_time_string() + try: + return ingest_time(value, get_current_time_string()) + except IsodatetimeError as exc: + raise WorkflowConfigError(str(exc)) from None + + class WorkflowConfig: """Class for workflow configuration items and derived quantities.""" @@ -468,7 +480,9 @@ def __init__( # after the call to init_cyclers, we can start getting proper points. init_cyclers(self.cfg) - self.cycling_type = get_interval_cls().get_null().TYPE + self.cycling_type: Literal['integer', 'iso8601'] = ( + get_interval_cls().get_null().TYPE + ) self.cycle_point_dump_format = get_dump_format(self.cycling_type) # Initial point from workflow definition (or CLI override above). @@ -756,17 +770,11 @@ def process_initial_cycle_point(self) -> None: if orig_icp is None: orig_icp = '1' icp = orig_icp - elif self.cycling_type == ISO8601_CYCLING_TYPE: + else: if orig_icp is None: raise WorkflowConfigError( "This workflow requires an initial cycle point.") - if orig_icp == "now": - icp = get_current_time_string() - else: - try: - icp = ingest_time(orig_icp, get_current_time_string()) - except IsodatetimeError as exc: - raise WorkflowConfigError(str(exc)) from None + icp = _parse_iso_cycle_point(orig_icp) self.evaluated_icp = None if icp != orig_icp: # now/next()/previous() was used, need to store @@ -807,8 +815,7 @@ def process_start_cycle_point(self) -> None: ) if startcp: # Start from a point later than initial point. - if self.options.startcp == 'now': - self.options.startcp = get_current_time_string() + self.options.startcp = _parse_iso_cycle_point(startcp) self.start_point = get_point(self.options.startcp).standardise() elif starttask: # Start from designated task(s). diff --git a/cylc/flow/option_parsers.py b/cylc/flow/option_parsers.py index 95b6313b217..e460e6cfc77 100644 --- a/cylc/flow/option_parsers.py +++ b/cylc/flow/option_parsers.py @@ -123,10 +123,11 @@ def _update_sources(self, other): ICP_OPTION = OptionSettings( ["--initial-cycle-point", "--icp"], help=( - "Set the initial cycle point." - " Required if not defined in flow.cylc." - "\nMay be either an absolute point or an offset: See" - f" {SHORTLINK_TO_ICP_DOCS} (Cylc documentation link)." + "Set the initial cycle point. " + "Required if not defined in flow.cylc.\n" + "Can be an absolute point or an offset relative to the " + "current time - see " + f"{SHORTLINK_TO_ICP_DOCS} (Cylc documentation link)." ), metavar="CYCLE_POINT or OFFSET", action='store', diff --git a/cylc/flow/scheduler_cli.py b/cylc/flow/scheduler_cli.py index af55f66d2ed..8c08d7e4962 100644 --- a/cylc/flow/scheduler_cli.py +++ b/cylc/flow/scheduler_cli.py @@ -15,7 +15,6 @@ # along with this program. If not, see . """Common logic for "cylc play" CLI.""" -from ansimarkup import parse as cparse import asyncio from copy import deepcopy from functools import lru_cache @@ -24,50 +23,62 @@ from pathlib import Path from shlex import quote import sys -from typing import TYPE_CHECKING, Tuple +from typing import ( + TYPE_CHECKING, + Tuple, +) +from ansimarkup import parse as cparse from packaging.version import Version -from cylc.flow import LOG, __version__ +from cylc.flow import ( + LOG, + __version__, +) from cylc.flow.exceptions import ( CylcError, ServiceFileError, WorkflowStopped, ) -from cylc.flow.scripts.ping import run as cylc_ping import cylc.flow.flags -from cylc.flow.id import upgrade_legacy_ids from cylc.flow.host_select import select_workflow_host from cylc.flow.hostuserutil import is_remote_host +from cylc.flow.id import upgrade_legacy_ids from cylc.flow.id_cli import parse_ids_async from cylc.flow.loggingutil import ( - close_log, RotatingLogFileHandler, + close_log, ) from cylc.flow.network.client import WorkflowRuntimeClient from cylc.flow.network.log_stream_handler import ProtobufStreamHandler from cylc.flow.option_parsers import ( + ICP_OPTION, + SHORTLINK_TO_ICP_DOCS, WORKFLOW_ID_ARG_DOC, CylcOptionParser as COP, - OptionSettings, Options, - ICP_OPTION, + OptionSettings, ) from cylc.flow.pathutil import get_workflow_run_scheduler_log_path from cylc.flow.remote import cylc_server_cmd -from cylc.flow.scheduler import Scheduler, SchedulerError -from cylc.flow.scripts.common import cylc_header from cylc.flow.run_modes import WORKFLOW_RUN_MODES -from cylc.flow.workflow_db_mgr import WorkflowDatabaseManager -from cylc.flow.workflow_files import ( - SUITERC_DEPR_MSG, - get_workflow_srv_dir, +from cylc.flow.scheduler import ( + Scheduler, + SchedulerError, ) +from cylc.flow.scripts.common import cylc_header +from cylc.flow.scripts.ping import run as cylc_ping from cylc.flow.terminal import ( cli_function, is_terminal, prompt, ) +from cylc.flow.workflow_db_mgr import WorkflowDatabaseManager +from cylc.flow.workflow_files import ( + SUITERC_DEPR_MSG, + get_workflow_srv_dir, +) + if TYPE_CHECKING: from optparse import Values @@ -162,10 +173,14 @@ OptionSettings( ["--start-cycle-point", "--startcp"], help=( - "Set the start cycle point, which may be after" - " the initial cycle point. If the specified start point is" - " not in the sequence, the next on-sequence point will" - " be used. (Not to be confused with the initial cycle point)"), + "Set the start cycle point, which may be after " + "the initial cycle point. " + "If the specified start point is not in the sequence, the next " + "on-sequence point will be used.\n" + "Can be an absolute point or an offset relative to the " + "current time, like the --initial-cycle-point option - see " + f"{SHORTLINK_TO_ICP_DOCS} (Cylc documentation link)." + ), metavar="CYCLE_POINT", action='store', dest="startcp", diff --git a/tests/unit/test_config.py b/tests/unit/test_config.py index eb236d7240b..74a711df7c6 100644 --- a/tests/unit/test_config.py +++ b/tests/unit/test_config.py @@ -314,6 +314,14 @@ def test_family_inheritance_and_quotes( None, id="ICP = now" ), + pytest.param( + ISO8601_CYCLING_TYPE, + {'initial cycle point': 'previous(T00)'}, + '20050102T0000+0530', + '20050102T0000+0530', + None, + id="ICP = prev" + ), pytest.param( ISO8601_CYCLING_TYPE, { @@ -413,6 +421,12 @@ def test_process_icp( '20050102T0615+0530', None ), + ( + 'previous(T00)', + None, + '20050102T0000+0530', + None, + ), ( None, None,