Unleash the power of uv shebangs
A shebang is the character sequence #!
used at the start of a text file in POSIX systems to specify which piece of software should be used to execute the contents of the file, for example:
#! python
print("Nobody expects the Spanish Inquisition!")
In theory, #! python
is sufficient. In practice, to ensure that your operating system can find the python
executable, one often uses #! /usr/bin/env python
instead. The executable env
is used to locate python
, wherever it might reside on your computer.
But this means that to use the above script, you need to have Python installed. Python is notorious for its dependency hell, meaning that it is very easy to find yourself in a situation where several independent Python distributions coexit within the same operating system (so that your shebang might be calling the wrong Python version), or where updating the Python library needed by one script (“dependency”) will break another script.
All this messiness can be avoided by using uv
, a Python dependency manager with the following selling points:
uv
is a self-contained executable which is very unlikely to break down even if you do something wrong.uv
uses independent “virtual environments”, sandboxing each script so that changing the dependencies for one script will not break anotheruv
is extremely fast, so that python itself and all required dependencies can be reinstalled from scratch without any noticeable overhead.
To use uv
, you first have to install it, then modify your shebang accordingly. There is absolutely no need to install Python for this to work.
#! /usr/bin/env uv --no-project run python
print("Nobody expects the Spanish Inquisition!")
In the above code, --no-project
means that if you were to invoke the script in a directory where another set of requirements normally applies (e.g., as a result of a pyproject.toml
file), these additional requirements would be ignored while running your script.
The true power of uv
shebangs lies in the ability to specify dependencies within the shebang line. For example, if your script needs numpy
:
#! /usr/bin/env uv run --no-project --with numpy python
from numpy import eye
print(eye(5))
One may require a specific version of numpy
:
#! /usr/bin/env uv run --no-project --with numpy==2.1.2 python
import numpy
print(numpy.__version__)
One may also require more than one dependency:
#! /usr/bin/env uv run --no-project --with matplotlib==3.9.2 --with scipy==1.14.1 python
Finally, one may require a specific version of Python:
#! /usr/bin/env uv run --no-project --with matplotlib --with scipy --python 3.11.6 python
As a fully functional example, here is a version of the doi2bibtex
script described here which only requires uv
to work properly:
#! /usr/bin/env uv run --no-project --with requests==2.32.3 --with pyperclip==1.9.0 python
import sys, requests, pyperclip
doi = sys.argv[1] # DOI is the first command-line argument
url = f'http://doi.org/{doi}' # construct the URL
# HTTP header to get BibTeX format back
headers = {'Accept': 'text/bibliography; style=bibtex'}
# send out the HTTP request
r = requests.get(url, headers = headers)
r.encoding = 'utf-8' # specify that the response is UTF8-encoded
bib = r.text[1:-1] # shave of the first and last characters of the response
pyperclip.copy(bib) # copy the result to the clipboard
print(bib) # and print it out as an indicator of success