Skip to content

Commit 886ebfa

Browse files
author
Janneke van der Zwaan
committed
Merge branch 'develop'
2 parents 5423267 + 93c0417 commit 886ebfa

File tree

9 files changed

+143
-21
lines changed

9 files changed

+143
-21
lines changed

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
# Change Log
22

3+
## 0.4.0
4+
5+
* Generate unique names for steps that are added to the workflow more than once (#31)
6+
* Pass all outputs from a step, instead of just one (#27)
7+
* Improve listing of workflow steps
8+
39
## 0.3.1
410

511
### Added

README.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,14 @@ for existing command line tools written in Python:
9292
* [argparse2tool](https://github.com/erasche/argparse2tool#cwl-specific-functionality): Generate CWL CommandLineTool wrappers (and/or Galaxy tool descriptions) from Python programs that use argparse. Also supports the [click](http://click.pocoo.org) argument parser.
9393
* [pypi2cwl](https://github.com/common-workflow-language/pypi2cwl): Automatically run argparse2cwl on any package in PyPi.
9494

95+
## Listing available steps
96+
97+
Steps loaded into the `WorkflowGenerator` can be listed with:
98+
99+
```
100+
print wf.list_steps()
101+
```
102+
95103
## Running workflows
96104

97105
Workflows created with scriptcwl can be run with:

scriptcwl/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
from .workflow import WorkflowGenerator
22
from .scriptcwl import load_steps
33

4-
__version__ = '0.3.1'
4+
__version__ = '0.4.0'

scriptcwl/step.py

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,9 @@ def set_input(self, name, value):
8787
raise ValueError('Invalid input "{}"'.format(name))
8888
self.step_inputs[name] = value
8989

90+
def _set_name_in_workflow(self, name):
91+
self.name_in_workflow = name
92+
9093
def output_to_input(self, name):
9194
"""Convert the name of an output to an input for a next Step.
9295
@@ -101,7 +104,7 @@ def output_to_input(self, name):
101104
"""
102105
if name not in self.output_names:
103106
raise ValueError('Invalid output "{}"'.format(name))
104-
return ''.join([self.name, '/', name])
107+
return ''.join([self.name_in_workflow, '/', name])
105108

106109
def _input_optional(self, inp):
107110
"""Returns True if a step input parameter is optional.
@@ -134,7 +137,7 @@ def to_obj(self):
134137
obj = CommentedMap()
135138
obj['run'] = self.run
136139
obj['in'] = self.step_inputs
137-
obj['out'] = [self.output_names[0]]
140+
obj['out'] = self.output_names
138141
if self.is_scattered:
139142
obj['scatter'] = self.scattered_inputs
140143
obj['scatterMethod'] = self.scatter_method
@@ -143,12 +146,12 @@ def to_obj(self):
143146

144147
def __str__(self):
145148
if len(self.optional_input_names) > 0:
146-
template = '{} = {}({}[, {}])'
149+
template = u'{} = wf.{}({}[, {}])'
147150
else:
148-
template = '{} = {}({})'
149-
return template.format(', '.join(self.output_names), self.python_name,
150-
', '.join(self.input_names),
151-
', '.join(self.optional_input_names))
151+
template = u'{} = wf.{}({})'
152+
return template.format(u', '.join(self.output_names), self.python_name,
153+
u', '.join(self.input_names),
154+
u', '.join(self.optional_input_names))
152155

153156
def __repr__(self):
154157
return str(self)

scriptcwl/workflow.py

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -139,10 +139,20 @@ def load(self, steps_dir=None, step_file=None):
139139
self.steps_library[n] = step
140140

141141
def list_steps(self):
142-
"""Prints the signature of all steps in the steps library.
142+
"""Return string with the signature of all steps in the steps library.
143143
"""
144+
steps = []
145+
workflows = []
146+
template = u' {:.<25} {}'
144147
for name, step in self.steps_library.iteritems():
145-
print 'Step "{}": {}'.format(name, step)
148+
if step.is_workflow:
149+
workflows.append(template.format(name, step))
150+
else:
151+
steps.append(template.format(name, step))
152+
153+
result = [u'Steps\n', u'\n'.join(steps), u'\n\nWorkflows\n',
154+
u'\n'.join(workflows)]
155+
return u''.join(result)
146156

147157
def _has_requirements(self):
148158
"""Returns True if the workflow needs a requirements section.
@@ -169,7 +179,7 @@ def _add_step(self, step):
169179
step (Step): a step from the steps library.
170180
"""
171181
self.has_workflow_step = self.has_workflow_step or step.is_workflow
172-
self.wf_steps[step.name] = step.to_obj()
182+
self.wf_steps[step.name_in_workflow] = step.to_obj()
173183

174184
def add_inputs(self, **kwargs):
175185
"""Add workflow inputs.
@@ -247,6 +257,16 @@ def _get_step(self, name, make_copy=True):
247257
s = copy.deepcopy(s)
248258
return s
249259

260+
def _generate_step_name(self, step_name):
261+
name = step_name
262+
i = 1
263+
264+
while name in self.wf_steps.keys():
265+
name = '{}-{}'.format(step_name, i)
266+
i += 1
267+
268+
return name
269+
250270
def to_obj(self):
251271
"""Return the created workflow as a dict.
252272
@@ -357,6 +377,11 @@ def _make_step(self, step, **kwargs):
357377
self.has_scatter_requirement = True
358378
step.is_scattered = True
359379

380+
# Make sure the step has a unique name in the workflow (so command line
381+
# tools can be added to the same workflow multiple times).
382+
name_in_wf = self._generate_step_name(step.name)
383+
step._set_name_in_workflow(name_in_wf)
384+
360385
outputs = []
361386
for n in step.output_names:
362387
oname = step.output_to_input(n)

setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
# Versions should comply with PEP440. For a discussion on single-sourcing
1111
# the version across setup.py and the project code, see
1212
# https://packaging.python.org/en/latest/single_source_version.html
13-
version='0.3.1',
13+
version='0.4.0',
1414

1515
description='Tool to generate and edit CWL workflows',
1616
long_description="""Tool to generate and edit workflows in common
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
#!/usr/bin/env cwlrunner
2+
cwlVersion: cwl:v1.0
3+
class: CommandLineTool
4+
baseCommand: ["python", "-m", "nlppln.commands.extract_annotations"]
5+
6+
inputs:
7+
in_files:
8+
type:
9+
type: array
10+
items: File
11+
inputBinding:
12+
position: 2
13+
out_dir:
14+
type: Directory?
15+
inputBinding:
16+
prefix: --out_dir=
17+
separate: false
18+
counselors:
19+
type:
20+
type: array
21+
items: string
22+
inputBinding:
23+
prefix: -c
24+
25+
stdout: missing_introductions.json
26+
27+
outputs:
28+
out_files:
29+
type:
30+
type: array
31+
items: File
32+
outputBinding:
33+
glob: "*.txt"
34+
meta_out:
35+
type: File
36+
outputBinding:
37+
glob: "missing_introductions.json"

tests/test_step.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,3 +51,26 @@ def test_argument_is_optional(self, step):
5151

5252
def test_argument_is_not_optional(self, step):
5353
assert not step._input_optional({'type': 'string'})
54+
55+
56+
class TestMultipleOutputArgs(object):
57+
@pytest.fixture
58+
def step(self):
59+
return Step('tests/data/tools/multiple-out-args.cwl')
60+
61+
def test_has_multiple_out_args(self, step):
62+
assert len(step.to_obj()['out']) == 2
63+
64+
65+
class TestStepNameInWorkflow(object):
66+
@pytest.fixture
67+
def step(self):
68+
return Step('tests/data/tools/echo.cwl')
69+
70+
def test_no_name_in_workflow(self, step):
71+
with pytest.raises(AttributeError):
72+
step.name_in_workflow == 'echo'
73+
74+
def test_set_name_in_workflow(self, step):
75+
step._set_name_in_workflow('echo')
76+
assert step.name_in_workflow == 'echo'

tests/test_workflow.py

Lines changed: 29 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ def test_load(self):
1717

1818
step_keys = wf.steps_library.keys()
1919
step_keys.sort()
20-
assert step_keys == ['echo', 'wc']
20+
assert step_keys == ['echo', 'multiple-out-args', 'wc']
2121

2222
def test_save_with_tools(self, tmpdir):
2323
wf = WorkflowGenerator()
@@ -107,12 +107,12 @@ def test_scatter_method_correct(self):
107107
scatter_methods = ['dotproduct', 'nested_crossproduct',
108108
'flat_crossproduct']
109109

110-
wf = WorkflowGenerator()
111-
wf.load('tests/data/tools')
110+
for method in scatter_methods:
111+
wf = WorkflowGenerator()
112+
wf.load('tests/data/tools')
112113

113-
msgs = wf.add_inputs(wfmessages='string[]')
114+
msgs = wf.add_inputs(wfmessages='string[]')
114115

115-
for method in scatter_methods:
116116
echoed = wf.echo(message=msgs, scatter='message', scatter_method=method)
117117
assert echoed == 'echo/echoed'
118118

@@ -129,12 +129,12 @@ def test_scatter_variable_correct(self):
129129
scatter_methods = ['dotproduct', 'nested_crossproduct',
130130
'flat_crossproduct']
131131

132-
wf = WorkflowGenerator()
133-
wf.load('tests/data/tools')
132+
for method in scatter_methods:
133+
wf = WorkflowGenerator()
134+
wf.load('tests/data/tools')
134135

135-
msgs = wf.add_inputs(wfmessages='string[]')
136+
msgs = wf.add_inputs(wfmessages='string[]')
136137

137-
for method in scatter_methods:
138138
echoed = wf.echo(message=msgs, scatter='message', scatter_method=method)
139139
assert echoed == 'echo/echoed'
140140

@@ -155,3 +155,23 @@ def test_missing_scatter_method_argument(self):
155155

156156
with pytest.raises(ValueError):
157157
wf.echo(message=msgs, scatter='message')
158+
159+
160+
class TestWorkflowGeneratorWithStepsAddedMultipleTimes(object):
161+
def test_generate_step_name(self):
162+
wf = WorkflowGenerator()
163+
wf.load('tests/data/tools')
164+
165+
wfmessage = wf.add_inputs(wfmessage='string')
166+
167+
name = wf._generate_step_name('echo')
168+
echoed = wf.echo(message=wfmessage)
169+
170+
assert name == 'echo'
171+
assert name == echoed.split('/')[0]
172+
173+
name = wf._generate_step_name('echo')
174+
echoed2 = wf.echo(message=wfmessage)
175+
176+
assert name != 'echo'
177+
assert name == echoed2.split('/')[0]

0 commit comments

Comments
 (0)