Skip to content

Commit 69e06f3

Browse files
committed
Prevent the default value from being used when values were already passed
1 parent 2be19f3 commit 69e06f3

File tree

5 files changed

+93
-5
lines changed

5 files changed

+93
-5
lines changed

cwltool/argparser.py

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -817,6 +817,42 @@ class DirectoryAppendAction(FSAppendAction):
817817
objclass = "Directory"
818818

819819

820+
class AppendAction(argparse.Action):
821+
"""An argparse action that clears the default values if any value is provided.
822+
823+
Attributes:
824+
_called (bool): Initially set to ``False``, changed if any value is appended.
825+
"""
826+
827+
def __init__(
828+
self,
829+
option_strings: List[str],
830+
dest: str,
831+
nargs: Any = None,
832+
**kwargs: Any,
833+
) -> None:
834+
"""Intialize."""
835+
super().__init__(option_strings, dest, **kwargs)
836+
self._called = False
837+
838+
def __call__(
839+
self,
840+
parser: argparse.ArgumentParser,
841+
namespace: argparse.Namespace,
842+
values: Union[str, Sequence[Any], None],
843+
option_string: Optional[str] = None,
844+
) -> None:
845+
g = getattr(namespace, self.dest, None)
846+
if g is None:
847+
g = []
848+
if values is not None and not self._called:
849+
# If any value was specified, we then clear the list of options before appending.
850+
# We cannot always clear the ``default`` attribute since it collects the ``values`` appended.
851+
self.default.clear()
852+
self._called = True
853+
g.append(values)
854+
855+
820856
def add_argument(
821857
toolparser: argparse.ArgumentParser,
822858
name: str,
@@ -864,7 +900,7 @@ def add_argument(
864900
elif inptype["items"] == "Directory":
865901
action = DirectoryAppendAction
866902
else:
867-
action = "append"
903+
action = AppendAction
868904
elif isinstance(inptype, MutableMapping) and inptype["type"] == "enum":
869905
atype = str
870906
elif isinstance(inptype, MutableMapping) and inptype["type"] == "record":

cwltool/executors.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -278,7 +278,7 @@ def __init__(self) -> None:
278278
self.pending_jobs = [] # type: List[JobsType]
279279
self.pending_jobs_lock = threading.Lock()
280280

281-
self.max_ram = int(psutil.virtual_memory().available / 2**20) # type: ignore[no-untyped-call]
281+
self.max_ram = int(psutil.virtual_memory().available / 2 ** 20) # type: ignore[no-untyped-call]
282282
self.max_cores = float(psutil.cpu_count())
283283
self.allocated_ram = float(0)
284284
self.allocated_cores = float(0)

cwltool/job.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -555,7 +555,7 @@ def get_tree_mem_usage(memory_usage: MutableSequence[Optional[int]]) -> None:
555555
_logger.info(
556556
"[job %s] Max memory used: %iMiB",
557557
self.name,
558-
round(memory_usage[0] / (2**20)),
558+
round(memory_usage[0] / (2 ** 20)),
559559
)
560560
else:
561561
_logger.debug(
@@ -943,7 +943,7 @@ def docker_monitor(
943943
_logger.info(
944944
"[job %s] Max memory used: %iMiB",
945945
self.name,
946-
int((max_mem_percent / 100 * max_mem) / (2**20)),
946+
int((max_mem_percent / 100 * max_mem) / (2 ** 20)),
947947
)
948948
if cleanup_cidfile:
949949
os.remove(cidfile)

tests/default_values_list.cwl

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
#!/usr/bin/env cwl-runner
2+
# From https://github.com/common-workflow-language/cwltool/issues/1632
3+
4+
cwlVersion: v1.2
5+
class: CommandLineTool
6+
7+
baseCommand: [cat]
8+
9+
stdout: "cat_file"
10+
11+
inputs:
12+
file_paths:
13+
type: string[]?
14+
inputBinding:
15+
position: 1
16+
default: ["/home/bart/cwl_test/test1"]
17+
18+
outputs:
19+
output:
20+
type: stdout

tests/test_toolargparse.py

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import argparse
22
from io import StringIO
33
from pathlib import Path
4-
from typing import Callable
4+
from typing import Any, Callable, Dict, List
55

66
import pytest
77

@@ -195,3 +195,35 @@ def test_argparser_without_doc() -> None:
195195
p = argparse.ArgumentParser()
196196
parser = generate_parser(p, tool, {}, [], False)
197197
assert parser.description is None
198+
199+
200+
@pytest.mark.parametrize(
201+
"job_order,expected_values",
202+
[
203+
# no arguments, so we expect the default value
204+
([], ["/home/bart/cwl_test/test1"]),
205+
# arguments, provided, one or many, meaning that the default value is not expected
206+
(["--file_paths", "/home/bart/cwl_test/test2"], ["/home/bart/cwl_test/test2"]),
207+
(
208+
[
209+
"--file_paths",
210+
"/home/bart/cwl_test/test2",
211+
"--file_paths",
212+
"/home/bart/cwl_test/test3",
213+
],
214+
["/home/bart/cwl_test/test2", "/home/bart/cwl_test/test3"],
215+
),
216+
],
217+
)
218+
def test_argparse_append_with_default(
219+
job_order: List[str], expected_values: List[str]
220+
) -> None:
221+
"""The appended arguments must not include the default. But if no appended argument, then the default is used."""
222+
loadingContext = LoadingContext()
223+
tool = load_tool(get_data("tests/default_values_list.cwl"), loadingContext)
224+
toolparser = generate_parser(
225+
argparse.ArgumentParser(prog="test"), tool, {}, [], False
226+
)
227+
cmd_line = vars(toolparser.parse_args(job_order)) # type: ignore[call-overload]
228+
file_paths = list(cmd_line["file_paths"])
229+
assert expected_values == file_paths

0 commit comments

Comments
 (0)