Skip to content

Weird behaviour in typehinting/type return when using pwf.api.inputs_to* #703

@ligerzero-ai

Description

@ligerzero-ai

Consider:

from pyiron_workflow import Workflow
import pyiron_workflow as pwf
from pyiron_workflow_lammps.lammps import LammpsInput
from typing import Any, List
wf0 = Workflow("test_mod_dataclass", delete_existing_savefiles=True)
lmp_input = LammpsInput(
    read_data_file="lammps.data",
    pair_coeff=(
        "*", "*",
        "/root/github_dev/test_workflow_nodes/2025_04_29_FeGB_Segregation_Workflows/final_model",
        "Fe"
    ),
    dump_every=10,
    dump_filename="dump.out",
    thermo_every=10,
    min_style="cg",
    etol=0.00001,
    ftol=0.00001,
    maxiter=1_000_000,
    maxeval=1_000_000,
)
# from typing import Any, List
# @pwf.as_function_node("collected_list")
# def collect_to_list(*args: Any) -> List[Any]:
#     """
#     Collects any number of positional arguments into a list.

#     Example:
#         >>> collect_to_list(1, 'foo', True, [3,4])
#         [1, 'foo', True, [3, 4]]
#     """
#     return list(args)

from pyiron_workflow_atomistics.utils import modify_dataclass_multi
@pwf.as_function_node("modded_dataclass")
def modify_dataclass(dataclass_instance, entry_name: str, entry_value: Any):
    from dataclasses import asdict
    from copy import deepcopy
    kwarg_dict = {entry_name: entry_value}
    data = deepcopy(asdict(dataclass_instance))   # deep-copies nested containers
    bad  = set(kwarg_dict) - data.keys()
    if bad:
        raise KeyError(f"Unknown field(s): {sorted(bad)}")

    data.update(**kwarg_dict)
    dataclass_instance = type(dataclass_instance)(**data)
    # re-construct a brand-new instance from the dict
    return dataclass_instance
@pwf.as_function_node("modded_dataclass_multi")
def modify_dataclass_multi(dataclass_instance: Any, entry_names: list[str], entry_values: list[Any]):
    """
    Wraps your single-entry node so you can pass lists of names & values.
    Usage:
      new = modify_dataclass_multi(old, ["a","b"], [1,2])
    """
    if len(entry_names) != len(entry_values):
        raise ValueError("entry_names and entry_values must have the same length")

    ds = dataclass_instance
    for name, val in zip(entry_names, entry_values):
        ds = modify_dataclass.node_function(ds, name, val)
    return ds
wf0.modify_to_rigid_keys_list = pwf.api.inputs_to_list(2, "maxiter", "maxeval")
wf0.modify_to_rigid_values_list = pwf.api.inputs_to_list(2, 0, 0)
wf0.rigid_LammpsInput = modify_dataclass_multi(lmp_input, entry_names= wf0.modify_to_rigid_keys_list, entry_values= wf0.modify_to_rigid_values_list)
wf0.run()

Will fail with:

---------------------------------------------------------------------------
ChannelConnectionError                    Traceback (most recent call last)
Cell In[7], line 61
     59 wf0.modify_to_rigid_keys_list = pwf.api.inputs_to_list(2, "maxiter", "maxeval")
     60 wf0.modify_to_rigid_values_list = pwf.api.inputs_to_list(2, 0, 0)
---> 61 wf0.rigid_LammpsInput = modify_dataclass_multi(lmp_input, entry_names= wf0.modify_to_rigid_keys_list, entry_values= wf0.modify_to_rigid_values_list)
     62 wf0.run()

File ~/miniconda3/envs/pyiron_workflow/lib/python3.12/site-packages/pyiron_workflow/node.py:312, in Node.__init__(self, label, parent, delete_existing_savefiles, autoload, autorun, checkpoint, *args, **kwargs)
    309 # A place for power-users to bypass node-injection
    311 self._setup_node()
--> 312 self._after_node_setup(
    313     *args,
    314     delete_existing_savefiles=delete_existing_savefiles,
    315     autoload=autoload,
    316     autorun=autorun,
    317     **kwargs,
    318 )

File ~/miniconda3/envs/pyiron_workflow/lib/python3.12/site-packages/pyiron_workflow/node.py:357, in Node._after_node_setup(self, delete_existing_savefiles, autoload, autorun, *args, **kwargs)
    354             self.load(backend=autoload)
    355             break
--> 357 self.set_input_values(*args, **kwargs)
    359 if autorun:
    360     with contextlib.suppress(ReadinessError):

File ~/miniconda3/envs/pyiron_workflow/lib/python3.12/site-packages/pyiron_workflow/io.py:447, in HasIO.set_input_values(self, *args, **kwargs)
    444 self._ensure_all_input_keys_present(kwargs.keys(), self.inputs.labels)
    446 for k, v in kwargs.items():
--> 447     self.inputs[k] = v

File ~/miniconda3/envs/pyiron_workflow/lib/python3.12/site-packages/pyiron_workflow/io.py:118, in IO.__setitem__(self, key, value)
    117 def __setitem__(self, key: str, value: Any) -> None:
--> 118     self.__setattr__(key, value)

File ~/miniconda3/envs/pyiron_workflow/lib/python3.12/site-packages/pyiron_workflow/io.py:94, in IO.__setattr__(self, key, value)
     92 def __setattr__(self, key: str, value: Any) -> None:
     93     if key in self.channel_dict:
---> 94         self._assign_value_to_existing_channel(self.channel_dict[key], value)
     95     elif isinstance(value, self._channel_class):
     96         if key != value.label:

File ~/miniconda3/envs/pyiron_workflow/lib/python3.12/site-packages/pyiron_workflow/io.py:110, in IO._assign_value_to_existing_channel(self, channel, value)
    108 def _assign_value_to_existing_channel(self, channel: OwnedType, value: Any) -> None:
    109     if isinstance(value, HasChannel):
--> 110         channel.connect(value.channel)
    111     else:
    112         self._assign_a_non_channel_value(channel, value)

File ~/miniconda3/envs/pyiron_workflow/lib/python3.12/site-packages/pyiron_workflow/channels.py:147, in Channel.connect(self, *others)
    145         other.connections.insert(0, self)
    146     else:
--> 147         raise ChannelConnectionError(
    148             self._connection_conjugate_failure_message(other)
    149         ) from None
    150 else:
    151     raise TypeError(
    152         f"Can only connect to {self.connection_conjugate()} "
    153         f"objects, but {self.full_label} ({self.__class__}) "
    154         f"got {other} ({type(other)})"
    155     )

ChannelConnectionError: The channel /test_mod_dataclass/modify_to_rigid_keys_list.list (<class 'pyiron_workflow.mixin.injection.OutputDataWithInjection'>) has the correct type (<class 'pyiron_workflow.channels.OutputData'>) to connect with /modify_dataclass_multi.entry_names (<class 'pyiron_workflow.channels.InputData'>), but is not a valid connection.Please check type hints, etc. /test_mod_dataclass/modify_to_rigid_keys_list.list.type_hint = <class 'list'>; /modify_dataclass_multi.entry_names.type_hint = list[str]

Whereas removal of the typehints from:

def modify_dataclass_multi(dataclass_instance: Any, entry_names: List, entry_values: List):

into

def modify_dataclass_multi(dataclass_instance: Any, entry_names, entry_values):

Will allow successful execution

Metadata

Metadata

Assignees

Labels

bugSomething isn't working

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions