Skip to main content

Python subprocess, but actually nice.

Project description

Zap

Python subprocess, but actually nice.

PyPI Downloads Python License Zero Dependencies


Before:

result = subprocess.run(["git", "status"], capture_output=True, text=True, check=True)
print(result.stdout)

After:

from zap import run
print(run("git status"))

Install

pip install zap-sh

Quick Start

from zap import run

# Run a command — returns a Result object
result = run("git status")
print(result.stdout)   # raw stdout
print(result.ok)       # True if exit code 0
print(result.code)     # exit code
print(result.lines)    # stdout as list of lines

# Just print it — str() returns stdout
print(run("ls -la"))

Pipe Chaining

# Pipe with strings — clean and simple
result = run("cat logs.txt") | "grep ERROR" | "wc -l"
print(result)  # number of error lines

Live Output

See output in real-time while still capturing it. No more staring at a blank screen during pip install or docker build.

# Stream output live to terminal — still captured in result
result = run("pip install flask", live=True)

# Works with long builds too
run("docker build -t myapp .", live=True)
run("make all", live=True)

Retry

Automatically retry flaky commands — network calls, API requests, CI scripts.

# Retry up to 3 times with 2s delay between attempts
result = run("curl -f https://api.example.com/health", retries=3, delay=2)

# Only retries on failure (non-zero exit) — succeeds immediately if ok
run("flaky-deploy-script.sh", retries=5, delay=5)

Check If a Command Exists

from zap import which

# Returns the path if found, None if not
if which("docker"):
    run("docker ps")
else:
    print("Docker not installed")

# Great for setup scripts
for cmd in ["git", "node", "python3"]:
    path = which(cmd)
    print(f"{cmd}: {path or 'NOT FOUND'}")

Temporary Directory

from zap import cd

# Change directory temporarily — auto-restores when done
with cd("/my/project"):
    run("git status")
    run("npm install")
    run("npm run build")
# Back to original directory here

# Safe even if something errors
with cd("/tmp"):
    run("some-command")  # if this fails, directory still restores

Error Handling

from zap import run, ZapError

# Raises ZapError on non-zero exit (default)
try:
    run("git push origin fake-branch")
except ZapError as e:
    print(e.stderr)
    print(e.code)

# Disable with check=False
result = run("might-fail", check=False)
if not result.ok:
    print("Failed:", result.stderr)

Options

# Timeout (seconds)
run("sleep 100", timeout=5)  # raises TimeoutError

# Working directory
run("ls", cwd="/tmp")

# Environment variables (merged with os.environ)
run("echo $API_KEY", env={"API_KEY": "secret"})

# Feed stdin
run("cat", stdin="hello from stdin")

# Safe mode — pass a list instead of string
run(["echo", "no shell injection"])

Async

from zap import run_async

result = await run_async("git status")
print(result.ok)

Same API as run() — just await it.

Result Object

Property Type Description
.stdout str Captured stdout
.stderr str Captured stderr
.code int Exit code
.ok bool True if exit code is 0
.lines list[str] Stdout split into lines
.json() Any Parse stdout as JSON
str(r) str Returns stdout stripped
bool(r) bool Returns .ok
r | "cmd" Result Pipe stdout to next command

run() Parameters

Parameter Type Default Description
command str | list required Command to run
check bool True Raise ZapError on non-zero exit
timeout float None Timeout in seconds
cwd str None Working directory
env dict None Extra env vars (merged with os.environ)
stdin str None String to feed as stdin
live bool False Stream output in real-time
retries int 0 Number of retry attempts on failure
delay float 1.0 Seconds between retries

How It Works

  • String command → runs with shell=True (convenience for scripting)
  • List command → runs with shell=False (safe, no shell injection)
  • check=True by default → raises ZapError on non-zero exit
  • live=True → uses Popen with selectors for real-time streaming
  • retries → catches failures and retries with configurable delay
  • Zero dependencies → pure Python stdlib
  • ~300 lines → read the whole source in 5 minutes

Support

If Zap saves you time, consider buying me a coffee.

Buy Me A Coffee

License

MIT

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

zap_sh-0.2.0.tar.gz (10.8 kB view details)

Uploaded Source

Built Distribution

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

zap_sh-0.2.0-py3-none-any.whl (7.6 kB view details)

Uploaded Python 3

File details

Details for the file zap_sh-0.2.0.tar.gz.

File metadata

  • Download URL: zap_sh-0.2.0.tar.gz
  • Upload date:
  • Size: 10.8 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/5.1.1 CPython/3.12.7

File hashes

Hashes for zap_sh-0.2.0.tar.gz
Algorithm Hash digest
SHA256 81644762b4a99d401448cb619628a6a05ad0ef9fab7186ca5ea97f3a147e7a33
MD5 9e5c2998feec48471a56e28a51cff91c
BLAKE2b-256 8bed8e05040859c7a9ccfc3b53b4c013c50605bac53511a577a793f24e7c1dbc

See more details on using hashes here.

File details

Details for the file zap_sh-0.2.0-py3-none-any.whl.

File metadata

  • Download URL: zap_sh-0.2.0-py3-none-any.whl
  • Upload date:
  • Size: 7.6 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/5.1.1 CPython/3.12.7

File hashes

Hashes for zap_sh-0.2.0-py3-none-any.whl
Algorithm Hash digest
SHA256 23b0bbc6de7676f8515c1943334dc3366adcccde063a30709ee77589c08410b1
MD5 e9b6b53739997af2ded0e3ce7798b17d
BLAKE2b-256 c899ecb0b2f6849ef5df3c82f6986e489382f364da387e54c4b797eadae276be

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