fix(python-setup): show setup for a packaging-only pyproject.toml - #2086
Conversation
*Why* A freshly-initialised bundle project never surfaced the setup entry. The `pyproject.toml` that `databricks bundle init default-python` generates declares `[project]` and `[build-system]` but no `[tool.uv]`, and ships no `uv.lock` — so the classifier fires `pyproject.pipOnly`, attributes the project to pip, and the gate hides the entry as a competing manager. The legacy pip checklist showed instead. That inverts the intent. uv manages such a project natively (it needs neither marker), and these are exactly the projects the feature exists to set up — the ERD's argument that "our own bundle-init project already ships with uv, so the automated path matches what new users land on" only holds once someone has run `uv lock`. The skew was already documented as a measurement caveat; this is it surfacing as a product defect. *What* Discount a pip attribution in the gate when it rests solely on the pyproject's shape: pip is ignored if `pyproject.pipOnly` fired and no substantive pip signal did (`requirements.txt`, `constraints.txt`, or an existing non-uv virtualenv). Those three stay disqualifying — they are positive evidence someone drives pip directly. Poetry and conda are never discounted. Fixed in the gate rather than in the classifier on purpose: the classifier feeds the package-manager telemetry dataset, and redefining `pyproject.pipOnly` there would silently change what the metric counts and break comparability with data already collected. The gate is the only consumer whose behaviour should change, so the detector now forwards `signals` for it to read. *Verification* - `yarn tsc --noEmit` clean; `yarn test:lint` clean (eslint + prettier). - `yarn test:unit`: 481 passing, 6 failing — the same pre-existing `CliWrapper` `listProfiles` failures as on `origin/main` (they shell out to a real CLI binary absent from the worktree). 474 passing before, so all 7 new cases pass and nothing regressed. - New coverage: shows for a shape-only pip attribution; still hides for each of the three substantive pip signals; shows for uv+shape-only pip; and poetry/conda alongside a shape-only pip still hide. - Reproduced against the real template first: a `bundle init` project with only `[project]`/`[build-system]` was hidden, and is shown after this change. Co-authored-by: Isaac
|
🤖 Integration tests ❌ 9 of 35 test jobs failed for |
| if (primary !== "uv" && primary !== "unknown") { | ||
| return false; | ||
| } |
There was a problem hiding this comment.
Should this change be applied to greenfieldSignal() as well?
There was a problem hiding this comment.
Good catch — done in f6b3cf9.
Extracted isUvSetupSuitable(detection) as the single source of truth and pointed both the gate and greenfieldSignal at it, so they can't drift. Knock-on: the deps seam became getDetection(), since the predicate needs managers + signals, not just primary (still one detection per click).
Note this flips isGreenfield from omitted to false for bundle-init projects, not to true — they do have a pyproject.toml. The win is that packageManager: "pip" + isGreenfield: undefined currently makes uv-suitable projects indistinguishable from real pip ones in the dataset.
494 tests passing (+7); exhaustive pass over all 3072 signal combinations confirms the gate and predicate agree everywhere.
…field telemetry *Why* Review feedback on #2086: the shape-only pip discount was applied in the visibility gate but not in `greenfieldSignal()`, which still keyed off `primary !== "uv" && primary !== "unknown"`. That left the two sides disagreeing about the same project. A freshly initialised bundle project reports `primary: "pip"`, so the gate showed the entry while the attempt event omitted `isGreenfield` entirely -- blanking the field for exactly the cohort the feature targets. The telemetry comment justified the omission on the grounds that these are pip projects whose missing pyproject.toml "says nothing", which is the reasoning this PR already rejected for the gate. *What* Extract `isUvSetupSuitable(detection)` as the single source of truth for "is this one of our projects", and have both consumers ask it: - `shouldShowPythonSetup` is now the flag check plus that predicate. - `greenfieldSignal` takes the detection instead of just `primary`, so the population it reports on is the gate's admitted population by construction rather than by comment. - The deps seam becomes `getDetection()` (was `getPackageManager()`), since the manager list and fired signals are both needed. Detection is still run once per click, not twice. - An unavailable/failed detection degrades to `{managers: [], signals: []}`, which reads as suitable -- preserving the previous `unknown` behaviour on the failure path instead of silently dropping the signal. - Correct the `isGreenfield` comment in telemetry/constants.ts, which documented the old `primary`-based rule. Dropping `"primary"` from the gate's parameter type also removes surface it no longer reads. *Verification* - `yarn test:unit`: 494 passing, 0 failing (487 before; +7 new cases). - `yarn test:lint` clean (eslint + prettier); `tsc --noEmit` clean. - Exhaustive check over all 3072 signal combinations: `shouldShowPythonSetup` and `isUvSetupSuitable` agree in every case (0 disagreements), and the only combinations where `isGreenfield` is now reported rather than omitted are the intended shape-only pip ones. - Verified against a real `bundle init default-python` project by running the actual collector: signals `["pyproject.pipOnly"]`, gate shows the entry, and the greenfield flag is now reported instead of omitted. Co-authored-by: Isaac
|
🤖 Integration tests triggered for |
|
If integration tests don't run automatically, an authorized user can run them manually by following the instructions below: Trigger: Inputs:
Checks will be approved automatically on success. |
Why
A freshly-initialised bundle project never surfaced the "Set up Python environment" entry.
The
pyproject.tomlthatdatabricks bundle init default-pythongenerates declares[project]and[build-system]but no[tool.uv], and ships nouv.lock. That fires thepyproject.pipOnlysignal, attributes the project to pip, and the gate hides the entry because pip counts as a competing manager. The legacy pip checklist showed instead.This inverts the intent. uv manages such a project natively — it needs neither a
[tool.uv]section nor a lockfile — and these are precisely the projects the feature exists to set up. The design doc's argument that "our own example/bundle-init project already ships with uv, so the fully automated path matches what new users land on" only holds once someone has runuv lock. The skew was already documented as a telemetry measurement caveat; this is the same root cause surfacing as a product defect.Measured on a real
bundle initproject, before and after:Note the first line: adding a
uv.lockis not a workaround. It correctly attributes uv, butpyproject.pipOnlystill fires, so pip stays inmanagersand the gate still rejects the project even with uv as primary. Before this change the only way to surface the entry was to hand-add a literal[tool.uv]section.What
Discount a pip attribution in the gate when it rests solely on the pyproject's shape. Pip is ignored when
pyproject.pipOnlyfired and no substantive pip signal did:requirements.txtconstraints.txtinterpreter.venv(an existing non-uv virtualenv)Those three remain disqualifying — they are positive evidence that someone drives pip directly, and offering uv setup there would fight their tooling. Poetry and conda are never discounted.
Why in the gate, not the classifier
Loosening
pyproject.pipOnlyinpackageManagerDetection.tswould have been the smaller diff, but that signal also feeds the package-manager telemetry dataset. Redefining it there would silently change what the metric counts and break comparability with data already collected — the classifier's job is to describe the project, and it is describing it accurately.The gate is the only consumer whose behaviour should change, so
PythonSetupManagerDetectornow forwardssignalsalongsideprimary/managersfor it to read. The manager list alone cannot express the distinction: a real pip workflow and a merely packaging-shapedpyproject.tomlboth read as"pip".Tests
yarn tsc --noEmitclean;yarn test:lintclean (eslint + prettier).yarn test:unit: 481 passing, 6 failing. The 6 are the pre-existingCliWrapperlistProfilesfailures, which also fail on unmodifiedmain(they shell out to a real CLI binary absent from the worktree). 474 passed before this change, so all 7 new cases pass and nothing regressed.This pull request and its description were written by Isaac.