On July 28, uv 0.12.0 shipped — the first major version of Astral's Rust-based Python package manager since 0.11.0 landed back in March. If you type uv init from muscle memory, the single thing to know is this: you no longer get a lonely main.py. You get a real, installable package. That, plus a batch of quieter security default-flips, is the whole story — and Astral says most people can upgrade without touching anything.
uv init now builds a package, not a scratch file#
Run uv init example on 0.12 and you get a src/example/ layout, a [build-system] powered by uv_build, and a [project.scripts] entry so uv run example works out of the box. Previously you got a top-level main.py and a pyproject.toml with no build system at all. The change stabilizes the packaged-init preview that has been baking for months, and the old behavior is one flag away: uv init --no-package example.
For a team of one, this is the good kind of breaking change. The src/ layout has long been the recommended way to avoid the classic "it imports in dev but not once installed" trap, and shipping a build system by default means uv build and uv publish just work when you decide to cut a release. The cost is a one-time surprise: your new-project skeleton looks different, and any scaffolding script or tutorial that assumed a root-level main.py needs a glance.
What it means: Your reflex uv init now produces a publishable package instead of a script. Learn the --no-package escape hatch, update any project template you keep, and otherwise enjoy the better default.
The security tightening you'll actually feel in CI#
The bulk of 0.12's breaking changes are about refusing sketchy inputs. uv now accepts only .tar.gz source distributions per PEP 625 and rejects legacy .tar.bz2 and .tar.xz archives; it also rejects wheels built with bzip2, LZMA, or XZ compression. It refuses wheels that could overwrite the environment's Python interpreter — including case-insensitive python variants — closing a genuinely nasty install-time footgun. And in hash-checking mode it now rejects MD5-only hashes, demanding at least one secure digest like SHA-256 per requirement.
There's a subtler one worth flagging: a --require-hashes directive inside a requirements.txt now enables hash-checking mode and can't be opted out of while it's present, with every requirement pinned by == and hashed. If your lockfile-to-requirements.txt export leaned on MD5 or loose pins, the failure will show up as a red CI run, not a local hiccup.
What it means: None of this touches happy-path installs from PyPI, but it can break pipelines that consume older or hand-rolled artifacts. If you build your own sdists or wheels, confirm they're .tar.gz with modern compression before you bump uv in CI.
The small default flips that bite scripts#
A few changes alter behavior without any new error message, which makes them the ones to actually memorize. Pre-release handling now prefers stable releases and falls back to pre-releases only if-necessary; the old if-necessary-or-explicit default survives as a deprecated alias. uv run script.py now discovers the project relative to the script's directory rather than your working directory, which can select a different environment than 0.11 did — opt out with uv run --project . script.py. And uv venv --clear now refuses to delete a directory that isn't a virtual environment unless you pass --force, which is a safety win but will stop a script that blindly cleared a path.
Rounding out the list: explicit SSL_CERT_FILE / SSL_CERT_DIR overrides are now honored even when they load no certificates (so a misconfigured bundle fails loudly instead of silently trusting the system store), uv pip gains a pip-compatible --cert flag, and pylock.toml files — the PEP 751 lock format — get stricter validation.
What it means: These won't throw at upgrade time; they change outcomes. If you have cron jobs or CI steps calling uv run, uv venv --clear, or --prerelease behavior, re-read those three lines of the changelog specifically.
Should you upgrade — and how#
uv is Astral's single tool to replace pip, pip-tools, pipx, poetry, pyenv, twine, and virtualenv, written in Rust and pitched at "10-100x faster than pip," so most of the ecosystem is on it and staying current is the path of least resistance. For local dev and any new project, upgrade now — the packaging default alone is worth it. (If you're still weighing the switch at all, we compared uv vs Poetry vs pip-tools earlier this year.)
The one discipline worth keeping: pin uv's version in CI (uv is easy to pin in your setup-uv step or your container image) rather than pulling latest, so a major bump lands when you read the changelog, not when a Tuesday build turns red. Upgrade the CLI with uv self update if you used the standalone installer, or pip install -U uv if you installed it that way. Two other stabilizations quietly help you: sdists are now written in a TOML 1.0-compatible form (with the original preserved as pyproject.toml.orig) so older build frontends can consume your project, and uv now raises the open-file limit toward the OS maximum on Linux and macOS, cutting the "too many open files" failures that plagued big installs.
What it means: Treat 0.12 as a green upgrade for humans and a pinned, read-the-notes upgrade for machines. The defaults got better and safer; the only tax is reading one breaking-changes list once.



