UV Usage: run, add, remove
I’ve been moving all my python code over to be managed by uv in the last few months. With the release of 0.3.x, I’ve gone from just playing around with it to fully buying into it as my python package manager.
The new release added a whole host of new sub-commands, initially marked as experimental, but the label dropped within days. Switching over to use them also meant changing how I was using uv
, which may mean I was using it the wrong way to begin with.
Below, I’m describing what I’ve been doing in the hopes it helps make life easier for anyone giving it a whirl.
Prior usage
Before the new release, I was basically using uv
as a sort of pip
and venv
replacement. I’d use it to create my virtual environment and then pip install
any dependencies into it. On my system it was so much faster that using it meant it basically got out of the way of actual development. It’s minor, but that feeling of instant response felt magical.
Those commands, uv pip
and uv venv
still work, but with the new workflow they feel almost redundant. I suppose for managing existing projects they still have a place, since you can use uv
immediately without having to change anything from a project that was set up using traditional pip
and venv
.
New workflow
Switching to the new commands meant changing the way I looked at a project. I’ll explain by working backwards, which I think will also help explain why anyone would want to bother doing it at all.
uv run
In a project managed by uv
, the run
command is the workhorse. It allows running any tool or part of the program with the environment fully set up as needed. For example, with zpdatafetch I have a binary named zpdata
which acts as a wrapper to provide access to all the library functions from the command-line. This helps both for ad-hoc data checks and for checking that the library still does what is expected while in development.
With uv run
there is no need to pip install
the package or alternatively, update the PYTHONPATH to include the local directory. Instead, I can simply use uv run zpdata ...
and uv takes care of managing the library path and even the binary search path to make sure I pick up the local copy.
It works just as well for wrapping pytest
so that running tests quickly is simple from the start of the project. Previously, I’d have used make
or doit
to make sure I had a quick command that did this, but now it’s just there ‘for free’ with uv
.
uv add
and uv remove
Speaking of pytest
- uv also changes the approach to managing dependencies of the project. In doing so, it finally supports the split of prod and dev dependencies as well. Instead of pip install
or uv pip install
the new workflow expects you to uv add <package>
. This still uses uv
’s hyper-fast magic to set the package up in your environment. For packages like pytest
adding the --dev
flag will keep it separate in the dev dependencies.
For zpdatafetch this means initial setup is now:
uv add beautifulsoup4 httpx lxml keychain
uv add --dev pytest ruff setuptools build wheel
Of course, that’s not needed in practice because all those dependencies are now captured in the pyproject.toml
file. So unless something’s changing, there’s no need to run them. On initial git clone
of the repository, it’s possible to install them with uv sync
or even just ignore that and go straight to uv run
which will do the same thing before the first script is run to ensure the environment is consistent.
Next post I update on how I migrated zpdatafetch to uv
including updating the github actions to use uv
for build and test before publishing to pypi.
Written on 27 August 2024