Contents

All‑in‑One Python Package Manager: UV!

📝Update (2025-09-06): I’ve added a new section on using –native-tls with corporate proxies. It covers why uv may fail with SSL errors at work and how to fix it by making uv trust your system certificates.

Meet uv – A Blazingly Fast, All‑in‑One Python Package Manager

In my last post I dove into Poetry, one of the best‑loved modern packaging tools. However, Poetry is just one piece of an toolkit: we still reach for pip to install packages, virtualenv to isolate them, pyenv to juggle Python versions, and maybe Pipenv or pip‑tools for lock‑files. Each solves its own niche, yet hopping between them adds friction. uv removes that friction. This single, project manager—written in Rust and typically 10-1000x faster-replaces the whole stack: installing Python itself, creating virtual environments, resolving and locking dependencies, and even publishing to PyPI, all behind one concise CLI.

Why Python Packaging Needed a Fresh Start

Pain PointTraditional LandscapeHow uv Fixes It
Tool sprawlpip / virtualenv / pyenv / Poetry / pipxCovers the full workflow
PerformancePure‑Python dependency resolution and single‑threaded downloadsRust core, SAT‑based solver (PubGrub)
Reproducibilityrequirements.txt is order‑sensitive & lacks metadataDeterministic uv.lock capturing hashes & markers

Since its public launch, uv already powers >10 % of all downloads on PyPI—evidence that developers crave a faster, simpler workflow.

The Lock‑File Landscape at a Glance

Before we dive into uv, it helps to remember what “locking” looks like in the existing ecosystem:

  1. Pipenv – Pipfile + Pipfile.lock
  2. Poetry – pyproject.toml + poetry.lock
  3. pip – requirements.txt created via pip freeze

Each of these pins versions, but they store different metadata and often disagree on the resolver algorithm. uv adopts pyproject.toml syntax and adds its own uv.lock that captures exact versions and SHA‑256 hashes for deterministic installs.


From init to publish

1. Spin‑up

uv init myapp        # ➊ scaffold project & git repo

uv chooses a Python interpreter (downloading if necessary), creates .venv, writes .python-version, and commits a minimal pyproject.toml.

2. Add Dependencies

uv add ruff pytest   # ➋ add linting & tests
  • Environment bootstrap – if .venv doesn’t exist, it’s created.
  • Dependency resolution – the PubGrub SAT solver computes a compatible graph (an NP‑hard problem) in milliseconds.
  • Lockfile updateuv.lock is regenerated atomically so that users can reproduce the exact set.
  • Installation

If you Need to remove something, uv remove pytest cleans both the environment and the lockfile.

3. Iterate Quickly

uv run hello.py      # ➌ execute code inside .venv
uv run uvicorn main:app --reload # run uvicorn
uvx ruff check       # ➍ run a CLI tool in a throw‑away env

uv includes a dedicated interface for interacting with tools. Tools can be invoked without installation using uv tool run, in which case their dependencies are installed in a temporary virtual environment isolated from the current project.

Because it is very common to run tools without installing them, a uvx alias is provided for uv tool run — the two commands are exactly equivalent. For brevity, the documentation will mostly refer to uvx instead of uv tool run.

4. Manage Python Versions Mid‑Stream

uv python install 3.12.0   # ➎ fetch new interpreter
# switch by editing .python-version, then:
uv sync                    # rebuild .venv deterministically

No sudo, no global state—interpreters live under ~/.local/share/uv/python.

5. Publish

uv publish          # ➏ upload to PyPI

Zero extra config; metadata comes from pyproject.toml.


Workspaces: Multiple Apps, One Cohesive Repo

uv can treat a directory tree as a workspace. Sub‑projects share the same interpreter and .venv, which is perfect for monorepos housing a library, CLI, and docs site side‑by‑side.

uv init api            # creates api/ inside current repo
uv init web --no-workspace  # standalone env if you prefer isolation

Switching between shared and isolated workflows is a flag away.


Dependency Groups for Dev & Prod

Sometimes you want linting and testing tools only in development. uv follows Poetry’s group syntax:

uv add --dev pytest ruff      # added under [tool.poetry.group.dev]
uv sync --no-group dev        # skip dev deps

Testing with Pytest (and a VS Code Tip)

When you start a project, it’s worth deciding up front where your test files will live. I like to keep them in a dedicated tests/ directory that sits alongside the src/ directory rather than inside it. That keeps production code and test code clearly separated and makes the project tree easy to scan:

project_root/
├── src/
│   ├── main.py
│   └── utils.py
└── tests/
    ├── test_main.py
    └── test_utils.py

With this layout, tools such as pytest find your tests automatically, and IDEs can apply different run configurations or linters to src/ and tests/ without extra setup.

Enable pytest discovery in VS Code by adding:

// .vscode/settings.json
{
  "python.testing.pytestEnabled": true,
  "python.testing.cwd": "${workspaceFolder}/tests"
}

Install and run tests:

uv add --dev pytest
uv run pytest -q

Migrating an Existing venv + pip Project

  1. Freeze current deps:
    pip freeze > requirements.txt
  2. Initialize uv in place and create a virtual environment:
    uv init .
    uv venv
  3. Import:
    uv pip install -r requirements.txt
    uv lock
  4. Delete the old .venv and enjoy the speed‑up.

Managing Private PyPI Repositories with uv

If you’re using uv as your Python package manager, you don’t always need to pass --index-url when installing packages from a private or internal PyPI mirror (e.g, at your work). You can configure it globally with

# ~/.config/uv/uv.toml
[[index]]
url = "http://----" 
default = true

Now, you can just run

uv add <package-name>

Using uv with Private Indexes and Corporate Proxies

If you work in a corporate environment, chances are you’re installing packages through an internal PyPI mirror and behind a company proxy. Here’s how to make uv work smoothly in that setup.

Configuring a Private PyPI Index

Instead of typing --index-url every time you add a package, you can configure your internal index globally:

# ~/.config/uv/uv.toml
[[index]]
url = "http://repo.mycompany.net/simple"
default = true

Now you can simply run:

uv add numpy

and uv will fetch packages from your internal repository automatically.

Setting Proxy Variables

If your network requires a proxy, set these environment variables:

export HTTP_PROXY="http://proxy.mycompany.net:3128"
export HTTPS_PROXY="http://proxy.mycompany.net:3128"

If authentication is required:

export HTTPS_PROXY="http://username:password@proxy.mycompany.net:3128"

And to bypass the proxy for local services:

export NO_PROXY="localhost,127.0.0.1,.mycompany.net"

Why --native-tls Matters

By default, uv uses Rustls for TLS/SSL. That’s fine at home, but at work you’ll often hit errors like:

certificate verify failed: unable to get local issuer certificate

This happens because Rustls doesn’t automatically trust your company’s custom root certificates.

The fix: tell uv to use your operating system’s certificate store:

uv add --native-tls requests

or make it permanent in your config:

# ~/.config/uv/uv.toml
[install]
native-tls = true

Now uv respects the certificates your IT team has already installed (OpenSSL on Linux, Schannel on Windows, SecureTransport on macOS).

Cheat Sheet

pip / virtualenvuv
python -m venv .venvuv venv
pip install pkguv add pkg
pip install -r requirements.txtuv pip install -r requirements.txt
pip uninstall pkguv remove pkg
pip freezeuv pip freeze
pip listuv pip list

I hope you enjoyed my post!