1515from pathlib import Path
1616from typing import Any , Literal , NamedTuple , Union
1717
18- from pydantic import BaseModel , ConfigDict , Field , field_validator
18+ from pydantic import BaseModel , ConfigDict , Field , field_validator , SerializeAsAny
1919from typing_extensions import TypeAlias
2020
2121from gxformat2 .schema .gxformat2 import (
22+ BaseInputParameter ,
2223 CreatorOrganization ,
2324 CreatorPerson ,
2425 FrameComment ,
2526 FreehandComment ,
2627 GalaxyWorkflow ,
28+ input_parameter_class ,
2729 MarkdownComment ,
2830 Report ,
2931 StepPosition ,
@@ -176,7 +178,7 @@ class NormalizedFormat2(_DictMixin, BaseModel):
176178 class_ : Literal ["GalaxyWorkflow" ] = Field (default = "GalaxyWorkflow" , alias = "class" )
177179 label : str | None = Field (default = None )
178180 doc : str | None = Field (default = None , description = "Annotation, joined if originally a list." )
179- inputs : list [WorkflowInputParameter ] = Field (
181+ inputs : list [SerializeAsAny [ BaseInputParameter ] ] = Field (
180182 default_factory = list , description = "Always a list, shorthands expanded."
181183 )
182184 outputs : list [WorkflowOutputParameter ] = Field (default_factory = list , description = "Always a list." )
@@ -266,6 +268,7 @@ def normalized_format2(
266268 if "steps" not in workflow :
267269 workflow = {** workflow , "steps" : {}}
268270 workflow = _pre_clean_steps (workflow )
271+ workflow = _pre_normalize_input_types (workflow )
269272 workflow = GalaxyWorkflow .model_validate (workflow )
270273 elif isinstance (workflow , GalaxyWorkflow ) and strict_structure :
271274 # Typed model input (e.g. nested sub-workflow recursion) -- dump and
@@ -325,20 +328,28 @@ def _normalize_input_type(value: Any) -> Any:
325328 return value
326329
327330
331+ def _validate_input_dict (d : dict [str , Any ]) -> BaseInputParameter :
332+ """Validate an input dict using the specific discriminated type."""
333+ type_val = d .get ("type" )
334+ if isinstance (type_val , list ):
335+ return WorkflowInputParameter .model_validate (d )
336+ return input_parameter_class (type_val ).model_validate (d )
337+
338+
328339def _normalize_inputs (
329- inputs : list [WorkflowInputParameter ] | dict [str , WorkflowInputParameter | str ] | dict [str , Any ],
330- ) -> list [WorkflowInputParameter ]:
340+ inputs : list [BaseInputParameter ] | dict [str , BaseInputParameter | str ] | dict [str , Any ] | Any ,
341+ ) -> list [BaseInputParameter ]:
331342 if isinstance (inputs , list ):
332- result = []
343+ result : list [ BaseInputParameter ] = []
333344 for inp in inputs :
334- if isinstance (inp , WorkflowInputParameter ):
345+ if isinstance (inp , BaseInputParameter ):
335346 result .append (inp )
336347 elif isinstance (inp , dict ):
337348 if "type" in inp :
338349 inp = {** inp , "type" : _normalize_input_type (inp ["type" ])}
339- result .append (WorkflowInputParameter . model_validate (inp ))
350+ result .append (_validate_input_dict (inp ))
340351 else :
341- result .append (WorkflowInputParameter . model_validate (inp ))
352+ result .append (_validate_input_dict (inp ))
342353 return result
343354
344355 # Dict form — keys are ids, values are WorkflowInputParameter, type string, or dict
@@ -347,8 +358,8 @@ def _normalize_inputs(
347358 if isinstance (value , str ):
348359 # Shorthand: input_name: "data"
349360 normalized_type = _normalize_input_type (value )
350- result .append (WorkflowInputParameter . model_validate ({ "id" : key , "type" : normalized_type } ))
351- elif isinstance (value , WorkflowInputParameter ):
361+ result .append (input_parameter_class ( normalized_type )( id = key , type_ = normalized_type ))
362+ elif isinstance (value , BaseInputParameter ):
352363 if value .id is None :
353364 value = value .model_copy (update = {"id" : key })
354365 result .append (value )
@@ -359,9 +370,9 @@ def _normalize_inputs(
359370 value = {** value , "type" : _normalize_input_type (value ["type" ])}
360371 if "format" in value and isinstance (value ["format" ], str ):
361372 value = {** value , "format" : [value ["format" ]]}
362- result .append (WorkflowInputParameter . model_validate (value ))
373+ result .append (_validate_input_dict (value ))
363374 else :
364- result .append (WorkflowInputParameter (id = key ))
375+ result .append (input_parameter_class ( None ) (id = key ))
365376 return result
366377
367378
@@ -392,6 +403,33 @@ def _normalize_outputs(
392403 return result
393404
394405
406+ def _pre_normalize_input_types (workflow : dict [str , Any ]) -> dict [str , Any ]:
407+ """Normalize input type aliases (File → data, etc.) before discriminator runs.
408+
409+ The discriminated union on ``Process.inputs`` routes based on the raw
410+ ``type`` field, so alias normalization must happen before model validation.
411+ """
412+ inputs = workflow .get ("inputs" )
413+ if inputs is None :
414+ return workflow
415+
416+ def norm_entry (entry : Any ) -> Any :
417+ if isinstance (entry , dict ) and "type" in entry :
418+ return {** entry , "type" : _normalize_input_type (entry ["type" ])}
419+ if isinstance (entry , str ):
420+ return _normalize_input_type (entry )
421+ return entry
422+
423+ new_inputs : dict [str , Any ] | list [Any ]
424+ if isinstance (inputs , dict ):
425+ new_inputs = {k : norm_entry (v ) for k , v in inputs .items ()}
426+ elif isinstance (inputs , list ):
427+ new_inputs = [norm_entry (v ) for v in inputs ]
428+ else :
429+ return workflow
430+ return {** workflow , "inputs" : new_inputs }
431+
432+
395433def _pre_clean_steps (workflow : dict [str , Any ]) -> dict [str , Any ]:
396434 """Resolve ``$link`` entries in step state dicts before model validation.
397435
0 commit comments