The Upgrade I Didn’t Want to Do

Managing Python 3.13 environments with uv wasn’t on my roadmap.
When Python 3.13 landed, my first instinct was familiar:

“I’ll deal with it later.”

Not because I dislike new Python versions —
but because every major upgrade brings the same problems:

  • Dependencies lag behind
  • Wheels aren’t ready
  • Tools fail quietly
  • CI behaves differently from local machines

I’ve learned the hard way that Python upgrades don’t fail in production.
They fail in environments.


Why Python 3.13 Feels Different

Python 3.13 isn’t just another version bump.

What matters for environment management is this:

  • Stricter deprecation enforcement
  • Internal refactors
  • Removed legacy behaviors
  • More packages with C extensions needing rebuilds

That means some packages:

  • Install but crash at runtime
  • Refuse to install due to version constraints

You don’t just need “a new Python”.
You need a safe way to test, isolate, and iterate.


Prerequisites

Before working with Python 3.13 using uv, make sure you have:

  • Python 3.10+ installed (newer is smoother)
  • uv installed
  • Basic familiarity with Python dependencies

Install uv once:

pip install uv

If Python 3.13 isn’t available locally, uv automatically downloads it when needed.


The “Just Use venv” Workflow Finally Broke

This used to be my default:

python3.12 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt

Then repeat the whole thing for every Python version.

With Python 3.13, this workflow started to fall apart:

  • Switching versions felt risky
  • Old virtualenvs leaked state
  • I avoided rebuilding environments
  • CI drifted away from local
I wasn’t testing Python 3.13 — I was avoiding my environment setup.

That’s not a Python problem.
That’s an environment problem.


The Shift: Environments Should Be Disposable

The core idea behind managing Python 3.13 environments with uv is simple:

Environments should be cheap to create, easy to reuse, and painless to discard.

uv is built around that philosophy.


Running Python 3.13 Instantly

This was the moment everything clicked:

uv run --python 3.13 python --version

No virtualenv creation.
No activation.
No guessing which Python is active.
If Python 3.13 isn’t installed, uv downloads it automatically and caches it.


What uv run Actually Does (Important Clarification)

uv run does not create a brand-new environment for every command.

Instead:

  • uv creates an isolated, project-scoped environment
  • That environment is cached and reused intelligently
  • If dependencies or Python version change, uv rebuilds only what’s necessary

This gives you isolation without constant rebuild costs.


Running Code on Python 3.13

Run a script:

uv run --python 3.13 python script.py

Run tests:

uv run --python 3.13 pytest

Same project.
Different interpreter.
No setup ceremony.


Installing Dependencies the Right Way (uv-Native)

Instead of manually installing into global interpreters, the recommended uv workflow is:

uv sync --python 3.13

This:

  • Creates a project-specific environment
  • Installs dependencies from pyproject.toml or lockfile
  • Keeps Python versions isolated cleanly

No manual venv management required.


pyproject.toml + Python 3.13 (Clean Workflow)

Example pyproject.toml:

[project]
requires-python = ">=3.10"
[project.dependencies]
fastapi = "*" 
uvicorn = "*"

Install and run with Python 3.13:

uv sync --python 3.13
uv run uvicorn app:app

One configuration.
Multiple interpreters.
No duplication.


Side-by-Side Python Version Testing

This is where uv becomes invaluable during upgrades:

uv run --python 3.11 pytest
uv run --python 3.12 pytest
uv run --python 3.13 pytest

Same code.
Same dependencies.
Three Python versions.
Failures surface immediately — not weeks later.

Python 3.13 stopped being risky once environments became cheap and repeatable.

CI Without Environment Pain

CI pipelines usually expose version problems late.
With uv, my CI steps became predictable:

# Install dependencies
uv sync --python 3.13

# Run tests
uv run pytest

No activation scripts.
No interpreter juggling.
Fewer moving parts.
That simplicity is the real win.


venv vs uv: Practical Comparison

AspectTraditional venvuv
Setup workflowMulti-stepSingle command
Multi-version testingManual switching--python flag
Environment isolationManualAutomatic
CleanupManual deletionuv cache clean
CI parityFragileStrong

This isn’t about raw speed — it’s about iteration speed and confidence.


Common Python 3.13 Gotchas (And What to Do)

C-Extension Packages

Libraries like numpy, pandas, and others must be rebuilt for Python 3.13.

If imports fail:

  • Check PyPI for updated wheels
  • Use pre-releases if available
  • Temporarily pin Python for that dependency

Version Constraints

Example blocker:

package>=3.8,<3.13

Options:

  • File an upstream issue
  • Track compatibility releases
  • Delay upgrade for that dependency only

uv makes these problems visible early, not hidden.


Troubleshooting Quick Fixes

Python 3.13 not found?
→ uv auto-downloads it, or install via pyenv if preferred.

Imports failing after upgrade?

uv sync --python 3.13 --reinstall

Dependency won’t install?
→ It may not support 3.13 yet. Check PyPI metadata.


Cleaning Up Experiments

Testing multiple Python versions creates cache state.

Cleanup is simple:

uv cache clean

No hunting for old virtualenvs.
No mystery directories.


What About pyenv, asdf, or conda?

These tools solve different problems:

  • pyenv / asdf → installing Python versions
  • conda → scientific stacks and binary ecosystems
  • uv → fast, project-scoped environments and installs

They complement each other.
You don’t have to choose just one.


When uv Might Not Be Necessary

If you:

  • Maintain a single long-lived environment
  • Rarely test new Python versions
  • Avoid frequent rebuilds

Then uv may feel unnecessary.

uv shines when environments are disposable and rebuilt often.

Final Thoughts

Managing Python 3.13 environments with uv didn’t make upgrades exciting.
If you’re still deciding where uv fits in your tooling stack, a deeper comparison against Poetry and PDM may help.
It made them boring.
And boring is exactly what environment management should be.
Python versions will keep evolving.
Dependencies will lag.
Breakage will happen.
What matters is having a workflow that lets you test early, isolate failures, and move forward with confidence.
For me, uv finally made Python upgrades feel manageable.

Categorized in: