uv 0.12 Makes Every New Project a Package
Run uv init on 0.12 and the main.py you expected is not there.
$ uv init example
$ cd example
$ uv run example
Hello from example!
Source code now lives in src/example/__init__.py, pyproject.toml carries a [build-system] using uv_build, and a [project.scripts] entry turns example into a runnable command. That is the headline breaking change in uv 0.12.0, released July 28, 2026.
uv shipped this default once before
Packaged-by-default is how uv init behaved back in v0.3. uv dropped it in v0.4 because the generated [build-system] pointed at Hatchling, and newcomers found an unexplained third-party build backend confusing. uv now ships its own backend, uv_build, so the layout returns without a second tool to explain.
The project installs itself now
The flat layout could declare dependencies but was never installed into its own virtual environment. A packaged project is, in editable mode, on the first uv run. Tests can import example without path manipulation, and other projects can depend on it directly.
Watch for one side effect: every uv add rebuilds and reinstalls your project alongside the new dependency, so the output gains a line like ~ example==0.1.0 (from file:///path/to/example).
Existing projects keep working
Only new uv init runs change. The one thing to check before upgrading is an upper bound on uv_build in your [build-system] table. Widen it so the backend can move to 0.12:
[build-system]
requires = ["uv_build>=0.11.32,<0.13"]
build-backend = "uv_build"Get the flat layout back with –no-package
uv init --no-package exampleThat still produces the main.py project with no build system, which remains the right shape for a script or an internal tool you never distribute. uv init: project types, flags, and examples compares every layout side by side.
--package also still works and now matches the default, so older tutorials that pass it produce the same project they always did.
Scan the rest of 0.12.0 before upgrading
Four other breaking changes ship in the same release:
- Source distributions must be
.tar.gz. PEP 625, the standard that fixed sdist filename and format ambiguity, requires it..tar.bz2and.tar.xzare now rejected, including when referenced by an existing lockfile. Legacy.zipsdists still work. - Pre-release handling defaults to
if-necessary. uv tries stable candidates first and falls back to pre-releases only when nothing stable satisfies the constraints. This matches pip and finally resolves pre-release requirements discovered in a dependency’s metadata. --require-hashesinside arequirements.txtis enforced. uv previously warned and installed anyway. MD5-only hashes are rejected in hash-checking mode.- Wheels cannot overwrite the interpreter. uv now rejects case variants like
Python.exeand data files that would install over the virtual environment’s Python.