Skip to content
This repository was archived by the owner on Jun 9, 2020. It is now read-only.

Commit 109977f

Browse files
authored
Merge pull request #2 from bmeg/workflow
Adding workflow builder
2 parents 77e4ee7 + 9ef4dcc commit 109977f

File tree

2 files changed

+335
-2
lines changed

2 files changed

+335
-2
lines changed

cwlgen/import_cwl.py

+198-2
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,25 @@
1414
import ruamel.yaml as ryaml
1515
import six
1616
import cwlgen
17+
import cwlgen.workflow
1718

1819
logging.basicConfig(level=logging.INFO)
1920
logger = logging.getLogger(__name__)
2021

2122
# Class(es) ------------------------------
2223

24+
def parse_cwl(cwl_path):
25+
with open(cwl_path) as yaml_file:
26+
cwl_dict = ryaml.load(yaml_file, Loader=ryaml.Loader)
27+
cl = cwl_dict['class']
28+
29+
if cl == "CommandLineTool":
30+
p = CWLToolParser()
31+
return p.import_cwl(cwl_path)
32+
if cl == "Workflow":
33+
p = CWLToolParser()
34+
return p.import_cwl(cwl_path)
35+
return None
2336

2437
class CWLToolParser(object):
2538
"""
@@ -46,6 +59,12 @@ def _load_id(self, tool, id_el):
4659
"""
4760
tool.id = id_el
4861

62+
def _load_requirements(self, tool, req_el):
63+
pass
64+
65+
def _load_hints(self, tool, req_el):
66+
pass
67+
4968
def _load_baseCommand(self, tool, command_el):
5069
"""
5170
Load the content of baseCommand into the tool.
@@ -178,6 +197,15 @@ def import_cwl(self, cwl_path):
178197
logger.warning(key + " content is not processed (yet).")
179198
return tool
180199

200+
def dict_or_idlist_items(v):
201+
if isinstance(v, dict):
202+
return v.items()
203+
o = []
204+
for i in v:
205+
e = dict(i)
206+
del e['id']
207+
o.append( (i['id'], e) )
208+
return o
181209

182210
class InputsParser(object):
183211
"""
@@ -283,7 +311,7 @@ def load_inputs(self, inputs, inputs_el):
283311
:type inputs_el: LIST or DICT
284312
"""
285313
# For the moment, only deal with the format exported by cwlgen
286-
for key, value in inputs_el.items():
314+
for key, value in dict_or_idlist_items(inputs_el):
287315
input_obj = cwlgen.CommandInputParameter(key)
288316
for key, element in value.items():
289317
try:
@@ -476,6 +504,17 @@ def _load_type(self, output_obj, type_el):
476504
"""
477505
output_obj.type = type_el
478506

