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, check=True, host_termination=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
ProcessFateobject describing the resolved outcome. Whencheck=True(the default), a non-zero subprocess return code is reported asCommandFailedError. This includes subprocesses terminated by flnr after a run timeout or host-requested termination; inspect the exception’sfatefield to distinguish the cause and final outcome. Monitor failures are reported asMonitorFailedErrorafter process teardown completes. Unrecoverable supervision failures are reported asSupervisionFailedError. If process exit cannot be confirmed during teardown,ProcessKillFailedErroris raised.flnrsupervises 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 untiloutput_drainexpires.By default,
merge_std_streams=Noneroutes output based on configured monitors.stdout_monitorssee stdout and, unlessstderr_monitorsare configured, stderr. Configuringstderr_monitorsroutes stderr separately. Explicitmerge_std_streams=Truealways merges stderr into stdout and rejectsstderr_monitors. Explicitmerge_std_streams=Falsekeeps stdout and stderr separate. Streams without monitors are connected toDEVNULL.Noneleaves host-driven termination handling disabled.HostTerminationRequest.HOST_SIGNALSinstalls temporary SIGINT and SIGTERM handlers for the duration of the call. While active,flnrowns 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:
- Parameters:
cwd (Path | None)
merge_std_streams (bool | None)
timeouts (ExecutionTimeouts | None)
stdout_monitors (Sequence[OutputMonitor] | None)
stderr_monitors (Sequence[OutputMonitor] | None)
environment_monitors (Sequence[EnvironmentMonitor] | None)
check (bool)
host_termination (HostTerminationRequest | _HostSignalsSentinel | None)
Process Fate¶
Public objects that describe subprocess outcome.
- class ProcessTerminationDecision(value, names=None, *, module=None, qualname=None, type=None, start=1, boundary=None)¶
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, inspectProcessTerminationMethod.- NO_INTERVENTION¶
The process finished before flnr had to enforce any lifecycle action.
- TIMEOUT¶
The configured
runtimeout expired. flnr therefore decided that the process must be stopped.
- EXTERNAL_REQUEST¶
Host termination request was active, flnr attempted to gracefully terminate.
- 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
runtimeout is breached, the decision remainsTIMEOUTeven if the process exits on its own before a requested signal takes effect.
- class ProcessTerminationMethod(value, names=None, *, module=None, qualname=None, type=None, start=1, boundary=None)¶
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 graceful termination.
- KILL¶
flnr resolved termination through forced termination.
- class ProcessFate(termination_decision, termination_method, returncode)¶
Represents subprocess outcome as resolved by flnr.
A
ProcessFateobject 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
returncodemay beNonewhen flnr could not confirm process exit within the allowed observation window.- Parameters:
termination_decision (ProcessTerminationDecision)
termination_method (ProcessTerminationMethod)
returncode (int | None)
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
killparameter may be left unspecified: ifNone(the default), theterminatevalue 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:
Graceful termination: The process is asked to terminate and given
terminateseconds to exit on its own. Monitors continue to run during this phase so final logs and state can be captured.Forced kill: If the process does not exit within the
terminategrace period, it is forcibly killed.
If flnr encounters an unrecoverable internal failure while reading process output:
The process is forcibly terminated immediately, without a graceful termination attempt.
After the forced kill (in either case):
Monitors are paused and the library waits up to the
killtimeout for confirmation that the process has exited.If no such confirmation arrives,
ProcessKillFailedErroris raised.Otherwise, monitors are resumed to allow any remaining output to be drained (subject to the
output_draintimeout).
Monitor Interfaces¶
Interfaces for output and environment monitors.
- class OutputMonitorDisableReason(value, names=None, *, module=None, qualname=None, type=None, start=1, boundary=None)¶
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_draintimeout. 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.
data comes with an associated timestamp that identifies the moment when this was read from the stream.
- 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:
- Parameters:
reason (OutputMonitorDisableReason)
ts (float)
- 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
pidis 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
pidis provided for logging/notification purposes only. By the timeon_startis called, the underlying process may already have exited.
- 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.
- on_end(process_fate)¶
Notifies when environment observation ends.
The callback receives the final
ProcessFateresolved by flnr.Usually this happens after process exit has been observed, but
process_fate.returncodemay beNoneif 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:
- Parameters:
process_fate (ProcessFate)
Monitor Failures¶
Identification of monitor failure reasons.
- class MonitorHook(value, names=None, *, module=None, qualname=None, type=None, start=1, boundary=None)¶
Identification of the method that caused failure.
- class OutputStream(value, names=None, *, module=None, qualname=None, type=None, start=1, boundary=None)¶
Identification of output stream for error reporting.
- class MonitorFailure(monitor, hook, exception, monitor_index, stream=None)¶
Information about a monitor failure instance.
- Parameters:
monitor (object)
hook (MonitorHook)
exception (Exception)
monitor_index (int)
stream (OutputStream | None)
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
ProcessFatetogether with any monitor exceptions collected during execution. Users are expected to inspect both pieces of information.- Parameters:
fate (ProcessFate)
monitor_failures (Sequence[MonitorFailure])
internal_exceptions (Sequence[BaseException])
message (str)
- Return type:
None
- exception CommandFailedError(*, fate, monitor_failures, internal_exceptions=(), message)¶
Raised when
check=Trueand 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
fatefield to distinguish the cause and final outcome.- Parameters:
fate (ProcessFate)
monitor_failures (Sequence[MonitorFailure])
internal_exceptions (Sequence[BaseException])
message (str)
- Return type:
None
- exception MonitorFailedError(*, fate, monitor_failures, internal_exceptions=(), message)¶
Raised when one or more monitor callbacks fail during execution.
- Parameters:
fate (ProcessFate)
monitor_failures (Sequence[MonitorFailure])
internal_exceptions (Sequence[BaseException])
message (str)
- 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:
fate (ProcessFate)
monitor_failures (Sequence[MonitorFailure])
internal_exceptions (Sequence[BaseException])
message (str)
- 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.returncodeis expected to beNone.Like tears in the rain, this moment will be lost in time. Your process, however, will not.
- Parameters:
fate (ProcessFate)
monitor_failures (Sequence[MonitorFailure])
internal_exceptions (Sequence[BaseException])
message (str)
- 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 usingHostTerminationRequest.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(), butclose()is ordinary lifecycle cleanup and must not race with futuretrigger()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_SIGNALStellsrun_ex()to temporarily install handlers forSIGINTandSIGTERMfor the duration of the call.Constraints:
Unix/POSIX only
main Python thread only
while active,
flnrowns SIGINT/SIGTERM handling for the duration of the callprevious handlers are restored when
run_ex()returns
This temporarily replaces normal SIGINT/SIGTERM handling for the call.
Use an instance of
HostTerminationRequestif 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:
- 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:
-
HOST_SIGNALS:
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_limitbefore a newline is seen, the buffered tail is emitted early as a fragment to keep carry-over memory bounded.
- 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\nto\n, decodes the result with the configured encoding usingerrors="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_baseis 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:
- 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.
- on_disable(reason, ts)¶
Flush buffered data and optionally append a diagnostic footer.
- Return type:
- Parameters:
reason (OutputMonitorDisableReason)
ts (float)
- 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.
- on_disable(reason, ts)¶
Flush the sink when monitoring ends.
- Return type:
- Parameters:
reason (OutputMonitorDisableReason)
ts (float)
Epigraph
- – If a producer can outpace a consumer, something must grow, block, or die.
(independently rediscovered, like most unpleasant truths)