How-to guides

These guides cover configuration loading, process control, and integrations.

How to load YAML with Hydra

Create config/worker.yaml:

# @package _global_
working_dir: /var/tmp/worker-supervisor
port: "127.0.0.1:9001"
program:
  worker:
    command: python /opt/jobs/worker.py
    environment:
      MODE: production

Load it relative to the calling script:

from supervisor_pydantic import SupervisorConvenienceConfiguration

config = SupervisorConvenienceConfiguration.load("config", "worker")

Pass Hydra overrides through overrides, for example overrides=["program.worker.environment.MODE=staging"].

How to manage a supervisord instance

Install Supervisor, write the configuration, and start the daemon:

pip install supervisor
config.write()
config.start(daemon=True)

if config.running():
    config.stop()

Call kill() only when graceful shutdown does not complete. Call rmdir() only after the instance has stopped.

How to replace a running instance’s configuration

Create a convenience configuration with the same working directory and config path as the existing instance. Apply it through the convenience commands:

from supervisor_pydantic.convenience import start_programs, start_supervisor, write_supervisor_config

if not write_supervisor_config(config.model_dump_json(), _exit=False):
    raise RuntimeError("Supervisor did not stop; existing configuration was preserved")
if not start_supervisor(config, _exit=False):
    raise RuntimeError("Supervisor did not start")
if not start_programs(config, _exit=False):
    raise RuntimeError("Programs did not start")

Plan for an interruption: a changed configuration stops supervisord and its managed processes before replacing the files. Keep the existing supervisord.conf until the convenience command applies the new settings. Unchanged configurations leave the running daemon in place. If graceful shutdown times out, resolve the shutdown failure before retrying; the files remain intact.

How to control programs over XML-RPC

Create a client from the same convenience configuration:

from supervisor_pydantic import SupervisorRemoteXMLRPCClient

client = SupervisorRemoteXMLRPCClient(config)
for process in client.getAllProcessInfo():
    print(process.name, process.state)

client.stopProcess("worker")
client.startProcess("worker")

Set host, protocol, port, username, and password for remote access. Restrict the HTTP server at the network layer because it controls processes.

How to forward program output incrementally

Create an XML-RPC client from your local or remote configuration. Keep a byte cursor for each process and output channel, and reuse the returned offset on the next poll:

import logging

from supervisor_pydantic import SupervisorRemoteXMLRPCClient

client = SupervisorRemoteXMLRPCClient(config)
log = logging.getLogger(__name__)
cursors = {}


def forward_logs():
    received = False
    for process in client.getProgramProcessInfo():
        name = f"{process.group}:{process.name}"
        for channel in ("stdout", "stderr"):
            key = (name, channel)
            chunk = client.readProcessLogChunk(name, channel, offset=cursors.get(key, 0))
            cursors[key] = chunk.offset
            if chunk.truncated:
                log.warning("%s %s log shrank; reading from start", name, channel)
            if chunk.text:
                received = True
                log.info("%s %s: %s", name, channel, chunk.text.rstrip("\n"))
    return received

Call forward_logs() during monitoring. After stopping workloads, keep calling it until it returns False before restarting or removing their files. To forward only future output, initialize each cursor with client.getProcessLogSize(name, channel) before starting the workload. Refer to the log cursor reference for rotation limits.

How to check workloads while an event listener runs

Use getProgramProcessInfo() when checking modeled programs. Use getAllProcessInfo() when inspecting the daemon, including event listeners:

from supervisor_pydantic.convenience import check_programs, start_programs, stop_programs

start_programs(config, _exit=False)
finished = check_programs(config, check_done=True, _exit=False)
stop_programs(config, _exit=False)

The convenience commands start, check, and stop configured workloads while leaving event listeners running. Stop the supervisord instance after stopping programs when you also want to shut down listeners.

How to use the model with airflow-config

Use the Airflow task model supplied by airflow-supervisor:

dags:
  nightly-supervisor:
    schedule: "@daily"
    tasks:
      run-job:
        _target_: airflow_supervisor.SupervisorTask
        cfg:
          working_dir: /var/tmp/nightly-supervisor
          port: "127.0.0.1:9001"
          program:
            nightly:
              command: python /opt/jobs/nightly.py

Refer to the airflow-supervisor tutorial for the generated lifecycle and Airflow installation extras.

How to use the convenience CLI

The installed _supervisor_convenience command exposes the lifecycle used by external orchestrators:

_supervisor_convenience --help

Commands configure and remove files, start and stop supervisord, and start, check, restart, or stop all configured programs. Use the API reference for the corresponding Python callables.