CLI tools hidden in the Python standard library
Seth Michael Larson pointed out that the Python gzip module can be used as a CLI tool like this:
python -m gzip --decompress pypi.db.gz
This is a neat Python feature: modules with a if __name__ == "__main__": block that are available on Python's standard import path can be executed from the terminal using python -m name_of_module.
Seth pointed out this is useful if you are on Windows and don't have the gzip utility installed.
This made me wonder: what other little tools are lurking in the Python standard library, available on any computer with a working Python installation?
Finding them with ripgrep
I decided to take a sniff around the standard library and see what I can find.
Jim Crist-Harif pointed me to python -m site, which outputs useful information about your installation:
python3.11 -m site
sys.path = [
'/opt/homebrew/Cellar/python@3.11/3.11.4_1/Frameworks/Python.framework/Versions/3.11/lib/python3.11',
'/opt/homebrew/Cellar/python@3.11/3.11.4_1/Frameworks/Python.framework/Versions/3.11/lib/python311.zip',
'/opt/homebrew/Cellar/python@3.11/3.11.4_1/Frameworks/Python.framework/Versions/3.11/lib/python3.11/lib-dynload',
'/opt/homebrew/lib/python3.11/site-packages',
'/opt/homebrew/opt/python@3.11/Frameworks/Python.framework/Versions/3.11/lib/python3.11/site-packages',
]
USER_BASE: '/Users/simon/Library/Python/3.11' (doesn't exist)
USER_SITE: '/Users/simon/Library/Python/3.11/lib/python/site-packages' (doesn't exist)
ENABLE_USER_SITE: True
This showed me that the standard library itself for my Homebrew installation of Python 3.11 is in /opt/homebrew/Cellar/python@3.11/3.11.4_1/Frameworks/Python.framework/Versions/3.11/lib/python3.11.
So I switched there and used ripgrep to find likely packages:
cd /opt/homebrew/Cellar/python@3.11/3.11.4_1/Frameworks/Python.framework/Versions/3.11/lib/python3.11
Then ran rg:
rg 'if __name__ =' -l | grep -v 'test/' \
| grep -v 'tests/' | grep -v idlelib | grep -v turtledemo
The -l option causes ripgrep to list matching files without showing the context of the match.
I built up those grep -v exclusions over a few iterations - idlelib/ and turtledemo/ have a bunch of matches that I wasn't interested in.
Update: Here's an alternative way of expressing that search:
rg 'if __name__ =' -l | grep -v -e 'test/' -e 'tests/' -e idlelib -e turtledemo
grep -vstill means "everything that doesn't match this" - but then the multiple-e '...'patterns are used to construct a "this pattern or this pattern or this pattern" filter. This saves on having to pipe throughgrep -vmultiple times. Thanks for the tip, dfc.
Here's the result:
tabnanny.py
pyclbr.py
netrc.py
heapq.py
fileinput.py
site.py
telnetlib.py
smtplib.py
timeit.py
__hello__.py
aifc.py
json/tool.py
asyncio/__main__.py
runpy.py
mailcap.py
tokenize.py
smtpd.py
sysconfig.py
tarfile.py
lib2to3/pgen2/tokenize.py
lib2to3/pgen2/driver.py
lib2to3/pgen2/literals.py
xmlrpc/server.py
xmlrpc/client.py
getopt.py
dbm/__init__.py
doctest.py
pickle.py
imaplib.py
compileall.py
shlex.py
ast.py
venv/__init__.py
py_compile.py
ensurepip/__main__.py
ensurepip/_uninstall.py
http/server.py
pickletools.py
poplib.py
quopri.py
calendar.py
pprint.py
symtable.py
pstats.py
inspect.py
pdb.py
platform.py
wsgiref/simple_server.py
random.py
ftplib.py
mimetypes.py
turtle.py
tkinter/dialog.py
xml/sax/expatreader.py
tkinter/colorchooser.py
tkinter/dnd.py
tkinter/filedialog.py
tkinter/messagebox.py
tkinter/simpledialog.py
tkinter/font.py
tkinter/scrolledtext.py
xml/sax/xmlreader.py
tkinter/__init__.py
code.py
difflib.py
pydoc.py
uu.py
imghdr.py
filecmp.py
profile.py
cgi.py
codecs.py
modulefinder.py
__phello__/__init__.py
__phello__/spam.py
multiprocessing/spawn.py
textwrap.py
base64.py
curses/textpad.py
curses/has_key.py
zipapp.py
cProfile.py
dis.py
webbrowser.py
nntplib.py
sndhdr.py
gzip.py
ctypes/util.py
zipfile.py
encodings/rot_13.py
distutils/fancy_getopt.py
That's a lot of neat little tools!
I haven't explored my way through all of them yet, but running python -m module_name usually outputs something useful, and adding -h frequently provides help.
A few highlights
Here are a few of the commands I've figured out so far.
http.server
To run a localhost webserver on port 8000, serving the content of the current directory:
python -m http.server
This takes an optional port. To change port, do this:
python -m http.server 8001
Pass -h for more options.
base64
python3.11 -m base64 -h
usage: .../base64.py [-h|-d|-e|-u|-t] [file|-]
-h: print this help message and exit
-d, -u: decode
-e: encode (default)
-t: encode and decode string 'Aladdin:open sesame'
asyncio
This provides a Python console with top-level await:
python -m asyncio