Turning existing functions into nodes

If you already have a module of functions, you do not need to put @node above each one. Two calls make nodes in bulk:

  • nodes_from_module for your own functions that have type hints.

  • nodes_from_ufuncs for numpy’s element-wise functions (ufuncs).

Both return a Scan: its nodes go straight into App, and its skipped lists the functions that could not become nodes, each with the reason. A function that does not fit is never an error.

Your own module

Say stats.py holds plain functions:

def zscore(x: float, mean: float, std: float) -> float:
    return (x - mean) / std


def percent(part: float, whole: float) -> float:
    return 100.0 * part / whole

One call makes both into nodes, in the Stats flyout of the add-node menu:

import element_51
import stats

scan = element_51.nodes_from_module(stats, category="Stats")
app = element_51.App(scan.nodes)
element_51.serve(app)

The functions are not changed. stats.zscore(82, 70, 8) still returns 1.5, so the same module keeps working in the rest of your code.

The ports follow the hints, exactly as with @node: each parameter is an input, and the return type is the output out (or one output per field of a pydantic model). The hints can be float, int, str or bool.

What the scan looks at:

  • Names that start with an underscore are left out, and so are names the module imported from somewhere else.

  • If the module has an __all__, only the names in it are scanned.

  • A function already made a node with @node, and a Node subclass, join the result as they are.

  • A second name for the same function (plus = add) makes no second node.

  • Two different functions with one node name: the later one is skipped.

A function with no type hint on a parameter or on the result, with *args or **kwargs, or with a hint that is not one of the four types is skipped. The reason names the parameter:

for item in scan.skipped:
    print(item.name, item.reason)

The example examples/h_wrapping/h2_own_module.py scans its own file and prints the one function it has to skip.

numpy’s functions

numpy’s ufuncs (sin, add, hypot, isnan, and so on) describe themselves, so no code has to be written for them:

import numpy as np

scan = element_51.nodes_from_ufuncs(np, category="Numpy")
app = element_51.App(scan.nodes)

On numpy 2.5 this makes 85 nodes. For each ufunc:

  • The inputs come from the number of arguments: x for one, x1 and x2 for two.

  • The outputs come from the number of results: out, or out1 and out2 for functions such as modf, which splits a number into its fractional and whole parts.

  • The port types come from the loops numpy lists for the function. The scan takes the first loop that works on float64 only, then the first on int64 only, then the first left. So sin takes a float and gives a float, isnan takes a float and gives a bool, and gcd takes two ints.

  • A number you give a port is turned into the port’s type before the call, so a whole number on a float port cannot overflow in an integer loop. The result is a plain float, int or bool.

A ufunc with two names, such as acos and arccos, makes one node, named by the ufunc itself (arccos). Five are skipped on numpy 2.5: matmul, matvec, vecdot and vecmat work on whole arrays, and isnat works on dates only.

Dividing by zero gives inf and taking the log of a negative number gives nan, as in numpy, without a warning. The page shows both as null.

Four nodes have a limit you may notice: logical_and, logical_or, logical_xor and logical_not take floats, and an output of type bool cannot be linked to a float input, so isnan cannot feed logical_not.

The example examples/h_wrapping/h1_numpy_ufuncs.py serves all 85 and a small graph of five.

What this does not do yet

  • Arrays. Every port carries one number or one text, so these nodes work on single values. A port for arrays of any shape is the next goal.

  • numpy functions that are not ufuncs, such as np.mean or np.clip. They have no type hints that Python can read at run time, so a scan cannot tell what they take. Wrap them in a typed function of your own and scan that.

  • Classes and methods.