Skip to content

Commit ed64ca6

Browse files
author
Peter Amstutz
committed
Uploads input files. Rewrites path of output files to URLs. Added test data.
Updated README. Bumped package to 2.0.
1 parent 1062e39 commit ed64ca6

File tree

9 files changed

+222
-7
lines changed

9 files changed

+222
-7
lines changed

README

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,32 @@
1-
Wrap cwltool to perform multiple executions of a given workflow where input
2-
objects are provided on stdin and output objects are printed on stdout.
1+
This is a proof of concept web service for the Common Workflow Language. It
2+
implements the following features:
3+
4+
* Hosting CWL files (upload/download) via HTTP PUT and GET.
5+
* Hosting data files (upload/download) via HTTP PUT and GET.
6+
* List files with HTTP GET
7+
* Accept job order via HTTP POST, execute job, and return output object.
8+
* Download output files with HTTP GET
9+
10+
Installation:
11+
12+
```
13+
python setup.py install
14+
```
15+
16+
Run standalone server:
17+
18+
```
19+
cwl-server
20+
```
21+
22+
Upload a CWL file:
23+
24+
```
25+
cwl-client --upload http://localhost:5000 testdata/revsort.cwl
26+
```
27+
28+
Run a job:
29+
30+
```
31+
cwl-client --upload http://localhost:5000 testdata/revsort-job.json
32+
```

cwl_client.py

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,20 @@ def discover_refs(item, base):
3434
r.extend(search_refs(yaml.load(data), os.path.dirname(item)))
3535
return r
3636

37+
38+
def search_for_files(item):
39+
r = []
40+
if isinstance(item, dict):
41+
if "path" in item:
42+
r.append(item["path"])
43+
for v in item.values():
44+
r.extend(search_for_files(v))
45+
elif isinstance(item, list):
46+
for a in item:
47+
r.extend(search_for_files(a))
48+
return r
49+
50+
3751
def main():
3852
parser = argparse.ArgumentParser()
3953

@@ -48,8 +62,9 @@ def main():
4862
print r.text
4963
return
5064

65+
(dr, fn) = os.path.split(args.item)
66+
5167
if args.upload:
52-
(dr, fn) = os.path.split(args.item)
5368
plan = discover_refs(fn, dr)
5469
for p in plan:
5570
dest = urlparse.urljoin(args.endpoint, p[0][len(dr):].lstrip('/'))
@@ -58,6 +73,13 @@ def main():
5873
else:
5974
with open(args.item) as f:
6075
data = f.read()
76+
77+
plan = search_for_files(yaml.load(data))
78+
for p in plan:
79+
dest = urlparse.urljoin(args.endpoint, p)
80+
with open(os.path.join(dr, p)) as f:
81+
r = requests.put(dest, data=f)
82+
6183
r = requests.post(args.endpoint, data=data)
6284
print r.text
6385

cwl_flask.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@
44
import subprocess
55
import tempfile
66
import json
7+
import cwltool.process
8+
import yaml
9+
import urlparse
710

811
app = Flask(__name__)
912

@@ -29,7 +32,7 @@ def handlecwl(workflow):
2932
return "Not found", 404, {"Content-Type": "text/plain"}
3033

3134
if request.method == 'POST':
32-
with tempfile.NamedTemporaryFile() as f:
35+
with tempfile.NamedTemporaryFile(dir="files") as f:
3336
f.write(request.stream.read())
3437
f.flush()
3538
outdir = tempfile.mkdtemp(dir=os.path.abspath("output"))
@@ -41,7 +44,9 @@ def handlecwl(workflow):
4144
(stdoutdata, stderrdata) = proc.communicate()
4245
proc.wait()
4346
if proc.returncode == 0:
44-
return stdoutdata, 200, {"Content-Type": "application/json"}
47+
outobj = yaml.load(stdoutdata)
48+
cwltool.process.adjustFiles(outobj, lambda x: urlparse.urljoin(request.url, x[len(os.getcwd()):]))
49+
return json.dumps(outobj, indent=4), 200, {"Content-Type": "application/json"}
4550
else:
4651
return json.dumps({"cwl:error":stderrdata}), 400, {"Content-Type": "application/json"}
4752
else:

setup.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
README = os.path.join(SETUP_DIR, 'README')
1212

1313
setup(name='cwltool_service',
14-
version='1.0.3',
14+
version='2.0',
1515
description='Common workflow language runner service',
1616
long_description=open(README).read(),
1717
author='Common workflow language working group',
@@ -27,7 +27,9 @@
2727
'yaml'
2828
],
2929
entry_points={
30-
'console_scripts': [ "cwltool-service=cwltool_service:main" ]
30+
'console_scripts': [ "cwltool-stream=cwltool_stream:main",
31+
"cwl-server=cwl_flask:main",
32+
"cwl-client=cwl_client:main"]
3133
},
3234
zip_safe=True
3335
)

testdata/revsort-job.json

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"input": {
3+
"class": "File",
4+
"path": "whale.txt"
5+
}
6+
}

