Skip to content

Parsing attribute access#281

Merged
liamhuber merged 14 commits into
parser-qolfrom
getattr
Jul 14, 2026
Merged

Parsing attribute access#281
liamhuber merged 14 commits into
parser-qolfrom
getattr

Conversation

@liamhuber

@liamhuber liamhuber commented Jul 14, 2026

Copy link
Copy Markdown
Member

This PR lets the parsers interpret .-based attribute access on symbols by injecting get-attribute nodes from the standard library.

import flowrep as fr

@fr.atomic
class MyData:
    def __init__(self, val):
        self.val = val

@fr.workflow
def wf(has_listy_val):
    reconstructed_listy_val = []
    for x in has_listy_val.val:
        d = MyData(x)
        reconstructed_listy_val.append(d.val)
    return reconstructed_listy_val

fr.tools.run_recipe(
    wf.flowrep_recipe,
    has_listy_val=MyData([1,2,3,4])
).output_ports
>>> {'reconstructed_listy_val': OutputDataPort(value=[1, 2, 3, 4], annotation=None)}

These work pretty much anywhere with three restrictions:

  1. They've got to start with a known symbol, because we've got to start somewhere
  2. They can't be used as the workflow return, because we want the expected output label to be glaringly obvious
  3. You can't get the attribute of a while-looped variable, because the graph formulation will not manage the reference the way python does

The first two are super easy to understand. The third can be seen in the example below. The static.val call is fine, because it's going to have the same value for the whole loop. The looped.val call works fine in python, but in the graph manager we don't manage the reference the same way, and the connection between the looped.val and the new looped gets lost. Trying to bump into this fails at parsing time, clearly, and with a productive suggestion

def less_than(x, y):
    return x < y

def bump(data):
    data.val += 1
    return data

try:
    @fr.workflow
    def wf(looped, static):
        while less_than(looped.val, static.val):
            looped = bump(looped)
        val_out = looped.val
        return val_out
except ValueError as e:
    print(e)

While-condition attribute access 'looped.val' is rooted at 'looped', which the loop body reassigns.
Python would re-read ... looped.val' every iteration, but a flowrep while-condition is a single call fed
by hoisted inputs -- there is no place inside the loop for the attribute access. Either bind it outside
the loop (e.g. v = looped.val) if you meant to read it once, or move the attribute access into the
condition function itself.

We can bind it outside and redeclare it inside to make it loop as expected

@fr.workflow
def wf(looped, static):
    lv = looped.val
    while less_than(lv, static.val):
        looped = bump(looped)
        lv = looped.val
    val_out = looped.val
    return val_out

fr.tools.run_recipe(wf.flowrep_recipe, looped=MyData(3), static=MyData(6)).output_ports
>>> {'val_out': OutputDataPort(value=6, annotation=None)}

liamhuber and others added 14 commits July 13, 2026 11:10
This has still a critical flaw when parsing attributes of the same name from two different objects. I'm going to commit as-is and then see if Claude will work with this more like human review process.

Co-authored-by: Claude <noreply@anthropic.com>
Signed-off-by: Liam Huber <liamhuber@greyhavensolutions.com>
Co-authored-by: Claude <noreply@anthropic.com>
Signed-off-by: Liam Huber <liamhuber@greyhavensolutions.com>
Co-authored-by: Claude <noreply@anthropic.com>
Signed-off-by: Liam Huber <liamhuber@greyhavensolutions.com>
Co-authored-by: Claude <noreply@anthropic.com>
Signed-off-by: Liam Huber <liamhuber@greyhavensolutions.com>
This actually goes too far, I think we can relax it. But Claude's first pass being more relaxed had bugs, so let's tighten all the way down then loosen it up one case at a time. I'll tolerate the churn for a bit of precision.

Co-authored-by: Claude <noreply@anthropic.com>
Signed-off-by: Liam Huber <liamhuber@greyhavensolutions.com>
Co-authored-by: Claude <noreply@anthropic.com>
Signed-off-by: Liam Huber <liamhuber@greyhavensolutions.com>
Co-authored-by: Claude <noreply@anthropic.com>
Signed-off-by: Liam Huber <liamhuber@greyhavensolutions.com>
Co-authored-by: Claude <noreply@anthropic.com>
Signed-off-by: Liam Huber <liamhuber@greyhavensolutions.com>
Co-authored-by: Claude <noreply@anthropic.com>
Signed-off-by: Liam Huber <liamhuber@greyhavensolutions.com>
Co-authored-by: Claude <noreply@anthropic.com>
Signed-off-by: Liam Huber <liamhuber@greyhavensolutions.com>
Co-authored-by: Claude <noreply@anthropic.com>
Signed-off-by: Liam Huber <liamhuber@greyhavensolutions.com>
Co-authored-by: Claude <noreply@anthropic.com>
Signed-off-by: Liam Huber <liamhuber@greyhavensolutions.com>
Co-authored-by: Claude <noreply@anthropic.com>
Signed-off-by: Liam Huber <liamhuber@greyhavensolutions.com>
This isn't strictly a problem with attribute access, but attribute access is how I found it. We now use a new field on the symbol scope to keep pinned names unavailable more robustly across scopes

Co-authored-by: Claude <noreply@anthropic.com>
Signed-off-by: Liam Huber <liamhuber@greyhavensolutions.com>
@github-actions

Copy link
Copy Markdown

Binder 👈 Launch a binder notebook on branch pyiron/flowrep/getattr

@codecov

codecov Bot commented Jul 14, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 100.00%. Comparing base (281b054) to head (f1ea244).
⚠️ Report is 2 commits behind head on parser-qol.

Additional details and impacted files
@@              Coverage Diff              @@
##           parser-qol      #281    +/-   ##
=============================================
  Coverage      100.00%   100.00%            
=============================================
  Files              39        41     +2     
  Lines            3105      3286   +181     
=============================================
+ Hits             3105      3286   +181     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@liamhuber

Copy link
Copy Markdown
Member Author

My takeaway from this is that the difficult thing with injection is that one needs to be much, much more careful about tracking symbol availability in different scopes. I'm not thrilled with the extra parser complexity here to achieve this. However, here, as a workflow writer myself, I find I really, really want the feature. And here @XzzX confirms that he also expects workflow writers to want the same capability:

For code the user will always write my_dc_symbol.field.object_attr.subattr and not use unpacking.

So although I have some misgivings around the additional complexity, I'm going to proceed with merging it down into the landing pad under the conclusion that the functionality boost outweighs the complexity pain.

@liamhuber liamhuber merged commit 4ac147e into parser-qol Jul 14, 2026
22 checks passed
@liamhuber liamhuber deleted the getattr branch July 14, 2026 18:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant