flnr API Reference

flnr (le flâneur).

Small subprocess supervision harness for CI and automation code.

run_ex(cmd, *, env=None, cwd=None, merge_std_streams=None, timeouts=None, stdout_monitors=None, stderr_monitors=None, environment_monitors=None, stdin=None, check=True, host_termination=None, tracer=None)

Run an external program as a child process and supervise it.

This synchronous entry point blocks the caller until the direct child reaches a resolved final state and output monitoring has finished or timed out, or until supervision fails. The subprocess is executed directly, without invoking a shell. run_ex() must not be called from an existing async context.

The return value is a ProcessFate object describing the resolved outcome. When check=True (the default), a non-zero subprocess return code is reported as CommandFailedError. This includes subprocesses terminated by flnr after a run timeout or host-requested termination; inspect the exception’s fate field to distinguish the cause and final outcome. Monitor failures are reported as MonitorFailedError after process teardown completes. Unrecoverable supervision failures are reported as SupervisionFailedError. If process exit cannot be confirmed during teardown, ProcessKillFailedError is raised.

flnr supervises the direct child process only. It does not manage the full descendant process tree. Descendants may outlive the direct child, keep inherited file descriptors open, and continue producing output until output_drain expires.

With merge_std_streams=None, stdout handling receives stdout and, unless stderr handling is configured, stderr too. Configure stderr handling to keep stderr separate. Output handling means either output monitors or flnr.BIND_TO_PARENT. Parent-bound output is passed through instead of monitored. Streams with no output handling are connected to DEVNULL. Explicit merge_std_streams=True always merges stderr into stdout and rejects stderr_monitors. Explicit merge_std_streams=False keeps stdout and stderr separate.

Child stdin is connected to DEVNULL by default. Pass stdin=flnr.INHERIT_STDIN to inherit stdin from the parent process.

Pass tracer=flnr.CommandTracer(logger) to log a shell-oriented command recipe before process creation. Custom tracer objects must implement flnr.CommandTracerProtocol to receive the command to execute, caller cwd, child environment, and host process environment before the child process is created.

  • None leaves host-driven termination handling disabled.

  • HostTerminationRequest.HOST_SIGNALS installs temporary SIGINT and SIGTERM handlers for the duration of the call. While active, flnr owns those handlers and restores the previous ones when the call returns. This mode is Unix/POSIX-only and is supported only when called from the main Python thread.

  • HostTerminationRequest() attaches the run to a caller-managed, sticky trigger source that may be reused across runs. Attaching to an already-triggered request does not prevent process creation ahead of time; the run observes termination as soon as supervision starts.

Return type:

ProcessFate

Parameters:

Process Fate

Public objects that describe subprocess outcome.

class ProcessTerminationDecision

Identify the supervisory decision flnr made while resolving outcome.

This enum describes why flnr decided to intervene, or why no intervention was required. It is not a statement about the intrinsic cause of process exit. For the observed process status, inspect ProcessFate.returncode. For how flnr carried that decision out, inspect ProcessTerminationMethod.

NO_INTERVENTION

The process finished before flnr had to enforce any lifecycle action.

TIMEOUT

The configured run timeout expired. flnr therefore decided that the process must be stopped.

EXTERNAL_REQUEST

A host termination request was active. flnr therefore decided that the process must be stopped.

INTERNAL_FAILURE

flnr observed an unrecoverable internal failure while supervising the process and therefore decided that the process must be stopped immediately.

Note

This value records flnr’s supervisory decision, not a claim about what the operating system process “really died from”. For example, once the run timeout is breached, the decision remains TIMEOUT even if the process exits on its own before a requested signal takes effect.

class ProcessTerminationMethod

Identify how flnr enforced process termination.

When flnr decides that a process must be stopped, it resolves a termination method and carries execution through that stage.

This value describes the enforcement path chosen by flnr. It is not a claim about what the process itself observed.

NONE

No enforcement was needed.

