I’ve been working with uv for over a year now, and honestly, it’s awesome! It gradually replaced my Python ecosystem tools one after another, and at this point, I rely on it for nearly everything.
Since, uv
is relatively “new”, I decided to write a quick blogpost about it. Let’s dig in.
Python versions: replacing pyenv
First things first: Python versions!
pyenv
is a great tool and a very powerful piece of the Python ecosystem. However, it can be quite complicated to set up, especially if you’re not a Python developer.
Another issue with pyenv
is that installing a new version often requires compiling from source, which can be cumbersome. It required a lot of unnecessary build tools, and the builds would fail from time to time, leaving me with unwanted compilation errors I had to deal with.
uv
solves these problems effectively. It uses pre-built python builds hosted by Astral (the company behind uv), while it still supports system Python versions i.e. in theory uv python
can be used alongside pyenv
or any other tools.
In addition to the Astral managed Python builds (python-build-standalone), uv
support pypy
and graalpy
distributions.
uv
distinguishes between two types of Python versions:
- Managed versions: these are versions managed by
uv
- System versions: these are all the other version which are not installed and not managed by
uv
. (System Python distributions, Pyenv versions …)
To install a new python version we use uv python
:
$ uv python list # list all available versions
$ uv python install 3.14 # install cpython 3.14
$ uv python install pypy-3.10 graalpy-1.11 # install pypy and graalpy versions
$ uv python uninstall 3.11 # remove cpython 3.11
Now that we have multiple versions we can specify the correct version we need using the -p[--python]
flag. If the Python version is not explicitly specified, uv
follows a simple version discovery algorithm:
- Check if there is any
.python-version
file in the current directory or any of its parent directories. - Check if there is a global
.python-version
in the user configuration directory. - Fallback to the python-preference specified via a cli option or in the user configuration file. By default
uv
prefersmanaged
versions
Here is an example, let’s consider the following tree and uv python configuration.
$ tree
├── p1
│ └── d1
└── p2
# Python version list
$ uv python list
cpython-3.13.7-linux-x86_64-gnu /sbin/python3.13 # system version
cpython-3.10.16-linux-x86_64-gnu ~/.local/share/uv/python/cpython-3.10.16-linux-x86_64-gnu/bin/python3.10 # managed version
$ cd p1
$ uv python pin 3.13 # pins the 3.13 version by creating .python-version file
$ cd d1
$ uv run python --version
# ==> Python 3.13.7 (inherited from parent dir pin)
$ cd ../../p2
$ uv run python --version
# ==> Python 3.10.16 (no version is pinned, falling back to managed)
$ echo 'python-preference = "system"' > $HOME/.config/uv/uv.toml
$ uv run python --version
# ==> Python 3.13.7 (system version is preferred in uv config)
$ uv run --python-preference managed python --version
# ==> Python 3.10.16 (managed is preferred in cli option)
$ cd ../../p1
$ uv run --python-preference managed python --version
# ==> Python 3.13.7 (.python-version take precedence over python-preference)
Using tools: Replacing pipx
I still remember when I first came across pipx
, it was a real lifesaver! For those unfamiliar, pipx
installs Python tools inside a virtual environment and adds an executable symlink to your path, letting you run your favorite tools without cluttering up your system.
With uv
, you get the same functionality through the uv tool
command. Let’s dive into some examples and use cases of uv tool
.
$ uv tool install pytest # install pytest globally inside a venv
$ uv tool install --python 3.10 rtv # install rtv with a specific python version
$ uv tool install --with pytoml yamllint # include pytoml as dependency in the same venv
$ uv tool list # list install tools
$ uv upgrade rtv # upgrade rtv
$ uv upgrade -p 3.12 rtv # upgrade rtv and the used python version as well
$ uv uninstall yamllint # uninstall yamllint
$ uv tool run cosway -t "Hello World" # run cosway without installing it
$ uvx cosway -t "Hello World" # uvx is an alias for uv tool run
Using
pipx
oruv tool
has become necessary after PEP 668.
Managing Python projects: Replacing Poetry and Pipenv
Compared to other language package managers like npm
or cargo
, pip
historically lacked many features, such as package locking and project management. This gap led the Python community to develop powerful tools like pipenv
, poetry
and many others.
Today, Python is catching up by introducing new standards through several PEPs, including PyProject.toml
(PEP 517, 518, 621, etc.) and pylock.toml
(PEP 751).
As a latecomer, uv was able to take advantage of these developments, implementing most of the standards set by these PEPs to offer modern package management capabilities without creating new standards and largely adhering to the official specifications (with the exception PEP 751).
$ uv init # Create a python project
$ ls
main.py pyproject.toml README.md
$ uv add typer # add typer as a dependency
$ uv add -r requirements.txt # import all dependencies from a file
$ ls -a # first uv add creates virtual environment and uv.lock and .python-version
.git .gitignore .python-version .venv main.py pyproject.toml README.md uv.lock
$ uv run main.py
$ uvx ruff check .
$ uv add --dev ruff # install ruff as a dev dependency
$ uv sync # sync dependencies from lock file
Even more
uv
also provides a range of powerful features, including uv venv
for creating and managing virtual environments, uv pip
for a pip-compatible interface for those familiar with pip, and uv build
and uv publish
for building and distributing Python packages.
Beyond all this, uv
is by an order of magnitude faster than pip and other similar tools.
To learn more about uv features, check the official documentation.
Thank you for reading, see you next time 👋🏾.