Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

cylc vr #5229

Merged
Merged

cylc vr #5229

Show file tree
Hide file tree
Changes from 27 commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
f01657f
Get Template Vars from database for played workflows.
wxtim Oct 31, 2022
a3c3ac4
Merge branch 'master' into 20221031T1340--HEAD--=master--second_attem…
wxtim Nov 30, 2022
757098e
clean integration test
wxtim Nov 30, 2022
2bd5da8
Update cylc/flow/option_parsers.py
wxtim Nov 30, 2022
3603496
Update tests/integration/conftest.py
wxtim Nov 30, 2022
bb857d2
Update CHANGES.md
wxtim Dec 9, 2022
5d89fdc
prevent 'against_source' being a valid Cylc VIP option.
wxtim Dec 9, 2022
b224a6a
remove unwated run-mod option
wxtim Dec 12, 2022
4ce97a2
add Cylc VRO
wxtim Dec 14, 2022
e4eff9c
fix broken test
wxtim Dec 15, 2022
db050a8
Get Template Vars from database for played workflows.
wxtim Oct 31, 2022
9e429af
Apply suggestions from code review
wxtim Jan 4, 2023
d6b7469
Merge branch 'new_get_old_tvars_branch' into VRO_new_II
wxtim Jan 4, 2023
696d365
removed file committed by mistake
wxtim Jan 4, 2023
d389fcc
Response to review
wxtim Jan 4, 2023
444d64a
fix to prevent fileparse asking for old tvars unless there's a db
wxtim Jan 4, 2023
e4245f9
fix to prevent fileparse asking for old tvars unless there is a db
wxtim Jan 4, 2023
75c7666
Merge branch 'new_get_old_tvars_branch' into VRO_new_II
wxtim Jan 5, 2023
7098d7b
rebadged vro to vr
wxtim Jan 5, 2023
9ace5ae
rebadged vro to vr
wxtim Jan 5, 2023
a2b9e1f
Merge branch '20221116T1324--vip&getTvars--implement_VRO' of github.c…
wxtim Jan 9, 2023
f31fd49
Response to review
wxtim Jan 9, 2023
10c2711
fix broken tests
wxtim Jan 9, 2023
a620f92
Update cylc/flow/scripts/view.py
wxtim Jan 10, 2023
7f7a716
response to review
wxtim Jan 10, 2023
ee83acd
de-flake a test
wxtim Jan 10, 2023
4a07bf9
Update CHANGES.md
wxtim Jan 11, 2023
735f39a
Update CHANGES.md
wxtim Jan 11, 2023
a870bdb
response to review
wxtim Jan 11, 2023
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
7 changes: 6 additions & 1 deletion CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,14 @@ ones in. -->

### Enhancements

