Ditch Pip and Poetry: Why uv is the Only Python Tool You Need

Programming tutorial - IT technology blog
Programming tutorial - IT technology blog

The End of Python Tooling Fatigue

Anyone who has spent a week in the Python ecosystem knows the specific frustration of dependency management. For years, we have been duct-taping workflows together. We used pip for installs, venv for isolation, pyenv for version switching, and Poetry or pip-tools to handle the actual mess of dependency resolution.

This fragmentation is exhausting. You end up needing four different tools just to reach a functional ‘Hello World.’ Even worse, pip is famously sluggish, and waiting for Poetry to lock dependencies can feel like watching paint dry. This is where uv changes the narrative. Built by the team at Astral (who created Ruff), uv is a single, unified tool written in Rust that aims to replace the entire Python stack.

What exactly is uv?

Fundamentally, uv is a high-performance Python package installer and resolver. However, calling it just an installer ignores its versatility. It is a Swiss Army knife that functions as a:

  • Package manager: A drop-in replacement for pip and pip-compile.
  • Environment manager: It handles virtualenv creation in milliseconds.
  • Python version manager: It downloads and manages Python versions, replacing pyenv.
  • Project manager: It manages lockfiles and dependencies like Poetry.

Because it is a standalone Rust binary, uv does not require a pre-existing Python installation to work. This solves the “chicken and egg” problem of installing a tool to manage the tool. In terms of raw performance, it typically resolves and installs packages 10x to 100x faster than pip by using a global cache and aggressive parallelization.

Getting Started

You can install uv in seconds. Unlike other managers that require complex shell configurations, uv is distributed as a clean, 7MB binary.

# On macOS and Linux
curl -LsSf https://astral.sh/uv/install.sh | sh

# On Windows
powershell -ExecutionPolicy ByPass -c "ir https://astral.sh/uv/install.ps1 | iex"

Verify the installation by running:

uv --version

Managing Python Versions Without the Hassle

I used to rely on pyenv, but it often felt finicky with shell hooks and build dependencies. uv handles this natively by fetching pre-built Python binaries. You can install specific versions with one command:

uv python install 3.11 3.12 3.13

The tool is intelligent. If your project requires Python 3.12, uv will automatically find it or download it for you. You no longer need to manually toggle versions when switching between repositories; uv reads your .python-version or pyproject.toml and handles the heavy lifting.

A Streamlined Project Workflow

Forget the old ritual of python -m venv .venv and the constant need to source activation scripts. uv introduces a unified workflow that I now use for every new project.

1. Initializing a project

mkdir my-new-app
cd my-new-app
uv init

This command generates a pyproject.toml and a sample hello.py. It feels familiar to anyone coming from the Rust or Node.js ecosystems.

2. Adding dependencies

Adding packages is near-instant. uv resolves the dependency tree and updates the uv.lock file before you can even reach for your coffee.

uv add fastapi uvicorn requests

In a recent project, switching our CI pipeline from pip install to uv sync reduced our build times from 4 minutes to just 28 seconds. This speed isn’t just a luxury; it fundamentally changes how often you are willing to iterate on your code.

3. Running code

Manual environment activation is now optional. You can execute scripts directly within the project’s locked environment using uv run:

uv run python hello.py
# Or launch a web server
uv run uvicorn main:app --reload

Portable Scripts with PEP 723

One of uv‘s standout features is support for PEP 723 (Inline Script Metadata). You can write a single Python file and define its dependencies directly inside the comments. This is perfect for sharing utility scripts without sending a separate requirements.txt.

# script.py
# /// script
# dependencies = ["requests", "rich"]
# ///

import requests
from rich import print

response = requests.get("https://api.github.com/zen")
print(f"[bold green]GitHub says:[/bold green] {response.text}")

Run it with uv run script.py. uv creates a hidden, temporary environment, installs the two packages, executes the code, and cleans up. It keeps your global environment pristine.

Optimizing Docker Builds

Dockerizing Python apps used to involve bulky layers and slow pip installs. uv streamlines this by allowing you to mount its cache or use the --frozen flag to ensure your production environment exactly matches your development lockfile.

FROM python:3.12-slim
COPY --from=ghcr.io/astral-sh/uv:latest /uv /uvx /bin/

WORKDIR /app
COPY pyproject.toml uv.lock ./
RUN uv sync --frozen

COPY . .
CMD ["uv", "run", "python", "main.py"]

This setup guarantees bit-for-bit reproducibility with zero overhead from legacy tools.

Why the Switch is Inevitable

The Python community is moving toward uv at a breakneck pace. It isn’t just about the 10x speed boost; it’s about reducing the mental tax of remembering which tool manages which part of your workflow. By consolidating everything into one binary, your setup becomes predictable and incredibly fast.

I have completely removed pyenv and virtualenv from my machine. Since uv respects pyproject.toml standards, the migration is virtually painless. Even if you use Poetry today, uv can manage those projects without requiring you to change a single line of configuration.

Final Thoughts

Environment management used to be the most tedious part of Python development. uv turns that frustration into a background task that you rarely have to think about. It is the “Cargo” moment Python has desperately needed for a decade. If you value your time, try uv on your next project. You won’t want to go back.

Share: