UV: A Rust-based, ‘cargo-like’ package manager for Python

An overview of the uv Python package manager and how to use it.
python
explanation
Published

2025-05-09

Modified

2025-05-10

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 classical venv/pip workflow.
  • A summary of the key uv features.
  • A uv command ‘cheat sheet’.

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:

  1. Installing directly using curl or wget.
  2. Using brew on macOS systems.
  3. 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

  1. Create a virtual environment:
python3 -m venv .venv
  1. Activate the virtual environment:
source .venv/bin/activate
  1. Initialise git version control:
git init
  1. Add a .gitignore:
touch .gitignore
  1. Add a README.md:
touch README.md
  1. Create a Python file:
touch main.py
  1. Update pip:
pip install --upgrade pip
  1. Install dependencies into the virtual environment:
pip install pandas geopandas
  1. Write the source code.

  2. Run the source code:

python main.py
  1. Export dependencies to make the environment reproducible:
pip freeze > requirements.txt

Using uv

  1. Setup project structure:
uv init
  1. Install dependencies into a virtual environment:
uv add pandas geopandas
  1. Write the source code.

  2. 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 for venv/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 a pyproject.toml file.
  • uv also uses dependency locking with the help of an uv.lock file. Similar to a requirements.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 the pyproject.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.

Note

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:

  1. Run uv init --python <VERSION> to set the minimum Python version at the project setup stage (note that “minimum” refers to the requires-python argument within the pyproject.toml file). This also sets the python version in the .python-version file if you do not optionally remove it.
  2. Use uv venv --python <VERSION> to set the Python version during virtual environment creation.
Note

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

Tips on 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 the uv 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!