[FIX] Assign recalculated node state to backing field in _create_graph (#845) - #868
Open
Leonard013 wants to merge 1 commit into
Open
[FIX] Assign recalculated node state to backing field in _create_graph (#845)#868Leonard013 wants to merge 1 commit into
Leonard013 wants to merge 1 commit into
Conversation
nipype#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 nipype#845 Co-authored-by: Claude <noreply@anthropic.com>
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #868 +/- ##
==========================================
- Coverage 88.37% 87.13% -1.24%
==========================================
Files 88 88
Lines 18221 18240 +19
Branches 3572 3576 +4
==========================================
- Hits 16102 15893 -209
- Misses 1736 1959 +223
- Partials 383 388 +5 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
effigies
approved these changes
Jul 13, 2026
effigies
left a comment
Contributor
There was a problem hiding this comment.
Looks right to me. Thanks!
Contributor
|
@tclose I'm guessing these are related to an upstream change in fileformats? |
Contributor
|
Yes, probably. I’m away until Friday but will have a look at it then
Get Outlook for iOS<https://aka.ms/o0ukef>
…________________________________
From: Chris Markiewicz ***@***.***>
Sent: Tuesday, 14 July 2026 01:45:04
To: nipype/pydra ***@***.***>
Cc: Tom Close ***@***.***>; Mention ***@***.***>
Subject: Re: [nipype/pydra] [FIX] Assign recalculated node state to backing field in _create_graph (#845) (PR #868)
[https://avatars.githubusercontent.com/u/83442?s=20&v=4]effigies left a comment (nipype/pydra#868)<#868 (comment)>
@tclose<https://github.com/tclose> I'm guessing these are related to an upstream change in fileformats?
FAILED pydra/compose/shell/tests/test_shell_fields.py::test_interface_template_more_complex - fileformats.core.exceptions.FileFormatsExtrasNotImplementedError: No implementation for 'generate_sample_data' extra for FileMock types
FAILED pydra/compose/shell/tests/test_shell_fields.py::test_interface_template_with_defaults - fileformats.core.exceptions.FileFormatsExtrasNotImplementedError: No implementation for 'generate_sample_data' extra for FileMock types
FAILED pydra/engine/tests/test_result.py::test_copyfile_workflow_conflicting_filenames - fileformats.core.exceptions.FileFormatsExtrasNotImplementedError: No implementation for 'generate_sample_data' extra for TextFileMock types
FAILED pydra/utils/tests/test_hash.py::test_hash_file - AssertionError: assert 'a372c0b478b3f750b57a4e6a1c4042ce' == 'f32ab20c4a86616e32bf2504e1ac5a22'
- f32ab20c4a86616e32bf2504e1ac5a22
+ a372c0b478b3f750b57a4e6a1c4042ce
FAILED pydra/utils/tests/test_typing.py::test_typing_implicit_cast_from_super[func-func] - fileformats.core.exceptions.FileFormatsExtrasNotImplementedError: No implementation for 'generate_sample_data' extra for MyFormatXMock types
FAILED pydra/utils/tests/test_typing.py::test_typing_implicit_cast_from_super[func-shell] - fileformats.core.exceptions.FileFormatsExtrasNotImplementedError: No implementation for 'generate_sample_data' extra for MyFormatXMock types
FAILED pydra/utils/tests/test_typing.py::test_typing_implicit_cast_from_super[shell-func] - fileformats.core.exceptions.FileFormatsExtrasNotImplementedError: No implementation for 'generate_sample_data' extra for MyFormatXMock types
FAILED pydra/utils/tests/test_typing.py::test_typing_implicit_cast_from_super[shell-shell] - fileformats.core.exceptions.FileFormatsExtrasNotImplementedError: No implementation for 'generate_sample_data' extra for MyFormatXMock types
FAILED pydra/utils/tests/test_typing.py::test_typing_cast[func-func] - fileformats.core.exceptions.FileFormatsExtrasNotImplementedError: No implementation for 'generate_sample_data' extra for MyFormatXMock types
FAILED pydra/utils/tests/test_typing.py::test_typing_cast[func-shell] - fileformats.core.exceptions.FileFormatsExtrasNotImplementedError: No implementation for 'generate_sample_data' extra for MyFormatXMock types
FAILED pydra/utils/tests/test_typing.py::test_typing_cast[shell-func] - fileformats.core.exceptions.FileFormatsExtrasNotImplementedError: No implementation for 'generate_sample_data' extra for MyFormatXMock types
FAILED pydra/utils/tests/test_typing.py::test_typing_cast[shell-shell] - fileformats.core.exceptions.FileFormatsExtrasNotImplementedError: No implementation for 'generate_sample_data' extra for MyFormatXMock types
—
Reply to this email directly, view it on GitHub<#868?email_source=notifications&email_token=AAIBRFUOWXWAKDQXICZEKXL5ET7YBA5CNFSNUABFM5UWIORPF5TWS5BNNB2WEL2JONZXKZKDN5WW2ZLOOQXTIOJVHE3TSNRTGA4KM4TFMFZW63VHNVSW45DJN5XKKZLWMVXHJLDGN5XXIZLSL5RWY2LDNM#issuecomment-4959796308>, or unsubscribe<https://github.com/notifications/unsubscribe-auth/AAIBRFU5UOLYJBECE4M4BI35ET7YBAVCNFSNUABFKJSXA33TNF2G64TZHMYTKMRRGI3DQMJRHNEXG43VMU5TIOBWGU2TGNZZGIZKC5QC>.
You are receiving this because you were mentioned.Message ID: ***@***.***>
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #845
Types of changes
Why
Running a workflow in which a node without its own splitter consumes the
combined outputs of two separate upstream
.split(...).combine(...)nodescrashes while the execution graph is being built:
This is exactly the shape of the nested-workflow example in #845 (the inner
workflow's
step3consumesstep1.outandstep2.out, both combined).Root cause / which object owns the state
Workflow._create_graph(nodes)is polymorphic — it is called with definitionNodes viaWorkflow.graph()(visualisation) and withNodeExecutionwrappersvia
Workflow.execution_graph()(to run). Both classes exposestateas aread-only property:
Node.statereturns itsself._statebacking field (the true owner of the state);NodeExecution.statemerely delegates toself.node.state.Neither has a setter, so the connection-state recalculation at the bottom of
_create_graphfails as soon as the
elsebranch is reached with aNodeExecution— i.e. astateless node fed by (multiple) combined upstreams.
What this changes
One line: assign to the
_statebacking field instead of the read-onlystateproperty.
Why this is the correct target and not a new setter:
Node._state. During execution the underlyingNode's state has already been finalised inWorkflow.construct()(viaNode._set_state()); the execution-graph pass must not overwrite it.NodeExecutionis a transient execution wrapper with no_statefield anda read-only-by-design
.state(it mirrors its node). Writingnode._stateonit is therefore inert — exactly what we want during execution — while for a real
definition
Node(thegraph()path) it sets the state as the original codeintended. (Note:
Node.stateis also read-only, so the old line would havecrashed the visualisation path too; this fixes that latent crash as well.)
I explicitly rejected the "obvious" alternative — adding a
@state.settertoNodeExecutiondelegating toself.node.state. It removes the crash butsilently produces wrong results: it forces the freshly re-derived
Stateontothe underlying node, which then mis-splits over its already-combined upstreams (the
#845 example returns an empty list instead of
[2, 4, 6]). The_statefix keepsthe node's finalised state intact and yields correct output.
Note on the call site: reaching into
node._statehere is consistent with themethod's existing idiom (
_create_graphalready accessesnode._task), but it isadmittedly a minimal fix. A cleaner long-term direction would let
NodeExecutionown its recalculated state (or guard the assignment to definition
Nodes) so thecall site no longer touches a private field — happy to take that route if preferred.
Tests
Added
test_wf_ndst_combine_two_upstreamsinpydra/compose/tests/test_workflow_run.py, reproducing the reported nestedsplit/combine scenario (a stateless node consuming two combined upstreams).
FAILED—AttributeError: property 'state' of 'NodeExecution' object has no setteratworkflow.py:391.PASSEDon bothdebugandcfworkers;outputs.out == [2, 4, 6].The original #845 script (nested
Outer/Inner) now runs to completion andreturns
OuterOutputs(out=[2, 4, 6]).Regression sweep (no new failures)
test_state.py+test_task_state.py+test_graph.py: 303 passed, 1 xfailed (pre-existing)test_workflow_run.py+test_workflow_fields.py+test_lazy.py: 287 passed, 2 skipped, 4 xfailed (pre-existing)ndstsplit/combine slice + new test: 84 passedblack --checkclean on both changed filesChecklist