TERMINATE

flnr resolved termination through the terminate stage.

KILL

flnr resolved termination through the kill stage.

class ProcessFate(termination_decision, termination_method, returncode)

Represents subprocess outcome as resolved by flnr.

A ProcessFate object combines three pieces of information:

  • the supervisory decision made by flnr

  • the termination method selected by flnr

  • the process return code observed by flnr, if available

returncode may be None when flnr could not confirm process exit within the allowed observation window.

Parameters:

Timeouts

Command execution timeouts.

class ExecutionTimeouts(run=None, terminate=5.0, output_drain=1.0, kill=None)

Duration of various time-sensitive aspects of command execution.

All timeouts are in seconds (fractional values allowed).

The kill parameter may be left unspecified: if None (the default), the terminate value is used for the post-kill wait.

Teardown Procedure

The library follows a staged escalation when the process must be stopped. The exact sequence depends on the supervisory decision that flnr reaches during execution.

If the ``run`` timeout expires:

  1. Terminate stage: The process is asked to terminate and given terminate seconds to exit on its own. Monitors continue to run during this phase so final logs and state can be captured.

  2. Forced kill: If the process does not exit within the terminate grace period, it is forcibly killed.

If flnr encounters an unrecoverable internal failure while reading process output:

  • The process is forcibly terminated immediately, without attempting the terminate stage.

After the forced kill (in either case):

  1. Monitors are paused and the library waits up to the kill timeout for confirmation that the process has exited.

  2. If no such confirmation arrives, ProcessKillFailedError is raised.

  3. Otherwise, monitors are resumed to allow any remaining output to be drained (subject to the output_drain timeout).

Parameters:

Monitor Interfaces

Interfaces for output and environment monitors.

class OutputMonitorDisableReason

Identify the reason for output monitor disable event.

flnr manages the lifecycle of each output monitor. A monitor starts active and may be disabled under certain circumstances. Once disabled, the transition is final. When disabling a monitor, flnr attempts to notify it by providing one of the reasons below.

EOF

The output stream associated with this monitor has ended. No further data will be available. This is the expected reason under normal operation.

ERROR

The monitor raised an unhandled exception when flnr attempted to interact with it. Monitors should not interrupt execution flow; if they throw, they are disabled.

DRAIN_TIMEOUT

The process finished execution, but flnr could not drain all remaining data from the output stream within the configured output_drain timeout. This may happen, for instance, when a subprocess spawns children that inherit and hold open its stdout or stderr descriptors.

class OutputMonitor

Interface that subprocess output monitors must implement.

Output monitors are treated as disposable, self-contained objects. Their purpose is to forward subprocess output to some sink (typically a file on disk), optionally applying simple transformations such as transcoding or timestamping.

If an output monitor raises an exception, that exception is saved and the monitor is disabled. The error is reported only after the monitored process finishes execution.

abstract process(data, ts)

Process data from subprocess output stream.

The timestamp identifies when flnr read this chunk from the stream.

Return type:

None

Parameters:
on_disable(reason, ts)

Notification callback called when output monitor is disabled.

After this call, process() will never be invoked again. Override to flush any buffered partial data or write a truncation marker. Implement as pass if you don’t need this notification.

Return type:

None

Parameters:
class EnvironmentMonitor(*, period)

Interface for execution-environment monitors.

Environment monitors are periodic user-supplied callbacks whose purpose is to observe the execution environment while the subprocess remains the current subject of observation.

These monitors are not process-control hooks. They are observational callbacks. The provided pid is a convenience handle that helps narrow the observation surface when needed.

If an environment monitor raises an exception, that exception is recorded and the monitor is disabled. Execution continues as if that monitor did not exist. The error is reported only after monitored execution finishes.

Parameters:

period (float)

on_start(pid, cmd)

Notifies when process corresponding to the command is created.

Note

pid is provided for logging/notification purposes only. By the time on_start is called, the underlying process may already have exited.

Return type:

