Python bindings to the processkit Rust crate —
asyncio-native, kernel-backed, no-orphan process containment.
Status: 1.0 — API frozen. See ROADMAP.md for how it was built.
The cookbook has task-oriented snippets for every feature; platform support & caveats documents the per-OS behaviour.
Thin PyO3 bindings to the processkit Rust crate. The Rust crate handles all the hard
platform code — Windows Job Object containment, Linux cgroup v2, race-free subprocess spawn.
The Python layer exposes a typed surface with context-manager teardown.
The synchronous surface is available today:
from processkit import Command, ProcessGroup
# Run and capture. A non-zero exit is data, not an exception.
result = Command("git", ["rev-parse", "HEAD"]).output()
print(result.stdout.strip(), result.code)
# Kill-on-exit container for a whole tree.
with ProcessGroup() as group:
group.start(Command("my-server"))
# ... use the server ...
# group exit reaps the whole tree, grandchildren includedoutput() captures a non-zero exit, a timeout, and a signal-kill as data
(result.code, result.timed_out, result.signal); run() returns trimmed
stdout and raises on failure. The raised exceptions carry structured fields
(NonZeroExit.code / .stdout / .stderr, Timeout.timeout_seconds, …) and
derive from the matching builtin where one exists (Timeout is a TimeoutError,
ProcessNotFound is a FileNotFoundError), so stdlib except clauses work. A
blocked sync call honours Ctrl+C — it raises KeyboardInterrupt and reaps the
process tree rather than hanging. Use output_bytes() for raw binary stdout.
The asyncio-native surface mirrors the sync one with an a-prefix, and adds
line streaming and interactive stdin:
import asyncio
from processkit import Command, ProcessGroup
async def main():
# Run-and-capture; cancelling the awaiting task reaps the whole tree.
result = await Command("git", ["rev-parse", "HEAD"]).aoutput()
print(result.stdout.strip(), result.code)
# Stream a child's stdout line by line; the context manager reaps the whole
# tree on exit (even on exception) — no reliance on Python's GC.
async with await Command("my-build", ["--watch"]).astart() as proc:
async for line in proc.stdout_lines():
print(line)
# Kill-on-exit container for a whole tree.
async with ProcessGroup() as group:
await group.astart(Command("my-server"))
# ... use the server ...
# async-with exit reaps the whole tree, grandchildren included
asyncio.run(main())Write to a child interactively with keep_stdin_open() + take_stdin(), or feed
input upfront with stdin_text() / stdin_bytes().
from processkit import Command, ProcessGroup, Supervisor, wait_for_port
# Shell-free pipelines.
top = (Command("ps", ["aux"]) | Command("grep", ["python"])).run()
# Resource-limited sandbox for an untrusted tree (Windows Job Object /
# Linux cgroup-v2 root). Lock down the environment and cap retained output too.
sandboxed = Command("untrusted-tool").env_clear().output_limit(max_bytes=1024 * 1024)
with ProcessGroup(memory_max=512 * 1024 * 1024, max_processes=64) as group:
group.start(sandboxed)
print(group.stats().active_process_count)
# Keep a service alive with restart + backoff.
outcome = Supervisor(Command("flaky-worker"), restart="on_crash",
max_restarts=10, backoff_initial=0.5).run()
# Readiness: start a server, then wait for its port (async).
# await wait_for_port("127.0.0.1", 8080, timeout=10)Resource limits are enforced by the Windows Job Object or a Linux cgroup-v2
root; under a container, systemd session, or other non-root cgroup the kernel
forbids them and ResourceLimit is raised. Signals, members(), stats(), and
limits raise Unsupported where the platform lacks them.
processkit follows Semantic Versioning. As of 1.0 the
public API — everything re-exported from import processkit and declared in the
type stubs — is stable: breaking changes land only in a new major version, so
1.x upgrades are backward-compatible. Anything underscore-prefixed is internal.
- Python 3.10 or later (abi3 wheel; Rust toolchain required to build from source)
- See platform support & caveats for per-OS behaviour.
The first release to PyPI is pending (pip install processkit will be the
supported path once published). Until then, build from source:
git clone https://github.com/ZelAnton/processkit-py
cd processkit-py
uv run maturin developSee CHANGELOG.md for the version history.
See CONTRIBUTING.md for build/test instructions and conventions. To report a security issue, follow SECURITY.md.
This project is licensed under the MIT License.