From 9440b59f5750de13957dae96e71bdbfa4ed4d059 Mon Sep 17 00:00:00 2001 From: woutdenolf Date: Mon, 11 May 2026 11:29:26 +0200 Subject: [PATCH 1/4] explicit CLI parameter addressing for workflow inputs --- CHANGELOG.md | 18 +++ src/ewoksutils/cli_utils/cli_arguments.py | 53 +++++++- src/ewoksutils/cli_utils/cli_parse.py | 109 +++++++++++++-- src/ewoksutils/tests/test_cli.py | 154 ++++++++++++++++++++-- 4 files changed, 308 insertions(+), 26 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c8010e3..1380af4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,24 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Added + +- Introduced explicit CLI parameter addressing for workflow inputs: + - `-pn / --parameter-nodeid` for inputs targeting a specific node id + - `-pt / --parameter-taskid` for inputs targeting a specific task identifier + - `-pl / --parameter-label` for for inputs targeting a specific node label + - `-ps / --parameter-start` for inputs targeting all start nodes + - `-pa / --parameter-all` for inputs targeting all nodes + +### Deprecated + +- CLI parameter `-p / --parameter` is deprecated and will be removed in a future release. + - Replace with `-pn`, `-pt`, or `-pl` to target specific nodes. + - Replace with `-ps` or `-pa` to target start or all nodes respectively. +- `--input-node-id` is deprecated in favor of explicit arguments (`-pn`, `-pt`, `-pl`) +- `--inputs` is deprecated in favor of explicit scoping arguments + `-ps` or `-pa` for start or all nodes respectively. + ## [1.9.2] - 2025-12-29 ### Changed diff --git a/src/ewoksutils/cli_utils/cli_arguments.py b/src/ewoksutils/cli_utils/cli_arguments.py index c71fb50..75aac2d 100644 --- a/src/ewoksutils/cli_utils/cli_arguments.py +++ b/src/ewoksutils/cli_utils/cli_arguments.py @@ -41,25 +41,70 @@ def workflow_arguments(action: str) -> List[CLIArg]: def ewoks_inputs_arguments() -> List[CLIArg]: return [ + CLIArg( + "parameters_nodeid", + ["-pn", "--parameter-nodeid"], + help="Input variable for a node addressed by node id.", + action="append", + metavar="[NODE_ID:]NAME=VALUE", + ), + CLIArg( + "parameters_taskid", + ["-pt", "--parameter-taskid"], + help="Input variable for a node addressed by task identifier.", + action="append", + metavar="[TASK_ID:]NAME=VALUE", + ), + CLIArg( + "parameters_label", + ["-pl", "--parameter-label"], + help="Input variable for a node addressed by node label.", + action="append", + metavar="[LABEL:]NAME=VALUE", + ), + CLIArg( + "parameters_start", + ["-ps", "--parameter-start"], + help="Input variable for all start nodes.", + action="append", + metavar="NAME=VALUE", + ), + CLIArg( + "parameters_all", + ["-pa", "--parameter-all"], + help="Input variable for all nodes.", + action="append", + metavar="NAME=VALUE", + ), + # Deprecated legacy option CLIArg( "parameters", ["-p", "--parameter"], - help="Input variable for a particular node" - " (or all start nodes when missing)", + help=( + "DEPRECATED: use -pn/-pt/-pl/-ps/-pa instead. " + "Input variable for a particular node " + "(or start/all nodes when missing depending on --inputs)." + ), action="append", metavar="[NODE:]NAME=VALUE", ), CLIArg( "node_attr", ["--input-node-id"], - help="The NODE attribute used when specifying an input parameter", + help=( + "DEPRECATED: use -pn/-pt/-pl instead. " + "The NODE attribute used when specifying an input parameter." + ), choices=["id", "label", "taskid"], default="id", ), CLIArg( "inputs", ["--inputs"], - help="Inputs without a specific node go to start/all nodes", + help=( + "DEPRECATED: use -ps/-pa instead. " + "Inputs without a specific node go to start/all nodes." + ), choices=["start", "all"], default="start", ), diff --git a/src/ewoksutils/cli_utils/cli_parse.py b/src/ewoksutils/cli_utils/cli_parse.py index c14e93d..3de6e7c 100644 --- a/src/ewoksutils/cli_utils/cli_parse.py +++ b/src/ewoksutils/cli_utils/cli_parse.py @@ -1,5 +1,7 @@ import json +import logging import os +import warnings from argparse import Namespace from glob import glob from json.decoder import JSONDecodeError @@ -7,35 +9,116 @@ from typing import List from typing import Tuple +logger = logging.getLogger(__name__) + def parse_ewoks_inputs_parameters(cli_args: Namespace) -> List[dict]: - return [ - parse_parameter(input_item, cli_args.node_attr, cli_args.inputs == "all") - for input_item in cli_args.parameters - ] + parameters = [] + + # Deprecated options + + if cli_args.parameters: + replacement = {"id": "-pn", "label": "-pl", "taskid": "-pt"}[cli_args.node_attr] + + if cli_args.inputs == "all": + default_target = "-pa" + else: + default_target = "-ps" + + _deprecated( + f"-p/--parameter is deprecated; " + f"use {replacement} for node-specific parameters " + f"and {default_target} for parameters without a node selector." + ) + + if cli_args.node_attr != "id": + replacement = {"label": "-pl", "taskid": "-pt"}[cli_args.node_attr] + + _deprecated( + f"--input-node-id={cli_args.node_attr} is deprecated; " + f"use {replacement} instead." + ) + + if cli_args.inputs != "start": + replacement = {"start": "-ps", "all": "-pa"}[cli_args.inputs] + + _deprecated( + f"--inputs={cli_args.inputs} is deprecated; " + f"use {replacement} instead." + ) + + parameters.extend( + parse_parameter( + input_item, + node_attr=cli_args.node_attr, + all_nodes=(cli_args.inputs == "all"), + ) + for input_item in cli_args.parameters + ) + + parameters.extend( + parse_parameter(input_item, node_attr="id") + for input_item in cli_args.parameters_nodeid + ) + + parameters.extend( + parse_parameter(input_item, node_attr="taskid") + for input_item in cli_args.parameters_taskid + ) + + parameters.extend( + parse_parameter(input_item, node_attr="label") + for input_item in cli_args.parameters_label + ) + + parameters.extend( + parse_parameter(input_item, all_nodes=False) + for input_item in cli_args.parameters_start + ) + + parameters.extend( + parse_parameter(input_item, all_nodes=True) + for input_item in cli_args.parameters_all + ) + + return parameters + + +def _deprecated(message: str) -> None: + warnings.warn(message, DeprecationWarning, stacklevel=3) + logger.warning(message) _NODE_ATTR_MAP = {"id": "id", "label": "label", "taskid": "task_identifier"} -def parse_parameter(input_item: str, node_attr: str, all: bool) -> dict: - """The format of `input_item` is `"[NODE]:name=value"`""" +def parse_parameter( + input_item: str, node_attr: str = None, all_nodes: bool = False +) -> dict: + """The format of `input_item` is `"[NODE:]NAME=VALUE"`.""" + node_and_name, _, value = input_item.partition("=") a, sep, b = node_and_name.partition(":") + if sep: node = a var_name = b else: node = None var_name = a + var_value = parse_value(value) + if node is None: - return {"all": all, "name": var_name, "value": var_value} - return { - _NODE_ATTR_MAP[node_attr]: node, - "name": var_name, - "value": var_value, - } + return {"all": all_nodes, "name": var_name, "value": var_value} + + if node_attr is None: + raise ValueError( + f"Node specified in parameter '{input_item}' " + "but no node attribute type was provided." + ) + + return {_NODE_ATTR_MAP[node_attr]: node, "name": var_name, "value": var_value} def parse_option(option: str) -> Tuple[str, Any]: @@ -60,7 +143,9 @@ def parse_workflows(cli_args: Namespace) -> Tuple[List[str], List[str]]: parsed_workflows = list() files = (filename for workflow in cli_args.workflows for filename in glob(workflow)) + for filename in sorted(files, key=os.path.getmtime): if filename not in parsed_workflows: parsed_workflows.append(filename) + return parsed_workflows, parsed_workflows diff --git a/src/ewoksutils/tests/test_cli.py b/src/ewoksutils/tests/test_cli.py index e31a196..bd8ddab 100644 --- a/src/ewoksutils/tests/test_cli.py +++ b/src/ewoksutils/tests/test_cli.py @@ -1,17 +1,15 @@ +import pytest + from ..cli_utils import cli_cancel_utils from ..cli_utils import cli_execute_utils from ..cli_utils import cli_submit_utils -def test_cli_execute(cli_interface): +def test_cli_execute_no_parameters(cli_interface): argv = [ "acyclic1", "acyclic2", "--test", - "-p", - "a=1", - "-p", - "task1:b=test", "--workflow-dir", "/tmp", ] @@ -23,6 +21,71 @@ def test_cli_execute(cli_interface): assert list(cli_args.graphs) == ["acyclic1", "acyclic2"] + execute_options = { + "inputs": [], + "merge_outputs": False, + "outputs": [], + "task_options": {}, + "varinfo": {"root_uri": "", "scheme": "nexus"}, + "load_options": {"representation": "test_core", "root_dir": "/tmp"}, + "execinfo": {}, + } + assert cli_args.execute_options == execute_options + + +def test_cli_execute_parameters(cli_interface): + argv = [ + "acyclic1", + "--test", + "-ps", + "a=1", + "-pa", + "b=2", + "-pn", + "node1:c=3", + "-pt", + "task1:d=4", + "-pl", + "label1:e=5", + ] + + cli_args = cli_interface( + argv, + cli_execute_utils.execute_arguments, + cli_execute_utils.parse_execute_argument, + ) + + execute_options = { + "inputs": [ + {"id": "node1", "name": "c", "value": 3}, + {"task_identifier": "task1", "name": "d", "value": 4}, + {"label": "label1", "name": "e", "value": 5}, + {"all": False, "name": "a", "value": 1}, + {"all": True, "name": "b", "value": 2}, + ], + "merge_outputs": False, + "outputs": [], + "task_options": {}, + "varinfo": {"root_uri": "", "scheme": "nexus"}, + "load_options": {"representation": "test_core"}, + "execinfo": {}, + } + + assert cli_args.execute_options == execute_options + + +def test_cli_execute_deprecated_parameters(cli_interface): + argv = ["acyclic1", "--test", "-p", "a=1", "-p", "task1:b=test"] + + with pytest.deprecated_call(match="-p/--parameter is deprecated"): + cli_args = cli_interface( + argv, + cli_execute_utils.execute_arguments, + cli_execute_utils.parse_execute_argument, + ) + + assert list(cli_args.graphs) == ["acyclic1"] + execute_options = { "inputs": [ {"all": False, "name": "a", "value": 1}, @@ -32,9 +95,80 @@ def test_cli_execute(cli_interface): "outputs": [], "task_options": {}, "varinfo": {"root_uri": "", "scheme": "nexus"}, - "load_options": {"representation": "test_core", "root_dir": "/tmp"}, + "load_options": {"representation": "test_core"}, "execinfo": {}, } + + assert cli_args.execute_options == execute_options + + +def test_cli_execute_deprecated_input_node_id(cli_interface): + argv = [ + "acyclic1", + "--test", + "--input-node-id", + "taskid", + "-p", + "task1:b=test", + ] + + with pytest.deprecated_call(match="-p/--parameter is deprecated"): + with pytest.deprecated_call(match="--input-node-id=taskid is deprecated"): + cli_args = cli_interface( + argv, + cli_execute_utils.execute_arguments, + cli_execute_utils.parse_execute_argument, + ) + + execute_options = { + "inputs": [ + { + "task_identifier": "task1", + "name": "b", + "value": "test", + } + ], + "merge_outputs": False, + "outputs": [], + "task_options": {}, + "varinfo": {"root_uri": "", "scheme": "nexus"}, + "load_options": {"representation": "test_core"}, + "execinfo": {}, + } + + assert cli_args.execute_options == execute_options + + +def test_cli_execute_deprecated_inputs_all(cli_interface): + argv = [ + "acyclic1", + "--test", + "--inputs", + "all", + "-p", + "a=1", + ] + + with pytest.deprecated_call(match="-p/--parameter is deprecated"): + with pytest.deprecated_call(match="--inputs=all is deprecated"): + cli_args = cli_interface( + argv, + cli_execute_utils.execute_arguments, + cli_execute_utils.parse_execute_argument, + ) + + execute_options = { + "inputs": [ + {"all": True, "name": "a", "value": 1}, + ], + "merge_outputs": False, + "outputs": [], + "task_options": {}, + "varinfo": {"root_uri": "", "scheme": "nexus"}, + "load_options": {"representation": "test_core"}, + "execinfo": {}, + } + assert cli_args.execute_options == execute_options @@ -43,10 +177,10 @@ def test_cli_submit(cli_interface): "acyclic1", "acyclic2", "--test", - "-p", + "-pn", "a=1", - "-p", - "task1:b=test", + "-pn", + "node1:b=test", "--workflow-dir", "/tmp", "--wait=inf", @@ -62,7 +196,7 @@ def test_cli_submit(cli_interface): execute_options = { "inputs": [ {"all": False, "name": "a", "value": 1}, - {"id": "task1", "name": "b", "value": "test"}, + {"id": "node1", "name": "b", "value": "test"}, ], "merge_outputs": False, "outputs": [], From 05390e45af0185b205e587ea82fec8fd48c54a42 Mon Sep 17 00:00:00 2001 From: woutdenolf Date: Mon, 11 May 2026 12:06:24 +0200 Subject: [PATCH 2/4] target is required for -pn/-pt/-pl and should be omitted for -pa/-ps --- src/ewoksutils/cli_utils/cli_arguments.py | 6 ++--- src/ewoksutils/cli_utils/cli_parse.py | 12 ++++----- src/ewoksutils/tests/test_cli.py | 33 +++++++++++++++++++++-- 3 files changed, 40 insertions(+), 11 deletions(-) diff --git a/src/ewoksutils/cli_utils/cli_arguments.py b/src/ewoksutils/cli_utils/cli_arguments.py index 75aac2d..413980d 100644 --- a/src/ewoksutils/cli_utils/cli_arguments.py +++ b/src/ewoksutils/cli_utils/cli_arguments.py @@ -46,21 +46,21 @@ def ewoks_inputs_arguments() -> List[CLIArg]: ["-pn", "--parameter-nodeid"], help="Input variable for a node addressed by node id.", action="append", - metavar="[NODE_ID:]NAME=VALUE", + metavar="NODE_ID:NAME=VALUE", ), CLIArg( "parameters_taskid", ["-pt", "--parameter-taskid"], help="Input variable for a node addressed by task identifier.", action="append", - metavar="[TASK_ID:]NAME=VALUE", + metavar="TASK_ID:NAME=VALUE", ), CLIArg( "parameters_label", ["-pl", "--parameter-label"], help="Input variable for a node addressed by node label.", action="append", - metavar="[LABEL:]NAME=VALUE", + metavar="LABEL:NAME=VALUE", ), CLIArg( "parameters_start", diff --git a/src/ewoksutils/cli_utils/cli_parse.py b/src/ewoksutils/cli_utils/cli_parse.py index 3de6e7c..603e2e3 100644 --- a/src/ewoksutils/cli_utils/cli_parse.py +++ b/src/ewoksutils/cli_utils/cli_parse.py @@ -7,6 +7,7 @@ from json.decoder import JSONDecodeError from typing import Any from typing import List +from typing import Optional from typing import Tuple logger = logging.getLogger(__name__) @@ -93,7 +94,7 @@ def _deprecated(message: str) -> None: def parse_parameter( - input_item: str, node_attr: str = None, all_nodes: bool = False + input_item: str, node_attr: Optional[str] = None, all_nodes: Optional[bool] = None ) -> dict: """The format of `input_item` is `"[NODE:]NAME=VALUE"`.""" @@ -110,13 +111,12 @@ def parse_parameter( var_value = parse_value(value) if node is None: + if all_nodes is None: + raise ValueError(f"{input_item} needs a target NODE:{input_item}") return {"all": all_nodes, "name": var_name, "value": var_value} - if node_attr is None: - raise ValueError( - f"Node specified in parameter '{input_item}' " - "but no node attribute type was provided." - ) + if node_attr is None and node is not None: + raise ValueError(f"{input_item} does not need the target '{node}'") return {_NODE_ATTR_MAP[node_attr]: node, "name": var_name, "value": var_value} diff --git a/src/ewoksutils/tests/test_cli.py b/src/ewoksutils/tests/test_cli.py index bd8ddab..f1a80a7 100644 --- a/src/ewoksutils/tests/test_cli.py +++ b/src/ewoksutils/tests/test_cli.py @@ -74,6 +74,35 @@ def test_cli_execute_parameters(cli_interface): assert cli_args.execute_options == execute_options +@pytest.mark.parametrize("argument", ["pn", "pt", "pl"]) +def test_cli_execute_missing_parameter_target(cli_interface, argument): + argv = ["acyclic1", "--test", f"-{argument}", "a=1"] # This is missing a target + + with pytest.raises(ValueError, match="a=1 needs a target NODE:a=1"): + _ = cli_interface( + argv, + cli_execute_utils.execute_arguments, + cli_execute_utils.parse_execute_argument, + ) + + +@pytest.mark.parametrize("argument", ["ps", "pa"]) +def test_cli_execute_unexpected_parameter_target(cli_interface, argument): + argv = [ + "acyclic1", + "--test", + f"-{argument}", + "node1:a=1", # This has un unexpected target + ] + + with pytest.raises(ValueError, match="node1:a=1 does not need the target 'node1'"): + _ = cli_interface( + argv, + cli_execute_utils.execute_arguments, + cli_execute_utils.parse_execute_argument, + ) + + def test_cli_execute_deprecated_parameters(cli_interface): argv = ["acyclic1", "--test", "-p", "a=1", "-p", "task1:b=test"] @@ -177,7 +206,7 @@ def test_cli_submit(cli_interface): "acyclic1", "acyclic2", "--test", - "-pn", + "-ps", "a=1", "-pn", "node1:b=test", @@ -195,8 +224,8 @@ def test_cli_submit(cli_interface): execute_options = { "inputs": [ - {"all": False, "name": "a", "value": 1}, {"id": "node1", "name": "b", "value": "test"}, + {"all": False, "name": "a", "value": 1}, ], "merge_outputs": False, "outputs": [], From 9ace192a614a6284540cf954a2fb9012d0d58577 Mon Sep 17 00:00:00 2001 From: woutdenolf Date: Fri, 29 May 2026 11:01:50 +0200 Subject: [PATCH 3/4] clarify deprecation logic --- src/ewoksutils/cli_utils/cli_parse.py | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/src/ewoksutils/cli_utils/cli_parse.py b/src/ewoksutils/cli_utils/cli_parse.py index 603e2e3..39ef2d3 100644 --- a/src/ewoksutils/cli_utils/cli_parse.py +++ b/src/ewoksutils/cli_utils/cli_parse.py @@ -19,7 +19,13 @@ def parse_ewoks_inputs_parameters(cli_args: Namespace) -> List[dict]: # Deprecated options if cli_args.parameters: - replacement = {"id": "-pn", "label": "-pl", "taskid": "-pt"}[cli_args.node_attr] + node_attr_replacement = {"id": "-pn", "label": "-pl", "taskid": "-pt"} + node_attr_default_value = "id" + inputs_replacement = {"start": "-ps", "all": "-pa"} + inputs_default_value = "start" + + # Deprecate -p + replacement = node_attr_replacement[cli_args.node_attr] if cli_args.inputs == "all": default_target = "-pa" @@ -32,16 +38,18 @@ def parse_ewoks_inputs_parameters(cli_args: Namespace) -> List[dict]: f"and {default_target} for parameters without a node selector." ) - if cli_args.node_attr != "id": - replacement = {"label": "-pl", "taskid": "-pt"}[cli_args.node_attr] + # Deprecate --input-node-id when used + if cli_args.node_attr != node_attr_default_value: + replacement = node_attr_replacement[cli_args.node_attr] _deprecated( f"--input-node-id={cli_args.node_attr} is deprecated; " f"use {replacement} instead." ) - if cli_args.inputs != "start": - replacement = {"start": "-ps", "all": "-pa"}[cli_args.inputs] + # Deprecate --inputs when used + if cli_args.inputs != inputs_default_value: + replacement = inputs_replacement[cli_args.inputs] _deprecated( f"--inputs={cli_args.inputs} is deprecated; " From 71b3a80ae6c71a7dbf6df1607430bac521098a54 Mon Sep 17 00:00:00 2001 From: woutdenolf Date: Fri, 29 May 2026 11:18:49 +0200 Subject: [PATCH 4/4] split parse_parameter in parse_targeted_parameter and parse_untargeted_parameter --- src/ewoksutils/cli_utils/cli_parse.py | 77 +++++++++++++++------------ src/ewoksutils/tests/test_cli.py | 2 +- 2 files changed, 44 insertions(+), 35 deletions(-) diff --git a/src/ewoksutils/cli_utils/cli_parse.py b/src/ewoksutils/cli_utils/cli_parse.py index 39ef2d3..76a378c 100644 --- a/src/ewoksutils/cli_utils/cli_parse.py +++ b/src/ewoksutils/cli_utils/cli_parse.py @@ -7,7 +7,6 @@ from json.decoder import JSONDecodeError from typing import Any from typing import List -from typing import Optional from typing import Tuple logger = logging.getLogger(__name__) @@ -56,37 +55,36 @@ def parse_ewoks_inputs_parameters(cli_args: Namespace) -> List[dict]: f"use {replacement} instead." ) - parameters.extend( - parse_parameter( - input_item, - node_attr=cli_args.node_attr, - all_nodes=(cli_args.inputs == "all"), - ) - for input_item in cli_args.parameters - ) + all_nodes = cli_args.inputs == "all" + for input_item in cli_args.parameters: + if parameter_has_target(input_item): + param = parse_targeted_parameter(input_item, cli_args.node_attr) + else: + param = parse_untargeted_parameter(input_item, all_nodes=all_nodes) + parameters.append(param) parameters.extend( - parse_parameter(input_item, node_attr="id") + parse_targeted_parameter(input_item, "id") for input_item in cli_args.parameters_nodeid ) parameters.extend( - parse_parameter(input_item, node_attr="taskid") + parse_targeted_parameter(input_item, "taskid") for input_item in cli_args.parameters_taskid ) parameters.extend( - parse_parameter(input_item, node_attr="label") + parse_targeted_parameter(input_item, "label") for input_item in cli_args.parameters_label ) parameters.extend( - parse_parameter(input_item, all_nodes=False) + parse_untargeted_parameter(input_item, all_nodes=False) for input_item in cli_args.parameters_start ) parameters.extend( - parse_parameter(input_item, all_nodes=True) + parse_untargeted_parameter(input_item, all_nodes=True) for input_item in cli_args.parameters_all ) @@ -101,32 +99,43 @@ def _deprecated(message: str) -> None: _NODE_ATTR_MAP = {"id": "id", "label": "label", "taskid": "task_identifier"} -def parse_parameter( - input_item: str, node_attr: Optional[str] = None, all_nodes: Optional[bool] = None -) -> dict: - """The format of `input_item` is `"[NODE:]NAME=VALUE"`.""" +def parse_targeted_parameter(input_item: str, node_attr: str) -> dict: + """Parse `NODE:NAME=VALUE`.""" - node_and_name, _, value = input_item.partition("=") - a, sep, b = node_and_name.partition(":") + node_and_var_name, _, var_value = input_item.partition("=") + node, sep, var_name = node_and_var_name.partition(":") - if sep: - node = a - var_name = b - else: - node = None - var_name = a + if not sep: + raise ValueError(f"{input_item} needs a target NODE:{input_item}") + + return { + _NODE_ATTR_MAP[node_attr]: node, + "name": var_name, + "value": parse_value(var_value), + } - var_value = parse_value(value) - if node is None: - if all_nodes is None: - raise ValueError(f"{input_item} needs a target NODE:{input_item}") - return {"all": all_nodes, "name": var_name, "value": var_value} +def parse_untargeted_parameter(input_item: str, all_nodes: bool) -> dict: + """Parse `NAME=VALUE`.""" + + var_name, _, var_value = input_item.partition("=") + _, sep, _ = var_name.partition(":") + + if sep: + raise ValueError(f"{input_item} does not accept a target node") + + return { + "all": all_nodes, + "name": var_name, + "value": parse_value(var_value), + } - if node_attr is None and node is not None: - raise ValueError(f"{input_item} does not need the target '{node}'") - return {_NODE_ATTR_MAP[node_attr]: node, "name": var_name, "value": var_value} +def parameter_has_target(input_item: str) -> bool: + """Check whether `"[NODE]:name=value"` has a target NODE.""" + node_and_var_name, _, _ = input_item.partition("=") + _, sep, _ = node_and_var_name.partition(":") + return bool(sep) def parse_option(option: str) -> Tuple[str, Any]: diff --git a/src/ewoksutils/tests/test_cli.py b/src/ewoksutils/tests/test_cli.py index f1a80a7..bcfb26a 100644 --- a/src/ewoksutils/tests/test_cli.py +++ b/src/ewoksutils/tests/test_cli.py @@ -95,7 +95,7 @@ def test_cli_execute_unexpected_parameter_target(cli_interface, argument): "node1:a=1", # This has un unexpected target ] - with pytest.raises(ValueError, match="node1:a=1 does not need the target 'node1'"): + with pytest.raises(ValueError, match="node1:a=1 does not accept a target"): _ = cli_interface( argv, cli_execute_utils.execute_arguments,