None

Parameters:
abstract observe(pid)

Periodic callback while the subprocess is being monitored.

Callbacks must not assume that the process is still running. The only guarantee is that flnr still treats this process as the current subject of monitoring.

Return type:

None

Parameters:

pid (int)

on_end(process_fate)

Notifies when environment observation ends.

The callback receives the final ProcessFate resolved by flnr.

Usually this happens after process exit has been observed, but process_fate.returncode may be None if flnr could not confirm process exit within the allowed observation window.

This callback is not guaranteed to run if the monitor itself fails earlier. It must not be relied upon for cleanup or critical logic.

Return type:

None

Parameters:

process_fate (ProcessFate)

Stream Bindings

class InheritStdin

Marker type for inheriting child stdin from the parent stdin.

class BindToParent

Marker type for binding a child output stream to its parent stream.

flnr.INHERIT_STDIN: InheritStdin

Inherit stdin directly from parent.

flnr.BIND_TO_PARENT: BindToParent

Bind child output directly to the corresponding parent stream.

Output routed this way is not observed by output monitors.

Monitor Failures

Identification of monitor failure reasons.

class MonitorHook

Identification of the method that caused failure.

class OutputStream

Identification of output stream for error reporting.

class MonitorFailure(monitor, hook, exception, monitor_index, stream=None)

Information about one monitor callback failure.

Parameters:
monitor

Monitor object that raised the exception.

hook

Monitor callback that failed.

exception

Exception raised by the monitor callback.

monitor_index

Zero-based index of the monitor in the configured monitor list.

stream

Output stream associated with an output monitor failure. None identifies an environment monitor failure.

property monitor_name: str

Human-readable monitor type.

property monitor_kind: str

Human-readable monitor kind.

property location: str

Identification of what method was called at the moment of failure.

Exceptions

Exceptions defined by the library.

exception ProcessExecutionError(*, fate, monitor_failures, internal_exceptions=(), message)

Base exception for failures raised during supervised process execution.

These exceptions carry the resolved ProcessFate together with any monitor exceptions collected during execution. Users are expected to inspect both pieces of information.

Parameters:
Return type:

None

exception CommandFailedError(*, fate, monitor_failures, internal_exceptions=(), message)

Raised when check=True and the observed return code is non-zero.

This also covers subprocesses terminated by flnr after a run timeout or host-requested termination if the resulting return code is non-zero. Inspect the fate field to distinguish the cause and final outcome.

Parameters:
Return type:

None

exception MonitorFailedError(*, fate, monitor_failures, internal_exceptions=(), message)

Raised when one or more monitor callbacks fail during execution.

Parameters:
Return type:

None

exception SupervisionFailedError(*, fate, monitor_failures, internal_exceptions=(), message)

Raised when flnr cannot continue supervising the process correctly.

In this situation flnr attempts to force-stop the process and report any failures it managed to record.

Reported output, process state, and collected diagnostics may be incomplete or unreliable.

This exception may indicate a serious failure in the execution environment itself, as opposed to an ordinary failure of the supervised process.

Parameters:
Return type:

None

exception ProcessKillFailedError(*, fate, monitor_failures, internal_exceptions=(), message)

Raised when flnr cannot confirm process exit after intervention.

This means flnr completed its supervisory actions but could not observe a final process exit state within the allowed time window.

In such cases fate.returncode is expected to be None.

Like tears in the rain, this moment will be lost in time. Your process, however, will not.

Parameters:
Return type:

None

Host Control

Facilities for host to request premature termination.

class HostTerminationRequest

Stable, latched host-termination trigger source.

This object can be passed to run_ex(host_termination=...) to let the caller control host-side termination requests explicitly, without using HostTerminationRequest.HOST_SIGNALS.

The object is stable and reusable across multiple runs. Once triggered, it remains triggered. A run that attaches to an already-triggered request will observe termination as soon as supervision starts. This does not prevent process creation ahead of time; it only means the run will converge on termination immediately after startup.