[#5229](https://github.com/cylc/cylc-flow/pull/5229) -
- Added a single command to validate a previously run workflow against changes
to its source and reinstall a workflow.
- Allows Cylc commands (including validate, list, view, config, and graph) to load template variables
configured by `cylc install` and `cylc play`.

[#5184](https://github.com/cylc/cylc-flow/pull/5184) - scan for active
runs of the same workflow at install time.
wxtim marked this conversation as resolved.
Show resolved Hide resolved

[#5094](https://github.com/cylc/cylc-flow/pull/5094) - Added a single
command to validate, install and play a workflow.
wxtim marked this conversation as resolved.
Show resolved Hide resolved

wxtim marked this conversation as resolved.
Show resolved Hide resolved
Expand Down
32 changes: 28 additions & 4 deletions cylc/flow/option_parsers.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,9 +80,16 @@ def __init__(self, argslist, sources=None, useif=None, **kwargs):
self.kwargs.update({kwarg: value})

def __eq__(self, other):
"""Args and Kwargs, but not other props equal."""
"""Args and Kwargs, but not other props equal.

(Also make an exception for kwargs['help'] to allow lists of sources
prepended to 'help' to be passed through.)
"""
return (
self.kwargs == other.kwargs
(
{k: v for k, v in self.kwargs.items() if k != 'help'}
== {k: v for k, v in other.kwargs.items() if k != 'help'}
)
and self.args == other.args
)

Expand Down Expand Up @@ -117,6 +124,22 @@ def _update_sources(self, other):
dest="icp"
)

AGAINST_SOURCE_OPTION = OptionSettings(
['--against-source'],
help=(
"Load the workflow configuration from the source directory it was"
" installed from using any options (e.g. template variables) which"
" have been set in the installation."
" This is useful if you want to see how changes made to the workflow"
" source would affect the installation if reinstalled."
" Note if this option is used the provided workflow must have been"
" installed by `cylc install`."
),
dest='against_source',
action='store_true',
default=False
)


icp_option = Option(
*ICP_OPTION.args, **ICP_OPTION.kwargs) # type: ignore[arg-type]
Expand Down Expand Up @@ -713,9 +736,10 @@ def combine_options_pair(first_list, second_list):
and first & second
):
# if any of the args are different:

if first.args == second.args:
# if none of the arg names are different.
raise Exception(f'Clashing Options \n{first}\n{second}')
raise Exception(
f'Clashing Options \n{first.args}\n{second.args}')
else:
first_args = first - second
second.args = second - first
Expand Down
45 changes: 44 additions & 1 deletion cylc/flow/parsec/fileparse.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
"""

import os
from optparse import Values
from pathlib import Path
import re
import sys
Expand All @@ -45,6 +46,9 @@
from cylc.flow.parsec.OrderedDict import OrderedDictWithDefaults
from cylc.flow.parsec.include import inline
from cylc.flow.parsec.util import itemstr
from cylc.flow.templatevars import get_template_vars_from_db
from cylc.flow.workflow_files import (
get_workflow_source_dir, check_flow_file)


# heading/sections can contain commas (namespace name lists) and any
Expand Down Expand Up @@ -343,6 +347,43 @@ def merge_template_vars(
return native_tvars


def _prepend_old_templatevars(fpath: str, template_vars: t.Dict) -> t.Dict:
"""If the fpath is in a rundir, extract template variables from database.

Args:
fpath: filepath of workflow config file.
template_vars: Template vars to prepend old tvars to.
"""
rundir = Path(fpath).parent
old_tvars = get_template_vars_from_db(rundir)
old_tvars.update(template_vars)
template_vars = old_tvars
return template_vars


def _get_fpath_for_source(fpath: str, opts: "Values") -> str:
"""If `--against-source` is set and a sourcedir can be found, return
sourcedir.
Else return fpath unchanged.

Raises:
ParsecError: If validate against source is set and this is not
an installed workflow then we don't want to continue.
"""
if getattr(opts, 'against_source', False):
thispath = Path(fpath)
source_dir = get_workflow_source_dir(thispath.parent)[0]
if source_dir:
retpath = check_flow_file(source_dir)
return str(retpath)
else:
raise ParsecError(
f'Cannot validate {thispath.parent} against source: '
'it is not an installed workflow.')
else:
return fpath


def read_and_proc(
fpath: str,
template_vars: t.Optional[t.Dict[str, t.Any]] = None,
Expand All @@ -355,7 +396,9 @@ def read_and_proc(
Jinja2 processing must be done before concatenation - it could be
used to generate continuation lines.
"""

template_vars = template_vars if template_vars is not None else {}
template_vars = _prepend_old_templatevars(fpath, template_vars)
fpath = _get_fpath_for_source(fpath, opts)
fdir = os.path.dirname(fpath)

# Allow Python modules in lib/python/ (e.g. for use by Jinja2 filters).
Expand Down
18 changes: 16 additions & 2 deletions cylc/flow/scripts/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,14 +49,17 @@
$ cylc config --initial-cycle-point=now myflow
"""

import asyncio

import os.path
from typing import List, Optional, TYPE_CHECKING

from cylc.flow.cfgspec.glbl_cfg import glbl_cfg
from cylc.flow.config import WorkflowConfig
from cylc.flow.id_cli import parse_id
from cylc.flow.id_cli import parse_id_async
from cylc.flow.exceptions import InputError
from cylc.flow.option_parsers import (
AGAINST_SOURCE_OPTION,
WORKFLOW_ID_OR_PATH_ARG_DOC,
CylcOptionParser as COP,
icp_option,
Expand Down Expand Up @@ -128,6 +131,9 @@ def get_option_parser() -> COP:
action='store_true', default=False, dest='print_platforms'
)

parser.add_option(
*AGAINST_SOURCE_OPTION.args, **AGAINST_SOURCE_OPTION.kwargs)

parser.add_cylc_rose_options()

return parser
Expand All @@ -149,6 +155,14 @@ def main(
options: 'Values',
*ids,
) -> None:
asyncio.run(_main(parser, options, *ids))


async def _main(
parser: COP,
options: 'Values',
*ids,
) -> None:

if options.print_platform_names and options.print_platforms:
options.print_platform_names = False
Expand Down Expand Up @@ -178,7 +192,7 @@ def main(
)
return

workflow_id, _, flow_file = parse_id(
workflow_id, _, flow_file = await parse_id_async(
*ids,
src=True,
constraint='workflows',
Expand Down
3 changes: 2 additions & 1 deletion cylc/flow/scripts/cylc.py
Original file line number Diff line number Diff line change
Expand Up @@ -202,7 +202,8 @@ def get_version(long=False):
'shutdown': 'stop',
'task-message': 'message',
'unhold': 'release',
'validate-install-play': 'vip'
'validate-install-play': 'vip',
'validate-reinstall': 'vr',
}


Expand Down
74 changes: 52 additions & 22 deletions cylc/flow/scripts/graph.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
$ cylc graph one -o 'one.svg'
"""

import asyncio
from difflib import unified_diff
from shutil import which
from subprocess import Popen, PIPE
Expand All @@ -45,8 +46,9 @@
from cylc.flow.config import WorkflowConfig
from cylc.flow.exceptions import InputError, CylcError
from cylc.flow.id import Tokens
from cylc.flow.id_cli import parse_id
from cylc.flow.id_cli import parse_id_async
from cylc.flow.option_parsers import (
AGAINST_SOURCE_OPTION,
WORKFLOW_ID_OR_PATH_ARG_DOC,
CylcOptionParser as COP,
icp_option,
Expand Down Expand Up @@ -108,9 +110,10 @@ def get_nodes_and_edges(
workflow_id,
start,
stop,
flow_file,
) -> Tuple[List[Node], List[Edge]]:
"""Return graph sorted nodes and edges."""
config = get_config(workflow_id, opts)
config = get_config(workflow_id, opts, flow_file)
if opts.namespaces:
nodes, edges = _get_inheritance_nodes_and_edges(config)
else:
Expand Down Expand Up @@ -194,13 +197,8 @@ def _get_inheritance_nodes_and_edges(
return sorted(nodes), sorted(edges)


def get_config(workflow_id: str, opts: 'Values') -> WorkflowConfig:
def get_config(workflow_id: str, opts: 'Values', flow_file) -> WorkflowConfig:
"""Return a WorkflowConfig object for the provided reg / path."""
workflow_id, _, flow_file = parse_id(
workflow_id,
src=True,
constraint='workflows',
)
template_vars = get_template_vars(opts)
return WorkflowConfig(
workflow_id, flow_file, opts, template_vars=template_vars
Expand Down Expand Up @@ -334,7 +332,7 @@ def open_image(filename):
img.show()


def graph_render(opts, workflow_id, start, stop) -> int:
def graph_render(opts, workflow_id, start, stop, flow_file) -> int:
"""Render the workflow graph to the specified format.

Graph is rendered to the specified format. The Graphviz "dot" format
Expand All @@ -349,6 +347,7 @@ def graph_render(opts, workflow_id, start, stop) -> int:
workflow_id,
start,
stop,
flow_file
)

# format the graph in graphviz-dot format
Expand Down Expand Up @@ -382,28 +381,42 @@ def graph_render(opts, workflow_id, start, stop) -> int:
return 0


def graph_reference(opts, workflow_id, start, stop, write=print) -> int:
def graph_reference(
opts, workflow_id, start, stop, flow_file, write=print,
) -> int:
"""Format the workflow graph using the cylc reference format."""
# get nodes and edges
nodes, edges = get_nodes_and_edges(
opts,
workflow_id,
start,
stop,
flow_file
)
for line in format_cylc_reference(opts, nodes, edges):
write(line)

return 0


def graph_diff(opts, workflow_a, workflow_b, start, stop) -> int:
async def graph_diff(
opts, workflow_a, workflow_b, start, stop, flow_file
) -> int:
"""Difference the workflow graphs using the cylc reference format."""

workflow_b, _, flow_file_b = await parse_id_async(
workflow_b,
src=True,
constraint='workflows',
)

# load graphs
graph_a: List[str] = []
graph_b: List[str] = []
graph_reference(opts, workflow_a, start, stop, write=graph_a.append),
graph_reference(opts, workflow_b, start, stop, write=graph_b.append),
graph_reference(
opts, workflow_a, start, stop, flow_file, write=graph_a.append),
graph_reference(
opts, workflow_b, start, stop, flow_file_b, write=graph_b.append),

# compare graphs
diff_lines = list(
Expand Down Expand Up @@ -494,6 +507,9 @@ def get_option_parser() -> COP:
action='store',
)

parser.add_option(
*AGAINST_SOURCE_OPTION.args, **AGAINST_SOURCE_OPTION.kwargs)

parser.add_cylc_rose_options()

return parser
Expand All @@ -507,20 +523,34 @@ def main(
start: Optional[str] = None,
stop: Optional[str] = None
) -> None:
result = asyncio.run(_main(parser, opts, workflow_id, start, stop))
sys.exit(result)


async def _main(
parser: COP,
opts: 'Values',
workflow_id: str,
start: Optional[str] = None,
stop: Optional[str] = None
) -> int:
"""Implement ``cylc graph``."""
if opts.grouping and opts.namespaces:
raise InputError('Cannot combine --group and --namespaces.')
if opts.cycles and opts.namespaces:
raise InputError('Cannot combine --cycles and --namespaces.')

workflow_id, _, flow_file = await parse_id_async(
workflow_id,
src=True,
constraint='workflows',
)

if opts.diff:
sys.exit(
graph_diff(opts, workflow_id, opts.diff, start, stop)
)
return await graph_diff(
opts, workflow_id, opts.diff, start, stop, flow_file)
if opts.reference:
sys.exit(
graph_reference(opts, workflow_id, start, stop)
)
sys.exit(
graph_render(opts, workflow_id, start, stop)
)
return graph_reference(
opts, workflow_id, start, stop, flow_file)

return graph_render(opts, workflow_id, start, stop, flow_file)
Loading