pip.wtf: Inline dependencies for small Python scripts.
# https://pip.wtf
def pip_wtf(command):
import os, os.path, sys
t = os.path.join(os.path.dirname(os.path.abspath(__file__)), ".pip_wtf." + os.path.basename(__file__))
sys.path = [p for p in sys.path if "-packages" not in p] + [t]
os.environ["PATH"] = t + os.path.sep + "bin" + os.pathsep + os.environ["PATH"]
os.environ["PYTHONPATH"] = os.pathsep.join(sys.path)
if os.path.exists(t): return
os.system(" ".join([sys.executable, "-m", "pip", "install", "-t", t, command]))
On GitHub and pip.wtf. Quick start: curl -sL pip.wtf/raw
I’ve had it. I just wanted to write a single-file Python script with one measly little external import. But the Python dependency management cabal just won’t stop treating me like I’m an idiot.
Pipx? Not for scripts. Poetry? “Oh, poor baby, did you forget your pyproject.toml?” Pip-run? Tired of fighting with persistence. Pip by itself with a little -U? I’ve gotta give them an extra flag to show I know how to wipe my own ass! What about baking it in to pip? If the rabble can ever settle on anything, even more TOML, it’ll only work with a version of pip that’s definitely not gonna be around on my ancient Debian VPS!
Well, Python, you’ve done it. I’m pissed. I’m giving up on you…r suite of god-awful, overbearing package managers and I’m going to do it myself, in my script, with no virtual environments, no pip wrappers, and no god damn TOML!
That’s pip_wtf
: a single function you copy to the top of your Python
script. It needs pip and that’s it. You call it just once, with a string
containing the back half of a pip install
command, then do your imports, and
then you’ve got a script that works on pretty much every platform and pretty
much every Python version since 2.7 (as long as pip is around).
#!/usr/bin/env python3
# https://pip.wtf
def pip_wtf(command):
import os, os.path, sys
t = os.path.join(os.path.dirname(os.path.abspath(__file__)), ".pip_wtf." + os.path.basename(__file__))
sys.path = [p for p in sys.path if "-packages" not in p] + [t]
os.environ["PATH"] = t + os.path.sep + "bin" + os.pathsep + os.environ["PATH"]
os.environ["PYTHONPATH"] = os.pathsep.join(sys.path)
if os.path.exists(t): return
os.system(" ".join([sys.executable, "-m", "pip", "install", "-t", t, command]))
# Now you just call it to install your packages:
# pip_wtf('the rest of the pip install command here')
# Here are some examples for different platforms:
import sys
if sys.version_info >= (3, 5):
# You gotta shell-escape your requirements if they would break on the terminal.
# If you're on Windows, remember Windows needs double-quotes, not single.
pip_wtf('beautifulsoup4 "requests>=1.0" pyyaml==5.3.1')
elif sys.version_info >= (3, 0):
# You can add anything else you want to the pip install command to help add
# special flags for difficult situations, like when the Pip version is too old
# to support automatic https URLs...
pip_wtf('--index-url https://pypi.python.org/simple/ beautifulsoup4==4.2.1 requests==2.13.0 pyyaml==3.10 urllib3==2.0.5')
else:
# It even works with Python 2.7 (kinda tough to find an environment with that these days).
pip_wtf('beautifulsoup4 requests pyyaml')
import requests
import yaml
from bs4 import BeautifulSoup
soup = BeautifulSoup(requests.get("https://pypi.org").content, "html.parser")
print(yaml.dump({"header": soup.find("h1").get_text()}))
How’s it work? Well, for /home/adder/bin/bite.py
:
pip_wtf()
packages get installed into /home/adder/bin/.pip_wtf.bite.py
on first runsys.path
(where it looks for modules) includes the .pip_wtf...
directory and ignores all other site-packages
directories just like a virtualenv wouldPATH
(where subprocesses look for binaries) includes the .pip_wtf...
directoryPYTHONPATH
(where subprocess’s binaries will look for Python modules) includes the .pip_wtf...
directoryYou’re totally in charge of the new directory and the pip_wtf behavior.
rm -rf
it first.return
.t
so it’s one .pip_wtf/
for your whole script directory, or import it, or something. Do what you want, dude! It’s all right there!Finally. Python dependencies that follow your orders instead of the other way around.