A Deep Dive into UV: The Fast Python Package Manager
uv is a next-generation package manager for Python that delivers exceptional speed and modern dependency management. It is designed to be a drop-in replacement for pip
, pip-tools
, and virtualenv
, providing a significantly faster and more reliable experience.
This article will walk you through getting started with uv
, demonstrating its key features, and showing how to optimize your Python development workflow with it.
Prerequisites
Before diving into uv
, ensure you have a recent version of Python installed on your machine. However, one of uv
's standout features is that it does not require Python to be pre-installed—it can download and manage Python versions for you.
Why use uv
?
Before diving into uv
, let's understand why it significantly improves over traditional Python package management. Here's what makes uv
special:
- Fast package management: While
pip
might take several seconds to install a package,uv
typically completes installations in milliseconds Automatic virtual environments: Instead of manually running
python -m venv
and remembering to activate environments,uv
handles this automatically.Modern dependency resolution: Unlike
pip
, which can sometimes produce inconsistent environments,uv
guarantees reproducible installations through its precise dependency resolution and lockfile system.Project-first approach: Rather than managing loose packages with
requirements.txt
,uv
usespyproject.toml
- the official Python packaging standard - providing better organization and version control.
Think of uv
as a power tool that combines the best aspects of pip
, virtualenv
, and pip-tools
into a single, faster, more reliable solution.
Throughout this tutorial, you'll see how these features work together to simplify your Python development workflow.
Installing uv
In this section, you will install the uv
package installer for Python.
While uv
can install Python, it's often already installed on most systems. This tutorial assumes you have Python installed. To check if Python is already installed on your system, run:
python3 --version
You should see output similar to:
Python 3.13.2
The easiest way to install uv
is by using the official installation script:
curl -LsSf https://astral.sh/uv/install.sh | sh
This will download and install uv
to ~/.local/bin
by default. Following that, restart your shell:
source $HOME/.local/bin/env
To verify that uv
has been installed successfully, run:
uv --version
uv 0.5.31 (e38ac4900 2025-02-12)
This output confirms that uv has been installed successfully and is ready to use.
Getting started with uv
Now that uv
is installed, let's explore its core functionality. At its heart, uv
is a fast package manager and virtual environment manager for Python. Unlike pip
, it automatically creates and manages virtual environments, ensuring a clean and isolated development setup.
To get started, create a project directory and move into it:
mkdir uv-demo && cd uv-demo
With uv
, you don’t need to manually create a virtual environment using venv
. Instead, simply run:
uv venv
Using CPython 3.13.2 interpreter at: /opt/homebrew/opt/python@3.13/bin/python3.13
Creating virtual environment at: .venv
Activate with: source .venv/bin/activate
This command does three things:
- It creates a virtual environment in
.venv
inside your project directory - It automatically selects the best Python version** available
- If the required Python version is missing,
uv
downloads and installs it
Next, activate the virtual environment:
source .venv/bin/activate
Once the virtual environment is ready, installing packages is incredibly fast. Instead of using pip
, use:
uv pip install requests
Resolved 5 packages in 1.60s
Prepared 5 packages in 2.55s
Installed 5 packages in 8ms
+ certifi==2025.1.31
+ charset-normalizer==3.4.1
+ idna==3.10
+ requests==2.32.3
+ urllib3==2.3.0
This is significantly faster than pip install
because uv
skips the traditional wheel-building step and installs packages in parallel.
You can also install multiple packages at once:
uv pip install requests numpy pandas
And if you need a specific version:
uv pip install 'Django>=5.0'
Once your packages are installed, you can run Python scripts within the uv
-managed environment without activating it manually. Create a file named app.py
:
import numpy as np
# Create a sample array
arr = np.array([1, 2, 3, 4, 5])
print(f"And here's the mean of the array: {np.mean(arr)}")
Run it using:
uv run python app.py
And here's the mean of the array: 3.0
This automatically uses the correct virtual environment, even if you move between projects.
You've seen how dependency management with uv
simplifies and optimizes Python development. If you're used to pip
, this might feel familiar, but uv
takes it a step further.
Instead of manually handling environments and installations, uv
automates and accelerates the process while ensuring consistency.
Creating projects with uv
In the last section, you learned how to create virtual environments and install packages using uv
. While this works, it's similar to traditional workflows with pip
and venv
.
Let's explore uv
's more powerful project management features that make dependency handling more robust and reproducible.
Instead of installing packages ad hoc, modern Python development best practices recommend declaring dependencies in a project configuration file.
uv
fully embraces this approach by using pyproject.toml
- the official Python packaging standard replacing traditional requirements.txt
files.
This provides several advantages:
- Declares both project metadata and dependencies in one place
- Supports precise version constraints and dependency groups
- Enables reproducible builds across different machines
- Integrates smoothly with modern Python packaging tools
Since we previously created a project directory, uv-demo
, let's start fresh with a properly structured project. First, exit the previous virtual environment and directory:
deactivate
cd ..
Initialize a new project using uv
:
uv init uv-managed-demo
This creates a directory uv-managed-demo
. Next, move into it:
cd uv-managed-demo
uv
generates the following essential files:
ls
.
├── README.md # A basic project readme
├── pyproject.toml # Defines project metadata and dependencies
└── hello.py # A sample script file
If you open pyproject.toml
, you'll see a structure like this:
[project]
name = "uv-managed-demo"
version = "0.1.0"
description = "Add your description here"
readme = "README.md"
requires-python = ">=3.13"
dependencies = []
At this point, the project hasn’t defined any dependencies yet.
Now that we have a properly structured project, let’s move on to managing dependencies efficiently with uv
.
Managing dependencies with uv
In the previous section, you explored how uv
allows you to structure your projects using pyproject.toml
, ensuring a clean and organized development environment.
This section will build upon that to show you how uv
simplifies dependency management, making package installation, removal, and synchronization effortless.
uv
allows you to declare dependencies in pyproject.toml
using the uv add
command. This not only installs the package but also updates your project configuration automatically.
For example, to add requests
:
uv add requests
Once installed, pyproject.toml
is updated to include:
...
dependencies = [
"requests>=2.32.3",
]
Unlike traditional package installation methods, uv
does more than just install a package—it locks the dependencies. When a package is installed, uv
creates a lock file (uv.lock
), which:
- Captures exact package versions to ensure that dependencies remain consistent across different machines
- Prevents version conflicts
- Optimizes future installations, since reinstalling from a lock file is much faster since dependencies are pre-resolved
With uv
, you can install multiple packages in a single command, making dependency management more efficient:
uv add numpy pandas
Or specify version constraints:
uv add "Django>=4.0"
This ensures that your project always installs a compatible version of Django, preventing accidental upgrades that could break functionality.
And if you no longer need a package, you can remove it easily:
uv remove requests
When you remove a package with uv
, it not only deletes the package from the virtual environment but also updates pyproject.toml
to reflect the change and modifies the lock file uv.lock
to maintain consistency.
This ensures your project remains clean, organized, and free from unnecessary dependencies, keeping your environment lightweight and well-managed.
If you're working on a team or setting up your project on a new machine, you don’t have to install every package manually. You can clone a repository that already has a pyproject.toml
and uv.lock
, and install the exact package versions by running:
uv sync
This guarantees that all dependencies align with the locked versions, the environment remains fully up-to-date, and no unnecessary packages are installed. Unlike pip install -r requirements.txt
, which can lead to inconsistencies, uv sync
enforces strict version control, ensuring a stable and reproducible setup across all environments.
Since uv
automatically manages the virtual environment, you don’t need to activate it manually. Instead, run scripts and commands directly:
uv run hello.py
Hello from uv-managed-demo!
With this modern approach to dependency management, you can ensure consistent environments across development and deployment, eliminating the dreaded "works on my machine" issues.
Managing dependencies with groups in uv
In most Python projects, dependencies fall into different categories based on their purpose. Instead of treating all dependencies the same, uv
provides dependency groups, allowing you to organize better and manage your project's packages.
Dependency groups allow you to separate dependencies based on their usage. This helps:
- Keep production environments lightweight by installing only necessary dependencies
- Organize tools more effectively, separating development tools from runtime dependencies
- Improve collaboration by ensuring everyone on the team works with the same set of tools
By default, when you add a package using uv add
, it is added to the main project dependencies under [project.dependencies]
in pyproject.toml
:
uv add fastapi
This results in:
...
[project]
dependencies = [
"fastapi>=0.100.0",
]
However, not all dependencies belong in production. You can group dependencies based on their use case using --group
.
development tools—such as testing frameworks and formatters—should not be bundled with production dependencies. To keep them separate, use the --dev
flag:
uv add --dev pytest black mypy
This creates a new dependency-groups
section:
[dependency-groups]
dev = [
"black>=25.1.0",
"mypy>=1.15.0",
"pytest>=8.3.4",
]
Now, these tools are part of the dev group and won’t be installed unless explicitly requested.
When working in development, you can run tools installed with --dev
using:
uv run --dev pytest
This ensures that pytest
runs only when needed, preventing unnecessary tools from cluttering production.
You can create different groups for various tools. For example, a linting group for formatters and linters:
uv add --group lint ruff flake8
The command adds ruff
and flake8
to a new lint
dependency group, keeping them separate from production dependencies. This ensures they are only installed when explicitly requested, preventing unnecessary bloat.
Resulting in:
[dependency-groups]
dev = [
"black>=25.1.0",
"mypy>=1.15.0",
"pytest>=8.3.4",
]
lint = [
"flake8>=7.1.1",
"ruff>=0.9.6",
]
You can now run linting tools using the --group lint
flag:
uv run --group lint flake8 hello.py
This keeps your environment organized and efficient. When you leveraging dependency groups in uv
, you can manage dependencies in a more structured, scalable, and efficient way.
Managing multiple Python versions
Managing multiple Python versions can be challenging, especially when different projects require different versions. uv
simplifies Python version management by automatically detecting, installing, and switching between Python versions based on project requirements.
Unlike tools like pyenv
, uv
integrates Python version control with dependency management, ensuring a consistent and optimized development experience.
If Python is installed on your system, uv
will automatically detect and use it without requiring additional configuration. To check the current Python version being used by uv
, run the following:
uv run python --version
While uv
automatically detects system-installed Python versions, you can also install specific versions. To install the latest Python version:
uv python install
Or install a specific version:
uv python install 3.12
Installed Python 3.12.9 in 45.18s
+ cpython-3.12.9-macos-aarch64-none
However, installing a version doesn't automatically make your project use it. If you run:
uv run python --version
Python 3.13.2
At this point, Python 3.12 is installed, but it is not automatically set as the default version for the project.
This means the project is still using the latest available version. To ensure the project uses Python 3.12, define it in pyproject.toml
:
[project]
requires-python = ">=3.12"
You can then pin the specific version using:
uv python pin 3.12
After pinning, check the version again:
uv run python --version
Now, uv
will update the project environment and dependencies to match the selected Python version:
Using CPython 3.12.9
Removed virtual environment at: .venv
Creating virtual environment at: .venv
Installed 22 packages in 126ms
Python 3.12.9
Automatic Python installation with uv
Its automatic Python installation is one of uv
’s most powerful features. If a required Python version is missing, uv
will download and install it on demand, ensuring your project always runs in the correct environment.
For example, if Python 3.11 is not installed, running the following command will automatically install Python 3.11 before executing the command:
uvx python@3.11 -c "print('Hello, world!')"
Similarly, if no Python version is available on your system, simply creating a virtual environment will install the latest Python version and set up the environment without requiring manual intervention.
uv venv
If you prefer manual control over Python installations, you can disable automatic downloads by running:
uv --no-python-downloads
This ensures uv
only uses pre-installed Python versions, giving you full control over your development environment.
By using uv
to manage Python versions, you no longer have to deal with the hassle of manually installing and switching between versions.
uv
handles everything for you, ensuring a stable, predictable, and fully optimized development environment.
Final thoughts
This article explored key features of uv
to help you modernize your Python workflow. With uv
, you can manage packages, virtual environments, and Python versions with a single, efficient tool.
To explore the tool in detail, check out the official documentation.
Thanks for reading!
Make your mark
Join the writer's program
Are you a developer and love writing and sharing your knowledge with the world? Join our guest writing program and get paid for writing amazing technical guides. We'll get them to the right readers that will appreciate them.
Write for us
Build on top of Better Stack
Write a script, app or project on top of Better Stack and share it with the world. Make a public repository and share it with us at our email.
community@betterstack.comor submit a pull request and help us build better products for everyone.
See the full list of amazing projects on github