uv is “an extremely fast Python package installer and resolver, written in Rust” from https://astral.sh. Recently, I have started using uv in my day-to-day Python workflows. After a few weeks of usage I am sold! uv will be my go-to package manager for Python projects moving forward. It is MUCH faster than pip and I really like the new workflow it provides me with uv pip compile
and uv pip sync
.
TL/DR
I am now using uv
instead of pip
for most of my projects. My workflow looks like this:
alias uvinit='uv venv && source .venv/bin/activate'
alias uvsync='uv pip compile requirements.in --quiet --output-file requirements.txt && uv pip sync requirements.txt'
uvinit
echo "pandas" > requirements.in
uvsync
Why uv?
For all of my future projects I plan to use uv because:
- uv is much faster than pip.
- uv pip compile and uv pip sync are a better workflow for managing dependencies.
- uv enforces best practices by forcing you to use virtual environments.
There is one exception. For package development, I plan to still use poetry (https://python-poetry.org) because it some features that are specific to package development that uv does not have (e.g. like poetry publish
).
Install uv
See https://github.com/astral-sh/uv for more details.
# On macOS and Linux.
curl -LsSf https://astral.sh/uv/install.sh | sh
# On Windows.
powershell -c "irm https://astral.sh/uv/install.ps1 | iex"
Workflow comparison
Creating a virtual environment
uv venv
python -m venv .venv
source .venv/bin/activate
pip install --upgrade pip wheel setuptools
Installing first package
Define your initial requirements.
pandas
Then install the requirements and the transitive dependencies.
# Identify all transitive dependencies and create requirements.txt
uv pip compile requirements.in --quiet --output-file requirements.txt
# Update environment to match requirements.txt
uv pip sync requirements.txt
# Install all packages in requirements.in and the transitive dependencies
pip install -r requirements.in
Adding new packages
Update requirements.in to include the additional packages you want to install.
pandas
rich
htmx
Then install the new packages and the transitive dependencies.
# Identify all transitive dependencies and create requirements.txt
uv pip compile requirements.in --quiet --output-file requirements.txt
# Update environment to match requirements.txt
uv pip sync requirements.txt
pip install -r requirements.in
Removing packages
Up until now, other than the speed improvements it may not be obvious why uv is better than pip. Lets say we want to remove the pandas package from our project. This is an area where uv really shines. With uv, we can remove the package from requirements.in and run uv pip compile
and uv pip sync
to update our environment.
With pip, we need to remove the package from requirements.txt and delete the current virtual environment,and then re-create it. If you were to use pip uninstall pandas
, it would remove pandas from our environment, but not all of the transitive dependencies that pandas brought in that we no longer require.
rich
htmx
# Identify all transitive dependencies and create requirements.txt
uv pip compile requirements.in --quiet --output-file requirements.txt
# Update environment to match requirements.txt
uv pip sync requirements.txt
# Deactivate the current virtual environment
deactivate
# Delete the virtual environment
rm -rf .venv
# Recreate the virtual environment
python -m venv .venv
source .venv/bin/activate
pip install --upgrade pip wheel setuptools
# Install all the packages again
pip install -r requirements.txt
Using aliases
It is true that for parts of the workflow using uv requires more typing. I have found these two aliases to be very helpful in my workflow.
alias uvinit='uv venv && source .venv/bin/activate'
alias uvsync='uv pip compile requirements.in --quiet --output-file requirements.txt && uv pip sync requirements.txt'
You can add these to your .bashrc
or .zshrc
file to make them available in all of your projects.
Now when I start a new project I run:
uvinit
Whenever I make a change to requirements.in I run:
uvsync