Updating Python, revisited

MP 157: It's so much easier now!

Back in August, I wrote a post about using uv to manage my overall Python environment, after years of using pyenv. One of the claims in that post was that it should be much easier to keep my environment updated over time, even across multiple Python versions. I'm using my laptop again after some time away from it, and it's time to see if that claim holds. I'm pretty sure it will!

Where I'm at

I last updated my laptop's Python environment this past summer. It's winter now, so I'm pretty sure things are out of date. Let's see what my current versions are.

One of the nice things about being a technical writer is that you end up documenting your own processes more thoroughly than you might otherwise. In the last post, I noted a command that shows all the uv-managed Python versions on my system, without any repetition:

$ uv python list --only-installed | grep uv/python | grep -v local/bin
cpython-3.14.0rc2-macos-aarch64-none    .local/.../python3.14
cpython-3.13.7-macos-aarch64-none       .local/.../python3.13
cpython-3.12.11-macos-aarch64-none      .local/.../python3.12
cpython-3.11.13-macos-aarch64-none      .local/.../python3.11
cpython-3.10.18-macos-aarch64-none      .local/.../python3.10
cpython-3.9.23-macos-aarch64-none       .local/.../python3.9

This is a custom version of the command uv python list --only-installed, which will show you all the versions of Python on your system that uv can find, whether they were installed by uv or not.

These interpreters are getting out of date. For example I have a pre-release version of Python 3.14, and there's a stable version out now. The latest release of 3.13 is 3.13.11, so I'm four point releases behind the latest version of 3.13.

In the past, I probably would have only updated the 3.14 interpreter, and taken care of the rest when I have more time. But now I should be able to update all these versions with one command. Let's try it!

Updating everything everywhere all at once

I've been looking forward to running this command for months now. Here goes:

$ uv python upgrade
warning: `uv python upgrade` is experimental and may change
  without warning. Pass `--preview-features python-upgrade`
  to disable this warning

Okay, I think this command worked without extra flags last time, but I'll try the full command it's suggesting:

$ uv python upgrade --preview-features python-upgrade
All versions already on latest supported patch release

Oof, that's definitely not accurate. I know these versions are out of date, especially the 3.14 version!

Let's upgrade uv:

$ brew upgrade uv
==> Upgrading uv
  0.8.15 -> 0.9.17

Now let's try the update command again:

$ uv python upgrade --preview-features python-upgrade
Installed 6 versions in 3.39s
 + cpython-3.9.25-macos-aarch64-none (python3.9)
 + cpython-3.10.19-macos-aarch64-none (python3.10)
 + cpython-3.11.14-macos-aarch64-none (python3.11)
 + cpython-3.12.12-macos-aarch64-none (python3.12)
 + cpython-3.13.11-macos-aarch64-none (python3.13)
 + cpython-3.14.2-macos-aarch64-none (python3.14)

That's pretty wild. In just over 3 seconds, I've got fully updated versions of all my Python interpreters! This is the easiest time I've ever had updating Python across my entire system.

Adding another Python

I've been using an early release version of Python 3.15 on my desktop, so let's add it to my laptop as well:

$ uv python install 3.15                             
Installed Python 3.15.0a2 in 1.18s
 + cpython-3.15.0a2-macos-aarch64-none (python3.15)

This is great! It's never been easier to install pre-release versions of Python, as well as the latest stable point releases. Now I can use any version of Python I want, for any project on my system.

Rebuilding venvs?

I was curious to see if I'd need to rebuild any virtual environments that were created with the older Python versions. It turns out I don't, because while uv added the latest version of each Python interpreter, it didn't remove any of the older ones:

$ uv python list --only-installed | grep uv/python | grep -v local/bin
cpython-3.15.0a2-macos-aarch64-none     .local/.../python3.15
cpython-3.14.2-macos-aarch64-none       .local/.../python3.14
cpython-3.14.0rc2-macos-aarch64-none    .local/.../python3.14
cpython-3.13.11-macos-aarch64-none      .local/.../python3.13
cpython-3.13.7-macos-aarch64-none       .local/.../python3.13
cpython-3.12.12-macos-aarch64-none      .local/.../python3.12
cpython-3.12.11-macos-aarch64-none      .local/.../python3.12
cpython-3.11.14-macos-aarch64-none      .local/.../python3.11
cpython-3.11.13-macos-aarch64-none      .local/.../python3.11
cpython-3.10.19-macos-aarch64-none      .local/.../python3.10
cpython-3.10.18-macos-aarch64-none      .local/.../python3.10
cpython-3.9.25-macos-aarch64-none       .local/.../python3.9
cpython-3.9.23-macos-aarch64-none       .local/.../python3.9

Now I have two instances of every major Python version. Any new virtual environment will be made with the latest point release, but older environments will continue to work until I choose to rebuild those environments.

Keeping a clean system

All my existing virtual environments are disposable. I use different versions of Python for different projects, but I don't need specific point releases. So I'm going to get rid of all those older interpreters, and I'm pretty sure I can do it in one command:

$ uv python uninstall 3.9.23 3.10.18 3.11.13 3.12.11 3.13.7 3.14.0rc2
Uninstalled 6 versions in 370ms
 - cpython-3.9.23-macos-aarch64-none
 - cpython-3.10.18-macos-aarch64-none
 - cpython-3.11.13-macos-aarch64-none
 - cpython-3.12.11-macos-aarch64-none
 - cpython-3.13.7-macos-aarch64-none
 - cpython-3.14.0rc2-macos-aarch64-none

That was easy! Now let's make sure I only have one of each interpreter:

$ uv python list --only-installed | grep uv/python | grep -v local/bin
cpython-3.15.0a2-macos-aarch64-none    .local/.../python3.15
cpython-3.14.2-macos-aarch64-none      .local/.../python3.14
cpython-3.13.11-macos-aarch64-none     .local/.../python3.13
cpython-3.12.12-macos-aarch64-none     .local/.../python3.12
cpython-3.11.14-macos-aarch64-none     .local/.../python3.11
cpython-3.10.19-macos-aarch64-none     .local/.../python3.10
cpython-3.9.25-macos-aarch64-none      .local/.../python3.9

This is exactly what I wanted, one instance of each interpreter, and each is on the latest point release available. I'll have to rebuild my virtual environments for each project I go back to, but that's really quick and easy using uv.

Conclusion

If you're setting up a new system, or looking for a cleaner, more consistent way to manage multiple Python environments, consider using uv to manage your overall environment. It's a fantastic tool, and it certainly helps me maintain a cleaner, more up to date system.