Package: aush
aushPythonic subprocess libraryIntroductionaushis a Python library to make calling other programs and getting their output as easy as possible. It's intended to be the best possible melding of shell scripting and Python. It'saushome.PhilosophyCommand line programs are basically “functions” available to call from any other language, whose signature looks like:list[str] -> (code: smallint, stdout: stream, stedrr: stream). That's what calls likeexecveare doing underneath.The goal of this library is to make calling those functions as natural as possible within Python.MotivationSo often I've found myself writing a small Bash/shell script that just calls some programs and maybe does some conditionals. Naturally, it has to start with#!/usr/bin/env bash
set -Eeuxo pipefailfor a "strict mode" that has some of the same failure properties you expect a program to have, such as that your program doesn't just keep running when a command fails, it won't silently proceed if you use undeclared variables, and so on.Then it frequently occurs to want to do something "hard" in shell, like string processing or data structures or even command-line argument parsing.
Python hasargparsein the stdlib, I don't want to mess withgetopt.
I want to be able to bust out some Pandas DataFrames or Requests requests, read json or whatever.So often when I start with Bash, I wind up transitioning the script to Python, and that often feelsterrible.
Pipes become an unnatural operation.
I can't just "redirect to a file" I need to know how to callsubprocess.runto capture stdout (there are a few different ways), open and close the file, and so on.
Instead,aushmakes the transition from shell to Python feelbetter.Here's theorigin thread on lobste.rs.Installationpip install aushExamplesI recently make acreate-pythonprogram that will initialize a Python project the same way every time, as aPoetryproject with the same set of default dependencies, linters, and so on.At first I created a shell script. Here's a portion:poetryinit\--no-interaction\--name="$1"\--author="$(gitconfig--getuser.name)"\--dev-dependency=pytest\--dev-dependency=ipython\--dev-dependency=pylint\--dev-dependency=mypy\--dev-dependency=black\--dev-dependency=pudb

poetryinstallHere's how that converts to Python:cmd=['git','config','--get','--global','user.name']username=run(cmd,check=True,capture_output=True,text=True).stdout.strip()cmd=['poetry','init','--no-interaction','--name',sys.argv[1],'--author',username,'--dev-dependency=pytest','--dev-dependency=ipython','--dev-dependency=pylint','--dev-dependency=mypy','--dev-dependency=black','--dev-dependency=pudb',]run(cmd,check=True)run(['poetry','install'],check=True)Here's how that looks now usingaush:poetry.init(no_interaction=True,name=args.name,author=git.config(get='user.name'),dev_dependency=['pytest','ipython','pylint','mypy','black','pudb'],)poetry.install()I think that finally feelsbetterthan either shell or Python alone. Feel free to look atcreate-python's historywhere I convert it from shell to Python and try various libraries along the way.UsageaushhasCommands andResults. You make a base command like:from aush import echo, git, python3Then, any new command is immutably created from another command by either dot or indexing notation:git.init()echo("*.sqlite3")>'.gitignore'git.add(all=True)git.commit(m="Initial commit")Here's an example of explicitly creating a command from another using index notation:django_manage=python3["manage.py"]django_manage.startapp(args.name)django_manage.migrate()django_manage.createsuperuser(noinput=True,username="admin",email=git.config(get='user.email'),_env=dict(DJANGO_SUPERUSER_PASSWORD='password'),)Here's how that call tocreatesuperuserlooks in equivalent shell:DJANGO_SUPERUSER_PASSWORD='password'python3manage.pycreatesuperuser\--noinput\--usernameadmin\--email"$(gitconfig--get=user.email)"Note that, like shell, inaushyou don't have to manually merge inos.environif you want to add new things to the environment.A program gets executed when a function call () is made on aCommand.
That returns aResult.
The result has.code,.stdout, and.stderrmembers to get the results of running the command.
Using theResultyou can also redirect stdout to a file withresult > 'filename',bool(result)indicates success or failure of the command, and more.aushcalls follow the following conventions:positional arguments are passed verbatimsingle-letter keyword arguments are converted to single-dash argumentslonger keyword arguments are converted to double-dash argumentsunderscores in keywords and program names are converted to dashesif the value of a keyword argument is an iterable (notstr), it is repeatedIf the conventions don't do what you want, you can always fall back to passing exactly the strings you want, positionally, in the list way of creating commands.aush Design Goals:aushshould provide themost convenientway to write "shell scripts"make it easy to call subprocesses and get their outputsmake subprocesses callslooklike idiomatic Pythonfavor convenience over performance/control (there are lower level APIs available to use)but still enable shell performance patterns, such as:piping and redirecting shouldn't have to wait for one process to complete in order to startaush can (optionally) log stdout/stderr "as they happen" same as you would for a shell scriptSafer subprocess managementbe "safer" than shell scripts or Pythonfewer opportunities for string substitution or incorrect input handling leading to deadlocksthrows exceptions on non-zero return codes (like set -e by default) to make it harder to miss error casesSecondary design goalsit should make good use of asyncio, and eventually help script applications like expectit should allow you to build tools around cli tools, like timestamping each line of output (something I miss from iTerm2)just like you'd use Pandas to deal with tabular data (I'll often use it just to massage csvs) I hopeaushcan be useful for calling subprocesses in your programs, and help writesaferprograms that interact with subprocesses, and bigger "shell scripts" than you would otherwiseAllowaushto be a command-line program itself that does something? Maybe when called as a module it can provide testing utilities like myargvorrandomtextgenerator. Maybe an "aush script" could find a way to automatically import anything not found in python space and try to run it? That way you can avoid the 'from aush import a million things' and turn logging on, etc.Non-goalsaushis not a new language or an interactive shell; it's just a Python libraryFAQQ. Pronunciation?A. It's pronounced "awwsh" ("au" as in "audible").Q. Where does the name come from?A. Originally I named it "autoshell", but when I went to put it on PyPI I found "autoshell" was taken, while the shorter present name wasn't.