Use the --output-file
flag for uv export
.
After writing this blog post I posted a few issues on the uv GitHub repo:
- #6780: Use uv init to lock to a particular Python version
- #6781: Add the option to uv add to pin to latest version of package
Based on what I learned in these issues I have made a few updates to this blog post:
- Use
.python-version
to specify desired Python version for the project. - Don’t pin package version in
pyproject.toml
, letuv.lock
handle the pinning. - Use
uv export
to generate arequirements.txt
file.
Astral recently announced a slew of new features for uv https://astral.sh/blog/uv-unified-python-packaging. uv is now a complete tool for all of your python needs:
- Managing your Python installation (I used to use pyenv)
- Managing Python projects (I used to use poetry)
- Building Python packages (I used to use poetry)
- Managing Python tooling (I used to use pipx)
- Running Python scripts (I would run scripts using my global Python interpreter or custom virtual environments)
As someone who works in data science, here is how I plan to integrate uv into my regular workflows.
TL/DR
uv is rapidly changing! This blog post was written using version 0.4.9
.
Install uv
Create a new project
My first decision is what version of Python I want to use. Typically, I want to use the most recent version, which is 3.12.5 (https://www.python.org/downloads/). You can check which versions of Python are available to uv by running:
To only see the most recent versions you can pipe the output to head
and grep
:
With my Python version selected, I am ready to bootstrap my project. Note that we pass the --app
flag because this is a project, not a Python library.
My project has now been bootstrapped with the following files:
.python-version
hello.py
pyproject.toml
README.md
Note: the README is empty.
Before proceeding, add a short project description to the README.md and the pyproject.toml description section.
Run your code
When using uv to manage your project, the preferred way to run your code is by using uv run <name-of-script>
(as opposed to python <name-of-script>
):
The first time that you invoke uv run
, a few things happen:
- uv will try to find the version of Python you want on your computer. If it is not available, uv will automatically install it for you.
- uv will create a virtual environment at
./.venv
, using your desired version of Python. - Lastly, uv will execute your script using the virtual environment.
It would also be valid to do the following:
However, I prefer to go all in with uv
. The main advantage is that uv run
always uses a virtual environment. You do not need to remember to create and activate one.
Add a package
To add packages with uv you use uv add <package>
(as opposed to pip install <package>
):
After running uv add
, the pyproject.toml has been updated to include requests:
One thing to note is how the requests package has been pinned:
Since this is a Data Science project, as opposed to a Python library that we would publish to PyPI, I want to be confident that I can re-run this code using all of the exact same dependencies on another machine. This is where the uv.lock
files comes in. Our project now has this structure:
The uv.lock
file describes the exact resolved versions of every package this is installed in my environment. Here is a snippet from the uv.lock
file that shows how requests is pinned to version 2.32.3
:
You can read more about the lock file here https://docs.astral.sh/uv/guides/projects/#uvlock.
Adding and removing packages
Additional packages can be added with the uv add <package-name>
command. To remove packages, use uv remove <package-name>
. The uv remove
command is similar to the pip uninstall
command. The most important difference is that uv remove
not only removes the package you specify but also all of the transitive dependencies. For example, let’s make the following changes:
First, let us check all of the packages in our environment:
Then, use uv add
and uv remove
to update the environment:
There are a few important things to note:
- httpx is now installed.
- certifi is still installed, even though we uninstalled requests. It is still in the environment because both httpx and requests depend on this library.
- Both pyproject.toml and uv.lock has been updated to reflect all of the changes.
Generating a requirements.txt
uv creates a pyproject.toml and a uv.lock file. These two files are all you need to recreate your Python environment anywhere. However, some tools won’t know what to do with these files and instead require a good old fashion requirements.txt. Use the following command to generate a requirements.txt:
requirements.txt
Running other tools (e.g. linters and formatters)
I often want to run additional tooling on my code base that is not a requirement for my project. For example, I like using the ruff formatter to format my code. I could include these tools as dependencies in my project, but I prefer to keep my project dependencies separate from my tooling dependencies. I used pipx for this in the past, but now I use the uvx
command. For example, here is how I would format my code using ruff:
Further Reading
- My original post on uv: Replacing pip with uv for Python projects
- uv docs: https://docs.astral.sh/uv/
- uv blog post: https://astral.sh/blog/uv-unified-python-packaging