Signature API#

One of the key features of mmodel that differs from other workflow libraries is the ability to handle signatures. A signature is the set of input parameters of a callable. The signature handling in mmodel closely resembles the signature handling in the Python standard library modules. As a result, the syntax of running models has a very low entry barrier and allows minimal code change to use existing Python functions as nodes.

Signature handling and binding#

We can inspect the signature of a callable using the inspect module. We can also change the function signature with the module by assigning a new signature to the function’s __signature__ attribute. All nodes in a mmodel graph can be defined using Node object, and all function signatures are converted to positional-or-keyword arguments. Additionally, users can supply inputs to modify the underlying function signature.

from mmodel import Node

def foo(a, b, /):
    return a + b

node1 = Node('foo', foo)
node2 = Node('foo', foo, inputs=['d', 'e'])

>>> print(node1)
foo

foo(a, b)
return: c
functype: function

>>> print(node2)
foo

foo(d, e)
return: f
functype: function

In the above example, function foo has two positional-only arguments. The resulting node1 adjusts the signature to have two positional-or-keyword arguments, and node2 changes the signature to arguments with different names. The conversion is helpful for built-in functions that have only positional-only arguments, and NumPy’s ufunc that do not show proper signatures.

We can execute the node with keyword arguments.

>>> node1(1, b=2)
3

>>> node2(d=3, e=2)
5

Executing nodes and models directly as callable also shows proper error messages with incorrect inputs.

>>> node2(1, 2, 3)
TypeError: too many positional arguments

>>> node2(1, 2. c=3)
TypeError: got an unexpected keyword argument 'c'

>>> node2(1)
TypeError: missing a required argument: 'e'

Note

These argument checking and error messages are archived by using inspect module in the __call__ method for both Node and Model modules. However, to reduce the overhead, the internal handling of the parameters is keyword only. Internally, node.node_func is used to execute the node, and model.model_func is used to execute the model. The handling of the parameter flow relies on the Handler class. Therefore, direct execution of the node_func and model_func does not track the input parameters correctly by design.