đ Untangling Pythonâs Tooling, Part 2: Meeting uv, the One-Stop Tool
In Part 1, we unpacked the Python tool zoo:
pipfor installing packagesvenvfor virtual environmentspipxfor global CLI toolspyenvfor managing Python versions
Each tool did one job reasonably well, but the mental overhead was⌠a lot.
In this part, we look at uv, a new tool from Astral (the folks behind Ruff) that aims to replace most of that stack with a single, fast, consistent interface. (docs.astral.sh)
This post is a gentle intro to uv and a practical âhow to start using it todayâ.
What is uv?
uv describes itself as:
âAn extremely fast Python package and project manager, written in Rust.â (docs.astral.sh)
Concretely, uv can:
Install and manage Python versions (like
pyenv) (GitHub)Create and manage virtual environments (like
virtualenv/venv) (GitHub)Manage project dependencies and lockfiles (like
pip+pip-tools+ parts ofpoetry) (docs.astral.sh)Run CLI tools and scripts in isolated environments (like
pipx) (GitHub)Build and publish packages to PyPI or other indexes (
uv build,uv publish) (docs.astral.sh)
All of this is 10â100x faster than pip in many real-world scenarios, thanks to a Rust implementation and aggressive caching. (docs.astral.sh)
If Part 1 was âunderstanding the messâ, Part 2 is âpress the big simplify buttonâ.
Installing uv
You can install uv via the official standalone installer: (GitHub)
macOS / Linux:
curl -LsSf https://astral.sh/uv/install.sh | sh
Windows (PowerShell):
powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex"
Or via package managers, for example:
brew install uv # Homebrew (macOS / Linux)
pipx install uv # pipx
pip install uv # plain pip (less recommended globally)
Check itâs installed:
uv --version
uv --help
Once that works, we can start replacing the old toolbox piece by piece.
Mapping Part 1 Concepts Onto uv
Quick mental model from Part 1 â uv:
| Old Tool | Role | Rough uv equivalent |
pip | Install packages | uv add, uv pip install |
pip-tools | Locking deps | uv lock, uv sync |
venv / virtualenv | Virtual environments | uv venv, project .venv auto-management |
pipx | Global CLI tools | uvx, uv tool install |
pyenv | Python versions | uv python install, uv python list |
build / twine | Build & upload | uv build, uv publish |
Weâll walk through this by building something concrete with uv.
Starting a Simple Project with uv
Letâs start with the simplest thing: a small script-style project.
uv init hello-uv
cd hello-uv
uv init will:
Create
pyproject.toml,.python-version,README.md,main.py(docs.astral.sh)Give you a âhello worldâ script ready to run:
uv run python main.py
# or simply
uv run main.py
uv run always uses the projectâs environment, and keeps it in sync with the lockfile before running. (docs.astral.sh)
Scaffolding a Real Package or App (uv init --package / --app)
For anything more seriousâlibraries, CLIs, apps you want to publishâuv can scaffold a packaged project in one command.
Library or reusable package
uv init --package data-processing-utils
cd data-processing-utils
This gives you a modern src/ layout: (docs.astral.sh)
data-processing-utils/
âââ .python-version
âââ README.md
âââ pyproject.toml
âââ src/
âââ data_processing_utils/
âââ __init__.py
Key points:
Code lives under
src/âŚpyproject.tomlincludes[project]metadata and a build systemThe project can be built and installed like any other modern Python package
CLI-style application package
If you want an app with a console entry point:
uv init --app --package cli-example
cd cli-example
uv will:
Create the same
src/cli_example/structureAdd a script entry point in
pyproject.toml(under[project.scripts]) (docs.astral.sh)
Then once youâve written a main() function, you can run it with:
uv run cli-example
This flows nicely into building and publishing, which weâll come back to.
Adding Dependencies: the pip Replacement
Inside a project managed by uv, you normally work with uv add:
uv add requests
uv add --dev pytest ruff
This will:
Ensure a
.venvexists for the projectInstall the packages into that environment
Update
pyproject.tomlwith your direct dependencies
You can still drop down to uv pip if you need pip-style commands:
uv pip install -r requirements.txt
uv pip list
uv pip show requests
Thatâs useful when migrating older projects. (docs.astral.sh)
Running Code and Commands with uv run
uv run is your âdo stuff in the right environmentâ button:
uv run python main.py
uv run -m weather_bot.cli
uv run ruff check .
Before each run, uv makes sure:
The lockfile matches
pyproject.tomlThe environment matches the lockfile
So your command always runs in a consistent state. (docs.astral.sh)
Testing with pytest: uv run pytest
Letâs wire in tests using pytest.
First, add it as a dev dependency:
uv add --dev pytest
Then, whenever you want to run your test suite:
uv run pytest
or, if you prefer the explicit module style:
uv run python -m pytest
You might see uv pytest mentioned in random blog posts or project docs, but thatâs not a built-in uv subcommand in the general CLIâit only works where a project defines its own wrapper or task. The portable, âworks everywhereâ way is simply uv run pytest. (CSDN Blog)
This also plays nicely with extra tooling:
uv add --dev pytest pytest-cov
uv run pytest --cov=your_package
Lockfiles: uv lock and uv sync
Reproducible environments were a big theme in Part 1. uv gives you a universal lockfile, uv.lock. (docs.astral.sh)
uv lock: decide and record versions
uv lock
This:
Resolves exact versions for all dependencies
Writes them to
uv.lock
Think of it as a structured, machine-readable version of pip freeze.
uv sync: make reality match the lockfile
uv sync
This:
Reads
uv.lockInstalls whatâs missing
Updates packages to the locked versions
On a fresh machine or in CI:
git clone ...
cd your-project
uv sync
uv run pytest
âŚand youâre running with the same dependency graph as on your laptop.
From Project to PyPI: uv build and uv publish
This is where the LinkedIn-style âtwo commands to publishâ slide comes from.
Assume youâve created a packaged project:
uv init --package data-processing-utils
# ...add code under src/data_processing_utils/...
uv add requests
uv add --dev pytest
uv lock
1. Build the distributions
uv build
This creates a dist/ directory with a wheel and an sdist, e.g.: (docs.astral.sh)
dist/
âââ data_processing_utils-0.1.0-py3-none-any.whl
âââ data_processing_utils-0.1.0.tar.gz
2. Publish to PyPI (or TestPyPI)
Youâll need an API token from PyPI or TestPyPI.
Basic flow:
# Build (again, in case something changed)
uv build
# Publish using a token
UV_TOKEN="pypi-xxxx-xxxx-xxxx" uv publish --token "$UV_TOKEN"
For TestPyPI or custom indexes, you can use flags such as --publish-url or configure named indexes in pyproject.toml. (docs.astral.sh)
After that, anyone can install your package using plain pip:
pip install data-processing-utils
So the basic âpackage lifecycleâ becomes:
uv init --package data-processing-utils
# ... code, tests, dependencies ...
uv build
uv publish
All with the same tool that managed your environment and dependencies.
Using uv Instead of pipx: uvx and uv tool
In Part 1, pipx was your friend for global CLI tools. With uv, you get:
uvxâ run tools in temporary, isolated envsuv tool installâ install tools globally, but isolated from your projects (GitHub)
Quick one-off:
uvx ruff check .
Install a tool for repeated use:
uv tool install ruff@latest
ruff --version
This keeps your âtoolingâ environment clean and separate from project dependencies, but without introducing yet another CLI.
Replacing pyenv: uv python
uv can also handle Python versions for you: (GitHub)
uv python install 3.12
uv python list
uv venv --python 3.12
Most of the time you wonât have to think about it: uv will download a suitable Python when needed for a project or virtualenv.
A Minimal uv Workflow (End-to-End)
Hereâs a compact, realistic project flow that touches most of the things weâve discussed:
# 1. Create an app-style package
uv init --app --package weather-bot
cd weather-bot
# 2. Add runtime + dev dependencies
uv add httpx
uv add --dev pytest ruff
# 3. Run the app while developing
uv run weather-bot
uv run python -m weather_bot.cli
# 4. Run tests & linting
uv run pytest
uv run ruff check .
# 5. Lock and sync
uv lock
uv sync
# 6. Build for distribution
uv build
# 7. Publish when ready
UV_TOKEN="pypi-xxxx-xxxx" uv publish --token "$UV_TOKEN"
Compared to the Part 1 world, notice whatâs missing:
No separate
pyenvinstall stepNo manual
python -m venv .venvNo âis this
piporpipx?â momentsNo juggling
requirements.txt+requirements-dev.txt+constraints.txt+twine
Just one tool with a consistent model from init â run â test â build â publish.
When Should You Start Using uv?
You donât need to rewrite your entire Python life in one go. Low-friction ways to start:
For a new repo, run
uv initoruv init --packageinstead of manually creating avenvandrequirements.txt.For CLI tools, reach for
uvx ruff,uvx httpx-cli, etc.For an existing project, use
uv pip install -r requirements.txtjust to get faster installs, then gradually move touv add,uv lock, anduv sync. (docs.astral.sh)
Once youâve used uv run and uv init --package a few times, it becomes very hard to go back to the old multi-tool dance.

