Epycs is a simple way to convert shell scripts to python
Project description
Epycs
Epycs is a simple way to convert shell scripts to python. It features
- A simple subprocess API
- A sane behaviour of exiting by default on subprocess failures
- A show-output-on-fail behaviour
The goal of this package is to be able to write shell-script equivalent code in python while still being terse, but adding a tons of goodness in terms of arithmetical expression, string manipulation, code reuse etc...
Say no to .sh and welcome .py with epycs, you'll thank me later.
Usage
This section is a complete, copy-pasteable cheat sheet of the public API. If you are an LLM/agent: everything you need to use epycs is below — no need to read the source or search the web.
pip install epycs # or: uv add epycs
Run a command
cmd is a magic shell: any attribute is resolved to a program on PATH.
from epycs import cmd
cmd.ls("-l", "/tmp") # runs `ls -l /tmp`, output goes to the terminal
cmd.git("clone", url, dest) # args are str()-ed automatically (Path, int, ...)
By default a command prints to the terminal and returns a
subprocess.CompletedProcess. To capture and parse the output instead, use
out_filter (see below).
Default behaviours (the "goodness" over raw subprocess)
- Exit on failure: a non-zero return code exits your script with that code
(like
set -e). Disable globally withexit_on_error = False. - Quiet-but-loud-on-fail: with
quiet=Trueoutput is hidden, unless the command fails, in which case it is dumped to stderr. text=Trueis the default, so captured output isstr, notbytes.
import epycs.subprocess as esp
esp.verbose = True # print every command to stderr (like `set -x`)
esp.exit_on_error = False # don't sys.exit() on non-zero; inspect .returncode
Capture & parse output: out_filter
out_filter captures stdout and runs it through a parser. Built-in filters
(pass the name as a string) or any callable(str) -> object:
from epycs import cmd
txt = cmd.cat("f.txt", out_filter="text") # str (raw)
lines = cmd.ls("-1", out_filter="text_lines") # list[str], split on \n
nul = cmd.find("-print0", out_filter="text_lines_0") # list[str], split on \0
data = cmd.curl(url, out_filter="json") # parsed JSON (dict/list)
root = cmd.cat("f.xml", out_filter="xml") # xml.etree Element
rows = cmd.cat("f.csv", out_filter="csv") # list[list[str]]
dicts = cmd.cat("f.csv", out_filter="csv_dict") # list[dict], header = keys
last = cmd.echo("hi", out_filter=lambda s: s.strip()) # custom callable
Available built-in filters: text, text_lines, text_lines_0, json,
xml, csv, csv_dict.
Build & reuse commands: find_program and .arg(...)
find_program(name, *aliases) resolves a program once (trying aliases in order,
raising ShellProgramNotFoundError if none found) and returns a reusable
ShellProgram. .arg(...) returns a new command with extra args and/or
default kwargs baked in (originals are never mutated):
from epycs import cmd, find_program
ls = find_program("ls", "/usr/bin/ls") # first match wins
echo = find_program("echo").arg(out_filter="text") # always capture as text
echo("hello") # -> "hello\n"
git = cmd.git.arg("-C", repo) # `git -C <repo> ...`
git("status", "--short", out_filter="text_lines")
cmd.echo.toto(out_filter="text") # attribute access also adds args -> `echo toto`
Keyword options
These epycs-specific kwargs work on any call; everything else is forwarded to
subprocess.run (e.g. cwd=, timeout=, input=):
| kwarg | effect |
|---|---|
out_filter |
capture stdout and parse it (see above) |
quiet=True |
hide output unless the command fails (then dump to stderr) |
stdout_tee=True |
capture stdout and still print it |
background=True |
start via Popen and return it immediately (no wait) |
additional_env={"K": "v"} |
add/override env vars; value None removes the var |
additional_pathenv={"PATH": ["/x"]} |
append entries to a PATH-like var |
cmd.long_task(quiet=True) # silent unless it fails
proc = cmd.server(background=True); proc.wait() # Popen, run concurrently
cmd.make(cwd="build", additional_env={"CC": "clang"})
ec = cmd.echo.arg("-c").env(TOTO="hi") # `.env(**vars)` == additional_env
Pipe commands with |
Chain commands with | exactly like a shell pipe. Operands are uncalled
programs (bake args with .arg(...)); the pipeline runs only when you call it:
from epycs import cmd
# stdout of each stage is wired to stdin of the next
errors = (cmd.cat.arg("app.log") | cmd.grep.arg("ERROR"))(out_filter="text_lines")
# args/kwargs of the terminal call go to the last stage
n = (cmd.cat.arg("app.log") | cmd.grep)("ERROR", out_filter="text")
Pipelines behave like a shell pipe with set -o pipefail: the pipeline's return
code is the rightmost non-zero stage code (0 if all succeed). Combined with
the default exit_on_error, any failing stage aborts the script — so
cmd.false | cmd.cat fails (a plain shell pipe would return cat's 0).
import epycs.subprocess as esp
esp.exit_on_error = False
(cmd.false | cmd.cat)().returncode # -> 1 (pipefail), not 0
Extras
from epycs.subprocess import source_shell_script, python_to_subprocess
from epycs.config import load_from
source_shell_script("env.sh") # apply a shell script's env to os.environ
# load_from looks at $NAME_CONFIG then ~/.config/<name>/<name>.{toml,json,...}
cfg = load_from("myapp") # returns parsed config, or None if absent
@python_to_subprocess # turn a python function into a pipeable subprocess
def worker(cmd_open): ...
Changelog
- v2.1.0
Pipe commands with the | operator, with shell-like pipefail semantics: the
pipeline's return code is the rightmost non-zero stage, so any failing stage
aborts the script under the default exit_on_error. out_filter and
stdout_tee apply to the last stage.
Justfile now builds and publishes with uv instead of build / twine.
- v2.0.0
Breaking: dropped support for Python < 3.13 (requires-python = ">=3.13").
Breaking: removed the deprecated ShellProgram methods with_arg,
with_default_kw and kwarg — use arg(...) instead.
pytest is now a real dependency; test/dev environment and VSCode setup.
Typing improvements: separate KW and KWVal types, typed path helper, clarified typing throughout.
Added an LLM/agent-friendly "Usage" cheat sheet to the README.
- v1.5.0
epycs.cmd directly accessible
ShellProgram.add supports kwargs, deprecated other methods, they will be removed
in a future version.
ShellProgram.env(name=value) for setting additional environment variables
tox testing on python 3.7 to 3.13
Various CI fixes
Using UV for package management
- v1.4.0
Improved handling of additional environment (allows any str-convertible,
and providing None deletes the env var)
Added new python_to_subprocess function, which turns a local python
function to a full-fledged subprocess, allowing for pure-python piping.
- v1.3.0
Added sourcing of shell scripts
Added new out_filter=text_lines_0 that splits by NUL character
- v1.2.0
Added epycs.config for lightweight user-defined config management
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 epycs-2.1.0.tar.gz.
File metadata
- Download URL: epycs-2.1.0.tar.gz
- Upload date:
- Size: 17.3 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.9.13 {"installer":{"name":"uv","version":"0.9.13"},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"22.04","id":"jammy","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
a0ffebfadf53f0eaa8a6dbb89f5c599ab098d46da1b02955b71ca96a37876d54
|
|
| MD5 |
9c7f78e75b23219d61652e858d23f5f8
|
|
| BLAKE2b-256 |
3a9df17c623f418170283f0b11871ca63f42bb5ae9c926fc166af54848b592f8
|
File details
Details for the file epycs-2.1.0-py3-none-any.whl.
File metadata
- Download URL: epycs-2.1.0-py3-none-any.whl
- Upload date:
- Size: 15.8 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.9.13 {"installer":{"name":"uv","version":"0.9.13"},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"22.04","id":"jammy","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
95334a7e22b3ba023ff675f92aef57c65d0afd65c46952b64ef3d49ddca86b09
|
|
| MD5 |
4a601a601dac41cf70f3577764413626
|
|
| BLAKE2b-256 |
c4366e3a37f5630b4636fd75a46dc47a8a197c2a23ebd26d97e46032f9b5c879
|