Creating a HostTerminationRequest allocates OS resources. Those resources may be released with close(), but close() is ordinary lifecycle cleanup and must not race with future trigger() calls.

trigger() may be called from a Python signal handler in this implementation. close() does not provide that guarantee.

HOST_SIGNALS: Final[_HostSignalsSentinel] = _HostSignalsSentinel

Sentinel value for run_ex(..., host_termination=...).

Passing HostTerminationRequest.HOST_SIGNALS tells run_ex() to temporarily install handlers for SIGINT and SIGTERM for the duration of the call.

Constraints:

  • Unix/POSIX only

  • main Python thread only

  • while active, flnr owns SIGINT/SIGTERM handling for the duration of the call

  • previous handlers are restored when run_ex() returns

This temporarily replaces normal SIGINT/SIGTERM handling for the call.

Use an instance of HostTerminationRequest if the application manages handling itself and wants to request termination explicitly.

trigger()

Trigger host termination request.

Once triggered, the request remains triggered.

This method may be called from a Python signal handler in this implementation. It is intentionally small and only performs the minimal work needed to wake attached runs.

Repeated calls after the request has already been triggered are harmless.

Return type:

None

close()

Release OS resources owned by this request object.

This is ordinary lifecycle cleanup. It is not safe to call from a Python signal handler, and it must not race with future trigger() calls.

In particular, if this object is still reachable from installed signal handlers, callers should not call close(). In that usage mode, the object is expected to remain alive for the lifetime of the program, or until it has been fully detached from all future signal delivery.

Once close() is called, the object is dead and must not be reused for later runs.

Return type:

None

Command Tracing

class CommandTracerProtocol(*args, **kwargs)

Protocol for command tracing implementations.

Implementations receive execution details before the child process is created.

Command arguments and environment variable values may be sensitive. Implementations should record only values appropriate for their output destination.

trace_command(*, cmd, cwd, env, host_env)

Receive execution details before the child process is created.

cmd represents the command to execute: the program path followed by its arguments. cwd is the caller-supplied working directory; None means the child inherits the parent process working directory. env is the environment passed to the child. host_env is a snapshot of the host process environment.

Return type:

None

Parameters:
class CommandTracer(logger, *, env_listing=_DEFAULT_ENV_LISTING, level=logging.INFO)

Log diagnostic command recipes.

Command recipes are shell-oriented renderings intended for inspection and logging. They are informational and are not guaranteed to be exact replay scripts.

By default, command tracing records the working directory and command line only. Environment values are not logged unless the caller explicitly enables environment tracing.

Use with_selected_environment() to trace explicit environment variables, with_changed_environment() to trace values changed from the host process environment, or with_recreated_environment() to trace the complete child environment from an empty base. These modes write environment variable values to logs, so callers should enable them only for values safe to expose in their logging destination.

Parameters:
classmethod with_changed_environment(logger, *, level=logging.INFO)

Create a tracer logging the command and modified child environment.

The log includes the command line and any child environment variables that differ from the host process. Host variables missing from the child are listed by name only.

Use this mode only if the changed values are safe for the configured log destinations.

Return type:

CommandTracer

Parameters:
classmethod with_selected_environment(logger, variables, *, level=logging.INFO)

Create a tracer logging the command and selected child variables.

This tracer restricts environment variable listing to explicitly requested keys. Select only variables with values safe for the configured log destinations.

Return type:

CommandTracer

Parameters:
classmethod with_recreated_environment(logger, *, level=logging.INFO)

Create a tracer logging a recreated child environment.

The log includes the command line and the complete child environment as assignments applied after clearing inherited environment values.

Use this mode only if the full child environment is safe for the configured log destinations.

Return type:

CommandTracer

Parameters:
trace_command(*, cmd, cwd, env, host_env)

Log the command recipe before process creation.

Return type:

None

Parameters:
class EnvListing(variables=(), clear_environment=False, removed_variables=(), missing_variables=())

