Skip to content

Commit 4c74d06

Browse files
authored
V1.1.0-dev1 support (#694)
Support for v1.1.0-dev1 end of March milestone.
1 parent d5a1ddd commit 4c74d06

File tree

397 files changed

+15075
-8051
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

397 files changed

+15075
-8051
lines changed

cwltool/builder.py

Lines changed: 70 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@
22
import copy
33
import os
44
import logging
5-
from typing import Any, Callable, Dict, List, Text, Type, Union
5+
import json
6+
from typing import Any, Callable, Dict, List, Text, Type, Union, Set
67

78
import six
89
from six import iteritems, string_types
@@ -11,6 +12,9 @@
1112
import schema_salad.validate as validate
1213
from schema_salad.sourceline import SourceLine
1314

15+
from rdflib import Graph, URIRef
16+
from rdflib.namespace import OWL, RDFS
17+
1418
from . import expression
1519
from .errors import WorkflowException
1620
from .mutation import MutationManager
@@ -32,6 +36,52 @@ def substitute(value, replace): # type: (Text, Text) -> Text
3236
else:
3337
return value + replace
3438

39+
def formatSubclassOf(fmt, cls, ontology, visited):
40+
# type: (Text, Text, Graph, Set[Text]) -> bool
41+
"""Determine if `fmt` is a subclass of `cls`."""
42+
43+
if URIRef(fmt) == URIRef(cls):
44+
return True
45+
46+
if ontology is None:
47+
return False
48+
49+
if fmt in visited:
50+
return False
51+
52+
visited.add(fmt)
53+
54+
uriRefFmt = URIRef(fmt)
55+
56+
for s, p, o in ontology.triples((uriRefFmt, RDFS.subClassOf, None)):
57+
# Find parent classes of `fmt` and search upward
58+
if formatSubclassOf(o, cls, ontology, visited):
59+
return True
60+
61+
for s, p, o in ontology.triples((uriRefFmt, OWL.equivalentClass, None)):
62+
# Find equivalent classes of `fmt` and search horizontally
63+
if formatSubclassOf(o, cls, ontology, visited):
64+
return True
65+
66+
for s, p, o in ontology.triples((None, OWL.equivalentClass, uriRefFmt)):
67+
# Find equivalent classes of `fmt` and search horizontally
68+
if formatSubclassOf(s, cls, ontology, visited):
69+
return True
70+
71+
return False
72+
73+
def checkFormat(actualFile, inputFormats, ontology):
74+
# type: (Union[Dict[Text, Any], List, Text], Union[List[Text], Text], Graph) -> None
75+
for af in aslist(actualFile):
76+
if not af:
77+
continue
78+
if "format" not in af:
79+
raise validate.ValidationException(u"Missing required 'format' for File %s" % af)
80+
for inpf in aslist(inputFormats):
81+
if af["format"] == inpf or formatSubclassOf(af["format"], inpf, ontology, set()):
82+
return
83+
raise validate.ValidationException(
84+
u"Incompatible file format, expected format(s) %s but file object is: %s" % (inputFormats, json.dumps(af, indent=4)))
3585

3686
class Builder(object):
3787
def __init__(self): # type: () -> None
@@ -54,6 +104,7 @@ def __init__(self): # type: () -> None
54104
self.js_console = False # type: bool
55105
self.mutation_manager = None # type: MutationManager
56106
self.force_docker_pull = False # type: bool
107+
self.formatgraph = None # type: Graph
57108

58109
# One of "no_listing", "shallow_listing", "deep_listing"
59110
# Will be default "no_listing" for CWL v1.1
@@ -70,8 +121,8 @@ def build_job_script(self, commands):
70121
else:
71122
return None
72123

73-
def bind_input(self, schema, datum, lead_pos=None, tail_pos=None):
74-
# type: (Dict[Text, Any], Any, Union[int, List[int]], List[int]) -> List[Dict[Text, Any]]
124+
def bind_input(self, schema, datum, lead_pos=None, tail_pos=None, discover_secondaryFiles=False):
125+
# type: (Dict[Text, Any], Any, Union[int, List[int]], List[int], bool) -> List[Dict[Text, Any]]
75126
if tail_pos is None:
76127
tail_pos = []
77128
if lead_pos is None:
@@ -105,9 +156,9 @@ def bind_input(self, schema, datum, lead_pos=None, tail_pos=None):
105156
schema = copy.deepcopy(schema)
106157
schema["type"] = t
107158
if not value_from_expression:
108-
return self.bind_input(schema, datum, lead_pos=lead_pos, tail_pos=tail_pos)
159+
return self.bind_input(schema, datum, lead_pos=lead_pos, tail_pos=tail_pos, discover_secondaryFiles=discover_secondaryFiles)
109160
else:
110-
self.bind_input(schema, datum, lead_pos=lead_pos, tail_pos=tail_pos)
161+
self.bind_input(schema, datum, lead_pos=lead_pos, tail_pos=tail_pos, discover_secondaryFiles=discover_secondaryFiles)
111162
bound_input = True
112163
if not bound_input:
113164
raise validate.ValidationException(u"'%s' is not a valid union %s" % (datum, schema["type"]))
@@ -119,17 +170,17 @@ def bind_input(self, schema, datum, lead_pos=None, tail_pos=None):
119170
if k in schema:
120171
st[k] = schema[k]
121172
if value_from_expression:
122-
self.bind_input(st, datum, lead_pos=lead_pos, tail_pos=tail_pos)
173+
self.bind_input(st, datum, lead_pos=lead_pos, tail_pos=tail_pos, discover_secondaryFiles=discover_secondaryFiles)
123174
else:
124-
bindings.extend(self.bind_input(st, datum, lead_pos=lead_pos, tail_pos=tail_pos))
175+
bindings.extend(self.bind_input(st, datum, lead_pos=lead_pos, tail_pos=tail_pos, discover_secondaryFiles=discover_secondaryFiles))
125176
else:
126177
if schema["type"] in self.schemaDefs:
127178
schema = self.schemaDefs[schema["type"]]
128179

129180
if schema["type"] == "record":
130181
for f in schema["fields"]:
131182
if f["name"] in datum:
132-
bindings.extend(self.bind_input(f, datum[f["name"]], lead_pos=lead_pos, tail_pos=f["name"]))
183+
bindings.extend(self.bind_input(f, datum[f["name"]], lead_pos=lead_pos, tail_pos=f["name"], discover_secondaryFiles=discover_secondaryFiles))
133184
else:
134185
datum[f["name"]] = f.get("default")
135186

@@ -147,15 +198,14 @@ def bind_input(self, schema, datum, lead_pos=None, tail_pos=None):
147198
if k in schema:
148199
itemschema[k] = schema[k]
149200
bindings.extend(
150-
self.bind_input(itemschema, item, lead_pos=n, tail_pos=tail_pos))
201+
self.bind_input(itemschema, item, lead_pos=n, tail_pos=tail_pos, discover_secondaryFiles=discover_secondaryFiles))
151202
binding = None
152203

