I'm in the process of getting rid of the source_code field in the pydantic models1, I was looking around for places where it's used. Of course, it gets examined when looking for return fields from atomic nodes!
This led to me worrying that non-local symbols will be unreasonably allowed. In fact, for atomic nodes, even blatant lies are allowed!
from flowrep.models.parsers import atomic_parser, workflow_parser
foo = 42
def my_func(x):
y = x + 1
return y, foo, bar
n = atomic_parser.parse_atomic(my_func)
This parses fine! That foo is not local inside the function is actually, IMO, OK -- if there is some non-local defined to which the function has access, it is going to have access to it when we import and use the function.
That bar doesn't even exist is actually, IMO, also ok. The underlying philosophy this supports is that atomic nodes give node designers both full freedom (we don't peek inside to restrict you with validations) and full responsibility (we don't peek inside to restrict you with validations2). I like this, as it gives a release valve for node designers to have total control somewhere, and clearly delineates where you can expect us to hold your hand (workflows, yes; atomic nodes, no).
Now, if you try even the weaker foo form of this in a workflow definition, you immediately run into a pretty informative error:
def my_wf(a):
b, c, _ = my_func(a)
return b, c, foo
m = workflow_parser.parse_workflow(my_wf)
>>> ValueError: Return symbol 'foo' is not defined. Available: ['a', 'b', 'c', '_']
This is because in the workflow parsing we really walk through the symbols to build up the flow of data starting from the workflow function inputs, and there is, in this scope, no visible source for foo. So it safely fails.
If you were to write a simple analogous recipe, you get a very similar error. I'm too lazy to write out all the child node business, but the point is that trying to return "foo" output without having any source for it gives you a problem:
from flowrep.models.nodes import workflow_model
workflow_model.WorkflowNode.model_validate(
{
"type": "workflow",
"inputs": ["a"],
"outputs": ["b", "c", "foo"],
"nodes": {},
"input_edges": {},
"edges": {},
"output_edges": {
"b": "a",
"c": "a",
}
}
)
>>> Value error, Missing output edge for: {'foo'}
Ultimately, something like these points and examples should appear in the documentation for the pydantic models, so I wanted to get it written down while I was thinking about it.
I'm in the process of getting rid of the
source_codefield in the pydantic models1, I was looking around for places where it's used. Of course, it gets examined when looking for return fields from atomic nodes!This led to me worrying that non-local symbols will be unreasonably allowed. In fact, for atomic nodes, even blatant lies are allowed!
This parses fine! That
foois not local inside the function is actually, IMO, OK -- if there is some non-local defined to which the function has access, it is going to have access to it when we import and use the function.That
bardoesn't even exist is actually, IMO, also ok. The underlying philosophy this supports is that atomic nodes give node designers both full freedom (we don't peek inside to restrict you with validations) and full responsibility (we don't peek inside to restrict you with validations2). I like this, as it gives a release valve for node designers to have total control somewhere, and clearly delineates where you can expect us to hold your hand (workflows, yes; atomic nodes, no).Now, if you try even the weaker
fooform of this in a workflow definition, you immediately run into a pretty informative error:This is because in the workflow parsing we really walk through the symbols to build up the flow of data starting from the workflow function inputs, and there is, in this scope, no visible source for
foo. So it safely fails.If you were to write a simple analogous recipe, you get a very similar error. I'm too lazy to write out all the child node business, but the point is that trying to return
"foo"output without having any source for it gives you a problem:Ultimately, something like these points and examples should appear in the documentation for the pydantic models, so I wanted to get it written down while I was thinking about it.
Footnotes
At least transiently; I'm open to putting it back in the future. Recent conversations with @samwaseda have uncovered that naive raw source code is often going to be useless. If a robust/cleanly failing parser like Detect local arguments and functions used in a function #164 contributes to can be finished, we could validate the source and then include it. ↩
Ok, if you want to parse your recipe from a python function and you want to split up its output to multiple channels, then we do need to be able to peek inside and use ast to count up the number of return fields, and to inspect the signature to know about the possible input fields. But this is a parser constraint and not a fundamental recipe constraint. ↩