507+
def _load_outputSource(self, output_obj, type_el):
508+
"""
509+
Load the content of type into the output object.
510+
511+
:param output_obj: output obj
512+
:type output_obj: : :class:`cwlgen.CommandInputParameter`
513+
:param type_el: Content of type
514+
:type type_el: STRING
515+
"""
516+
output_obj.outputSource = type_el
517+
479518
def load_outputs(self, outputs, outputs_el):
480519
"""
481520
Load the content of outputs into the outputs list.
@@ -486,7 +525,7 @@ def load_outputs(self, outputs, outputs_el):
486525
:type outputs_el: LIST or DICT
487526
"""
488527
# For the moment, only deal with the format exported by cwlgen
489-
for key, value in outputs_el.items():
528+
for key, value in dict_or_idlist_items(outputs_el):
490529
output_obj = cwlgen.CommandOutputParameter(key)
491530
for key, element in value.items():
492531
try:
@@ -550,3 +589,160 @@ def load_outbinding(self, output_obj, outbinding_el):
550589
except AttributeError:
551590
logger.warning(key + " content for outputBinding is not processed (yet).")
552591
output_obj.outputBinding = outbinding_obj
592+
593+
594+
class StepsParser(object):
595+
"""
596+
Class to parse content of steps of workflow from existing CWL Workflow.
597+
"""
598+
def __init__(self, basedir=None):
599+
self.basedir = basedir
600+
601+
def _load_in(self, step_obj, in_el):
602+
for key, val in in_el.items():
603+
o = cwlgen.workflow.WorkflowStepInput(key)
604+
if isinstance(val, dict) and 'default' in val:
605+
o.default = val['default']
606+
else:
607+
o.src = val
608+
step_obj.inputs.append(o)
609+
610+
def _load_out(self, step_obj, in_el):
611+
for val in in_el:
612+
o = cwlgen.workflow.WorkflowStepOutput(val)
613+
step_obj.outputs.append(o)
614+
615+
def _load_run(self, step_obj, in_el):
616+
if isinstance(in_el, basestring):
617+
path = os.path.join(self.basedir, in_el)
618+
#logger.info("Parsing: %s", path)
619+
step_obj.run = parse_cwl(path)
620+
621+
622+
def load_steps(self, steps_obj, steps_elm):
623+
"""
624+
Load the content of step into the output object.
625+
626+
:param output_obj: output object
627+
:type output_obj: :class:`cwlgen.CommandOutputParameter`
628+
:param outbinding_el: Content of outputBinding element
629+
:type outbinding_el: DICT
630+
"""
631+
for key, value in steps_elm.items():
632+
step_obj = cwlgen.workflow.WorkflowStep(key)
633+
for key, element in value.items():
634+
try:
635+
getattr(self, '_load_{}'.format(key))(step_obj, element)
636+
except AttributeError:
637+
logger.warning(key + " content for input is not processed (yet).")
638+
steps_obj.append(step_obj)
639+
640+
641+
class CWLWorkflowParser(object):
642+
643+
def __init__(self, basedir=None):
644+
self.basedir = basedir
645+
646+
def _init_workflow(self, cwl_dict):
647+
"""
648+
Init tool from existing CWL tool.
649+
650+
:param cwl_dict: Full content of CWL file
651+
:type cwl_dict: DICT
652+
"""
653+
return cwlgen.workflow.Workflow()
654+
655+
def import_cwl(self, cwl_path):
656+
"""
657+
Load content of cwl into the :class:`cwlgen.workflow.Workflow` object.
658+
659+
:param cwl_path: Path of the CWL tool to be loaded.
660+
:type cwl_path: STRING
661+
:return: CWL tool content in cwlgen model.
662+
:rtype: :class:`cwlgen.workflow.Workflow`
663+
"""
664+
with open(cwl_path) as yaml_file:
665+
cwl_dict = ryaml.load(yaml_file, Loader=ryaml.Loader)
666+
tool = self._init_workflow(cwl_dict)
667+
self.basedir = os.path.dirname(cwl_path)
668+
for key, element in cwl_dict.items():
669+
try:
670+
getattr(self, '_load_{}'.format(key))(tool, element)
671+
except AttributeError:
672+
logger.warning(key + " workflow content is not processed (yet).")
673+
return tool
674+
675+
def _load_id(self, tool, id_el):
676+
"""
677+
Load the content of id into the tool.
678+
679+
:param tool: Tool object from cwlgen
680+
:type tool: :class:`cwlgen.CommandLineTool`
681+
:param id_el: Content of id
682+
:type id_el: STRING or [STRING]
683+
"""
684+
tool.id = id_el
685+
686+
def _load_cwlVersion(self, tool, cwl_version_el):
687+
"""
688+
Load the content of cwlVersion into the tool.
689+
690+
:param tool: Tool object from cwlgen
691+
:type tool: :class:`cwlgen.workflow.Workflow`
692+
:param cwl_version_el: Content of cwlVersion
693+
:type cwl_version_el: STRING
694+
"""
695+
tool.cwlVersion = cwl_version_el
696+
697+
698+
def _load_class(self, tool, class_el):
699+
"""
700+
Display message to inform that cwlgen only deal with Workflow for the moment.
701+
702+
:param tool: Workflow object from cwlgen
703+
:type tool: :class:`cwlgen.workflow.Workflow`
704+
:param class_el: Content of class
705+
:type class_el: STRING
706+
"""
707+
if class_el != 'Workflow':
708+
logger.warning('cwlgen library only handle Workflow for the moment')
709+
710+
def _load_requirements(self, tool, req_el):
711+
pass
712+
713+
def _load_inputs(self, tool, inputs_el):
714+
"""
715+
Load the content of inputs into the tool.
716+
717+
:param tool: Tool object from cwlgen
718+
:type tool: :class:`cwlgen.workflow.Workflow`
719+
:param inputs_el: Content of inputs
720+
:type inputs_el: LIST or DICT
721+
"""
722+
inp_parser = InputsParser()
723+
inp_parser.load_inputs(tool.inputs, inputs_el)
724+
725+
def _load_outputs(self, tool, outputs_el):
726+
"""
727+
Load the content of inputs into the tool.
728+
729+
:param tool: Tool object from cwlgen
730+
:type tool: :class:`cwlgen.workflow.Workflow`
731+
:param outputs_el: Content of outputs
732+
:type outputs_el: LIST or DICT
733+
"""
734+
inp_parser = OutputsParser()
735+
inp_parser.load_outputs(tool.outputs, outputs_el)
736+
737+
738+
def _load_steps(self, tool, outputs_el):
739+
"""
740+
Load the content of inputs into the tool.
741+
742+
:param tool: Tool object from cwlgen
743+
:type tool: :class:`cwlgen.workflow.Workflow`
744+
:param outputs_el: Content of outputs
745+
:type outputs_el: LIST or DICT
746+
"""
747+
inp_parser = StepsParser(self.basedir)
748+
inp_parser.load_steps(tool.steps, outputs_el)