153204
if schema["type"] == "File":
154205
self.files.append(datum)
155-
if binding:
156-
if binding.get("loadContents"):
157-
with self.fs_access.open(datum["location"], "rb") as f:
158-
datum["contents"] = f.read(CONTENT_LIMIT)
206+
if (binding and binding.get("loadContents")) or schema.get("loadContents"):
207+
with self.fs_access.open(datum["location"], "rb") as f:
208+
datum["contents"] = f.read(CONTENT_LIMIT)
159209

160210
if "secondaryFiles" in schema:
161211
if "secondaryFiles" not in datum:
@@ -175,14 +225,20 @@ def bind_input(self, schema, datum, lead_pos=None, tail_pos=None):
175225
if not found:
176226
if isinstance(sfname, dict):
177227
datum["secondaryFiles"].append(sfname)
178-
else:
228+
elif discover_secondaryFiles:
179229
datum["secondaryFiles"].append({
180230
"location": datum["location"][0:datum["location"].rindex("/")+1]+sfname,
181231
"basename": sfname,
182232
"class": "File"})
233+
else:
234+
raise WorkflowException("Missing required secondary file '%s' from file object: %s" % (
235+
sfname, json.dumps(datum, indent=4)))
183236

184237
normalizeFilesDirs(datum["secondaryFiles"])
185238

239+
if "format" in schema:
240+
checkFormat(datum, self.do_eval(schema["format"]), self.formatgraph)
241+
186242
def _capture_files(f):
187243
self.files.append(f)
188244
return f

