Skip to content

Commit 5860b72

Browse files
Merge pull request #8 from InformaticsMatters/prototype-work-alan
More development
2 parents d197845 + de1a509 commit 5860b72

File tree

9 files changed

+263
-25
lines changed

9 files changed

+263
-25
lines changed

tests/job-definitions/job-definitions.yaml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,3 +116,11 @@ jobs:
116116
type: string
117117
default: clustered.sdf
118118
pattern: "^[A-Za-z0-9_/\\.\\-]+\\.(smi|sdf)$"
119+
120+
rdkit-molprops:
121+
command: >-
122+
addcol.py --inputFile {{ inputFile }} --outputFile {{ outputFile }} --name {{ name }} --value {{ value }}
123+
124+
cluster-butina:
125+
command: >-
126+
addcol.py --inputFile {{ inputFile }} --outputFile {{ outputFile }} --name {{ name }} --value {{ value }}

tests/jobs/addcol.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import argparse
2+
3+
parser = argparse.ArgumentParser(
4+
prog="addcol",
5+
description="Takes a SMILES string and writes it to a file",
6+
)
7+
parser.add_argument("-i", "--inputFile", required=True)
8+
parser.add_argument("-o", "--outputFile", required=True)
9+
parser.add_argument("-n", "--name", required=True)
10+
parser.add_argument("-v", "--value", required=True)
11+
args = parser.parse_args()
12+
13+
with open(args.inputFile, "rt", encoding="utf8") as input_file:
14+
content = input_file.read()
15+
with open(args.outputFile, "wt", encoding="utf8") as output_file:
16+
output_file.write(content)

tests/test_decoder.py

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,13 @@
2323
_SHORTCUT_EXAMPLE_1_WORKFLOW: Dict[str, Any] = yaml.safe_load(workflow_file)
2424
assert _SHORTCUT_EXAMPLE_1_WORKFLOW
2525

26+
_SIMPLE_PYTHON_MOLPROPS_WORKFLOW_FILE: str = os.path.join(
27+
os.path.dirname(__file__), "workflow-definitions", "simple-python-molprops.yaml"
28+
)
29+
with open(_SIMPLE_PYTHON_MOLPROPS_WORKFLOW_FILE, "r", encoding="utf8") as workflow_file:
30+
_SIMPLE_PYTHON_MOLPROPS_WORKFLOW: Dict[str, Any] = yaml.safe_load(workflow_file)
31+
assert _SIMPLE_PYTHON_MOLPROPS_WORKFLOW
32+
2633

2734
def test_validate_minimal():
2835
# Arrange
@@ -78,3 +85,56 @@ def test_validate_shortcut_example_1():
7885

7986
# Assert
8087
assert error is None
88+
89+
90+
def test_validate_python_simple_molprops():
91+
# Arrange
92+
93+
# Act
94+
error = decoder.validate_schema(_SIMPLE_PYTHON_MOLPROPS_WORKFLOW)
95+
96+
# Assert
97+
assert error is None
98+
99+
100+
def test_get_workflow_variables():
101+
# Arrange
102+
103+
# Act
104+
wf_variables = decoder.get_variable_names(_SIMPLE_PYTHON_MOLPROPS_WORKFLOW)
105+
106+
# Assert
107+
assert len(wf_variables) == 1
108+
assert "candidateMolecules" in wf_variables
109+
110+
111+
def test_get_workflow_description():
112+
# Arrange
113+
114+
# Act
115+
description = decoder.get_description(_SIMPLE_PYTHON_MOLPROPS_WORKFLOW)
116+
117+
# Assert
118+
assert description == "A simple python experimental workflow"
119+
120+
121+
def test_get_workflow_name():
122+
# Arrange
123+
124+
# Act
125+
name = decoder.get_name(_SIMPLE_PYTHON_MOLPROPS_WORKFLOW)
126+
127+
# Assert
128+
assert name == "python-workflow"
129+
130+
131+
def test_get_workflow_steps():
132+
# Arrange
133+
134+
# Act
135+
steps = decoder.get_steps(_SIMPLE_PYTHON_MOLPROPS_WORKFLOW)
136+
137+
# Assert
138+
assert len(steps) == 2
139+
assert steps[0]["name"] == "step1"
140+
assert steps[1]["name"] == "step2"

