What is it?
The uv tool from astral.sh and the tox-uv plugin for tox.
What’s great about it?
uv
is gaining popularity for Python projects because it’s modern, ergonomic and fast.
You can use uv
to create a single lock file for all of your project’s runtime and development dependencies. uv
manages this lock file according to the dependencies and dependency groups that you specify in pyproject.toml
.
The great thing about tox-uv
is that it makes tox
aware of the way you’ve arranged your project’s dependencies.
This lets you set up separate dependency groups for static analysis, unit tests, integration tests, and even docs, then reference these groups from the corresponding tox environments. So that, for example, running tox -e unit
installs the locked dependencies from your dev dependency group before running pytest
.
Now there’s a single source of truth for all your deps: declaration in pyproject.yaml
and pins in uv.lock
.
Quick start: use tox with tox-uv
sudo snap install astral-uv --classic
uv tool install tox --with tox-uv
uv tool update-shell
# restart shell if needed
tox
Make sure that tox
in the path is the one installed via uv tool
(or in any case it’s installed with the required plugin) and not your older tox
. You can run tox --version
to make sure.
Use uv with Charmcraft
We’re working with the Charmcraft team to switch the kubernetes and machine profiles of charmcraft init to use tox-uv
, starting from Charmcraft 4.
Even better, Charmcraft already supports uv as a build system when packing charms. To use uv, specify the uv plugin in charmcraft.yaml, then run charmcraft pack as before:
parts:
charm:
plugin: uv
source: .
build-snaps:
- astral-uv
When Charmcraft packs the charm, uv
figures out the charm’s dependencies using the same lock file as tox
. This setup should also be coming to the Kubernetes and machine profiles in Charmcraft 4!
Simple example: Dima’s toy charm:
- hexanator/pyproject.toml at main · dimaqq/hexanator · GitHub
- hexanator/tox.ini at main · dimaqq/hexanator · GitHub
- hexanator/charmcraft.yaml at main · dimaqq/hexanator · GitHub
Complex example: canonical/operator, the Ops library:
- operator/pyproject.toml at main · canonical/operator · GitHub
- operator/tox.ini at main · canonical/operator · GitHub
- operator/.github/workflows/framework-tests.yaml at main · canonical/operator · GitHub
- Also uses the “workspace” feature, which is suitable for our case: one monorepo that contains several Python packages that all share the same dependencies: a superset of dependency names and an intersection of allowed dependency versions.
Caveats
The tox-uv
plugin dropped support for Python 3.8 before the dependency-groups
feature has landed. This means that if you still target Ubuntu 20.04 and run your unit tests on Python 3.8, you need to do some extra work:
- ensure that
tox
run on modern Python (e.g. system, or the latest, 3.13) - use Python 3.8 for the commands that
tox
invokes
Here’s an example of doing just that in our repo, which allows running tox -e py3.8-unit
Other options
We’re still experimenting with both make
and just
in some of our repos. We’re not set on one tool to rule them all, but we wanted to report good experience with uv+tox-uv
specifically.
References
Your Charm Tech engineers, @dimaqq and @davidwilding.