Skip to content

Commit fe0f1b7

Browse files
jmchiltonclaude
andcommitted
Fix mypy errors: enable pydantic plugin, fix type mismatches
Enable pydantic.mypy plugin to resolve alias kwargs (type_, in_, format_version). Fix type narrowing with asserts, use concrete comment types, proper model constructors (NativeStepInput, NativeReport), typed POST_JOB_ACTIONS, creator conversion. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent bbcf98a commit fe0f1b7

File tree

8 files changed

+67
-27
lines changed

8 files changed

+67
-27
lines changed

gxformat2/_labels.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ def __init__(self):
1212
self.seen_labels = set()
1313
self.anonymous_labels = 0
1414

15-
def ensure_new_output_label(self, label: str):
15+
def ensure_new_output_label(self, label: str | None):
1616
"""Ensure supplied label has value or generate an anonymous one."""
1717
if label is None:
1818
self.anonymous_labels += 1

gxformat2/normalized/_expanded.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ class ExpandedWorkflowStep(NormalizedWorkflowStep):
3434
class ExpandedFormat2(NormalizedFormat2):
3535
"""Format2 workflow with all references expanded."""
3636

37-
steps: list[ExpandedWorkflowStep] = Field(default_factory=list)
37+
steps: list[ExpandedWorkflowStep] = Field(default_factory=list) # type: ignore[assignment]
3838

3939

4040
class ExpandedNativeStep(NormalizedNativeStep):
@@ -46,7 +46,7 @@ class ExpandedNativeStep(NormalizedNativeStep):
4646
class ExpandedNativeWorkflow(NormalizedNativeWorkflow):
4747
"""Native workflow with all subworkflow references resolved."""
4848

49-
steps: dict[str, ExpandedNativeStep] = Field(default_factory=dict)
49+
steps: dict[str, ExpandedNativeStep] = Field(default_factory=dict) # type: ignore[assignment]
5050

5151

5252
ExpandedWorkflowStep.model_rebuild()
@@ -180,7 +180,7 @@ def _expand_native(wf: NormalizedNativeWorkflow, ctx: _ExpansionContext) -> Expa
180180
expanded_steps[key] = ExpandedNativeStep(**step_data, subworkflow=expanded_sub)
181181

182182
# Expand subworkflows dict too
183-
expanded_subworkflows: dict[str, ExpandedNativeWorkflow] | None = None
183+
expanded_subworkflows: dict[str, NormalizedNativeWorkflow] | None = None
184184
if wf.subworkflows:
185185
expanded_subworkflows = {k: _expand_native(v, ctx) for k, v in wf.subworkflows.items()}
186186

gxformat2/normalized/_format2.py

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@
1515
from pydantic import BaseModel, ConfigDict, Field
1616

