2. Develop a CLI with server and client code execution

We’ve learned how to create a simple command-line interface. But in real-world use-cases, an App Builder wants to provide more complex functionalities where trusted code is executed on the client side.

Lightning provides a flexible way to create complex CLI without much effort.

In this example, we’ll create a CLI to dynamically run Notebooks:


1. Implement a complex CLI

First of all, lets’ create the following file structure:

app_folder/
    commands/
        notebook/
            run.py
    app.py

We’ll use the Jupyter-Component. Follow the installation steps on the repo to install the Component.

Add the following code to commands/notebook/run.py:

from argparse import ArgumentParser
from uuid import uuid4

from pydantic import BaseModel

from lightning.app.utilities.commands import ClientCommand


class RunNotebookConfig(BaseModel):
    name: str
    cloud_compute: str


class RunNotebook(ClientCommand):
    description = "Run a Notebook."

    def run(self):
        # 1. Define your own argument parser. You can use argparse, click, etc...
        parser = ArgumentParser(description='Run Notebook Parser')
        parser.add_argument("--name", type=str, default=None)
        parser.add_argument("--cloud_compute", type=str, default="cpu")
        hparams = parser.parse_args()

        # 2. Invoke the server side handler by sending a payload.
        response = self.invoke_handler(
            config=RunNotebookConfig(
                name=hparams.name or str(uuid4()),
                cloud_compute=hparams.cloud_compute,
            ),
        )

        # 3. Print the server response.
        print(response)

Add the following code to app.py:

from commands.notebook.run import RunNotebook, RunNotebookConfig
from lit_jupyter import JupyterLab

from lightning.app import LightningFlow, LightningApp, CloudCompute
from lightning.app.structures import Dict


class Flow(LightningFlow):
    def __init__(self):
        super().__init__()
        self.notebooks = Dict()

    # 1. Annotates the handler input with the Notebook config.
    def run_notebook(self, config: RunNotebookConfig):
        if config.name in self.notebooks:
            return f"The Notebook {config.name} already exists."
        else:
            # 2. Dynamically creates the Notebook if it doesn't exist and runs it.
            self.notebooks[config.name] = JupyterLab(
                cloud_compute=CloudCompute(config.cloud_compute)
            )
            self.notebooks[config.name].run()
            return f"The Notebook {config.name} was created."

    def configure_commands(self):
        # 3. Returns a list of dictionaries with the format:
        # {"command_name": CustomClientCommand(method=self.custom_server_handler)}
        return [{"run notebook": RunNotebook(method=self.run_notebook)}]

    def configure_layout(self):
        # 4. Dynamically displays the Notebooks in the Lightning App View.
        return [{"name": n, "content": w} for n, w in self.notebooks.items()]


app = LightningApp(Flow())

2. Run the App and check the API documentation

In a terminal, run the following command and open http://127.0.0.1:7501/docs in a browser.

lightning_app run app app.py
Your Lightning App is starting. This won't take long.
INFO: Your app has started. View it in your browser: http://127.0.0.1:7501/view

3. Connect to a running App

In another terminal, connect to the running App. When you connect to an App, the Lightning CLI is replaced by the App CLI. To exit the App CLI, you need to run lightning_app disconnect.

lightning_app connect localhost

Storing `run_notebook` under /Users/thomas/.lightning/lightning_connection/commands/run_notebook.py
You can review all the downloaded commands under /Users/thomas/.lightning/lightning_connection/commands folder.
You are connected to the local Lightning App.

To see a list of available commands:

lightning_app --help

You are connected to the cloud Lightning App: localhost.
Usage: lightning_app [OPTIONS] COMMAND [ARGS]...

--help     Show this message and exit.

Lightning App Commands
    run notebook Run a Notebook.

To find the arguments of the commands:

lightning_app run notebook --help

You are connected to the cloud Lightning App: localhost.
usage: notebook [-h] [--name NAME] [--cloud_compute CLOUD_COMPUTE]

Run Notebook Parser

optional arguments:
    -h, --help            show this help message and exit
    --name NAME
    --cloud_compute CLOUD_COMPUTE

4. Execute a command

And then you can trigger the command-line exposed by your App.

Run the first Notebook with the following command:

lightning_app run notebook --name="my_notebook"
WARNING: Lightning Command Line Interface is an experimental feature and unannounced changes are likely.
The notebook my_notebook was created.

And run a second notebook.

lightning_app run notebook --name="my_notebook_2"
WARNING: Lightning Command Line Interface is an experimental feature and unannounced changes are likely.
The notebook my_notebook_2 was created.

Here is a recording of the Lightning App:

5. Disconnect from the App

To exit the App CLI, you need to run lightning disconnect.

lightning_app disconnect
You are disconnected from the local Lightning App.