cwlgen/workflow.py

+137
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
2+
3+
import ruamel.yaml
4+
import six
5+
6+
from . import CWL_SHEBANG, Parameter
7+
from .utils import *
8+
9+
10+
class Workflow(object):
11+
'''
12+
Contain all informations to describe a CWL workflow.
13+
'''
14+
__CLASS__ = 'Workflow'
15+
16+
def __init__(self):
17+
self.steps = []
18+
self.inputs = []
19+
self.outputs = []
20+
21+
def export(self, outfile=None):
22+
"""
23+
Export the workflow in CWL either on STDOUT or in outfile.
24+
"""
25+
# First add representer (see .utils.py) for multiline writting
26+
ruamel.yaml.add_representer(literal, literal_presenter)
27+
cwl_workflow = {k: v for k, v in vars(self).items() if v is not None and\
28+
type(v) is str}
29+
cwl_workflow['class'] = self.__CLASS__
30+
cwl_workflow['cwlVersion'] = 'v1.0'
31+
32+
cwl_workflow['inputs'] = {}
33+
cwl_workflow['outputs'] = {}
34+
35+
if self.steps:
36+
cwl_workflow['steps'] = {}
37+
for step in self.steps:
38+
cwl_workflow['steps'][step.id] = step.get_dict()
39+
40+
if self.inputs:
41+
cwl_workflow['inputs'] = {}
42+
for i in self.inputs:
43+
cwl_workflow['inputs'][i.id] = i.get_dict()
44+
45+
if self.outputs:
46+
cwl_workflow['outputs'] = {}
47+
for out in self.outputs:
48+
cwl_workflow['outputs'][out.id] = out.get_dict()
49+
50+
# Write CWL file in YAML
51+
if outfile is None:
52+
six.print_(CWL_SHEBANG, "\n", sep='')
53+
six.print_(ruamel.yaml.dump(cwl_workflow))
54+
else:
55+
out_write = open(outfile, 'w')
56+
out_write.write(CWL_SHEBANG + '\n\n')
57+
out_write.write(ruamel.yaml.dump(cwl_workflow))
58+
out_write.close()
59+
60+
class InputParameter(Parameter):
61+
def __init__(self, param_id, label=None, secondary_files=None, param_format=None,
62+
streamable=False, doc=None, input_binding=None, default=None, param_type=None):
63+
Parameter.__init__(self, param_id=param_id, label=label,
64+
secondary_files=secondary_files, param_format=param_format,
65+
streamable=streamable, doc=doc, param_type=param_type)
66+
67+
68+
class WorkflowStep(object):
69+
def __init__(self, id, inputs=None, outputs=None, run=None):
70+
self.id = id
71+
if inputs:
72+
self.inputs = inputs
73+
else:
74+
self.inputs = []
75+
if outputs:
76+
self.outputs = outputs
77+
else:
78+
self.outputs = []
79+
self.run = run
80+
81+
def get_dict(self):
82+
'''
83+
Transform the object to a [DICT] to write CWL.
84+
85+
:return: dictionnary of the object
86+
:rtype: DICT
87+
'''
88+
dict_param = {'id' : self.id, "run" : self.run} #{k: v for k, v in vars(self).items() if v is not None and v is not False}
89+
90+
if self.inputs:
91+
dict_param['in'] = {}
92+
for i in self.inputs:
93+
if i.src:
94+
dict_param['in'][i.id] = i.src
95+
elif i.default:
96+
dict_param['in'][i.id] = {"default" : i.default}
97+
98+
if self.outputs:
99+
dict_param['out'] = []
100+
for i in self.outputs:
101+
dict_param['out'].append(i)
102+
103+
return dict_param
104+
105+
class WorkflowStepInput(object):
106+
def __init__(self, id, src=None, default=None):
107+
self.id = id
108+
self.src = src
109+
self.default = default
110+
111+
def get_dict(self):
112+
'''
113+
Transform the object to a [DICT] to write CWL.
114+
115+
:return: dictionnary of the object
116+
:rtype: DICT
117+
'''
118+
dict_param = {} #{k: v for k, v in vars(self).items() if v is not None and v is not False}
119+
if self.src:
120+
dict_param["src"] = self.src
121+
if self.default:
122+
dict_param["default"] = self.default
123+
return dict_param
124+
125+
class WorkflowStepOutput(object):
126+
def __init__(self, id):
127+
self.id = id
128+
129+
130+
131+
class WorkflowOutputParameter(Parameter):
132+
def __init__(self, param_id, outputSource, label=None, secondary_files=None, param_format=None,
133+
streamable=False, doc=None, param_type=None):
134+
Parameter.__init__(self, param_id=param_id, label=label,
135+
secondary_files=secondary_files, param_format=param_format,
136+
streamable=streamable, doc=doc, param_type=param_type)
137+
self.outputSource = outputSource

0 commit comments

Comments
 (0)