Introduction
Python is surely my favorite programming language. Its simplicity allows you to develop scripts, backends, or even websites in no time at all, without racking your brains.
But there has always been one thing I disliked about Python: its packaging tools. For a long time, I stayed away from all these tools like Poetry, because frankly, I didn’t understand a thing!
And then, I made a discovery that not only showed me that packaging Python code is a very simple task, but also completely changed all my habits.
I’m talking about uv.
Created by the same talented team at Astral (who had already delighted us with their tool ruff), uv is presented as an “extremely fast” Python package installer and resolver. This tool is what you might call a game-changer: once you’ve tried it, there’s no going back!
So, what’s so special about uv? Why all the excitement? That’s what we’re going to break down together. Hold on tight, you might just fall in love!
This guide is based on my experience with uv, and the examples were tested with version 0.7.3. Depending on your configuration, especially behind certain corporate proxies, you might need to add the
--native-tls
option to some uv commands if you encounter SSL connection issues.
uv, what is it exactly?
In a nutshell, uv is a command-line tool that aims to replace pip, pip-tools, venv, pyenv, and even parts of virtualenv and pipx, while being much, much faster. Yes, all of that!
Like many trendy new tools, it’s written in Rust. Sorry, I should say, it’s written in ✨ Rust ✨. This largely explains its lightning-fast performance.
Preamble
Before we dive headfirst into uv and how it works, I’d first like to explain how uv handles dependencies, virtual environments, and Python version installation. This was rather confusing for me the first time I encountered it, so I think it’s important to mention it upfront.
Dependency Management
uv bases its entire configuration on your pyproject.toml
file. If you were used to using a requirements.txt
file, for example, be aware that uv will completely ignore it.
Since I’m nice, here’s a gist of a pyproject.toml
template that I use every time I start a new Python project.
pyproject.toml
|
|
Instead of adding your dependencies manually, you can now use uv add <package_name>
, and it will be added to your pyproject.toml
.
Be careful though, as some external tools still rely on a
requirements.txt
file. You might therefore need to run the commanduv pip compile -o requirements.txt
in your CI/CD pipeline.
Virtual Environment
Like me, you might have been used to using the command source .venv/bin/activate
to activate your virtual environment. With uv, it’s a bit different. Your .venv
will still be created, but by using uv, it will default to your virtual environment. Let’s take an example.
Where previously you would have had to do these kinds of commands to run your script:
|
|
Now, uv removes virtual environment management from your workflow. Thus, the commands above will be replaced by these:
|
|
When you add a new package to your project (with the uv add
command), uv automatically takes care of creating a virtual environment if one doesn’t already exist.
Then, by running your script via uv run
, uv will automatically place itself in your virtual environment.
Python Installation
You might have used pyenv to install your different Python versions. With uv, all that is a thing of the past!
When you want to start a project with a specific Python version, you have several ways to do it:
- Use the
.python-version
file - Create a virtual environment:
uv venv --python 3.11.6
- Launch a specific Python version:
uvx python@3.12
What you need to remember is that uv doesn’t rely on a globally installed Python version but will always try to use the version specific to your project.
Alright, I think you know enough to begin your initiation. Let’s go!
Installation
Installing uv is child’s play. You have several options, choose the one that suits you best (feel free to consult the official installation documentation if needed):
|
|
Once installed, you can update it very easily:
|
|
And there you have it, uv is ready to use!
Managing Python Versions with uv
One of uv’s nice features is its ability to install specific Python versions. No more need to go through the official Python website, juggle with pyenv, or other tools.
To install the latest stable version of Python:
|
|
Need a particular version? No problem:
|
|
You can then use this version to run a script:
|
|
As I explained a little earlier, Python versions installed by uv are not “globally” available on your system via the simple python command. To use them, you must go through
uv run --python <version>
, or activate them in a virtual environment created with that specific version.
uvx for Running Packages on the Fly
You might be familiar with npx in the JavaScript world? uvx (the shortcut for uv tool run
) is the equivalent offered by uv. It allows you to run a Python CLI command (like ruff, black, ipython, etc.) in a temporary environment with the specified dependencies, without polluting your project or your system.
For example, to run a specific version of ruff:
|
|
Or to run ipython with requests temporarily available:
|
|
Extremely practical for one-shots or for testing tools!
Project Management with uv
Alright, all these commands are very nice, but I imagine you’re wondering how to integrate uv into a new project, or an existing one.
So let me show you the full power of uv for creating and managing a Python project.
Starting a New Project
To initialize a new project with uv:
|
|
This command will create a few files for you:
.python-version
: Specifies the Python version to use for this project (e.g.,3.11
).main.py
: An example Python file.pyproject.toml
: The central file for your project’s configuration, including its dependencies. This is the modern standard in Python.
Now, let’s see how to manage our dependencies.
With uv, pyproject.toml
becomes your source of truth for dependencies.
To add a new dependency to your project:
|
|
To add a development dependency (only for dev, like ruff or pytest):
|
|
⚠️ Very important: The first time you add a dependency, uv will generate a
uv.lock
file. This file contains the exact versions of all your dependencies (direct and indirect) that were resolved. Thisuv.lock
file is crucial and MUST be committed to your GitHub repository. It ensures that the versions of your packages are the same for everyone, according to your working environment.
Once your dependencies (especially dev dependencies) are added, you can use them with uv run
:
|
|
To remove a package:
|
|
In case you are using uv in an enterprise with a private index, you can configure that too!
By default, uv uses PyPI. If you work in a corporate environment (with an Artifactory or another private index), you can configure uv to use it via the pyproject.toml
file.
|
|
Then, you can, for example, export environment variables to authenticate.
|
|
Feel free to consult the uv documentation on indexes for more details.
Migrating an Existing Project to uv
Do you have a project with a good old requirements.txt
? Migration is quite simple:
- If you don’t have a
pyproject.toml
, create one (feel free to use the one provided above). - Run the following commands to add your requirements to your
pyproject.toml
:uv add -r requirements.txt
anduv add --dev -r requirements-dev.txt
. - Once all dependencies are migrated, you can delete your old requirements files.
If you were using other tools like Poetry, migration tools exist (such as:
uvx migrate-to-uv
).
And there you have it, your project is powered by uv!
Joining a Project That Already Uses uv
If you clone a project that is already managed by uv (so it should have a pyproject.toml
and a uv.lock
), setting it up is incredibly simple. You just need to run:
|
|
This magic command will read the uv.lock
file and install exactly the same versions of all the packages listed there. Isn’t that beautiful?
Build and Publish Your Project
uv doesn’t stop there and also offers commands for building and publishing.
To build your package:
|
|
To publish to PyPI (or a private index):
|
|
And to quickly test if your freshly built package installs and imports correctly:
|
|
And there you go! You’ve published your package in the blink of an eye!
Cleaning Your Cache
Over time, uv (just like pip) accumulates a cache of downloaded packages. While this allows for quick installation of your dependencies across multiple projects, in the long run, it could take up quite a bit of space on your computer. To clean it, simply run the following command:
|
|
Conclusion
As you’ve understood, uv isn’t just “another package manager.” It’s a real breath of fresh air in the Python ecosystem.
- Speed: This is the first thing that shocks you when you use it. Installations, resolutions, everything is incredibly faster than pip. On large projects, the time savings are phenomenal.
- Unification: uv brings together functionalities that previously required multiple tools (pip, venv, pip-tools, or even pyenv for basic needs). Having a single, coherent interface greatly simplifies the workflow.
- Compliance: The uv developers base all their choices on Python guidelines (those famous PEPs). You can therefore be sure that your
pyproject.toml
respects modern Python packaging standards. - Reliability: The lockfile system (
uv.lock
) ensures reproducible builds, an essential point for teamwork and continuous integration.
Since I started using uv, my interactions with Python dependency management have become faster, simpler, and more enjoyable. It’s the kind of tool that, once adopted, makes you wonder how you ever managed before.
So, if you’re looking to modernize your Python usage and gain productivity (and peace of mind!), I can only encourage you to give uv a try. Trying it is very often adopting it!
References
- Official uv documentation: https://docs.astral.sh/uv
- uv GitHub repo: https://github.com/astral-sh/uv
- A year of uv: pros, cons, and should you switch? (bitecode.dev)
- uv tricks (bitecode.dev)
Bonus: uv cheatsheet
Here’s a list of super handy uv commands. A cheatsheet, if you will. Feel free to come back and consult it as needed!
|
|