Terminal Subprocess with multiple arguments

Hello together,

I’m trying to use the subprocess element of the terminal widget to run a command with flags and arguments.

Here’s a minimal example:

#!/usr/bin/env python3

import panel as pn

pn.extension("terminal")

term = pn.widgets.Terminal("This is a basic terminal\n\n")
term.write("We will run ls -l\n")
term.subprocess.run("ls", "-l")
term.flush()
term.write("Now we run the next command: ls -l /bin\n")
term.subprocess.run("ls", "-l", "/bin")

pn.Row(term).servable()

The first command works as expected. In the second command, I get:

ValueError: Error. A child process is already running. Cannot start another.

Interesting, if I do this in a class (e.g. for a Pipeline), I get the same error, and I can then also no longer go back to a previous stage. The following example can be used. The first part is from the previous example, just commenting out the part that does not work:

#!/usr/bin/env python3

import panel as pn
import param

pn.extension("terminal")

term = pn.widgets.Terminal("This is a basic terminal\n\n")
term.write("We will run ls -l\n")
term.subprocess.run("ls", "-l")
# term.flush()
# term.write("Now we run the next command: ls -l /bin\n")
# term.subprocess.run("ls", "-l", "/bin")


class PipelineElement(param.Parameterized):
    def panel(self):
        return pn.pane.Markdown(f"# {self.__class__.__name__}")


class Stage1(PipelineElement):
    def panel(self):
        return pn.pane.Markdown("# Stage 1")


class Stage2(PipelineElement):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.term = pn.widgets.Terminal("This is a basic terminal\n\n")
        self.run_button = pn.widgets.Button(name="Run", button_type="primary")
        self.run_button.on_click(self.run)

    def run(self, event):
        self.term.write("We will run ls -l\n")
        self.term.subprocess.run("ls", "-l")
        self.term.flush()
        self.term.write("Now we run the next command: ls -l /bin\n")
        self.term.subprocess.run("ls", "-l", "/bin")

    def panel(self):
        return pn.Column(pn.pane.Markdown("# Stage 2"), self.run_button, self.term)


dag = pn.pipeline.Pipeline()
dag.add_stage("Stage1", Stage1)
dag.add_stage("Stage2", Stage2)
dag.define_graph({"Stage1": "Stage2"})

template = pn.template.VanillaTemplate(title="Terminals and Pipeline")
template.main.append(term)
template.main.append(dag)
template.servable()

I’ve tried following the online documentation, but somehow it isn’t working as expected.

The code is also available here as a GitHub Gist.

Any help would be greatly appreciated.

Cheers,
Paul