Skip to main content

A Rust-backed subprocess wrapper with split stdout/stderr streaming

Reason this release was yanked:

Broken

Project description

running-process

Linux x86 Linux ARM Windows x86 Windows ARM macOS x86 macOS ARM

running-process is a Rust-backed subprocess runtime with a thin Python API.

The pipe-backed API keeps stdout and stderr separate, preserves raw bytes until decode time, and defaults to UTF-8 with errors="replace" when you ask for text. The PTY API is separate because terminal sessions are chunk-oriented and should not be forced through line normalization.

Pipe-backed API

from running_process import RunningProcess

process = RunningProcess(
    ["python", "-c", "import sys; print('out'); print('err', file=sys.stderr)"]
)

process.wait()

print(process.stdout)          # stdout only
print(process.stderr)          # stderr only
print(process.combined_output) # combined compatibility view

Captured data values stay plain str | bytes. Live stream handles are exposed separately:

if process.stdout_stream.available():
    print(process.stdout_stream.drain())

Process priority is a first-class launch option:

from running_process import CpuPriority, RunningProcess

process = RunningProcess(
    ["python", "-c", "import time; time.sleep(1)"],
    nice=CpuPriority.LOW,
)

nice= behavior:

  • accepts either a raw int niceness or a platform-neutral CpuPriority
  • on Unix, it maps directly to process niceness
  • on Windows, positive values map to below-normal or idle priority classes and negative values map to above-normal or high priority classes
  • 0 leaves the default scheduler priority unchanged
  • positive values are the portable default; negative values may require elevated privileges
  • the enum intentionally stops at HIGH; there is no realtime tier

Available helpers:

  • get_next_stdout_line(timeout)
  • get_next_stderr_line(timeout)
  • get_next_line(timeout) for combined compatibility reads
  • drain_stdout()
  • drain_stderr()
  • drain_combined()
  • stdout_stream.available()
  • stderr_stream.available()
  • combined_stream.available()

RunningProcess.run(...) supports common subprocess.run(...) style cases including:

  • capture_output=True
  • text=True
  • encoding=...
  • errors=...
  • shell=True
  • env=...
  • nice=...
  • stdin=subprocess.DEVNULL
  • input=... in text or bytes form

Unsupported subprocess.run(...) kwargs now fail loudly instead of being silently ignored.

Expect API

expect(...) is available on both the pipe-backed and PTY-backed process APIs.

import re
import subprocess
from running_process import RunningProcess

process = RunningProcess(
    ["python", "-c", "print('prompt>'); import sys; print('echo:' + sys.stdin.readline().strip())"],
    stdin=subprocess.PIPE,
)

process.expect("prompt>", timeout=5, action="hello\n")
match = process.expect(re.compile(r"echo:(.+)"), timeout=5)
print(match.groups)

Supported action= forms:

  • str or bytes: write to stdin
  • "interrupt": send Ctrl-C style interrupt when supported
  • "terminate"
  • "kill"
  • callable(match, process)

Pipe-backed expect(...) matches line-delimited output. If the child writes prompts without trailing newlines, use the PTY API instead.

PTY API

Use RunningProcess.pseudo_terminal(...) for interactive terminal sessions. It is chunk-oriented by design and preserves carriage returns and terminal control flow instead of normalizing it away.

from running_process import ExpectRule, RunningProcess

pty = RunningProcess.pseudo_terminal(
    ["python", "-c", "import sys; sys.stdout.write('name?'); sys.stdout.flush(); print('hello ' + sys.stdin.readline().strip())"],
    text=True,
    expect=[ExpectRule("name?", "world\n")],
    expect_timeout=5,
)

print(pty.output)

PTY behavior:

  • accepts str and list[str] commands
  • auto-splits simple string commands into argv when shell syntax is not present
  • uses shell mode automatically when shell metacharacters are present
  • keeps output chunk-buffered by default
  • preserves \r for redraw-style terminal output
  • supports write(...), read(...), drain(), available(), expect(...), resize(...), and send_interrupt()
  • supports nice=... at launch
  • supports restore_callback= and cleanup_callback= so terminal restoration and cleanup paths are explicit and testable
  • supports interrupt_and_wait(...) for staged interrupt escalation
  • supports wait_for_idle(...) with activity filtering
  • exposes exit_reason, interrupt_count, interrupted_by_caller, and exit_status

