Module Lifecycle¶
A node, group, or graph (a module) goes through the following steps in its lifetime:
Definition: The module type is defined in Python code which the Python interpreter reads into memory.
Construction: We construct an instance of the module. If the module is a graph, then the user explicitly constructs a graph like
graph = MyGraph()
. Otherwise, a module is implicitly constructed by its containing graph when that graph is constructed.Setup: As a LabGraph graph is starting up,
setup()
is called on every module. If a group contains some other modules, the group’ssetup()
will always be called before those modules’setup()
s are called. During thesetup()
call, the module can prepare itself for execution. If the module is a group, then it can also configure the modules it contains - see Configuration.Execution: When the graph is ready, LabGraph will begin execution of all modules simultaneously.
Cleanup: When all modules have terminated, LabGraph will run
cleanup()
on every module.cleanup()
will be called in the same order thatsetup()
was called.
Configuration¶
LabGraph provides a way to concisely specify configuration that is given to a graph and forwarded to its descendant nodes. We start by defining a configuration type:
class MyNodeConfig(df.Config):
num_trials: int
participant_name: str
Config
classes actually just Message
classes, except that they also provide special utilities for configuring graphs, as you’ll see.
We specify the configuration for a Node
, Group
, or Graph
by giving it a config
type annotation:
class MyNode(df.Node):
config: MyNodeConfig
...
Then we can configure it by calling configure()
on it. We configure a node or group in the setup()
method of its containing group:
class MyGroupConfig(df.Config):
...
class MyGroup(df.Config):
config: MyGroupConfig
MY_NODE: MyNode
def setup(self) -> None:
# Cascade config to MY_NODE based on the
# group's config
my_node_config = MyNodeConfig(...)
self.MY_NODE.configure(my_node_config)
The exception to this is graphs, which we can construct and configure directly:
my_graph = MyGraph()
my_graph.configure(MyGraphConfig(...))
Rather than hard-coding the top-level configuration like this, though, we can automatically build the configuration from from command-line arguments like so:
my_config = MyGraphConfig.fromargs()
The df.run
function actually gets configuration using fromargs()
, so we can also just run a graph using command-line arguments like so:
df.run(MyGraph)