Skip to content

Commit 459958b

Browse files
committed
Various cleanups to make the docs better.
1 parent a67c898 commit 459958b

13 files changed

+125
-100
lines changed

cwltool/checker.py

+43-31
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
"""Static checking of CWL workflow connectivity."""
22

3-
from collections import namedtuple
43
from collections.abc import Iterator, MutableMapping, MutableSequence, Sized
5-
from typing import Any, Literal, Optional, Union, cast
4+
from typing import Any, Literal, NamedTuple, Optional, Union, cast
65

76
from schema_salad.exceptions import ValidationException
87
from schema_salad.sourceline import SourceLine, bullets, strip_dup_lineno
@@ -184,8 +183,8 @@ def static_checker(
184183
for param in workflow_inputs + step_outputs:
185184
src_dict[cast(str, param["id"])] = param
186185

187-
step_inputs_val = check_all_types(src_dict, step_inputs, "source", param_to_step)
188-
workflow_outputs_val = check_all_types(
186+
step_inputs_val = _check_all_types(src_dict, step_inputs, "source", param_to_step)
187+
workflow_outputs_val = _check_all_types(
189188
src_dict, workflow_outputs, "outputSource", param_to_step
190189
)
191190

@@ -199,27 +198,34 @@ def static_checker(
199198
sink = warning.sink
200199
linkMerge = warning.linkMerge
201200
sinksf = sorted(
202-
p["pattern"] for p in sink.get("secondaryFiles", []) if p.get("required", True)
201+
cast(str, p["pattern"])
202+
for p in cast(MutableSequence[CWLObjectType], sink.get("secondaryFiles", []))
203+
if p.get("required", True)
204+
)
205+
srcsf = sorted(
206+
cast(str, p["pattern"])
207+
for p in cast(MutableSequence[CWLObjectType], src.get("secondaryFiles", []))
203208
)
204-
srcsf = sorted(p["pattern"] for p in src.get("secondaryFiles", []))
205209
# Every secondaryFile required by the sink, should be declared
206210
# by the source
207211
missing = missing_subset(srcsf, sinksf)
212+
src_name = shortname(cast(str, src["id"]))
213+
sink_id = cast(str, sink["id"])
214+
sink_name = shortname(sink_id)
208215
if missing:
209216
msg1 = "Parameter '{}' requires secondaryFiles {} but".format(
210-
shortname(sink["id"]),
217+
sink_name,
211218
missing,
212219
)
213220
msg3 = SourceLine(src, "id").makeError(
214-
"source '%s' does not provide those secondaryFiles." % (shortname(src["id"]))
221+
"source '%s' does not provide those secondaryFiles." % (src_name)
215222
)
216223
msg4 = SourceLine(src.get("_tool_entry", src), "secondaryFiles").makeError(
217224
"To resolve, add missing secondaryFiles patterns to definition of '%s' or"
218-
% (shortname(src["id"]))
225+
% (src_name)
219226
)
220227
msg5 = SourceLine(sink.get("_tool_entry", sink), "secondaryFiles").makeError(
221-
"mark missing secondaryFiles in definition of '%s' as optional."
222-
% shortname(sink["id"])
228+
"mark missing secondaryFiles in definition of '%s' as optional." % (sink_name)
223229
)
224230
msg = SourceLine(sink).makeError(
225231
"{}\n{}".format(msg1, bullets([msg3, msg4, msg5], " "))
@@ -229,13 +235,13 @@ def static_checker(
229235
msg = SourceLine(sink, "type").makeError(
230236
"'%s' is not an input parameter of %s, expected %s"
231237
% (
232-
shortname(sink["id"]),
233-
param_to_step[sink["id"]]["run"],
238+
sink_name,
239+
param_to_step[sink_id]["run"],
234240
", ".join(
235241
shortname(cast(str, s["id"]))
236242
for s in cast(
237243
list[dict[str, Union[str, bool]]],
238-
param_to_step[sink["id"]]["inputs"],
244+
param_to_step[sink_id]["inputs"],
239245
)
240246
if not s.get("not_connected")
241247
),
@@ -247,12 +253,11 @@ def static_checker(
247253
msg = (
248254
SourceLine(src, "type").makeError(
249255
"Source '%s' of type %s may be incompatible"
250-
% (shortname(src["id"]), json_dumps(src["type"]))
256+
% (src_name, json_dumps(src["type"]))
251257
)
252258
+ "\n"
253259
+ SourceLine(sink, "type").makeError(
254-
" with sink '%s' of type %s"
255-
% (shortname(sink["id"]), json_dumps(sink["type"]))
260+
" with sink '%s' of type %s" % (sink_name, json_dumps(sink["type"]))
256261
)
257262
)
258263
if linkMerge is not None:
@@ -274,12 +279,12 @@ def static_checker(
274279
msg = (
275280
SourceLine(src, "type").makeError(
276281
"Source '%s' of type %s is incompatible"
277-
% (shortname(src["id"]), json_dumps(src["type"]))
282+
% (shortname(cast(str, src["id"])), json_dumps(src["type"]))
278283
)
279284
+ "\n"
280285
+ SourceLine(sink, "type").makeError(
281286
" with sink '{}' of type {}".format(
282-
shortname(sink["id"]), json_dumps(sink["type"])
287+
shortname(cast(str, sink["id"])), json_dumps(sink["type"])
283288
)
284289
)
285290
)
@@ -291,16 +296,17 @@ def static_checker(
291296
exception_msgs.append(msg)
292297

293298
for sink in step_inputs:
299+
sink_type = cast(Union[str, list[str], list[CWLObjectType], CWLObjectType], sink["type"])
294300
if (
295-
"null" != sink["type"]
296-
and "null" not in sink["type"]
301+
"null" != sink_type
302+
and "null" not in sink_type
297303
and "source" not in sink
298304
and "default" not in sink
299305
and "valueFrom" not in sink
300306
):
301307
msg = SourceLine(sink).makeError(
302308
"Required parameter '%s' does not have source, default, or valueFrom expression"
303-
% shortname(sink["id"])
309+
% shortname(cast(str, sink["id"]))
304310
)
305311
exception_msgs.append(msg)
306312

@@ -313,23 +319,29 @@ def static_checker(
313319
raise ValidationException(all_exception_msg)
314320

315321

316-
SrcSink = namedtuple("SrcSink", ["src", "sink", "linkMerge", "message"])
322+
class _SrcSink(NamedTuple):
323+
"""An error or warning message about a connection between two points of the workflow graph."""
324+
325+
src: CWLObjectType
326+
sink: CWLObjectType
327+
linkMerge: Optional[str]
328+
message: Optional[str]
317329

318330

319-
def check_all_types(
331+
def _check_all_types(
320332
src_dict: dict[str, CWLObjectType],
321333
sinks: MutableSequence[CWLObjectType],
322334
sourceField: Union[Literal["source"], Literal["outputSource"]],
323335
param_to_step: dict[str, CWLObjectType],
324-
) -> dict[str, list[SrcSink]]:
336+
) -> dict[str, list[_SrcSink]]:
325337
"""
326338
Given a list of sinks, check if their types match with the types of their sources.
327339
328340
:raises WorkflowException: if there is an unrecognized linkMerge value
329341
(from :py:func:`check_types`)
330342
:raises ValidationException: if a sourceField is missing
331343
"""
332-
validation: dict[str, list[SrcSink]] = {"warning": [], "exception": []}
344+
validation: dict[str, list[_SrcSink]] = {"warning": [], "exception": []}
333345
for sink in sinks:
334346
if sourceField in sink:
335347
valueFrom = cast(Optional[str], sink.get("valueFrom"))
@@ -356,7 +368,7 @@ def check_all_types(
356368
srcs_of_sink += [src_dict[parm_id]]
357369
if is_conditional_step(param_to_step, parm_id) and pickValue is None:
358370
validation["warning"].append(
359-
SrcSink(
371+
_SrcSink(
360372
src_dict[parm_id],
361373
sink,
362374
linkMerge,
@@ -380,7 +392,7 @@ def check_all_types(
380392

381393
if pickValue is not None:
382394
validation["warning"].append(
383-
SrcSink(
395+
_SrcSink(
384396
src_dict[parm_id],
385397
sink,
386398
linkMerge,
@@ -399,7 +411,7 @@ def check_all_types(
399411
Union[list[str], CWLObjectType], snk_typ
400412
): # Given our type names this works even if not a list
401413
validation["warning"].append(
402-
SrcSink(
414+
_SrcSink(
403415
src_dict[parm_id],
404416
sink,
405417
linkMerge,
@@ -419,11 +431,11 @@ def check_all_types(
419431
check_result = check_types(src, sink, linkMerge, valueFrom)
420432
if check_result == "warning":
421433
validation["warning"].append(
422-
SrcSink(src, sink, linkMerge, message=extra_message)
434+
_SrcSink(src, sink, linkMerge, message=extra_message)
423435
)
424436
elif check_result == "exception":
425437
validation["exception"].append(
426-
SrcSink(src, sink, linkMerge, message=extra_message)
438+
_SrcSink(src, sink, linkMerge, message=extra_message)
427439
)
428440

429441
return validation

cwltool/command_line_tool.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -276,7 +276,7 @@ def revmap_file(builder: Builder, outdir: str, f: CWLObjectType) -> Optional[CWL
276276
)
277277
revmap_f = builder.pathmapper.reversemap(path)
278278

279-
if revmap_f and not builder.pathmapper.mapper(revmap_f[0]).type.startswith("Writable"):
279+
if revmap_f and not builder.pathmapper.mapper(revmap_f[0]).type.startswith("Writable"): # type: ignore[union-attr]
280280
f["location"] = revmap_f[1]
281281
elif (
282282
uripath == outdir

cwltool/errors.py

+7
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,13 @@
1111
from cwl_utils.errors import GraphTargetMissingException as GraphTargetMissingException
1212
from cwl_utils.errors import WorkflowException as WorkflowException
1313

14+
__all__ = (
15+
"GraphTargetMissingException",
16+
"WorkflowException",
17+
"UnsupportedRequirement",
18+
"ArgumentException",
19+
)
20+
1421

1522
class UnsupportedRequirement(WorkflowException):
1623
pass

cwltool/main.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
import warnings
1717
from codecs import getwriter
1818
from collections.abc import Mapping, MutableMapping, MutableSequence, Sized
19+
from importlib.resources import files
1920
from typing import IO, Any, Callable, Optional, Union, cast
2021

2122
import argcomplete
@@ -96,7 +97,6 @@
9697
CWLOutputType,
9798
HasReqsHints,
9899
adjustDirObjs,
99-
files,
100100
normalizeFilesDirs,
101101
processes_to_kill,
102102
trim_listing,

cwltool/mutation.py

+15-9
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,16 @@
1-
from collections import namedtuple
2-
from typing import cast
1+
"""Support for InplaceUpdateRequirement."""
2+
3+
from typing import NamedTuple, cast
34

45
from .errors import WorkflowException
56
from .utils import CWLObjectType
67

7-
MutationState = namedtuple("MutationState", ["generation", "readers", "stepname"])
8+
9+
class _MutationState(NamedTuple):
10+
generation: int
11+
readers: list[str]
12+
stepname: str
13+
814

915
_generation = "http://commonwl.org/cwltool#generation"
1016

@@ -20,11 +26,11 @@ class MutationManager:
2026

2127
def __init__(self) -> None:
2228
"""Initialize."""
23-
self.generations: dict[str, MutationState] = {}
29+
self.generations: dict[str, _MutationState] = {}
2430

2531
def register_reader(self, stepname: str, obj: CWLObjectType) -> None:
2632
loc = cast(str, obj["location"])
27-
current = self.generations.get(loc, MutationState(0, [], ""))
33+
current = self.generations.get(loc, _MutationState(0, [], ""))
2834
obj_generation = obj.get(_generation, 0)
2935

3036
if obj_generation != current.generation:
@@ -40,7 +46,7 @@ def register_reader(self, stepname: str, obj: CWLObjectType) -> None:
4046

4147
def release_reader(self, stepname: str, obj: CWLObjectType) -> None:
4248
loc = cast(str, obj["location"])
43-
current = self.generations.get(loc, MutationState(0, [], ""))
49+
current = self.generations.get(loc, _MutationState(0, [], ""))
4450
obj_generation = obj.get(_generation, 0)
4551

4652
if obj_generation != current.generation:
@@ -55,7 +61,7 @@ def release_reader(self, stepname: str, obj: CWLObjectType) -> None:
5561

5662
def register_mutation(self, stepname: str, obj: CWLObjectType) -> None:
5763
loc = cast(str, obj["location"])
58-
current = self.generations.get(loc, MutationState(0, [], ""))
64+
current = self.generations.get(loc, _MutationState(0, [], ""))
5965
obj_generation = obj.get(_generation, 0)
6066

6167
if len(current.readers) > 0:
@@ -73,11 +79,11 @@ def register_mutation(self, stepname: str, obj: CWLObjectType) -> None:
7379
)
7480
)
7581

76-
self.generations[loc] = MutationState(current.generation + 1, current.readers, stepname)
82+
self.generations[loc] = _MutationState(current.generation + 1, current.readers, stepname)
7783

7884
def set_generation(self, obj: CWLObjectType) -> None:
7985
loc = cast(str, obj["location"])
80-
current = self.generations.get(loc, MutationState(0, [], ""))
86+
current = self.generations.get(loc, _MutationState(0, [], ""))
8187
obj[_generation] = current.generation
8288

8389
def unset_generation(self, obj: CWLObjectType) -> None:

cwltool/pathmapper.py

+22-28
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,10 @@
1-
import collections
21
import logging
32
import os
43
import stat
54
import urllib
65
import uuid
76
from collections.abc import ItemsView, Iterable, Iterator, KeysView
8-
from typing import Optional, cast
7+
from typing import NamedTuple, Optional, cast
98

109
from mypy_extensions import mypyc_attr
1110
from schema_salad.exceptions import ValidationException
@@ -16,31 +15,24 @@
1615
from .stdfsaccess import abspath
1716
from .utils import CWLObjectType, dedup, downloadHttpFile
1817

19-
MapperEnt = collections.namedtuple("MapperEnt", ["resolved", "target", "type", "staged"])
20-
""" Mapper entries.
2118

22-
.. py:attribute:: resolved
23-
:type: str
19+
class MapperEnt(NamedTuple):
20+
"""Mapper entries."""
2421

25-
The "real" path on the local file system (after resolving relative paths
26-
and traversing symlinks
27-
28-
.. py:attribute:: target
29-
:type: str
30-
31-
The path on the target file system (under stagedir)
32-
33-
.. py:attribute:: type
34-
:type: str
35-
36-
The object type. One of "File", "Directory", "CreateFile", "WritableFile",
37-
or "CreateWritableFile".
38-
39-
.. py:attribute:: staged
40-
:type: bool
41-
42-
If the File has been staged yet
43-
"""
22+
resolved: str
23+
"""
24+
The "real" path on the local file system (after resolving relative paths
25+
and traversing symlinks
26+
"""
27+
target: str
28+
"""The path on the target file system (under stagedir)"""
29+
type: Optional[str]
30+
"""
31+
The object type. One of "File", "Directory", "CreateFile", "WritableFile",
32+
or "CreateWritableFile".
33+
"""
34+
staged: Optional[bool]
35+
"""If the File has been staged yet."""
4436

4537

4638
@mypyc_attr(allow_interpreted_subclasses=True)
@@ -149,7 +141,7 @@ def visit(
149141
ab = abspath(path, basedir)
150142
if "contents" in obj and path.startswith("_:"):
151143
self._pathmap[path] = MapperEnt(
152-
obj["contents"],
144+
cast(str, obj["contents"]),
153145
tgt,
154146
"CreateWritableFile" if copy else "CreateFile",
155147
staged,
@@ -247,8 +239,10 @@ def reversemap(
247239
return (k, v[0])
248240
return None
249241

250-
def update(self, key: str, resolved: str, target: str, ctype: str, stage: bool) -> MapperEnt:
251-
"""Update an existine entry."""
242+
def update(
243+
self, key: str, resolved: str, target: str, ctype: Optional[str], stage: Optional[bool]
244+
) -> MapperEnt:
245+
"""Update an existing entry."""
252246
m = MapperEnt(resolved, target, ctype, stage)
253247
self._pathmap[key] = m
254248
return m

0 commit comments

Comments
 (0)