There is also a compatibility alias: RunningProcess.psuedo_terminal(...).

You can also inspect the intended interactive launch semantics without launching a child:

from running_process import RunningProcess

spec = RunningProcess.interactive_launch_spec("console_isolated")
print(spec.ctrl_c_owner)
print(spec.creationflags)

Supported launch specs:

  • pseudo_terminal
  • console_shared
  • console_isolated

For an actual launch, use RunningProcess.interactive(...):

process = RunningProcess.interactive(
    ["python", "-c", "print('hello from interactive mode')"],
    mode="console_shared",
    nice=5,
)
process.wait()

Abnormal Exits

By default, nonzero exits stay subprocess-like: you get a return code and can inspect exit_status.

process = RunningProcess(["python", "-c", "import sys; sys.exit(3)"])
process.wait()
print(process.exit_status)

If you want abnormal exits to raise, opt in:

from running_process import ProcessAbnormalExit, RunningProcess

try:
    RunningProcess.run(
        ["python", "-c", "import sys; sys.exit(3)"],
        capture_output=True,
        raise_on_abnormal_exit=True,
    )
except ProcessAbnormalExit as exc:
    print(exc.status.summary)

Notes:

  • keyboard interrupts still raise KeyboardInterrupt
  • kill -9 / SIGKILL is classified as an abnormal signal exit
  • possible OOM conditions are exposed as a hint on exit_status.possible_oom
  • OOM cannot be identified perfectly across platforms from exit status alone, so it is best-effort rather than guaranteed

Text and bytes

Pipe mode is byte-safe internally:

  • invalid UTF-8 does not break capture
  • text mode decodes with UTF-8 and errors="replace" by default
  • binary mode returns bytes unchanged
  • \r\n is normalized as a line break in pipe mode
  • bare \r is preserved

PTY mode is intentionally more conservative:

  • output is handled as chunks, not lines
  • redraw-oriented \r is preserved
  • no automatic terminal-output normalization is applied

Development

./install
./lint
./test

./install bootstraps the repo-local Rust toolchain into ./.cargo and ./.rustup.

./test runs the Rust tests, rebuilds the native extension with the unoptimized dev profile, runs the non-live Python tests, and then runs the @pytest.mark.live coverage that exercises real OS process and signal behavior.

If you want to invoke pytest directly, set RUNNING_PROCESS_LIVE_TESTS=1 and run uv run pytest -m live.

For direct Rust commands, prefer the repo-local trampolines:

./_cargo check --workspace
./_cargo fmt --all --check
./_cargo clippy --workspace --all-targets -- -D warnings

Notes

  • stdout and stderr are no longer merged by default.
  • combined_output exists for compatibility when you need the merged view.
  • RunningProcess(..., use_pty=True) is no longer the preferred path; use RunningProcess.pseudo_terminal(...) for PTY sessions.
  • The test suite checks that running_process.__version__, package metadata, and manifest versions stay in sync.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

running_process-3.0.0.tar.gz (26.7 kB view details)

Uploaded Source

Built Distributions

If you're not sure about the file name format, learn more about wheel file names.

running_process-3.0.0-cp311-cp311-win_arm64.whl (237.9 kB view details)

Uploaded CPython 3.11Windows ARM64

running_process-3.0.0-cp311-cp311-win_amd64.whl (243.7 kB view details)

Uploaded CPython 3.11Windows x86-64

running_process-3.0.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (337.3 kB view details)

Uploaded CPython 3.11manylinux: glibc 2.17+ x86-64

running_process-3.0.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl (325.7 kB view details)

Uploaded CPython 3.11manylinux: glibc 2.17+ ARM64

running_process-3.0.0-cp311-cp311-macosx_11_0_arm64.whl (337.0 kB view details)

