Ever since Hynek recommended it, I've been trying to use the uv packaging tool. While the uv docs are excellent (including guides and integration guides), there's a missing part in my opinion: how do you migrate from existing tooling? There's no guide to migrate from Poetry or PDM, for example. And more importantly to me, nothing if you want to migrate a library to uv, which is the subject of this blog post.
The typical workflow of a library is different than the workflow of an application:
- Libraries are published to PyPI, not deployed to a Docker registry
- Libraries should allow a wide range of dependency versions, while applications pin them.
- Libraries typically use pip and pip-tools, not Poetry or PDM.
Thankfully, since uv is designed to be the 'one tool' for Python packaging, it supports libraries too.
Should you migrate at all?
First, you need to be OK with Astral reimplementing the whole Python packaging ecosystem in Rust without having a clear business model. This has been discussed at length, with interesting takes from both sides.
Second, there are still technical limitations:
- uv does not work well with dynamic versions, which is very common in open source libraries
- Since PEP 751 is not ready yet, uv uses its own lock file,
uv.lock
. It's not generally supported by other tools:- Nox does not support it (but has documented workarounds).
- dependabot does not support uv.lock and alternatives such as gha-update do not either.
With that said, the experience of using uv itself is really good. You no longer have to mess with virtualenvs, pip-tools, pipx and so on. And the speed is amazing.
Requirements
OK, let's suppose you do want to migrate. There are a few requirements.
uv itself You can install uv with your favorite package manager or see the installation instructions for more options.
pyproject.toml If you still have a setup.py file in your repository, you need to move to pyproject.toml first. You can stick to setuptools or use an alternative like hatch.
Python You don't have to install Python separately! If needed, uv will install Python for you using python-build-standalone. While convenient, it comes with a number of quirks. That said, you can choose to install Python yourself, for example using the excellent Fedora builds or the macOS Python.org installer + MOPup. If uv finds a suitable Python installation in the PATH, it will use it.
Other tools You also don't need to install pip, virtualenv, etc. as uv reimplements those tools. And you can install tools like nox, tox or pre-commit with uv tool install
.
Migrating
With that out of the way, let's see how you're going to use uv in the different parts of your project.
Shell integration With uv, there's no need to activate your virtualenv. There's also no need for a separate step to install dependencies. The only command needed here is uv sync
. This command will:
- Install Python if needed
- Create a virtual environment for you, ignoring any existing ones
- Install the dependencies listed in pyproject.toml and create a
uv.lock
file in the process.
When everything is installed and the cache is warm, running uv sync
is faster than git status
! Which means that you can run uv sync
as part of your shell prompt and always have an up-to-date installation.
Test automation
- If you use nox, set
@nox.session(venv_backend='uv')
andUV_PROJECT_ENVIRONMENT
as documented in the nox cookbook. - If you only use pytest, run
uv run pytest
. - If you use tox, use the tox-uv plugin.
To avoid running uvx nox
(or tox) every time, run uv tool install nox
(or tox) to install it. Now you can simply run nox
(or tox). This also means you no longer need pipx.
GitHub Actions Use the setup-uv action. It also supports caching, which is useful for not hitting PyPI servers too hard, not for speed.
Dependency pinning While libraries don't pin dependencies in pyproject.toml
, they still commonly use a pinned requirements file for their test dependencies. If this is your case, you can commit your uv.lock
file to git. It is cross-platform and supports multiple Python versions! At the time of writing, Renovate supports it, but dependabot does not.
pre-commit Follow https://docs.astral.sh/uv/guides/integration/pre-commit/.
twine Use uv publish. That said, it's better to use the PyPI publish GitHub Action which is more secure and abstracts the uploading tool for you.
Examples
Looking for inspiration? Those projects use uv:
Do you know about other projects? Please let me know.
Conclusion
Migrating your Python library to uv is not as hard as it looks, while bringing a number of benefits:
- using a single packaging tool,
- not having to worry about virtualenvs anymore,
- enjoying categorically faster install speeds,
- using a lock file that works across Python and OS versions.
I'm on Mastodon!
Comments