testdata/revsort.cwl

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
#
2+
# This is a two-step workflow which uses "revtool" and "sorttool" defined above.
3+
#
4+
class: Workflow
5+
description: "Reverse the lines in a document, then sort those lines."
6+
7+
# Requirements specify prerequisites and extensions to the workflow.
8+
# In this example, DockerRequirement specifies a default Docker container
9+
# in which the command line tools will execute.
10+
requirements:
11+
- class: DockerRequirement
12+
dockerPull: debian:8
13+
14+
15+
# The inputs array defines the structure of the input object that describes
16+
# the inputs to the workflow.
17+
#
18+
# The "reverse_sort" input parameter demonstrates the "default" field. If the
19+
# field "reverse_sort" is not provided in the input object, the default value will
20+
# be used.
21+
inputs:
22+
- id: "#input"
23+
type: File
24+
description: "The input file to be processed."
25+
- id: "#reverse_sort"
26+
type: boolean
27+
default: true
28+
description: "If true, reverse (decending) sort"
29+
30+
# The "outputs" array defines the structure of the output object that describes
31+
# the outputs of the workflow.
32+
#
33+
# Each output field must be connected to the output of one of the workflow
34+
# steps using the "connect" field. Here, the parameter "#output" of the
35+
# workflow comes from the "#sorted" output of the "sort" step.
36+
outputs:
37+
- id: "#output"
38+
type: File
39+
source: "#sorted.output"
40+
description: "The output with the lines reversed and sorted."
41+
42+
# The "steps" array lists the executable steps that make up the workflow.
43+
# The tool to execute each step is listed in the "run" field.
44+
#
45+
# In the first step, the "inputs" field of the step connects the upstream
46+
# parameter "#input" of the workflow to the input parameter of the tool
47+
# "revtool.cwl#input"
48+
#
49+
# In the second step, the "inputs" field of the step connects the output
50+
# parameter "#reversed" from the first step to the input parameter of the
51+
# tool "sorttool.cwl#input".
52+
steps:
53+
- inputs:
54+
- { id: "#rev.input", source: "#input" }
55+
outputs:
56+
- { id: "#rev.output" }
57+
run: { import: revtool.cwl }
58+
59+
- inputs:
60+
- { id: "#sorted.input", source: "#rev.output" }
61+
- { id: "#sorted.reverse", source: "#reverse_sort" }
62+
outputs:
63+
- { id: "#sorted.output" }
64+
run: { import: sorttool.cwl }

testdata/revtool.cwl

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
#
2+
# Simplest example command line program wrapper for the Unix tool "rev".
3+
#
4+
class: CommandLineTool
5+
description: "Reverse each line using the `rev` command"
6+
7+
# The "inputs" array defines the structure of the input object that describes
8+
# the inputs to the underlying program. Here, there is one input field
9+
# defined that will be called "input" and will contain a "File" object.
10+
#
11+
# The input binding indicates that the input value should be turned into a
12+
# command line argument. In this example inputBinding is an empty object,
13+
# which indicates that the file name should be added to the command line at
14+
# a default location.
15+
inputs:
16+
- id: "#input"
17+
type: File
18+
inputBinding: {}
19+
20+
# The "outputs" array defines the structure of the output object that
21+
# describes the outputs of the underlying program. Here, there is one
22+
# output field defined that will be called "output", must be a "File" type,
23+
# and after the program executes, the output value will be the file
24+
# output.txt in the designated output directory.
25+
outputs:
26+
- id: "#output"
27+
type: File
28+
outputBinding:
29+
glob: output.txt
30+
31+
# The actual program to execute.
32+
baseCommand: rev
33+
34+
# Specify that the standard output stream must be redirected to a file called
35+
# output.txt in the designated output directory.
36+
stdout: output.txt

testdata/sorttool.cwl

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
# Example command line program wrapper for the Unix tool "sort"
2+
# demonstrating command line flags.
3+
class: CommandLineTool
4+
description: "Sort lines using the `sort` command"
5+
6+
# This example is similar to the previous one, with an additional input
7+
# parameter called "reverse". It is a boolean parameter, which is
8+
# intepreted as a command line flag. The value of "prefix" is used for
9+
# flag to put on the command line if "reverse" is true, if "reverse" is
10+
# false, no flag is added.
11+
#
12+
# This example also introduced the "position" field. This indicates the
13+
# sorting order of items on the command line. Lower numbers are placed
14+
# before higher numbers. Here, the "--reverse" flag (if present) will be
15+
# added to the command line before the input file path.
16+
inputs:
17+
- id: "#reverse"
18+
type: boolean
19+
inputBinding:
20+
position: 1
21+
prefix: "--reverse"
22+
- id: "#input"
23+
type: File
24+
inputBinding:
25+
position: 2
26+
27+
outputs:
28+
- id: "#output"
29+
type: File
30+
outputBinding:
31+
glob: output.txt
32+
33+
baseCommand: sort
34+
stdout: output.txt

testdata/whale.txt

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
Call me Ishmael. Some years ago--never mind how long precisely--having
2+
little or no money in my purse, and nothing particular to interest me on
3+
shore, I thought I would sail about a little and see the watery part of
4+
the world. It is a way I have of driving off the spleen and regulating
5+
the circulation. Whenever I find myself growing grim about the mouth;
6+
whenever it is a damp, drizzly November in my soul; whenever I find
7+
myself involuntarily pausing before coffin warehouses, and bringing up
8+
the rear of every funeral I meet; and especially whenever my hypos get
9+
such an upper hand of me, that it requires a strong moral principle to
10+
prevent me from deliberately stepping into the street, and methodically
11+
knocking people's hats off--then, I account it high time to get to sea
12+
as soon as I can. This is my substitute for pistol and ball. With a
13+
philosophical flourish Cato throws himself upon his sword; I quietly
14+
take to the ship. There is nothing surprising in this. If they but knew
15+
it, almost all men in their degree, some time or other, cherish very
16+
nearly the same feelings towards the ocean with me.

0 commit comments

Comments
 (0)