Build1 publisher3 min readPublished
uv 0.12 flipped the uv init flag one cheatsheet had long recommended
Since uv 0.12.0 landed on July 28, 2026, uv init has written a src layout and a uv_build build-system table by default. The old --package tip is now backwards, and --no-package is the way back to a flat main.py.
The Engineer · Build desk
What happened
- uv 0.12.0, released on July 28, 2026, made uv init produce a packaged project by default: a src layout, a [project.scripts] entry and a [build-system] table.
- The author of a uv cheatsheet he pastes to colleagues checked it against a current build and found that the two entries he trusted most no longer held.
- Scrapy's continuous integration runs its matrix through uvx --with tox-uv tox and sets UV_PYTHON_PREFERENCE to only-system so uv uses the interpreter actions/setup-python installed.
Compiled by The EngineerSomething wrong?How this is made
Why it matters
- cost Every internal setup note, README and pasted Slack snippet that names --package now teaches the opposite of what uv does; editing one line is trivial, and finding all the copies is the hard part.
- constraint New projects arrive carrying a backend version range, so anyone building the repository needs uv_build at 0.12.15 or later and below 0.13, and the upper bound expires on its own schedule.
- capability With the project installed into its own virtual environment, imports stop depending on which directory the developer happens to be in. New repositories shed a class of works-on-my-machine reports.
- exposure A continuous integration job that does not constrain interpreter provenance can go green against a Python that uv downloaded and that no user of the package is running.
Packaged by default changes what lands in the repository, not just where the files sit. A project scaffolded by uv 0.12.15 carries a `[build-system]` table with `requires = ["uv_build>=0.12.15,<0.13.0"]` and `build-backend = "uv_build"` [3]. The lower bound is the version of uv that wrote the file, so a colleague on an older uv_build cannot build the project, and the range stops short of 0.13 [22]. There is also a `[project.scripts]` entry, `demo = "demo:main"` for a project called demo [4].
The payoff is the import path. Before the change, `uv init` wrote a bare `main.py` at the top level with no build system, which meant your own code was not installed into its own virtual environment and was importable only by accident of whichever directory you were standing in, according to the post [6]. The packaged layout puts the module at `src/demo/__init__.py` instead [5]. The flat shape is still available behind `uv init --no-package`, and existing projects are untouched [7].
The entry in the author's shared reference said the reverse: that `uv init --package` was the flag you needed for a build system and a src layout, which was true in April 2026 and is backwards now [8]. He reports two entries he trusted most, one wrong since late July and one that had never been right on either version he tested [9]. Late July is when 0.12.0 shipped, on July 28, 2026 [21]. The post calls this normal for a tool moving at uv's pace and still recommends uv as the default [19].
Between 0.12.0 on July 28 and 0.12.15 on September 15 there are 49 days and fifteen patch releases, about one every 3.3 days if the numbering ran without gaps [20].
John, described in the post as its author's developer engagement manager, said uv replaced pip and venv for him, tooling he called fine, just slow and dated next to modern alternatives [12]. He tried Poetry, liked the idea, and never got it to stick; what he wanted was cargo for Python [13]. Speed closed the gap, per the post, and so did a way to run a tool you have not installed, which Node developers have had as npx for years and Python lacked until uvx [14]. His agents run `uv sync`, and Scrapy and Scrapy Cloud work as they did before [15]. His only complaint was deleting the `main.py` that `uv init` leaves behind [16], and on 0.12.15 that file is not generated at all [11].
The setting worth stealing is in Scrapy's continuous integration workflow, which pins `astral-sh/setup-uv`, drives the test matrix through `uvx --with tox-uv tox`, and sets `UV_PYTHON_PREFERENCE: only-system` with a comment saying it makes uv use the interpreter `actions/setup-python` already installed instead of downloading one of its own [17]. That transfers only if your job already installs the interpreter you intend to test against. Otherwise uv fetches its own, and the post's warning is that you get a build passing against a Python your users are not running [18].
What to watch
- Whether the <0.13.0 upper bound in the generated uv_build pin forces a pyproject edit in every project scaffolded during the 0.12 line.
- Whether the Zyte cheatsheet repository corrects the --package entry, and how long the old wording survives in other teams' shared references.
- Whether uv's release notes start separating default-behaviour changes from ordinary patch fixes at the current 0.12.x cadence.