tests/test_workflow_engine_examples.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -214,3 +214,34 @@ def test_workflow_engine_shortcut_example_1(basic_engine):
214214
# This test should generate a file in the simulated project directory
215215
assert project_file_exists(output_file_a)
216216
assert project_file_exists(output_file_b)
217+
218+
219+
@pytest.mark.skip(reason="The engine does not currently create the required variables")
220+
def test_workflow_engine_simple_python_molprops(basic_engine):
221+
# Arrange
222+
da, md = basic_engine
223+
# Make sure files that should be generated by the test
224+
# do not exist before we run the test.
225+
output_file_a = "a.sdf"
226+
assert not project_file_exists(output_file_a)
227+
output_file_b = "b.sdf"
228+
assert not project_file_exists(output_file_b)
229+
230+
# Act
231+
r_wfid = start_workflow(
232+
md, da, "simple-python-molprops", {"candidateMolecules": "C"}
233+
)
234+
235+
# Assert
236+
wait_for_workflow(da, r_wfid)
237+
# Additional, detailed checks...
238+
# Check we only have one RunningWorkflowStep, and it succeeded
239+
response = da.get_running_workflow_steps(running_workflow_id=r_wfid)
240+
assert response["count"] == 2
241+
assert response["running_workflow_steps"][0]["done"]
242+
assert response["running_workflow_steps"][0]["success"]
243+
assert response["running_workflow_steps"][1]["done"]
244+
assert response["running_workflow_steps"][1]["success"]
245+
# This test should generate a file in the simulated project directory
246+
assert project_file_exists(output_file_a)
247+
assert project_file_exists(output_file_b)

tests/test_workflow_validator_for_create_level.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,3 +102,23 @@ def test_validate_shortcut_example_1():
102102
# Assert
103103
assert error.error_num == 0
104104
assert error.error_msg is None
105+
106+
107+
def test_validate_simple_python_molprops():
108+
# Arrange
109+
workflow_file: str = os.path.join(
110+
os.path.dirname(__file__), "workflow-definitions", "simple-python-molprops.yaml"
111+
)
112+
with open(workflow_file, "r", encoding="utf8") as workflow_file:
113+
workflow: dict[str, Any] = yaml.load(workflow_file, Loader=yaml.FullLoader)
114+
assert workflow
115+
116+
# Act
117+
error = WorkflowValidator.validate(
118+
level=ValidationLevel.CREATE,
119+
workflow_definition=workflow,
120+
)
121+
122+
# Assert
123+
assert error.error_num == 0
124+
assert error.error_msg is None
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
---
2+
kind: DataManagerWorkflow
3+
kind-version: "2024.1"
4+
name: python-workflow
5+
description: A simple python experimental workflow
6+
variables:
7+
inputs:
8+
- name: candidateMolecules
9+
type: squonk/x-smiles
10+
outputs:
11+
- name: clusteredMolecules
12+
from:
13+
- step: step2
14+
output: outputFile
15+
as: clustered-molecules.smi
16+
17+
steps:
18+
- name: step1
19+
description: Add column 1
20+
specification: >-
21+
{
22+
"collection": "workflow-engine-unit-test-jobs",
23+
"job": "rdkit-molprops",
24+
"version": "1.0.0",
25+
"variables": {
26+
"name": "col1",
27+
"value": 123
28+
}
29+
}
30+
inputs:
31+
- input: inputFile
32+
from:
33+
workflow-input: candidateMolecules
34+
outputs:
35+
- output: outputFile
36+
as: __step1__out.smi
37+
38+
- name: step2
39+
Description: Add column 2
40+
specification: >-
41+
{
42+
"collection": "workflow-engine-unit-test-jobs",
43+
"job": "cluster-butina",
44+
"version":"1.0.0",
45+
"variables": {
46+
"name":"col2",
47+
"value":"999"
48+
}
49+
}
50+
inputs:
51+
- input: inputFile
52+
from:
53+
step: step1
54+
output: outputFile
55+
outputs:
56+
- output: outputFile
57+
as: __step2__out.smi

workflow/decoder.py

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,11 +52,24 @@ def get_steps(definition: dict[str, Any]) -> list[dict[str, Any]]:
5252
return response
5353

5454

