Shortcuts

App Component Tree

Audience: Users who want to know how components can be composed with each other.

Level: Basic


What is an Application Component Tree?

Components can be nested to form component trees where the LightningFlows are its branches and LightningWorks are its leaves.

This design enables users to organize and maintain their code with more ease, but more importantly, this helps creating an ecosystem with re-usable components.

Here’s a basic application with four flows and two works (associated tree structure):

Basic App Components
import lightning as L
from lightning.app.testing.helpers import EmptyFlow, EmptyWork


class FlowB(L.LightningFlow):
    def __init__(self):
        super().__init__()
        self.flow_d = EmptyFlow()
        self.work_b = EmptyWork()

    def run(self):
        ...


class FlowA(L.LightningFlow):
    def __init__(self):
        super().__init__()
        self.flow_b = FlowB()
        self.flow_c = EmptyFlow()
        self.work_a = EmptyWork()

    def run(self):
        ...


app = L.LightningApp(FlowA())

A Lightning app runs all flows into a single process. Its flows coordinate the execution of the works each running in their own independent processes.


How do I define my application component tree?

In order to define your application component tree, you need create a tree of components and attach them to your root flow.

You can attach your components in the __init__ method of a flow.

import lightning as L


class RootFlow(L.LightningFlow):
    def __init__(self):
        super().__init__()
        # The `Work` component is attached here.
        self.work = Work()

        # The `NestedFlow` component is attached here.
        self.nested_flow = NestedFlow()

Once done, simply add the root flow to a Lightning app as follows:

app = L.LightningApp(RootFlow())

Is my application component tree static?

No, Lightning supports dynamic flows and works.

You can simply attach your components in the run method of a flow using the Python functions hasattr, setattr, and getattr.

class RootFlow(L.LightningFlow):
    def run(self):

        if not hasattr(self, "work"):
            # The `Work` component is attached here.
            setattr(self, "work", Work())
        # Run the `Work` component.
        getattr(self, "work").run()

        if not hasattr(self, "nested_flow"):
            # The `NestedFlow` component is attached here.
            setattr(self, "nested_flow", NestedFlow())
        # Run the `NestedFlow` component.
        getattr(self, "wonested_flowrk").run()

But it is usually more readable to use Lightning built-in Dict or List as follows:

from lightning_app.structures import Dict


class RootFlow(L.LightningFlow):
    def __init__(self):
        super().__init__()
        self.dict = Dict()

    def run(self):
        if "work" not in self.dict:
            # The `Work` component is attached here.
            self.dict["work"] = Work()
        self.dict["work"].run()

        if "nested_flow" not in self.dict:
            # The `NestedFlow` component is attached here.
            self.dict["nested_flow"] = NestedFlow()
        self.dict["nested_flow"].run()