From 04673bbe160e8e52025d2ad022d4c344ec79122a Mon Sep 17 00:00:00 2001 From: Leonardo Scappatura <95079472+Leonard013@users.noreply.github.com> Date: Sun, 12 Jul 2026 07:08:44 +0200 Subject: [PATCH] [FIX] Assign recalculated node state to backing field in _create_graph (#845) `Workflow._create_graph` runs both over definition `Node`s (via `graph()`) and over `NodeExecution` wrappers (via `execution_graph()`). Both expose `state` as a read-only property, so recalculating a node's connection state with `node.state = state.State(...)` raised AttributeError: property 'state' of 'NodeExecution' object has no setter whenever a stateless node consumed the combined outputs of two upstream split/combine nodes -- e.g. nested workflows using `.split(...).combine(...)`. Assign to the `_state` backing field instead. For definition `Node`s this sets the state exactly as before. For the transient `NodeExecution` wrapper the write is inert, which is the correct behaviour: the underlying node's state is already finalised during construction, and overwriting it in the execution-graph pass mis-splits the node and produces empty results. Adds a regression test that reproduces the reported nested split/combine scenario (fails with AttributeError before, passes after). Fixes #845 Co-authored-by: Claude --- pydra/compose/tests/test_workflow_run.py | 35 ++++++++++++++++++++++++ pydra/engine/workflow.py | 2 +- 2 files changed, 36 insertions(+), 1 deletion(-) diff --git a/pydra/compose/tests/test_workflow_run.py b/pydra/compose/tests/test_workflow_run.py index 7c34bdb4d..05609d819 100644 --- a/pydra/compose/tests/test_workflow_run.py +++ b/pydra/compose/tests/test_workflow_run.py @@ -867,6 +867,41 @@ def Worky(x, y): assert outputs.out == [11, 12, 22, 24, 33, 36] +def test_wf_ndst_combine_two_upstreams(worker: str, tmp_path: Path): + """Regression test for GH#845. + + A node with no splitter of its own that takes inputs from two separate + upstream nodes which have each been split and combined used to raise + ``AttributeError: property 'state' of 'NodeExecution' object has no setter`` + while building the execution graph (``Workflow._create_graph``). + """ + + @python.define + def Step1(a: int) -> int: + return a + + @python.define + def Step2(a: int, b: int) -> int: + return a + b + + @python.define + def Step3(a: list, b: list) -> list: + return a + + @workflow.define + def Worky(a: list): + step1 = workflow.add(Step1().split(a=a).combine("a")) + step2 = workflow.add(Step2().split(("a", "b"), a=a, b=step1.out).combine("a")) + step3 = workflow.add(Step3(a=step2.out, b=step1.out)) + return step3.out + + worky = Worky(a=[1, 2, 3]) + + outputs = worky(worker=worker, cache_root=tmp_path) + + assert outputs.out == [2, 4, 6] + + # workflows with structures A -> B -> C diff --git a/pydra/engine/workflow.py b/pydra/engine/workflow.py index c9e2d151f..8207c1782 100644 --- a/pydra/engine/workflow.py +++ b/pydra/engine/workflow.py @@ -388,7 +388,7 @@ def _create_graph( new_other_states=other_states, new_combiner=combiner ) else: - node.state = state.State( + node._state = state.State( node.name, splitter=None, other_states=other_states,