# A Deep Dive into UV: The Fast Python Package Manager

[uv](https://github.com/astral-sh/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](https://www.python.org/downloads/) installed on your machine. However, one of `uv`'s standout features is that [it does not require Python to be pre-installed](https://docs.astral.sh/uv/guides/install-python/#automatic-python-downloads)—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` uses `pyproject.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:

```command
python3 --version
```
You should see output similar to:

```command
Python 3.13.2
```

The easiest way to install `uv` is by using the official installation script:

```command
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:

```command
source $HOME/.local/bin/env
```

To verify that `uv` has been installed successfully, run:

```command
uv --version
```

```text
[output]
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:

```command
mkdir uv-demo && cd uv-demo
```

With `uv`, you don’t need to manually create a virtual environment using `venv`. Instead, simply run:

```command
uv venv
```

```text
[output]
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:

```command
source .venv/bin/activate
```

Once the virtual environment is ready, installing packages is incredibly fast. Instead of using `pip`, use:

```command
uv pip install requests
```
```text
[output]

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:

```command
uv pip install requests numpy pandas
```

And if you need a specific version:

```command
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`:
 

```python
[label 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:

```command
uv run python app.py
```

```text
[output]
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:

```command
deactivate
```
```command
cd ..
```
Initialize a new project using `uv`:

```command
uv init uv-managed-demo
```
This creates a directory `uv-managed-demo`. Next, move into it:

```command
cd uv-managed-demo
```
`uv` generates the following essential files:

```command
ls
```

```text
[output]
.
├── 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:

```text
[label pyproject.toml]
[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`:

```command
uv add requests
```

Once installed, `pyproject.toml` is updated to include:  

```text
[label pyproject.toml]
...
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:


```command
uv add numpy pandas
```

Or specify version constraints:


```command
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:


```command
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:


```command
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:


```command
uv run hello.py
```
```text
[output]
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`:  

```command
uv add fastapi
```

This results in:  

```toml
[label pyproject.toml]
...
[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:  

```command
uv add --dev pytest black mypy
```

This creates a new `dependency-groups` section:  

```toml
[label pyproject.toml]

[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:  

```command
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:  

```command
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:  

```toml
[label pyproject.toml]

[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:


```command
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:  

```commnd
uv run python --version
```

While `uv` automatically detects system-installed Python versions, you can also install specific versions. To install the latest Python version: 

```command
uv python install
```

Or install a specific version:

```command
uv python install 3.12
```

```text
[output]
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:

```command
uv run python --version
```

```text
[output]
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`:

```toml
[project]
requires-python = ">=3.12"
```

You can then pin the specific version using:  

```command
uv python pin 3.12
```

After pinning, check the version again:


```command
uv run python --version

```

Now, `uv` will update the project environment and dependencies to match the selected Python version:


```text
[output]
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:  

```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.  
 

```comand
uv venv
```


If you prefer **manual control over Python installations**, you can disable automatic downloads by running:  

```command
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](https://docs.astral.sh/uv/).  

Thanks for reading!