1717
from gxformat2.schema.gxformat2 import (
18-
BaseComment,
1918
CreatorOrganization,
2019
CreatorPerson,
2120
FrameComment,
@@ -126,6 +125,7 @@ def normalized_format2(
126125
workflow = from_galaxy_native(workflow)
127126
elif "$graph" in workflow and "class" not in workflow:
128127
workflow = _resolve_graph(workflow)
128+
assert isinstance(workflow, dict)
129129
# Migrate legacy 'name' to 'label'
130130
if "name" in workflow and "label" not in workflow:
131131
workflow = {**workflow, "label": workflow["name"]}
@@ -137,6 +137,7 @@ def normalized_format2(
137137
if "steps" not in workflow:
138138
workflow = {**workflow, "steps": {}}
139139
workflow = GalaxyWorkflow.model_validate(workflow)
140+
assert isinstance(workflow, GalaxyWorkflow)
140141
return _normalize_workflow(workflow)
141142

142143

@@ -309,6 +310,7 @@ def _normalize_step(step: WorkflowStep) -> NormalizedWorkflowStep:
309310
label=step.label,
310311
doc=_join_doc(step.doc),
311312
type_=step.type_,
313+
in_=in_list,
312314
tool_id=step.tool_id,
313315
tool_version=step.tool_version,
314316
tool_shed_repository=step.tool_shed_repository,
@@ -319,7 +321,6 @@ def _normalize_step(step: WorkflowStep) -> NormalizedWorkflowStep:
319321
runtime_inputs=step.runtime_inputs,
320322
errors=step.errors,
321323
uuid=step.uuid,
322-
in_=in_list,
323324
out=out_list,
324325
run=run,
325326
)
@@ -398,16 +399,16 @@ def _normalize_step_outputs(
398399

399400

400401
def _normalize_comments(
401-
comments: list[BaseComment] | dict[str, BaseComment] | None,
402+
comments: list[Format2Comment] | dict[str, Format2Comment] | None,
402403
) -> list[Format2Comment]:
403404
if comments is None:
404405
return []
405406
if isinstance(comments, list):
406-
return comments
407+
return list(comments)
407408

408409
result: list[Format2Comment] = []
409410
for key, comment in comments.items():
410-
if isinstance(comment, BaseComment):
411+
if isinstance(comment, (TextComment, MarkdownComment, FrameComment, FreehandComment)):
411412
if comment.label is None:
412413
comment = comment.model_copy(update={"label": key})
413414
result.append(comment)
@@ -463,6 +464,7 @@ def _inline_graph_refs(workflow: dict[str, Any], lookup: dict[str, dict[str, Any
463464
continue
464465
run = step.get("run")
465466
if _is_graph_id_reference(run):
467+
assert isinstance(run, str)
466468
ref_id = run[1:]
467469
if ref_id not in lookup:
468470
raise Exception(f"$graph reference '{run}' not found in graph entries")

gxformat2/normalized/_native.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,7 @@ def normalized_native(
135135
workflow = json.load(f)
136136
if isinstance(workflow, dict):
137137
workflow = load_native(workflow, strict=False)
138+
assert isinstance(workflow, NativeGalaxyWorkflow)
138139
return _normalize_workflow(workflow)
139140

140141

@@ -164,7 +165,7 @@ def _normalize_workflow(wf: NativeGalaxyWorkflow) -> NormalizedNativeWorkflow:
164165
logo_url=wf.logo_url,
165166
doi=wf.doi,
166167
source_metadata=wf.source_metadata,
167-
comments=[c.model_dump(by_alias=True) for c in wf.comments] if wf.comments else [],
168+
comments=list(wf.comments) if wf.comments else [],
168169
steps=steps,
169170
subworkflows=subworkflows,
170171
)
@@ -187,6 +188,7 @@ def _normalize_step(step: NativeStep) -> NormalizedNativeStep:
187188
id=step.id or 0,
188189
name=step.name,
189190
type_=step.type_,
191+
in_=step.in_,
190192
label=step.label,
191193
annotation=step.annotation,
192194
when=step.when,
@@ -207,5 +209,4 @@ def _normalize_step(step: NativeStep) -> NormalizedNativeStep:
207209
post_job_actions=step.post_job_actions or {},
208210
subworkflow=subworkflow,
209211
tool_representation=step.tool_representation,
210-
in_=step.in_,
211212
)

gxformat2/to_format2.py

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,6 @@
3131
)
3232
from .options import ConversionOptions
3333
from .schema.gxformat2 import (
34-
BaseComment,
3534
CreatorOrganization,
3635
CreatorPerson,
3736
FrameComment,
@@ -361,8 +360,8 @@ def _build_pick_value_format2_step(
361360
label=display_label,
362361
doc=step.annotation or None,
363362
type_=WorkflowStepType.pick_value,
364-
state=state,
365363
in_=in_list,
364+
state=state,
366365
out=out_list,
367366
position=_convert_position(step.position) if not compact else None,
368367
)
@@ -465,7 +464,7 @@ def _to_source(output_name: str, label_map: dict[str, str], step_id: int) -> str
465464
return f"{output_label}/{output_name}"
466465

467466

468-
_CREATOR_CLASS_MAP: dict[str, type] = {
467+
_CREATOR_CLASS_MAP: dict[str, type[CreatorPerson] | type[CreatorOrganization]] = {
469468
"Person": CreatorPerson,
470469
"Organization": CreatorOrganization,
471470
}
@@ -484,7 +483,7 @@ def _convert_creators(
484483
return result
485484

486485

487-
_COMMENT_TYPE_MAP: dict[str, type[BaseComment]] = {
486+
_COMMENT_TYPE_MAP: dict[str, type[TextComment] | type[MarkdownComment] | type[FrameComment] | type[FreehandComment]] = {
488487
"text": TextComment,
489488
"markdown": MarkdownComment,
490489
"frame": FrameComment,

gxformat2/to_native.py

Lines changed: 44 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
import logging
1212
import uuid as uuid_mod
1313
from pathlib import Path
14-
from typing import Any, Literal, overload
14+
from typing import Any, Callable, Literal, overload, TypedDict
1515

1616
from ._labels import Labels
1717
from .model import (
@@ -36,15 +36,22 @@
3636
)
3737
from .options import ConversionOptions
3838
from .schema.gxformat2 import (
39-
BaseComment,
39+
FrameComment,
40+
FreehandComment,
4041
GalaxyWorkflow,
42+
MarkdownComment,
43+
TextComment,
4144
WorkflowInputParameter,
4245
WorkflowOutputParameter,
4346
WorkflowStepOutput,
4447
)
4548
from .schema.native import (
49+
NativeCreatorOrganization,
50+
NativeCreatorPerson,
4651
NativeInputConnection,
4752
NativePostJobAction,
53+
NativeReport,
54+
NativeStepInput,
4855
NativeStepType,
4956
NativeWorkflowOutput,
5057
StepPosition,
@@ -53,7 +60,13 @@
5360

5461
log = logging.getLogger(__name__)
5562

56-
POST_JOB_ACTIONS = {
63+
class _PJADef(TypedDict):
64+
action_class: str
65+
default: Any
66+
arguments: Callable[..., dict[str, Any]]
67+
68+
69+
POST_JOB_ACTIONS: dict[str, _PJADef] = {
5770
"hide": {
5871
"action_class": "HideDatasetAction",
5972
"default": False,
@@ -180,7 +193,7 @@ def __init__(self, options: ConversionOptions):
180193

181194
def step_id(self, label_or_id: str | int) -> int:
182195
if label_or_id in self.labels:
183-
return self.labels[label_or_id]
196+
return self.labels[label_or_id] # type: ignore[index]
184197
return int(label_or_id)
185198

186199
def step_output(self, value: str) -> tuple[int, str]:
@@ -212,6 +225,8 @@ def _build_native_workflow(
212225
# Convert comments
213226
comments = _build_native_comments(wf.comments, ctx)
214227

228+
native_creators = _convert_creators_to_native(wf.creator) if wf.creator else None
229+
report = NativeReport(markdown=wf.report.markdown) if wf.report else None
215230
return NormalizedNativeWorkflow(
216231
a_galaxy_workflow="true",
217232
format_version="0.1",
@@ -221,8 +236,8 @@ def _build_native_workflow(
221236
tags=wf.tags,
222237
license=wf.license,
223238
release=wf.release,
224-
creator=wf.creator,
225-
report={"markdown": wf.report.markdown} if wf.report else None,
239+
creator=native_creators,
240+
report=report,
226241
steps=native_steps,
227242
comments=comments,
228243
)
@@ -244,7 +259,7 @@ def _build_input_step(
244259
else:
245260
multiple = False
246261

247-
type_str = input_type.value if hasattr(input_type, "value") else str(input_type) if input_type else "data"
262+
type_str = input_type.value if input_type is not None and hasattr(input_type, "value") else str(input_type) if input_type else "data"
248263
if type_str in ("File", "data", "data_input"):
249264
step_type = NativeStepType.data_input
250265
elif type_str in ("collection", "data_collection", "data_collection_input"):
@@ -290,13 +305,13 @@ def _build_input_step(
290305
return NormalizedNativeStep(
291306
id=order_index,
292307
type_=step_type,
308+
in_=in_,
293309
label=label,
294310
name=raw_label,
295311
annotation=_join_doc(inp.doc) or "",
296312
tool_state=tool_state,
297313
position=position,
298-
inputs=[{"name": raw_label, "description": ""}],
299-
in_=in_,
314+
inputs=[NativeStepInput(name=raw_label, description="")],
300315
)
301316

302317

@@ -482,7 +497,7 @@ def _build_pause_step(
482497
annotation=step.doc or "",
483498
tool_state={"name": name},
484499
input_connections=input_connections,
485-
inputs=[{"name": name, "description": ""}],
500+
inputs=[NativeStepInput(name=name, description="")],
486501
position=position,
487502
uuid=step.uuid,
488503
)
@@ -516,7 +531,7 @@ def _build_pick_value_step(
516531
tool_state=tool_state,
517532
input_connections=input_connections,
518533
post_job_actions=post_job_actions,
519-
inputs=[{"name": name, "description": ""}],
534+
inputs=[NativeStepInput(name=name, description="")],
520535
position=position,
521536
uuid=step.uuid,
522537
)
@@ -636,7 +651,7 @@ def _wire_workflow_outputs(
636651

637652

638653
def _build_native_comments(
639-
comments: list[BaseComment],
654+
comments: list[TextComment | MarkdownComment | FrameComment | FreehandComment],
640655
ctx: _ConversionContext,
641656
) -> list:
642657
"""Convert Format2 comments to native format."""
@@ -726,6 +741,23 @@ def _default_position(position: Any, order_index: int) -> StepPosition:
726741
return StepPosition(left=10 * order_index, top=10 * order_index)
727742

728743

744+
_NATIVE_CREATOR_MAP: dict[str, type[NativeCreatorPerson] | type[NativeCreatorOrganization]] = {
745+
"Person": NativeCreatorPerson,
746+
"Organization": NativeCreatorOrganization,
747+
}
748+
749+
750+
def _convert_creators_to_native(
751+
creators: list,
752+
) -> list[NativeCreatorPerson | NativeCreatorOrganization]:
753+
result: list[NativeCreatorPerson | NativeCreatorOrganization] = []
754+
for c in creators:
755+
d = c.model_dump(by_alias=True, exclude_none=True) if hasattr(c, "model_dump") else c
756+
cls = _NATIVE_CREATOR_MAP.get(d.get("class", ""), NativeCreatorPerson)
757+
result.append(cls.model_validate(d))
758+
return result
759+
760+
729761
def _join_doc(doc: str | list[str] | None) -> str | None:
730762
if doc is None:
731763
return None

setup.cfg

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
[mypy]
22
ignore_missing_imports = true
3+
plugins = pydantic.mypy
4+
5+
[pydantic-mypy]
6+
init_typed = true
37

48
[nosetests]
59
verbosity=1

uv.lock

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)