A bit more than a week ago, I posted this on Mastodon after I read several posts in a long thread on using UV for Python projects. Simon has created a blog post summing up and linking to the most significant posts in this long thread — uv under discussion on Mastodon.
Now, a week later, I have to admit that I was wrong. I still stand with my opinion that the speed improvement is not crucial to me, but it is of course nice to have.
So, what has changed my mind? The short answer: I totally misunderstood what UV can do for me since the release of version 0.4.0.
For a longer explanation, I have to describe my requirements for my local Python environment. I need to have various Python versions installed locally to test my work and my personal projects. Ranging from Python 3.8 to 3.13.
I also need them for running some dated scripts, that need upgrades, but I don’t have the time to do so for a long time.
I also require decent dependency management in my projects that goes beyond manually editing a pyproject.toml
file. Likewise, I am way too accustomed to poetry add ...
. And I run a number of Python-based tools — djhtml, poetry, ipython, llm, mkdocs, pre-commit, tox, …
So far, I have fulfilled these requirements with a changing mixture of Homebrew, pipx and pyenv. This works, but can be sometimes complicated to keep in sync, and at least pyenv and pipx sometimes had issues with my fish shell, that I were never able to figure out before some kind of self-healing fixed it. Basically, nothing is wrong with the above-mentioned tools.
Inspired from posts by Anže and Jeff I had on my reading list, I decided to give UV another try and see if I can switch over to a single tool to manage my requirements.
I started by removing all Python installations, pyenv, pipx and Homebrew from my machine. Rendering me unable to do my work.
Managing python versions
So far, I have switched back and forth between Homebrew and pyenv to manage my local Python installations. UV can solve this easily, too. I just had to run the following commands to get my required range of Python versions installed.
uv python install 3.8
uv python install 3.9
uv python install 3.10
uv python install 3.11
uv python install 3.12
uv python install 3.13
FishAs I mentioned above, I have to run these versions from time to time interactively, and I also have some scripts that need to be run with a certain version of Python.
Homebrew solved this by putting them all on the global PATH. The versions installed with UV are not immediately available for use. But this can be easily solved with some handy aliases.
alias python3.8 'uv run --python=3.8 python3'
alias python3.9 'uv run --python=3.9 python3'
alias python3.10 'uv run --python=3.10 python3'
alias python3.11 'uv run --python=3.11 python3'
alias python3.12 'uv run --python=3.12 python3'
alias python3.13 'uv run --python=3.13 python3'
alias python3 python3.12
FishSolved. See the documentation for uv python.
Managing tools
The above-mentioned list of tools was partially managed by Homebrew and pipx on my system. I normally used the Homebrew version until I had to inject some plugin or extra dependencies to it. Then I switched to pipx to use pipx inject
. With UV, you can do the very same.
uv tool install --python=3.12 djhtml
uv tool install --python=3.12 poetry
uv tool install --python=3.12 ipython
uv tool install --python=3.12 llm
uv tool install --python=3.12 --with pre-commit-uv pre-commit
uv tool install --python=3.12 --with tox-uv tox
uv tool install --python=3.12 --with mkdocs-material mkdocs
FishPersonally, I find the syntax with the --with
argument a bit more appealing than to use two commands to install and inject the extra dependencies. But this is just personal taste.
pre-commit-uv
and tox-uv
are two helpful extensions for pre-commit
and tox
, which make these tools use uv for managing the underlying virtual environments.
Solved. See the documentation for uv tool.
Managing projects
So far, I used UV only as a pip replacement in my django-tailwind-cli project to speed up some tasks. It was a quick win to shave off some seconds on CI/CD runs and also when running tox
locally.
But actually, it can replace poetry for me in most of the projects I have to deal with. See the documentation on working with projects. It has all the tools in place to create a new project, to manage dependencies and also to build a package. The only missing part is publishing to PyPI, which has to be solved using twine at the moment. I am sure, they will integrate this too.
Anže described in detail how to manage a Django project with UV. A great article you should check out.
What I like to highlight in this section is, how uvx
can remove some superfluous dev dependencies from your project. Even though pipx run
would have done the very same for me, I never had the idea to use it in my justfile
or GitHub Actions like that. Using this technique, I could kick tox, mkdocs and some more tools from my dev dependencies and still get the same tooling. The lines below are from the justfile in my django-tailwind-cli project.
# run pre-commit rules on all files
@lint: create_venv
uvx --with pre-commit-uv pre-commit run --all-files
# run test suite
@test: create_venv
uvx --with tox-uv tox
# serve docs during development
@serve-docs:
uvx --with mkdocs-material mkdocs serve
# build documenation
@build-docs:
uvx --with mkdocs-material mkdocs build
# publish documentation on github
@publish-docs: build-docs
uvx --with mkdocs-material mkdocs gh-deploy --force
FishSolved.
Am I happy?
To be honest — I am.
I started basically with an empty machine, downloaded a single binary and used it to set up my complete development environment with it. There was a post on Mastodon, that describes this as one of the interesting aspects of UV for training sessions or people starting with Python. I have to agree that this is an appealing aspect. At least if you go beyond the topics possible just with the official installer.
I really like the environment I have set up. And I also enjoy the overall tooling and CLI decisions taken by the people at Astral. Let’s see if this impression lasts over a full working week. Otherwise, you will see another post next Sunday. 🤣
The elephant in the room
To finish this post, let’s discuss the elephant in the room, as Armin Ronacher named it in his post from the end of August.
There is an elephant in the room which is that Astral is a VC funded company. What does that mean for the future of these tools? Here is my take on this: for the community having someone pour money into it can create some challenges. For the PSF and the core Python project this is something that should be considered. However having seen the code and what uv is doing, even in the worst possible future this is a very forkable and maintainable thing. I believe that even in case Astral shuts down or were to do something incredibly dodgy licensing wise, the community would be better off than before uv existed.
I agree with Armin. There is a risk attached, but eventually the community is better off than before. I also tend to believe the words of Charlie Marsh, creator of uv and ruff, he wrote in several posts as he entered the before mentioned discussion on Mastodon.
And if the worst possible future happens, I hope that some tools take inspiration from uv or some standard solution that provides tooling like that is added to the official installer. We need a bit more than pip inside the standard distribution from my perspective. Rust, node and go show that it is possible and also able to create a momentum.
Appendix: Steps to set up my environment
Here are the steps I took to set up my environment.
# Install the current version of uv and uvx to $HOME/.cargo/bin
curl -LsSf https://astral.sh/uv/install.sh | sh
# Install Python versions
uv python install 3.8
uv python install 3.9
uv python install 3.10
uv python install 3.11
uv python install 3.12
uv python install 3.13
# Define the aliases
alias python3.8 'uv run --python=3.8 python3'
alias python3.9 'uv run --python=3.9 python3'
alias python3.10 'uv run --python=3.10 python3'
alias python3.11 'uv run --python=3.11 python3'
alias python3.12 'uv run --python=3.12 python3'
alias python3.13 'uv run --python=3.13 python3'
alias python3 python3.12
# Install tools to $HOME/.local/bin
uv tool install --python=3.12 djhtml
uv tool install --python=3.12 poetry
uv tool install --python=3.12 ipython
uv tool install --python=3.12 llm
uv tool install --python=3.12 --with pre-commit-uv pre-commit
uv tool install --python=3.12 --with tox-uv tox
uv tool install --python=3.12 --with mkdocs-material mkdocs
FishTo set up a minimal environment for Python might look like that.
# Install the current version of uv and uvx to $HOME/.cargo/bin
curl -LsSf https://astral.sh/uv/install.sh | sh
# Install Python versions
uv python install 3.12
# Define the aliases
alias python3.12 'uv run --python=3.12 python3'
alias python3 python3.12
Fish