Andrew Gilliland
Back to Articles

uv Is the npm of Python

Why I Switched to uv

I spent most of my time in the JavaScript ecosystem before picking up Python. When I did, the tooling felt… scattered. You need pip to install packages, venv to create virtual environments, and there’s no lockfile by default. Coming from npm where everything just works out of one tool, it was a rough adjustment.

Then I found uv. It’s a Python package manager written in Rust, and it’s fast. Like, really fast. But speed isn’t the main reason I switched. uv just makes Python feel as ergonomic as Node.

The Mental Model

If you know npm, you already know the concepts behind uv. Here’s a quick mapping:

npmuv
npm inituv init
npm install expressuv add requests
npm remove expressuv remove requests
package.jsonpyproject.toml
package-lock.jsonuv.lock
node_modules/.venv/
npxuv run
nvm install 20uv python install 3.12

Once I saw it this way, everything clicked.

Getting Started

Install uv:

brew install uv

Or if you’re not on macOS:

curl -LsSf https://astral.sh/uv/install.sh | sh

Create a new project:

uv init my-project
cd my-project

This gives you a pyproject.toml (your package.json equivalent) and sets up a virtual environment automatically.

Adding Dependencies

uv add requests

That’s it. uv creates the .venv, installs the package, and updates both pyproject.toml and uv.lock. No activating environments. No pip freeze > requirements.txt. Just uv add.

Running Scripts

Instead of activating a virtual environment every time, just use uv run:

uv run python main.py

Think of it like npx, it runs the command inside the project’s environment without you having to manage it manually.

Virtual Environments

In Node, dependencies are scoped to the project via node_modules. Python uses virtual environments for the same thing, but traditionally you had to create and activate them yourself.

uv handles this automatically. When you run uv init or uv add, it creates a .venv in your project directory. When you use uv run, it uses that environment. You don’t have to think about it.

Installing Python Itself

uv can even manage Python versions:

uv python install 3.12

This is like nvm install 20 in the Node world. No more Googling “how to install Python 3.12 on macOS.”

Defining Scripts

In package.json you’re used to defining scripts like "dev": "next dev". In Python, [project.scripts] in pyproject.toml lets you define CLI entry points:

[project.scripts]
dev = "my_app.main:run"

This maps the command dev to the run function in my_app/main.py. After running uv sync, you can call it with:

uv run dev

Unlike npm scripts, these aren’t arbitrary shell commands, they point to Python functions. But in practice, uv run covers the gap. You can run any command inside the project’s environment directly:

uv run pytest
uv run ruff check .
uv run python main.py

No script definitions needed. Just uv run + the command.

The Takeaway

uv made Python tooling feel familiar to me as a JavaScript developer. One tool for project setup, dependency management, virtual environments, and running scripts. If you’re coming from the JS ecosystem and Python’s tooling has felt like a maze, give uv a shot.

Check out the uv docs to get started.

Table of Contents