55-
def get_workflow_name(definition: dict[str, Any]) -> str:
55+
def get_name(definition: dict[str, Any]) -> str:
5656
"""Given a Workflow definition this function returns its name."""
5757
return str(definition.get("name", ""))
5858

5959

60-
def get_workflow_description(definition: dict[str, Any]) -> str | None:
60+
def get_description(definition: dict[str, Any]) -> str | None:
6161
"""Given a Workflow definition this function returns its description (if it has one)."""
6262
return definition.get("description")
63+
64+
65+
def get_variable_names(definition: dict[str, Any]) -> list[str]:
66+
"""Given a Workflow definition this function returns all the names of the
67+
variables defined at the workflow level."""
68+
wf_variable_names: set[str] = set()
69+
variables: dict[str, Any] | None = definition.get("variables")
70+
if variables:
71+
for input_variable in variables.get("inputs", []):
72+
name: str = input_variable["name"]
73+
assert name not in wf_variable_names
74+
wf_variable_names.add(name)
75+
return list(wf_variable_names)

workflow/workflow-schema.yaml

Lines changed: 44 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,13 @@ properties:
2727
type: array
2828
items:
2929
$ref: "#/definitions/step"
30+
variables:
31+
type: object
32+
properties:
33+
inputs:
34+
type: array
35+
items:
36+
$ref: "#/definitions/workflow-input-parameter"
3037
required:
3138
- kind
3239
- kind-version
@@ -46,21 +53,35 @@ definitions:
4653
A value compatible with Kubernetes variables
4754
to allow it to be used ins Pod Label
4855
49-
variable-name:
56+
parameter-name:
5057
type: string
51-
pattern: ^[a-zA-Z_][a-zA-Z0-9_]{,63}$
52-
description: >-
53-
A Job/Step variable name, as used in the Data Manager Job Specification
58+
pattern: ^[a-zA-Z_][a-zA-Z0-9_-]*$
5459

55-
# Declaration of a step from anotehr step
60+
workflow-input-parameter:
61+
type: object
62+
properties:
63+
name:
64+
$ref: '#/definitions/parameter-name'
65+
66+
# Declaration of a value from a workflow input (variable)
67+
from-workflow-input:
68+
type: object
69+
additionalProperties: false
70+
properties:
71+
workflow-input:
72+
$ref: '#/definitions/parameter-name'
73+
required:
74+
- workflow-input
75+
76+
# Declaration of a value from another step
5677
from-step-output:
5778
type: object
5879
additionalProperties: false
5980
properties:
6081
step:
6182
$ref: '#/definitions/rfc1035-label-name'
6283
output:
63-
$ref: '#/definitions/variable-name'
84+
$ref: '#/definitions/parameter-name'
6485
required:
6586
- step
6687
- output
@@ -71,22 +92,33 @@ definitions:
7192
additionalProperties: false
7293
properties:
7394
input:
74-
$ref: '#/definitions/variable-name'
95+
$ref: '#/definitions/parameter-name'
7596
from:
7697
$ref: '#/definitions/from-step-output'
7798
required:
7899
- input
79100

101+
step-input-from-workflow:
102+
type: object
103+
additionalProperties: false
104+
properties:
105+
input:
106+
$ref: '#/definitions/parameter-name'
107+
from:
108+
$ref: '#/definitions/from-workflow-input'
109+
required:
110+
- input
111+
80112
# A Step output (with an 'as' - a declared value)
81113
step-output-as:
82114
type: object
83115
additionalProperties: false
84116
properties:
85117
output:
86-
$ref: '#/definitions/variable-name'
118+
$ref: '#/definitions/parameter-name'
87119
as:
88120
type: string
89-
description: The value to set the variable to
121+
description: The value to set the parameter to
90122
required:
91123
- output
92124
- as
@@ -105,12 +137,13 @@ definitions:
105137
inputs:
106138
type: array
107139
items:
108-
oneOf:
140+
anyOf:
109141
- $ref: "#/definitions/step-input-from-step"
142+
- $ref: "#/definitions/step-input-from-workflow"
110143
outputs:
111144
type: array
112145
items:
113-
oneOf:
146+
anyOf:
114147
- $ref: "#/definitions/step-output-as"
115148
required:
116149
- name

0 commit comments

Comments
 (0)