Python subprocess, but actually nice.
Project description
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
ZapErroron non-zero exit - live=True → uses
Popenwithselectorsfor 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.
License
MIT
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
81644762b4a99d401448cb619628a6a05ad0ef9fab7186ca5ea97f3a147e7a33
|
|
| MD5 |
9e5c2998feec48471a56e28a51cff91c
|
|
| BLAKE2b-256 |
8bed8e05040859c7a9ccfc3b53b4c013c50605bac53511a577a793f24e7c1dbc
|
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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
23b0bbc6de7676f8515c1943334dc3366adcccde063a30709ee77589c08410b1
|
|
| MD5 |
e9b6b53739997af2ded0e3ce7798b17d
|
|
| BLAKE2b-256 |
c899ecb0b2f6849ef5df3c82f6986e489382f364da387e54c4b797eadae276be
|