Skip to content
Open
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
1 change: 1 addition & 0 deletions changes.d/7024.feat.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Ensure `next()/previous()` syntax works for the `cylc play --startcp` option.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Ensure `next()/previous()` syntax works for the `cylc play --startcp` option.
Add support for the `next()/previous()` syntax with the `cylc play --startcp` option.

29 changes: 18 additions & 11 deletions cylc/flow/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
Dict,
Iterable,
List,
Literal,
Mapping,
Optional,
Set,
Expand Down Expand Up @@ -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."""

Expand Down Expand Up @@ -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).
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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).
Expand Down
9 changes: 5 additions & 4 deletions cylc/flow/option_parsers.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)."
Comment on lines +126 to +130
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why change this?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For clarity

),
metavar="CYCLE_POINT or OFFSET",
action='store',
Expand Down
51 changes: 33 additions & 18 deletions cylc/flow/scheduler_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
"""Common logic for "cylc play" CLI."""

from ansimarkup import parse as cparse
import asyncio
from copy import deepcopy
from functools import lru_cache
Expand All @@ -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
Expand Down Expand Up @@ -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)"),
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(I notice leading spaces have crept into the codebase - not sure what the motivation is, as this is not how we write paragraphs ordinarily. Perhaps we should specify the style to follow to avoid inconsistency?)

Copy link
Member

@oliver-sanders oliver-sanders Oct 23, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

???

Whether the space goes at the start or end of the line is arbitrary, neither is "how we write paragraphs ordinarily" since we don't write this whitespace in plain text. So neither way is right or wrong, natural or unnatural, it's just a style choice like any other.

Leading spaces are preferable because it is much easier to spot when a space has been omitted as they are all aligned in the first column (we have had several cases where tailing spaces were omitted) and with this arrangement commenting out or removing a line will never leave an erroneous space.

It's exactly the same reason why placing operators at the start rather than end of the line is considered preferable (PEP8, flake8, black, ruff, prettier).

FYI: There are no linting rules for strings, so style cannot be enforced.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree with @oliver-sanders

"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)."
Comment on lines +176 to +182
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why change this?

),
metavar="CYCLE_POINT",
action='store',
dest="startcp",
Expand Down
14 changes: 14 additions & 0 deletions tests/unit/test_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
{
Expand Down Expand Up @@ -413,6 +421,12 @@ def test_process_icp(
'20050102T0615+0530',
None
),
(
'previous(T00)',
None,
'20050102T0000+0530',
None,
),
(
None,
None,
Expand Down
Loading