Module Lifecycle

A node, group, or graph (a module) goes through the following steps in its lifetime:

  1. Definition: The module type is defined in Python code which the Python interpreter reads into memory.

  2. 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.

  3. Setup: As a LabGraph graph is starting up, setup() is called on every module. If a group contains some other modules, the group’s setup() will always be called before those modules’ setup()s are called. During the setup() 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.

  4. Execution: When the graph is ready, LabGraph will begin execution of all modules simultaneously.

  5. Cleanup: When all modules have terminated, LabGraph will run cleanup() on every module. cleanup() will be called in the same order that setup() 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)