uv is the hot new package manager for Python. I’ve seen an increasing amount of good press about it as more developers try it out, but I think a lot of people are missing what really makes uv the best Python package manager yet.
A lot of attention has been given to uv’s performance improvements over pip, Poetry, and other popular Python tools. Yes, uv is written in Rust and is faster at installing and resolving dependencies. But performance isn’t the best thing about it.
The real power of uv is how it simplifies the entire workflow of working in Python.
My life before Python
I didn’t realize how beautiful of a tool npm is until I switched over to Python as my primary backend language a few years ago. I started my career working with Node.js and took for granted the fact that npm comes bundled with Node and handles everything from dependency management to running scripts. When coding in JS, I rarely wasted any time debugging issues with npm. It just worked.
After accepting a new job that was primarily Python-based, I was struck with buyers remorse when I discovered the dumpster fire that is Python package management. pip, the only tool that comes bundled with Python itself, only takes care of the basics. Unlike Node, Python installs packages globally by default, so you need another tool to set up a “virtual environment”. Also unlike Node, Python typically comes pre-installed on macOS, which means you need yet another tool to install newer Python versions and ensure you’re invoking the correct version.
Before uv, starting a new Python project meant first determining the version of Python you need, then installing it with pyenv, then creating a new venv, then activating the venv, then installing packages with pip. Or you could use a tool like Poetry, which my company was using at the time and seemed promising, but in reality didn’t make my life much easier.
Life after uv
uv has completely changed the user experience of working with Python. What used to be a patchwork of tools to get a single project off the ground is now one tool that just works. uv can install Python, transparently manage virtual environments, install packages, and run tools.
Perhaps the biggest win amongst these is that you don’t even need to think about virtual environments anymore. Virtual environments and all of the tooling around them was Python’s most annoying point of friction for me.
uv has completely abstracted away virtual environments and all the headaches of dealing with them. There is no more deciding whether to use venv or virtualenv, deciding what to name the folder, or remembering to activate the environment. With uv init
, uv add
, and uv run
, you can set up and run a Python project without ever thinking about the underlying virtual environment.
If you’re an experienced Python developer or new to the community, even if you think you’re satisfied with your workflow, give uv a try. It’s the closest thing we have yet to a truly great end-to-end package manager for Python.
Some helpful uv commands
Install uv
This is uv’s standard install script:
curl -LsSf https://astral.sh/uv/install.sh | sh
Or if you’re on macOS, you can use homebrew:
brew install uv
There are more options in the uv docs.
Install python
uv can install Python for you! To install the latest version, simply run:
uv python install
Or if you want a specific version, you can run it like:
uv python install 3.12
Create a new python project
“Projects” are at the heart of uv’s power. To start a new one, run:
uv init
This will create a new project inside the current directory with the following structure:
├── .gitignore
├── .python-version
├── README.md
├── main.py
└── pyproject.toml
The pyproject.toml
is Python’s equivalent to a package.json
and can handle more configuration than a requirements.txt
file.
Install a dependency for a project
Say you want to install an HTTP request library like httpx. That would look like:
uv add httpx
This installs the dependency and also adds it to your pyproject.toml
.
You can also add dev dependencies with the —-dev
flag:
uv add --dev pytest
uv add
can do a lot, so I recommend looking at the documentation here for more details.
Install all dependencies for an existing project
If you’re cloning and setting up an existing project that is managed with uv, you’ll be pleasantly surprised to find that uv actually installs everything for you transparently when you invoke uv run
, however there might still be times when you want to explicitly install the project dependencies. To do this, run:
uv sync
This is the equivalent of a project-level npm install
. But it’s not strictly necessary in uv. Use it when you feel you need it.
Run a script
If you’re just running a plain Python file, uv run
will ensure it runs in a dedicated environment with all the installed dependencies:
uv run example.py
Or if you’re trying to run a local FastAPI server using FastAPI’s own executable, you can run it like this:
uv run fastapi dev app.py
All of the dependencies are available to the program as part of the uv-managed environment.
A note about uv pip
uv offers an interface to pip, so you can run a command like uv pip install httpx
if you’d like. But this is an example of how I believe you’d be missing out on the real power of uv. The commands above leverage uv’s project management tools, which reduce the load on the developer in having to think about the setup and configuration of the environment. But if you still need pip for some reason, uv pip install
is there.
If you’ve made it this far, thank you for reading! If you’d like to work with me, add me on LinkedIn and send me a DM. I’ll be setting up more contact methods soon, but for now that is the best way to reach me.