Skip to content

ZelAnton/processkit-py

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

19 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

processkit

CI CodeQL Python License: MIT

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.

What it does

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 included

output() 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.

Async & streaming

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().

Higher-level features

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.

Stability

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.

Requirements

Installation

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 develop

Changelog

See CHANGELOG.md for the version history.

Contributing

See CONTRIBUTING.md for build/test instructions and conventions. To report a security issue, follow SECURITY.md.

License

This project is licensed under the MIT License.

About

Async child-process management for Python: kernel-backed, no-orphan process trees — nothing your subprocesses spawn outlives your program.

Topics

Resources

License

Code of conduct

Contributing

Security policy

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors