Skip to content

Commit e3554e7

Browse files
jmchiltonclaude
andcommitted
Fix workflow lint crash when tool_state is dict instead of JSON string
Some .ga workflows have tool_state as a dict rather than a JSON-encoded string. Handle both forms to avoid json.loads TypeError. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent f51705c commit e3554e7

3 files changed

Lines changed: 120 additions & 1 deletion

File tree

planemo/workflow_lint.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -269,7 +269,11 @@ def check_json_for_untyped_params(j):
269269
tool_state = step.get("tool_state", {})
270270
pjas = step.get("out", {})
271271
else:
272-
tool_state = json.loads(step.get("tool_state", "{}"))
272+
raw_tool_state = step.get("tool_state", {})
273+
if isinstance(raw_tool_state, str):
274+
tool_state = json.loads(raw_tool_state)
275+
else:
276+
tool_state = raw_tool_state
273277
pjas = step.get("post_job_actions", {})
274278

275279
if check_json_for_untyped_params(tool_state):
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
{
2+
"a_galaxy_workflow": "true",
3+
"annotation": "",
4+
"format-version": "0.1",
5+
"name": "bp (imported from uploaded file)",
6+
"steps": {
7+
"0": {
8+
"annotation": "",
9+
"content_id": null,
10+
"errors": null,
11+
"id": 0,
12+
"input_connections": {},
13+
"inputs": [],
14+
"label": null,
15+
"name": "Input dataset",
16+
"outputs": [],
17+
"position": {
18+
"bottom": 730.3166656494141,
19+
"height": 82.55000305175781,
20+
"left": 1155,
21+
"right": 1355,
22+
"top": 647.7666625976562,
23+
"width": 200,
24+
"x": 1155,
25+
"y": 647.7666625976562
26+
},
27+
"tool_id": null,
28+
"tool_state": {"optional": false},
29+
"tool_version": null,
30+
"type": "data_input",
31+
"uuid": "4219d43a-e182-49c0-bee3-8422e6344911",
32+
"workflow_outputs": []
33+
},
34+
"1": {
35+
"annotation": "",
36+
"content_id": "toolshed.g2.bx.psu.edu/repos/bgruening/text_processing/tp_replace_in_column/1.1.3",
37+
"errors": null,
38+
"id": 1,
39+
"input_connections": {},
40+
"inputs": [
41+
{
42+
"description": "runtime parameter for tool Replace Text",
43+
"name": "infile"
44+
}
45+
],
46+
"label": null,
47+
"name": "Replace Text",
48+
"outputs": [
49+
{
50+
"name": "outfile",
51+
"type": "input"
52+
}
53+
],
54+
"position": {
55+
"bottom": 744.8333282470703,
56+
"height": 114.06666564941406,
57+
"left": 1465,
58+
"right": 1665,
59+
"top": 630.7666625976562,
60+
"width": 200,
61+
"x": 1465,
62+
"y": 630.7666625976562
63+
},
64+
"post_job_actions": {
65+
"TagDatasetActionoutfile": {
66+
"action_arguments": {
67+
"tags": "${report_name}"
68+
},
69+
"action_type": "TagDatasetAction",
70+
"output_name": "outfile"
71+
}
72+
},
73+
"tool_id": "toolshed.g2.bx.psu.edu/repos/bgruening/text_processing/tp_replace_in_column/1.1.3",
74+
"tool_shed_repository": {
75+
"changeset_revision": "ddf54b12c295",
76+
"name": "text_processing",
77+
"owner": "bgruening",
78+
"tool_shed": "toolshed.g2.bx.psu.edu"
79+
},
80+
"tool_state": {"infile": {"__class__": "RuntimeValue"}, "replacements": [{"__index__": 0, "column": "1", "find_pattern": "${report_name}", "replace_pattern": ""}], "__page__": null, "__rerun_remap_job_id__": null},
81+
"tool_version": "1.1.3",
82+
"type": "tool",
83+
"uuid": "e429e21f-f01e-4efb-b665-56f454cbe38b",
84+
"workflow_outputs": [
85+
{
86+
"label": null,
87+
"output_name": "outfile",
88+
"uuid": "e4066fe7-c9a2-43af-a9f5-30a75a5b2107"
89+
}
90+
]
91+
}
92+
},
93+
"tags": [],
94+
"uuid": "66708ffe-c08c-4647-a7e9-fc7f731206a1",
95+
"version": 2
96+
}

tests/test_cmd_workflow_lint.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,25 @@ def test_best_practices_linting_ga(self):
171171
for warning in warnings:
172172
assert warning in result.output
173173

174+
def test_best_practices_linting_ga_dict_tool_state(self):
175+
workflow_path = "/".join((TEST_DATA_DIR, "wf14-unlinted-best-practices-dict-tool-state.ga"))
176+
lint_cmd = ["workflow_lint", workflow_path]
177+
result = self._runner.invoke(self._cli.planemo, lint_cmd)
178+
179+
warnings = [
180+
"Workflow is not annotated.",
181+
"Workflow does not specify a creator.",
182+
"Workflow does not specify a license.",
183+
"Workflow step with ID 0 has no annotation.",
184+
"Workflow step with ID 0 has no label.",
185+
"Workflow missing test cases.",
186+
"Workflow step with ID 1 specifies an untyped parameter as an input.",
187+
"Workflow step with ID 1 specifies an untyped parameter in the post-job actions.",
188+
]
189+
190+
for warning in warnings:
191+
assert warning in result.output
192+
174193
def test_author_identifier_best_practices_linting_ga(self):
175194
workflow_path = "/".join((TEST_DATA_DIR, "wf19-unlinted-author-identifier-best-practices.ga"))
176195
lint_cmd = ["workflow_lint", workflow_path]

0 commit comments

Comments
 (0)