This page contains:
- An introduction to
uv
as Python package manager. - Instructions for installing
uv
. - Advice on using
uv
, by comparing it with a more classicalvenv
/pip
workflow. - A summary of the key
uv
features. - A
uv
command ‘cheat sheet’.
uv
not yet having a v1.0.0 release…
uv
is under active development. Use with caution in production environments. Minor updates in uv
may include breaking changes and new features, as per the documentation. This page will be updated regularly, but there might be differences between this page’s last modified date and a uv
version release date. Always compare these dates to spot any potential inconsistencies.
Introduction
With knowledge in both Rust and Python, I was thrilled to learn about uv
- a tool similar to cargo
for Python, made using Rust!
Just like how cargo
handles tasks in Rust, uv
simplifies managing dependencies, creating virtual environments, and building packages in Python. Plus, it’s faster than existing tools due to its Rust backend 🦀.
Over the last few months, I’ve been testing uv
as an alternative to my usual choices for dependency and virtual environment management: venv
+ pip
. This article delves deeper into uv
, compares it with venv
+ pip
, and highlights its unique advantages.
Please note that this article does not replace the official uv
documentation and getting started guide. Instead, it provides a quick summary of the main features of uv
in one place, helping those interested in switching from traditional Python workflows to uv
.
Installation
To set up uv
, it’s recommended to visit the primary setup guide for the most recent steps. You can choose from several methods such as:
- Installing directly using
curl
orwget
. - Using
brew
on macOS systems. - Obtaining it through PyPI.
Comparing uv
with a venv
/pip
workflow
To get a feel for uv
’s capabilities, let’s compare it to setting up and managing Python projects using a venv
/pip
approach.
A basic Python project usually needs (at least): one Python file, version control, a virtual environment, a way of sharing dependencies with others, and some form of documentation - even if just a README.md
.
Here’s a comparison between the workflows of venv
/pip
and uv
:
Using venv
- Create a virtual environment:
python3 -m venv .venv
- Activate the virtual environment:
source .venv/bin/activate
- Initialise
git
version control:
git init
- Add a
.gitignore
:
touch .gitignore
- Add a
README.md
:
touch README.md
- Create a Python file:
touch main.py
- Update pip:
pip install --upgrade pip
- Install dependencies into the virtual environment:
pip install pandas geopandas
Write the source code.
Run the source code:
python main.py
- Export dependencies to make the environment reproducible:
pip freeze > requirements.txt
Using uv
- Setup project structure:
uv init
- Install dependencies into a virtual environment:
uv add pandas geopandas
Write the source code.
Run the source code:
uv run main.py
Comparing the two workflows shows:
- Creating a minimal Python project using
uv
takes fewer steps. It’s more concise. - When setting up a project,
uv
builds the main structure automatically. This reduces the chance of mistakes during setup and provides a consistent structure across projects. uv
is faster when creating a virtual environment and installing dependencies. It takes fractions of a second compared to several seconds forvenv
/pip
. This speed will be beneficial, especially for larger Python packages with many dependencies to install and resolve.
The files within the directories and the dependency management methods used are different too:
venv
directory structure:
.
├── .git
├── .gitignore
├── .venv
├── main.py
├── README.md
└── requirements.txt
uv
directory structure:
.
├── .git
├── .gitignore
├── .python-version
├── .venv
├── main.py
├── pyproject.toml
├── README.md
└── uv.lock
uv
follows the latest PEP standards (PEP 517 and PEP 621). This means that all project information, dependencies, and build settings can be managed from a single place using apyproject.toml
file.uv
also uses dependency locking with the help of anuv.lock
file. Similar to arequirements.txt
file, it records the exact state of the resolved project dependencies for better reproducibility and usability across multiple machines. Make sure you check this file into version control for these reasons.- Additionally,
uv
has a.python-version
file that sets a default Python version for the project environment. This helps with consistency between setups on different machines. However, it’s not strictly necessary.uv
will automatically resolve a suitable Python version based on thepyproject.toml
specification. Even in this case, it can still be useful to make sure all developers use the same python version.
This basic comparison shows uv
provides a much better way of working than the traditional venv
/pip
method. But what else can you do with it?
Other uv
features
Installing Python
Since uv
is not connected to Python itself, it can effectively manage and install Python distributions - similar to pyenv
!
To install the latest or a specific version of Python, simply run uv python install
or uv python install <VERSION>
. To uninstall the latest or requested Python version, use uv python uninstall
or uv python uninstall <VERSION>
.
You can also list all available and installed Python versions using uv python list
, and add the --only-installed
flag to display only those currently installed.
uv
can use any existing Python installations already on your machine available here.
See the uv
Python installation documentation page for more information.
Use specific Python versions
Setting Python versions for a project can be done in two ways:
- Run
uv init --python <VERSION>
to set the minimum Python version at the project setup stage (note that “minimum” refers to therequires-python
argument within thepyproject.toml
file). This also sets the python version in the.python-version
file if you do not optionally remove it. - Use
uv venv --python <VERSION>
to set the Python version during virtual environment creation.
You can still adjust the requires-python
argument within the pyproject.toml
file as needed, for example to specify a range of valid Python versions for the project.
For more information, see the uv Project Python versions documentation page
uv
Python versions
- If the requested Python version isn’t installed,
uv
will automatically install a suitable version based on constraints. To disable this behaviour, you can turn off auto-install. - Use
uv python dir
to find the Python installation directory for the project. - For more details on how
uv
searches for, installs, and uses Python versions, see theuv
documentation.
Working on different types of project? No problem!
Working on a Python project can have different structures depending on whether it’s a web app, CLI tool, or data analysis.
Thankfully, uv
handles this well. uv
uses the terms “application”, “package”, and “library” for various project types (similar to how Cargo does for Rust). During the uv init
stage, you can choose a type of project by adding either the --app
, --package
, or --lib
flag. Here’s a summary:
uv init flag |
Use-cases | Description |
---|---|---|
--app |
Scripts, analyses, web servers, and CLI interfaces (and similar) | A simple directory structure with a main.py and the other required project configuration files. |
--package |
Same as --app , but with additional focus on building/distribution |
As above, but moves all Python source code into a subdirectory of src using the project name. Also adds additional entrypoint definitions (for CLI usage) and build/distribution information to the pyproject.toml . |
--lib |
Python modules or packages | Similar to --package , but without the entrypoint definition in the pyproject.toml . Also adds a py.typed to indicate that type hinting will be used (so it can be checked by type checkers). |
For more details, visit the creating and configuring project sections of the uv
documentation.
Managing dependencies
We’ve already seen during the comparison with venv
/pip
the command uv add <PACKAGE>
adds dependencies to a project. But it also allows you to group dependencies using flags like --dev
, --group
. For example, uv add --dev pytest
and uv add --group docs quartodoc
will add pytest
to a test
group and quartodoc
to a docs
group respectively. This is useful for separating out dependencies that aren’t part of the core source code, reducing the number of required dependencies for the project.
You can specify exact versions of packages using uv add <PACKAGE>==<VERSION>
, and even constrain them with typical pip
comparator operators (e.g., uv add "pandas>=1.0.0,<2.0.0"
). To remove dependencies from a project, use the command uv remove <PACKAGE>
.
For more details on managing dependencies, like those from GitHub/other sources, see the managing dependencies section of the uv
documentation.
Displaying dependencies
Knowing the installed dependencies within a virtual environment can be helpful, and there are several ways to do this with uv
.
One method is using uv pip list
, which shows the installed dependencies in a table format (more on [the uv
interface similar to venv
/pip
later][comparing-uv-with-a-venvpip-workflow]).
Another choice is uv tree
, which displays the project dependencies as a tree/graph. An example output can be found below:
uv-test v0.1.0
├── geopandas v1.0.1
│ ├── numpy v2.2.5
│ ├── packaging v25.0
│ ├── pandas v2.2.3
│ │ ├── numpy v2.2.5
│ │ ├── python-dateutil v2.9.0.post0
│ │ │ └── six v1.17.0
│ │ ├── pytz v2025.2
│ │ └── tzdata v2025.2
│ ├── pyogrio v0.11.0
│ │ ├── certifi v2025.4.26
│ │ ├── numpy v2.2.5
│ │ └── packaging v25.0
│ ├── pyproj v3.7.1
│ │ └── certifi v2025.4.26
│ └── shapely v2.1.0
│ └── numpy v2.2.5
└── pandas v2.2.3 (*)
(*) Package tree already displayed
Keeping recorded and actual dependencies in-sync
I think this is the best feature of uv
!
Sometimes, dependencies installed within virtual environments and their definitions in pyproject.toml
/requirements.txt
files can become out of sync. For example, this may happen when manually upgrading a dependency in the virtual environment using pip
, and then forgetting to update the pyproject.toml
file accordingly.
uv
comes to the rescue (and not for the first time 🙂)! The command uv sync
uses the uv.lock
file to synchronise and resolve any dependency discrepancies. Even though you can manually check for discrepancies using uv lock --check
, uv
can also do this automatically! For example, when using uv run
to execute python files, uv
automatically locks and synchronises the Project environment before execution. This ensures everything is up-to-date before running, and the virtual environment/project configuration can no longer diverge.
For more insight on uv
’s locking/synch-ing functionality, see the uv documentation.
But I want to keep that venv
/pip
feel…
Tools like venv
, pip
, and others are widely used in the development community because they provide low-level APIs to manage every aspect of a virtual environment/dependency workflow. This also creates familiarity and dependency, making it challenging to switch to newer tools such as uv
when they become available.
However, uv
addresses both issues! If you’re not ready to transition to the core uv
API immediately or prefer accessing lower-level API calls provided by venv
and pip
, uv
includes commands like uv venv
and uv pip
. These can be used to maintain compatibility with venv
/pip
when using uv
. For example, python3 -m venv .venv
becomes uv venv .venv
, and pip install pandas
becomes uv pip install pandas
.
For more information on these aspects, see the pip interface section of the uv
documentation. Be sure to also check out the uv
/pip
compatibility page for more specific details on the pip interface compared with venv
/pip
.
Using CLI tools
Many Python packages and dependencies can be used as Command Line tools. For example, you can execute pre-commit
across all files within a project by calling pre-commit run --all-files
.
In the context of uv
, this is also possible using uvx
or uv tool run
. Using the previous example, this becomes uvx pre-commit run --all-files
. The main advantage of running tools with uvx
/uv tool run
is that uv
ensures the project is fully synchronised before executing, just like uv run
.
For more information on using tools with UV
, see the UV
using tools documentation page.
Building and Publishing
With uv
, you can also build Python packages into source/binary distributions and upload them to a registry using the commands uv build
and uv publish
. This enables UV
to support the latter stages of a development workflow, such as building and deploying a Python package to PyPI. For more details on how to do this, see the UV
building and publishing documentation page.
Integrations
Finally, uv
provides many integrations to enhance the workflow even more. Some notable integrations include:
- pre-commit hooks, which help ensure the project dependencies are fully up-to-date and synchronised before committing.
- GitHub actions, for incorporating
uv
into CI/CD workflows. - Docker image, to use
uv
within containers. - FastAPI, for developing FastAPI-based projects.
For a complete list of uv
integrations, see the integrations documentation page.
uv
command ‘cheat sheet’
Here is a summary of uv
commands - a quick summary of everything discussed in this page. For more complete and detailed guidance, always refer to the uv
documentation.
Python install/list/uninstall
uv command |
Description |
---|---|
uv python install |
Install the latest version of Python. |
uv python install <VERSION> |
Install a specific version of Python. |
uv python dir |
Display the Python installation directory. |
uv python list |
List all available and currently installed Python versions. |
uv python list --only-installed |
List only installed Python versions. |
uv python uninstall |
Uninstall the latest version of Python. |
uv python uninstall <VERSION> |
Uninstall a specific version of Python. |
init
uv command |
Description |
---|---|
uv init |
Initialise a Python project within an existing directory. |
uv init <PROJECT_NAME> |
Initialise a Python project within a new directory. |
uv init --python <PYTHON_VERSION> |
Initialise a Python project with a specific minimum python version. |
uv init --app |
Initialise a Python project as an application (e.g., web servers, scripts, and CLIs). |
uv init --package |
Initialise a Python project as a packaged application (i.e. an application with a build section in the pyproject.toml ). |
uv init --lib |
Initialise a Python project as a library. |
uv init --no-pin-python |
Do not create a .python-version file for the project. |
uv init --build-backend <BACKEND> |
Initialise a Python project with a specific build backend. |
add/remove/dependency management
uv command |
Description |
---|---|
uv add <PACKAGE> |
Add dependency to project. |
uv add <PACKAGE>==<VERSION> |
Add a dependency to the project, fixing the version. |
uv add "<PACKAGE>>=<VERSION_1>, <<VERSION_2>" |
Add a dependency to the project, setting the version between a range. |
uv add --dev <PACKAGE> |
Add dependency to the development dependency group. |
uv add --group <GROUP> <PACKAGE> |
Add dependency to the specific group. |
uv add --upgrade <PACKAGE>==<VERSION |
Upgrade an existing package to a specific version. |
uv remove <PACKAGE> |
Remove dependency from the project. |
uv pip list |
List project dependencies. |
uv tree |
Visualise project dependencies in tree format. |
run/sync/lock
uv command |
Description |
---|---|
uv run <OPTION> |
Run a command/script within the project environment (automatically locking and synch-ing before execution). |
uv sync |
Synchronise the pyproject.toml definition, uv.lock and virtual environment. |
uv lock --check |
Check if the lock file is up-to-date. |
uv lock --upgrade-package <PACKAGE> |
Upgrade package to the latest version, whilst trying to keep the remainder of the lockfile in check. |
tools
uv command |
Description |
---|---|
uvx <COMMAND> |
Run a command provided by a Python package. |
uv tool run <COMMAND> |
Same as above. |
build/distribution
uv command |
Description |
---|---|
uv build |
Build Python packages into source distributions and wheels. |
uv publish |
Publish a Python distribution to a registry. |
Final Thoughts
Changing virtual environment/dependency management tools is like stepping out of a pair of old slippers - they’re familiar, comfortable, and it’s not something you’d like to do without a very good reason. For me, this is venv
+ pip
. However, uv
brings that new running show vibe - fast, innovative features that are likely to be much better for you in the long term.
The notable uv
advantages discussed on this page are:
- Improved dependency management, resolution, and synchronisation.
- A complete package: from Python installation right through to package distribution.
- Automated project setup and configuration.
- Enforcement of the latest PEP standards.
Of course others tools, like PDM and poetry, offer some of these features (and different ones too). And for sure, uv
has room for improvement, such as providing test framework integration (at least during the initial project setup). But ultimately, the rapid Rust backend, an ‘all-in-one’ tool, and an active development community, means uv
is an incredibly exciting choice with lots of advantages - it’s definitely worth having ‘uv’ in your toolkit!