Environment rendering instructions for command recipes.

EnvListing is returned by command tracer environment-listing callbacks. It describes what the renderer should include in the command recipe.

variables are environment assignments to render, in order.

clear_environment means the recipe should start from an empty environment before applying variables.

removed_variables are inherited environment variable names to remove from the rendered recipe. Renderers ignore this field when clear_environment is true.

missing_variables are names requested by the listing callback but absent from the child environment. They are reported by CommandTracer as missing, not rendered as assignments.

Parameters:
list_changed_environment(child_env, host_env)

List child environment values changed from the host process environment.

Unchanged host process environment variables are not listed. Host process variables missing from the child environment are listed by name, without their values.

Return type:

EnvListing

Parameters:
list_recreated_environment(child_env, host_env)

List the complete child environment from an empty environment base.

Return type:

EnvListing

Parameters:
list_selected_environment(variables)

Create a helper that lists selected environment variables.

Tracer-enabled call sites should not select secret variables for display.

Return type:

Callable[[Mapping[str, str], Mapping[str, str]], EnvListing]

Parameters:

variables (Sequence[str])

list_no_environment(child_env, host_env)

List no environment variables.

Return type:

EnvListing

Parameters:

Monitoring Utilities

Reference output monitors and output-processing helpers.

class IncrementalLineSplitter(buffer_limit=_DEFAULT_ILS_BUFFER_LIMIT)

Incrementally split byte chunks into newline-terminated records.

Parameters:

buffer_limit (int)

feed(chunk)

Feed a chunk of bytes, get resulting lines as a byte sequence.

If an empty chunk is fed, this transforms currently accumulated bytes into a line without newline sequence at the end.

Note

If buffered trailing data grows beyond buffer_limit before a newline is seen, the buffered tail is emitted early as a fragment to keep carry-over memory bounded.

Return type:

Sequence[bytes]

Parameters:

chunk (bytes)

class TextOutputMonitor(*, sink, ils_buffer_limit=_DEFAULT_ILS_BUFFER_LIMIT, encoding='latin-1', auto_flush=True, timestamp_precision=None, timestamp_base=None, append_disable_marker=False)

Reference monitor for line-oriented text output.

This monitor buffers incoming bytes, emits complete records terminated by \n, normalizes a trailing \r\n to \n, decodes the result with the configured encoding using errors="replace", and writes the resulting text to a text sink.

Buffered partial data is flushed when the monitor is disabled.

Optional timestamping prefixes each emitted line with a monotonic timestamp. If timestamp_base is provided, timestamps are emitted relative to that monotonic timestamp value. Disable markers, when enabled, are appended as a diagnostic footer and keep their embedded raw monotonic timestamp values regardless of the configured timestamp style.

Parameters:
  • sink (TextIO)

  • ils_buffer_limit (int)

  • encoding (str)

  • auto_flush (bool)

  • timestamp_precision (int | None)

  • timestamp_base (float | None)

  • append_disable_marker (bool)

process(data, ts)

Process a chunk of subprocess output.

Complete lines are decoded and written to the sink immediately. Partial lines are buffered across calls. An empty chunk (b"") flushes any buffered tail without adding a newline.

If timestamping is enabled, each emitted line is prefixed with the timestamp associated with this call.

Return type:

None

Parameters:
on_disable(reason, ts)

Flush buffered data and optionally append a diagnostic footer.

Return type:

None

Parameters:
class BinaryOutputMonitor(*, sink)

Built-in monitor that forwards raw output bytes to a binary sink.

Parameters:

sink (IOBase)

process(data, ts)

Write a chunk of raw output bytes to the sink.

Return type:

None

Parameters:
on_disable(reason, ts)

Flush the sink when monitoring ends.

Return type:

None

Parameters:

Epigraph

– If a producer can outpace a consumer, something must grow, block, or die.

(independently rediscovered, like most unpleasant truths)