Skip to content

Process

process

Defines subprocess execution helpers for the launcher core

Overview
  • run → Executes an external command to completion

  • stream → Yields the combined stdout/stderr of an external command's execution line by line so a front-end can render live output

  • StreamHandle → An optional cancellation token a caller passes to stream to stop a long-running command (e.g. docker compose logs -f) on demand from another thread

  • Neither prints anything, presentation is the caller's job

StreamHandle

Cancellation token that stops a running stream from another thread

The Problem It Solves
  • stream follows a command's output by blocking on readline until the next line arrives

  • For a tailing command like docker compose logs -f, lines can be minutes apart, so the consumer thread sits parked inside that read

  • The only way to release the read is to make the command's output stream reach EOF, which means killing the process that holds it

  • That process is created inside stream and is not visible to whatever owns the stop control (a GUI button, a signal handler), which typically lives on a different thread

  • StreamHandle is the shared object that bridges the two. The caller creates it, hands it to stream, and later calls cancel to stop the command from the outside

How It Works With stream
  • The caller constructs a StreamHandle and passes it to stream

  • As soon as stream starts the subprocess it calls bind, giving the handle the live process to act on

  • When the caller invokes cancel, the whole process tree is killed (see _kill_process_tree). Its stdout closes, the blocked readline returns "", and the generator finishes normally

  • A killed process exits non-zero, so stream checks cancelled and skips raising CalledProcessError for that case. A deliberate stop is a normal end, not a command failure

Threading And Ordering
  • cancel is meant to be called from a different thread than the one running the generator

  • Killing the tree only signals OS processes, so this is safe

  • bind also covers the race condition where cancel is called before the process has started. The handle remembers the request and kills the tree the moment it is bound

Example
handle = StreamHandle()
gen = stream(["docker", "compose", "logs", "-f"], handle=handle)
# ... use `gen` on a worker thread, render each line ...
# ... from the UI thread, when the user clicks "Stop"
handle.cancel()

cancelled property

Whether a stop was requested through cancel

stream reads this after the process exits to decide whether a non-zero exit code is a real failure or the result of a deliberate stop

Returns:

Type Description
bool

True once cancel has been called, even if the process had not

bool

started yet at that moment

bind(process)

Hands the handle the live process started by stream

Called by stream right after it spawns the subprocess, so a later cancel has something to terminate. If cancel already ran before the process existed, the process is terminated immediately here so an early stop request is not lost

Parameters:

Name Type Description Default
process Popen[str]

The subprocess stream just started and is about to read from

required

cancel()

Requests a stop, terminating the streamed process if it is running

Marks the handle cancelled (so stream treats the resulting non-zero exit as a clean stop) and terminates the bound process. When the process has not started yet, bind performs the termination once it does. Safe to call from a thread other than the one using the generator

run(cmd, *, cwd=None, check=True)

Runs an external command in a subprocess to completion and captures its output

Parameters:

Name Type Description Default
cmd list[str]

The external command to run and its arguments

required
cwd Path | None

The directory from which to run the command. When set to None, defaults to the directory from which the current python proccess was started from.

None
check bool

Raises an exception on a non-zero exit status when True

True

Returns:

Type Description
CompletedProcess[str]

The completed process with the captured stdout

Raises:

Type Description
CalledProcessError

If the command fails and check=True

FileNotFoundError

If the executable is not found

stream(cmd, *, cwd=None, check=True, handle=None)

Runs an external command in a subprocess and yields its combined output (stdout + stderr) line by line

handle
  • When a StreamHandle is given, handle.cancel (typically from another thread) terminates the command early and the resulting non-zero exit is treated as a clean stop rather than a failure

  • See StreamHandle for more information

Parameters:

Name Type Description Default
cmd list[str]

The external command to run and its arguments

required
cwd Path | None

The directory from which to run the command. When set to None, defaults to the directory from which the current python proccess was started from.

None
check bool

Raises an exception on a non-zero exit status when True

True
handle StreamHandle | None

An optional cancellation token

None

Yields:

Type Description
str

Each stripped output line as it is produced

Returns:

Type Description
int

The command's exit code

Raises:

Type Description
CalledProcessError

If the command fails and check=True, unless the run was stopped through handle

FileNotFoundError

If the executable is not found