cwltool/command_line_tool.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -470,7 +470,7 @@ def register_mut(f):
470470
def register_reader(f):
471471
if f["location"] not in muts:
472472
builder.mutation_manager.register_reader(j.name, f)
473-
readers[f["location"]] = f
473+
readers[f["location"]] = copy.copy(f)
474474

475475
for li in j.generatefiles["listing"]:
476476
li = cast(Dict[Text, Any], li)
@@ -630,8 +630,6 @@ def collect_output(self, schema, builder, outdir, fs_access, compute_checksum=Tr
630630
f.seek(0, 2)
631631
filesize = f.tell()
632632
files["size"] = filesize
633-
if "format" in schema:
634-
files["format"] = builder.do_eval(schema["format"], context=files)
635633

636634
optional = False
637635
single = False
@@ -687,6 +685,10 @@ def collect_output(self, schema, builder, outdir, fs_access, compute_checksum=Tr
687685
sfitem["class"] = "Directory"
688686
primary["secondaryFiles"].append(sfitem)
689687

688+
if "format" in schema:
689+
for primary in aslist(r):
690+
primary["format"] = builder.do_eval(schema["format"], context=primary)
691+
690692
# Ensure files point to local references outside of the run environment
691693
adjustFileObjs(r, cast( # known bug in mypy
692694
# https://github.com/python/mypy/issues/797
@@ -702,4 +704,4 @@ def collect_output(self, schema, builder, outdir, fs_access, compute_checksum=Tr
702704
f, builder, outdir, fs_access,
703705
compute_checksum=compute_checksum)
704706
return out
705-
return r
707+
return r

cwltool/executors.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ def execute(self, t, # type: Process
5656
"tmp_outdir_prefix") else tempfile.mkdtemp()
5757
self.output_dirs.add(kwargs["outdir"])
5858
kwargs["mutation_manager"] = MutationManager()
59+
kwargs["toplevel"] = True
5960

6061
jobReqs = None
6162
if "cwl:requirements" in job_order_object:

cwltool/main.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -459,6 +459,7 @@ def main(argsl=None, # type: List[str]
459459
tool_file_uri)
460460
except Exception as e:
461461
_logger.error(Text(e), exc_info=args.debug)
462+
return 1
462463

463464
if args.overrides:
464465
overrides.extend(load_overrides(file_uri(os.path.abspath(args.overrides)), tool_file_uri))

cwltool/process.py

Lines changed: 2 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -352,55 +352,6 @@ def cleanIntermediate(output_dirs): # type: (Set[Text]) -> None
352352
shutil.rmtree(a, True)
353353

354354

355-
def formatSubclassOf(fmt, cls, ontology, visited):
356-
# type: (Text, Text, Graph, Set[Text]) -> bool
357-
"""Determine if `fmt` is a subclass of `cls`."""
358-
359-
if URIRef(fmt) == URIRef(cls):
360-
return True
361-
362-
if ontology is None:
363-
return False
364-
365-
if fmt in visited:
366-
return False
367-
368-
visited.add(fmt)
369-
370-
uriRefFmt = URIRef(fmt)
371-
372-
for s, p, o in ontology.triples((uriRefFmt, RDFS.subClassOf, None)):
373-
# Find parent classes of `fmt` and search upward
374-
if formatSubclassOf(o, cls, ontology, visited):
375-
return True
376-
377-
for s, p, o in ontology.triples((uriRefFmt, OWL.equivalentClass, None)):
378-
# Find equivalent classes of `fmt` and search horizontally
379-
if formatSubclassOf(o, cls, ontology, visited):
380-
return True
381-
382-
for s, p, o in ontology.triples((None, OWL.equivalentClass, uriRefFmt)):
383-
# Find equivalent classes of `fmt` and search horizontally
384-
if formatSubclassOf(s, cls, ontology, visited):
385-
return True
386-
387-
return False
388-
389-
390-
def checkFormat(actualFile, inputFormats, ontology):
391-
# type: (Union[Dict[Text, Any], List, Text], Union[List[Text], Text], Graph) -> None
392-
for af in aslist(actualFile):
393-
if not af:
394-
continue
395-
if "format" not in af:
396-
raise validate.ValidationException(u"Missing required 'format' for File %s" % af)
397-
for inpf in aslist(inputFormats):
398-
if af["format"] == inpf or formatSubclassOf(af["format"], inpf, ontology, set()):
399-
return
400-
raise validate.ValidationException(
401-
u"Incompatible file format %s required format(s) %s" % (af["format"], inputFormats))
402-
403-
404355
def fillInDefaults(inputs, job):
405356
# type: (List[Dict[Text, Text]], Dict[Text, Union[Dict[Text, Any], List, Text]]) -> None
406357
for e, inp in enumerate(inputs):
@@ -606,6 +557,7 @@ def _init_job(self, joborder, **kwargs):
606557
builder.debug = kwargs.get("debug")
607558
builder.js_console = kwargs.get("js_console")
608559
builder.mutation_manager = kwargs.get("mutation_manager")
560+
builder.formatgraph = self.formatgraph
609561

610562
builder.make_fs_access = kwargs.get("make_fs_access") or StdFsAccess
611563
builder.fs_access = builder.make_fs_access(kwargs["basedir"])
@@ -640,13 +592,7 @@ def _init_job(self, joborder, **kwargs):
640592
builder.tmpdir = builder.fs_access.realpath(kwargs.get("tmpdir") or tempfile.mkdtemp())
641593
builder.stagedir = builder.fs_access.realpath(kwargs.get("stagedir") or tempfile.mkdtemp())
642594

643-
if self.formatgraph:
644-
for i in self.tool["inputs"]:
645-
d = shortname(i["id"])
646-
if d in builder.job and i.get("format"):
647-
checkFormat(builder.job[d], builder.do_eval(i["format"]), self.formatgraph)
648-
649-
builder.bindings.extend(builder.bind_input(self.inputs_record_schema, builder.job))
595+
builder.bindings.extend(builder.bind_input(self.inputs_record_schema, builder.job, discover_secondaryFiles=kwargs.get("toplevel")))
650596

651597
if self.tool.get("baseCommand"):
652598
for n, b in enumerate(aslist(self.tool["baseCommand"])):

cwltool/schemas/.travis.yml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
language: python
2+
python:
3+
- 2.7
4+
before_script:
5+
- git clone https://github.com/common-workflow-language/schema_salad.git salad && cd ./salad && pip install . && cd ..
6+
script:
7+
- for target in v1.0/v1.0/*.cwl; do schema-salad-tool ./salad/schema_salad/tests/test_schema/CommonWorkflowLanguage.yml ${target} || exit 1; done

cwltool/schemas/CITATION

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,11 @@ To cite the Common Workflow Language standard in a publication, please use:
33
Amstutz, Peter; Crusoe, Michael R; Tijanić, Nebojša; Chapman, Brad;
44
Chilton, John; Heuer, Michael; Kartashov, Andrey; Kern, John; Leehr, Dan;
55
Ménager, Hervé; Nedeljkovich, Maya; Scales, Matt; Soiland-Reyes, Stian;
6-
Stojanovic, Luka (2016): Common Workflow Language, v1.0. figshare.
6+
Stojanovic, Luka (2016): Common Workflow Language, v1.0. Specification,
7+
Common Workflow Language working group. https://w3id.org/cwl/v1.0/
78
https://dx.doi.org/10.6084/m9.figshare.3115156.v2
89

9-
@data{common-workflow-language-draft3,
10+
@data{cwl,
1011
doi = {10.6084/m9.figshare.3115156.v2},
1112
url = {http://dx.doi.org/10.6084/m9.figshare.3115156.v2},
1213
author = {Peter Amstutz; Michael R. Crusoe; Nebojša Tijanić; Brad Chapman;
@@ -15,6 +16,13 @@ Hervé Ménager; Maya Nedeljkovich; Matt Scales; Stian Soiland-Reyes;
1516
Luka Stojanovic
1617
},
1718
publisher = {Figshare},
19+
institution = {Common Workflow Language working group},
1820
title = {Common Workflow Language, v1.0},
1921
year = {2016}
2022
}
23+
24+
# Are you editing this file?
25+
# Synchronize any changes made with
26+
# README.md
27+
# and
28+
# https://github.com/common-workflow-language/user_guide/blob/gh-pages/CITATION

0 commit comments

Comments
 (0)