graph2cwl 0.0.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,96 @@
1
+ Metadata-Version: 2.4
2
+ Name: graph2cwl
3
+ Version: 0.0.1
4
+ Summary: Helper to generate cwl workflows
5
+ Requires-Python: >=3.13
6
+ Description-Content-Type: text/markdown
7
+ Requires-Dist: cwl-utils>=0.40
8
+
9
+ # graph2cwl
10
+
11
+ Python module that interfaces with cwl-utils in order to generate cwl files.
12
+
13
+ ## Getting Started
14
+
15
+ ```bash
16
+ pip install graph2cwl
17
+ ```
18
+
19
+ Here is a code snippet :
20
+
21
+ ```python
22
+ from graph2cwl import WorkflowCreator
23
+
24
+ wf = WorkflowCreator(id="wf")
25
+ wf.add_step(id="step_1", run="./my_file.py")
26
+ wf.add_step(id="step_2", run="./my_file_2.py")
27
+ wf.add_dependency("step_1", "step_2", dep_name="step_1_out")
28
+ wf.add_workflow_input("input_1")
29
+ wf.add_workflow_output("output", outputSource="step_1/step_1_out")
30
+ wf.add_dependency("input_1", "step_2")
31
+ wf.add_dependency("input_1", "step_1")
32
+
33
+ wf.to_yaml("wf.yaml")
34
+ ```
35
+
36
+ This creates the following valid cwl:
37
+
38
+ ```yaml
39
+ id: w
40
+ class: Workflow
41
+ inputs:
42
+ - id: input_1
43
+ type: File
44
+ outputs:
45
+ - id: output
46
+ outputSource: step_1/step_1_out
47
+ type: File
48
+ cwlVersion: v1.2
49
+ steps:
50
+ - id: step_1
51
+ in:
52
+ - id: xehsftvk
53
+ source: input_1
54
+ out:
55
+ - step_1_out
56
+ run: my_file.py
57
+ - id: step_2
58
+ in:
59
+ - id: fwfmzpkk
60
+ source: step_1/step_1_out
61
+ - id: ellhmrzg
62
+ source: input_1
63
+ out: []
64
+ run: my_file_2.py
65
+ ```
66
+
67
+ which can be visualized (see [cwl2nx](https://github.com/mariusgarenaux/cwl2nx)):
68
+
69
+ ```text
70
+ • w/input_1
71
+ ├─• w/step_1
72
+ │ ╰─• w/step_1/step_1_out
73
+ │ ├─• w/output
74
+ ╰───┴─• w/step_2
75
+ ```
76
+
77
+ ## Advanced usage
78
+
79
+ To embed the cwl with additionnal information, you must follow the cwl specification, implemented in cwl-utils. For example, to insert additional informations to a step, you can add the `hints` argument, or the `extension_field` one :
80
+
81
+ ```python
82
+ from graph2cwl import WorkflowCreator
83
+
84
+ wf = WorkflowCreator(id="w")
85
+ wf.add_step(id="step_1", run="./my_file.py", hints={"hint1": "value1"})
86
+ wf.add_step(id="step_2", run="./my_file_2.py")
87
+ wf.add_dependency("step_1", "step_2", dep_name="step_1_out")
88
+ wf.add_workflow_input("input_1", extension_fields={"additional_field": "het"})
89
+ wf.add_workflow_output("output", outputSource="step_1/step_1_out")
90
+ wf.add_dependency("input_1", "step_2")
91
+ wf.add_dependency("input_1", "step_1")
92
+
93
+ wf.to_yaml("wf2.yaml")
94
+ ```
95
+
96
+ See : https://cwl-utils.readthedocs.io/en/latest/autoapi/cwl_utils/parser/cwl_v1_2/index.html#cwl_utils.parser.cwl_v1_2.WorkflowStep for the list of supported arguments.
@@ -0,0 +1,88 @@
1
+ # graph2cwl
2
+
3
+ Python module that interfaces with cwl-utils in order to generate cwl files.
4
+
5
+ ## Getting Started
6
+
7
+ ```bash
8
+ pip install graph2cwl
9
+ ```
10
+
11
+ Here is a code snippet :
12
+
13
+ ```python
14
+ from graph2cwl import WorkflowCreator
15
+
16
+ wf = WorkflowCreator(id="wf")
17
+ wf.add_step(id="step_1", run="./my_file.py")
18
+ wf.add_step(id="step_2", run="./my_file_2.py")
19
+ wf.add_dependency("step_1", "step_2", dep_name="step_1_out")
20
+ wf.add_workflow_input("input_1")
21
+ wf.add_workflow_output("output", outputSource="step_1/step_1_out")
22
+ wf.add_dependency("input_1", "step_2")
23
+ wf.add_dependency("input_1", "step_1")
24
+
25
+ wf.to_yaml("wf.yaml")
26
+ ```
27
+
28
+ This creates the following valid cwl:
29
+
30
+ ```yaml
31
+ id: w
32
+ class: Workflow
33
+ inputs:
34
+ - id: input_1
35
+ type: File
36
+ outputs:
37
+ - id: output
38
+ outputSource: step_1/step_1_out
39
+ type: File
40
+ cwlVersion: v1.2
41
+ steps:
42
+ - id: step_1
43
+ in:
44
+ - id: xehsftvk
45
+ source: input_1
46
+ out:
47
+ - step_1_out
48
+ run: my_file.py
49
+ - id: step_2
50
+ in:
51
+ - id: fwfmzpkk
52
+ source: step_1/step_1_out
53
+ - id: ellhmrzg
54
+ source: input_1
55
+ out: []
56
+ run: my_file_2.py
57
+ ```
58
+
59
+ which can be visualized (see [cwl2nx](https://github.com/mariusgarenaux/cwl2nx)):
60
+
61
+ ```text
62
+ • w/input_1
63
+ ├─• w/step_1
64
+ │ ╰─• w/step_1/step_1_out
65
+ │ ├─• w/output
66
+ ╰───┴─• w/step_2
67
+ ```
68
+
69
+ ## Advanced usage
70
+
71
+ To embed the cwl with additionnal information, you must follow the cwl specification, implemented in cwl-utils. For example, to insert additional informations to a step, you can add the `hints` argument, or the `extension_field` one :
72
+
73
+ ```python
74
+ from graph2cwl import WorkflowCreator
75
+
76
+ wf = WorkflowCreator(id="w")
77
+ wf.add_step(id="step_1", run="./my_file.py", hints={"hint1": "value1"})
78
+ wf.add_step(id="step_2", run="./my_file_2.py")
79
+ wf.add_dependency("step_1", "step_2", dep_name="step_1_out")
80
+ wf.add_workflow_input("input_1", extension_fields={"additional_field": "het"})
81
+ wf.add_workflow_output("output", outputSource="step_1/step_1_out")
82
+ wf.add_dependency("input_1", "step_2")
83
+ wf.add_dependency("input_1", "step_1")
84
+
85
+ wf.to_yaml("wf2.yaml")
86
+ ```
87
+
88
+ See : https://cwl-utils.readthedocs.io/en/latest/autoapi/cwl_utils/parser/cwl_v1_2/index.html#cwl_utils.parser.cwl_v1_2.WorkflowStep for the list of supported arguments.
@@ -0,0 +1,8 @@
1
+ [project]
2
+
3
+ name = "graph2cwl"
4
+ version = "0.0.1"
5
+ description = "Helper to generate cwl workflows"
6
+ readme = "README.md"
7
+ requires-python = ">=3.13"
8
+ dependencies = ["cwl-utils>=0.40"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1 @@
1
+ from .creator import WorkflowCreator, WorkflowStepCreator
@@ -0,0 +1,264 @@
1
+ # Base python dependencies
2
+ from typing import Literal, Optional, Any, Type
3
+ import random
4
+ import re
5
+
6
+ # Externel dependencies
7
+ from ruamel.yaml import YAML
8
+ from cwl_utils.parser import cwl_v1_0, cwl_v1_1, cwl_v1_2
9
+
10
+ CWLVersion = Literal["v1.0", "v1.1", "v1.2"]
11
+
12
+
13
+ def random_name():
14
+ return "".join(
15
+ random.choice(re.escape("abcdefghijklmnopqrstuvwxyz")) for _ in range(8)
16
+ )
17
+
18
+
19
+ class WorkflowStepCreator:
20
+ def __init__(
21
+ self,
22
+ WorkflowStep: (
23
+ Type[cwl_v1_0.WorkflowStep]
24
+ | Type[cwl_v1_1.WorkflowStep]
25
+ | Type[cwl_v1_2.WorkflowStep]
26
+ ),
27
+ id: str,
28
+ kwargs: Optional[dict[str, Any]] = None,
29
+ ):
30
+ """
31
+ Buffer class of cwl_utils WorkflowStep; that stores the initialization arguments
32
+ while the dependencies between task are created.
33
+ """
34
+ self.WorkflowStep = WorkflowStep
35
+ self.id = id
36
+ if kwargs is None:
37
+ kwargs = {}
38
+ self.kwargs = kwargs
39
+
40
+ if self.kwargs.get("out", None) is None:
41
+ self.kwargs["out"] = []
42
+
43
+ if self.kwargs.get("in_", None) is None:
44
+ self.kwargs["in_"] = []
45
+
46
+ def create_cwl_utils_obj(
47
+ self,
48
+ ) -> cwl_v1_0.WorkflowStep | cwl_v1_1.WorkflowStep | cwl_v1_2.WorkflowStep:
49
+ return self.WorkflowStep(id=self.id, **self.kwargs)
50
+
51
+
52
+ class WorkflowCreator:
53
+ """
54
+ Helper class that allows to create cwl files, only from the steps.
55
+ It deals with WorkflowStepInput, WorkflowStepOutput so that
56
+ the end user does not have to use them.
57
+ """
58
+
59
+ def __init__(self, cwl_version: CWLVersion = "v1.2", id: Optional[str] = None):
60
+ if cwl_version not in ["v1.0", "v1.1", "v1.2"]:
61
+ raise ValueError(
62
+ f"Unexpected cwl version : `{cwl_version}`. Expected one among `v1.0`, `v1.1`, `v1.2`."
63
+ )
64
+ match cwl_version:
65
+ case "v1.0":
66
+ self.cwl_module = cwl_v1_0
67
+ case "v1.1":
68
+ self.cwl_module = cwl_v1_1
69
+ case "v1.2":
70
+ self.cwl_module = cwl_v1_2
71
+
72
+ self.cwl_version = cwl_version
73
+ self.id = id
74
+ self.WorkflowStep = self.cwl_module.WorkflowStep
75
+ self.WorkflowOutputParameter = self.cwl_module.WorkflowOutputParameter
76
+ if hasattr(self.cwl_module, "WorkflowInputParameter"):
77
+ self.WorkflowInputParameter = (
78
+ self.cwl_module.WorkflowInputParameter # pyright: ignore
79
+ )
80
+
81
+ self.WorkflowStepInput = self.cwl_module.WorkflowStepInput
82
+ self.WorkflowStepOutput = self.cwl_module.WorkflowStepOutput
83
+ self.Workflow = self.cwl_module.Workflow
84
+
85
+ self.all_step_creators: dict[str, WorkflowStepCreator] = {}
86
+ self.all_workflow_inputs: dict[str, Any] = {}
87
+ self.all_workflow_outputs: dict[str, Any] = {}
88
+
89
+ def add_step(
90
+ self,
91
+ id: str,
92
+ **kwargs,
93
+ ) -> WorkflowStepCreator:
94
+ """
95
+ Adds a step to the workflow creator.
96
+
97
+ Parameters :
98
+ ---
99
+ - id (str): the name of the step
100
+ - **kwargs : any named arguments (different of the one above)
101
+ that will be given to cwl_utils.parser.cwl_<version>.WorkflowStep creator.
102
+ """
103
+ new_step = WorkflowStepCreator(self.WorkflowStep, id=id, kwargs=kwargs)
104
+ self.all_step_creators[id] = new_step
105
+ return new_step
106
+
107
+ def _add_dependency_from_input_param_to_step(
108
+ self, from_input: str, to_step: str, dep_name: Optional[str] = None
109
+ ):
110
+ if from_input not in self.all_workflow_inputs:
111
+ raise KeyError(
112
+ f"No workflow input parameter was found with id `{from_input}`. Available inputs are : {self.all_workflow_inputs}"
113
+ )
114
+
115
+ if to_step not in self.all_step_creators:
116
+ raise KeyError(
117
+ f"Could not find any step of the workflow with id `{to_step}`"
118
+ )
119
+
120
+ end_step = self.all_step_creators[to_step]
121
+
122
+ dep_name = random_name() if dep_name is None else dep_name
123
+
124
+ # creates output of start step
125
+
126
+ # creates input parameter for the end step
127
+ step_input = self.WorkflowStepInput(dep_name, source=from_input)
128
+ end_step.kwargs["in_"].append(step_input)
129
+
130
+ def _add_dependency_from_step_to_step(
131
+ self,
132
+ from_step: str,
133
+ to_step: str,
134
+ dep_name: Optional[str] = None,
135
+ ):
136
+ """
137
+ Creates a dependency between the step with id `from_step` and
138
+ the one with if `to_step`.
139
+ """
140
+ if from_step not in self.all_step_creators:
141
+ raise KeyError(
142
+ f"Could not find any step of the workflow with id `{from_step}`"
143
+ )
144
+ if to_step not in self.all_step_creators:
145
+ raise KeyError(
146
+ f"Could not find any step of the workflow with id `{to_step}`"
147
+ )
148
+ start_step = self.all_step_creators[from_step]
149
+ end_step = self.all_step_creators[to_step]
150
+
151
+ dep_name = random_name() if dep_name is None else dep_name
152
+
153
+ # creates output of start step
154
+ start_step.kwargs["out"].append(dep_name)
155
+
156
+ # creates input parameter for the end step
157
+ step_input = self.WorkflowStepInput(
158
+ random_name(), source=f"{start_step.id}/{dep_name}"
159
+ )
160
+ end_step.kwargs["in_"].append(step_input)
161
+
162
+ def add_dependency(
163
+ self, from_elem: str, to_step: str, dep_name: Optional[str] = None
164
+ ):
165
+ """
166
+ Creates a dependency between either :
167
+ - a workflow input parameter and a step
168
+ - two steps of the workflow
169
+
170
+ Parameters :
171
+ ---
172
+ - from_elem (str): either an id of a input parameter of the workflow,
173
+ or the id of a step. The method first seek for a matching step.
174
+ - to_step (str): the id of the end step
175
+ - dep_name (Optional[str] = None): the name of the dependency
176
+ """
177
+ if from_elem not in self.all_step_creators:
178
+ if from_elem in self.all_workflow_inputs:
179
+ self._add_dependency_from_input_param_to_step(
180
+ from_elem, to_step, dep_name
181
+ )
182
+ return
183
+ else:
184
+ raise ValueError(
185
+ f"Could not find any object of the workflow with id `{from_elem}`"
186
+ )
187
+
188
+ self._add_dependency_from_step_to_step(from_elem, to_step, dep_name)
189
+
190
+ def add_workflow_input(self, id: str, **kwargs):
191
+ """
192
+ Adds an input to the workflow with id. Any keyword argument can be given,
193
+ it will be forwarded to cwl_utils WorkflowInputParameter constructor.
194
+ """
195
+ if "type_" not in kwargs:
196
+ kwargs["type_"] = "File"
197
+
198
+ self.all_workflow_inputs[id] = self.WorkflowInputParameter(id=id, **kwargs)
199
+
200
+ def add_workflow_output(self, id: str, **kwargs):
201
+ """
202
+ Adds an output to the workflow with id. Any keyword argument can be given,
203
+ it will be forwarded to cwl_utils WorkflowInputParameter constructor.
204
+
205
+ Parameters :
206
+ ---
207
+ - id (str) : the id of the Workflow Output Parameter
208
+ """
209
+ if "type_" not in kwargs:
210
+ kwargs["type_"] = "File"
211
+
212
+ self.all_workflow_outputs[id] = self.WorkflowOutputParameter(id=id, **kwargs)
213
+
214
+ def build(self, **kwargs):
215
+ """
216
+ Creates the cwl_utils Workflow Object
217
+ """
218
+ all_cwl_steps = []
219
+ for each_step_id, each_step in self.all_step_creators.items():
220
+ try:
221
+ cwl_step = each_step.create_cwl_utils_obj()
222
+ except Exception as e:
223
+ raise Exception(
224
+ f"Could not create the step with id {each_step_id}"
225
+ ) from e
226
+ else:
227
+ all_cwl_steps.append(cwl_step)
228
+
229
+ return self.Workflow(
230
+ id=self.id,
231
+ inputs=list(self.all_workflow_inputs.values()),
232
+ outputs=list(self.all_workflow_outputs.values()),
233
+ steps=all_cwl_steps,
234
+ cwlVersion=self.cwl_version,
235
+ **kwargs,
236
+ )
237
+
238
+ def to_dict(self, **kwargs) -> dict:
239
+ """
240
+ Shortcut that calls self.create; followed by cwl_utils save method.
241
+ Returns a YAML/JSON friendly python dictionnary
242
+ """
243
+ return self.build().save(**kwargs)
244
+
245
+ def to_yaml(self, file_name: str, **kwargs):
246
+ """
247
+ Build the cwl and save it to a yaml file located at `file_name`
248
+ """
249
+ yaml = YAML()
250
+ with open(file_name, "wt") as f:
251
+ yaml.dump(self.to_dict(**kwargs), f)
252
+
253
+
254
+ if __name__ == "__main__":
255
+ wf = WorkflowCreator(id="w")
256
+ wf.add_step(id="step_1", run="./my_file.py")
257
+ wf.add_step(id="step_2", run="./my_file_2.py")
258
+ wf.add_dependency("step_1", "step_2", dep_name="step_1_out")
259
+ wf.add_workflow_input("input_1")
260
+ wf.add_workflow_output("output", outputSource="step_1/step_1_out")
261
+ wf.add_dependency("input_1", "step_2")
262
+ wf.add_dependency("input_1", "step_1")
263
+
264
+ print(wf.to_dict())
@@ -0,0 +1,96 @@
1
+ Metadata-Version: 2.4
2
+ Name: graph2cwl
3
+ Version: 0.0.1
4
+ Summary: Helper to generate cwl workflows
5
+ Requires-Python: >=3.13
6
+ Description-Content-Type: text/markdown
7
+ Requires-Dist: cwl-utils>=0.40
8
+
9
+ # graph2cwl
10
+
11
+ Python module that interfaces with cwl-utils in order to generate cwl files.
12
+
13
+ ## Getting Started
14
+
15
+ ```bash
16
+ pip install graph2cwl
17
+ ```
18
+
19
+ Here is a code snippet :
20
+
21
+ ```python
22
+ from graph2cwl import WorkflowCreator
23
+
24
+ wf = WorkflowCreator(id="wf")
25
+ wf.add_step(id="step_1", run="./my_file.py")
26
+ wf.add_step(id="step_2", run="./my_file_2.py")
27
+ wf.add_dependency("step_1", "step_2", dep_name="step_1_out")
28
+ wf.add_workflow_input("input_1")
29
+ wf.add_workflow_output("output", outputSource="step_1/step_1_out")
30
+ wf.add_dependency("input_1", "step_2")
31
+ wf.add_dependency("input_1", "step_1")
32
+
33
+ wf.to_yaml("wf.yaml")
34
+ ```
35
+
36
+ This creates the following valid cwl:
37
+
38
+ ```yaml
39
+ id: w
40
+ class: Workflow
41
+ inputs:
42
+ - id: input_1
43
+ type: File
44
+ outputs:
45
+ - id: output
46
+ outputSource: step_1/step_1_out
47
+ type: File
48
+ cwlVersion: v1.2
49
+ steps:
50
+ - id: step_1
51
+ in:
52
+ - id: xehsftvk
53
+ source: input_1
54
+ out:
55
+ - step_1_out
56
+ run: my_file.py
57
+ - id: step_2
58
+ in:
59
+ - id: fwfmzpkk
60
+ source: step_1/step_1_out
61
+ - id: ellhmrzg
62
+ source: input_1
63
+ out: []
64
+ run: my_file_2.py
65
+ ```
66
+
67
+ which can be visualized (see [cwl2nx](https://github.com/mariusgarenaux/cwl2nx)):
68
+
69
+ ```text
70
+ • w/input_1
71
+ ├─• w/step_1
72
+ │ ╰─• w/step_1/step_1_out
73
+ │ ├─• w/output
74
+ ╰───┴─• w/step_2
75
+ ```
76
+
77
+ ## Advanced usage
78
+
79
+ To embed the cwl with additionnal information, you must follow the cwl specification, implemented in cwl-utils. For example, to insert additional informations to a step, you can add the `hints` argument, or the `extension_field` one :
80
+
81
+ ```python
82
+ from graph2cwl import WorkflowCreator
83
+
84
+ wf = WorkflowCreator(id="w")
85
+ wf.add_step(id="step_1", run="./my_file.py", hints={"hint1": "value1"})
86
+ wf.add_step(id="step_2", run="./my_file_2.py")
87
+ wf.add_dependency("step_1", "step_2", dep_name="step_1_out")
88
+ wf.add_workflow_input("input_1", extension_fields={"additional_field": "het"})
89
+ wf.add_workflow_output("output", outputSource="step_1/step_1_out")
90
+ wf.add_dependency("input_1", "step_2")
91
+ wf.add_dependency("input_1", "step_1")
92
+
93
+ wf.to_yaml("wf2.yaml")
94
+ ```
95
+
96
+ See : https://cwl-utils.readthedocs.io/en/latest/autoapi/cwl_utils/parser/cwl_v1_2/index.html#cwl_utils.parser.cwl_v1_2.WorkflowStep for the list of supported arguments.
@@ -0,0 +1,9 @@
1
+ README.md
2
+ pyproject.toml
3
+ src/graph2cwl/__init__.py
4
+ src/graph2cwl/creator.py
5
+ src/graph2cwl.egg-info/PKG-INFO
6
+ src/graph2cwl.egg-info/SOURCES.txt
7
+ src/graph2cwl.egg-info/dependency_links.txt
8
+ src/graph2cwl.egg-info/requires.txt
9
+ src/graph2cwl.egg-info/top_level.txt
@@ -0,0 +1 @@
1
+ cwl-utils>=0.40
@@ -0,0 +1 @@
1
+ graph2cwl