Uploaded CPython 3.11macOS 11.0+ ARM64

running_process-3.0.0-cp311-cp311-macosx_10_12_x86_64.whl (341.3 kB view details)

Uploaded CPython 3.11macOS 10.12+ x86-64

File details

Details for the file running_process-3.0.0.tar.gz.

File metadata

  • Download URL: running_process-3.0.0.tar.gz
  • Upload date:
  • Size: 26.7 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.8.11

File hashes

Hashes for running_process-3.0.0.tar.gz
Algorithm Hash digest
SHA256 f7cd77387b879c85d2f6a08dc4124c359943e8c19c0a6408d62d0df73c5176c0
MD5 461cab8957f4cf4b78fa45b087f2a107
BLAKE2b-256 adaaf711397ee90bf0d28d10a8b45e3cb9878ba74ba011eb124abbcb0ebaac50

See more details on using hashes here.

File details

Details for the file running_process-3.0.0-cp311-cp311-win_arm64.whl.

File metadata

File hashes

Hashes for running_process-3.0.0-cp311-cp311-win_arm64.whl
Algorithm Hash digest
SHA256 dac2b44ea6c82df780832440ecee9c99512353c4c0d95868183ca7fb290ce3ee
MD5 5107ee7405959d73560fc0ceeb741e54
BLAKE2b-256 e46c003e9ee11cace2ac203d784821f91410463de821348f31cc79b5595fed7f

See more details on using hashes here.

File details

Details for the file running_process-3.0.0-cp311-cp311-win_amd64.whl.

File metadata

File hashes

Hashes for running_process-3.0.0-cp311-cp311-win_amd64.whl
Algorithm Hash digest
SHA256 4e2e82477cbe0052f94b0591824f558cdfe01b543194b7340a5af3881f5837a7
MD5 91ec3035f910daf6ac1aa744913c1ded
BLAKE2b-256 9619e3d47d0f6f940fa032db9c6cc9fb019c7880d8499ce10b9207834b1aac68

See more details on using hashes here.

File details

Details for the file running_process-3.0.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.

File metadata

File hashes

Hashes for running_process-3.0.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Algorithm Hash digest
SHA256 a33b8c04b2259562a51082d63adfa01511dd31bb8532512542bae901aa9c26cb
MD5 9ca0aa88bdda4d1f9396e0d3cb6f5e09
BLAKE2b-256 2f04939df90a01983a404204b2ba470679fe2432fbec79dc3e0a4d2cb183d3fa

See more details on using hashes here.

File details

Details for the file running_process-3.0.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl.

File metadata

File hashes

Hashes for running_process-3.0.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
Algorithm Hash digest
SHA256 6b484b2ad8ba804ad8e0a015e459424174be93e4205f680e220412320e19a14c
MD5 89508ba5f4c38834565e54fe7e06a369
BLAKE2b-256 00c7714bbf81d523a8c6bfaa7548aea92870338f04dd22fdad7643faabcef6e2

See more details on using hashes here.

File details

Details for the file running_process-3.0.0-cp311-cp311-macosx_11_0_arm64.whl.

File metadata

File hashes

Hashes for running_process-3.0.0-cp311-cp311-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 f216bc33c0f57a8c1f91344cd592a44980437bff67742c5d019dfa13977569b2
MD5 36d4e34684cd929b52d65461eab30128
BLAKE2b-256 acf02b6d98dcfe26e0ca4f1b1bdd4654e73c9764080f84a48932c07056998389

See more details on using hashes here.

File details

Details for the file running_process-3.0.0-cp311-cp311-macosx_10_12_x86_64.whl.

File metadata

File hashes

Hashes for running_process-3.0.0-cp311-cp311-macosx_10_12_x86_64.whl
Algorithm Hash digest
SHA256 3155f256b9804fb23b4ea7696416fd997833ed348ea3477c5b689cf830243157
MD5 c8a8fb88daf03ace806749a0d035b834
BLAKE2b-256 1c9a5f1af7c21d4ac18330505d5b45c20280f7ebe0822f6f3667c6e97324b21b

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page