Skip to content

fix(python-setup): show setup for a packaging-only pyproject.toml - #2086

Merged
rugpanov merged 3 commits into
mainfrom
rugpanov/python-setup-gate-bare-pyproject
Aug 6, 2026
Merged

fix(python-setup): show setup for a packaging-only pyproject.toml#2086
rugpanov merged 3 commits into
mainfrom
rugpanov/python-setup-gate-bare-pyproject

Conversation

@rugpanov

@rugpanov rugpanov commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

Why

A freshly-initialised bundle project never surfaced the "Set up Python environment" 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. That fires the pyproject.pipOnly signal, 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 run uv 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 init project, before and after:

managers: ["uv", "pip"]
signals:  ["uv.lock", "pyproject.pipOnly"]
gate: false   ← main
gate: true    ← this PR

Note the first line: adding a uv.lock is not a workaround. It correctly attributes uv, but pyproject.pipOnly still fires, so pip stays in managers and 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.pipOnly fired and no substantive pip signal did:

  • requirements.txt
  • constraints.txt
  • interpreter.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.pipOnly in packageManagerDetection.ts would 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 PythonSetupManagerDetector now forwards signals alongside primary/managers for it to read. The manager list alone cannot express the distinction: a real pip workflow and a merely packaging-shaped pyproject.toml both read as "pip".

Tests

  • yarn tsc --noEmit clean; yarn test:lint clean (eslint + prettier).
  • yarn test:unit: 481 passing, 6 failing. The 6 are the pre-existing CliWrapper listProfiles failures, which also fail on unmodified main (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.
  • New coverage: shows for a shape-only pip attribution; still hides for each of the three substantive pip signals individually; shows for uv + shape-only pip; and poetry/conda alongside a shape-only pip still hide.
  • Verified end to end against a real bundle-init project by running the actual signal collector and gate over it — not just unit fixtures — and separately in a dogfood VSIX build.

This pull request and its description were written by Isaac.

*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
@rugpanov

rugpanov commented Aug 6, 2026

Copy link
Copy Markdown
Contributor Author

🤖 Integration tests ❌ 9 of 35 test jobs failed for c6f690b4 (26 passed).
View run

Comment on lines -37 to -39
if (primary !== "uv" && primary !== "unknown") {
return false;
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this change be applied to greenfieldSignal() as well?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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
@rugpanov

rugpanov commented Aug 6, 2026

Copy link
Copy Markdown
Contributor Author

🤖 Integration tests triggered for f6b3cf9e — ⏳ running.
View run

@rugpanov
rugpanov enabled auto-merge (squash) August 6, 2026 12:37
@rugpanov
rugpanov temporarily deployed to test-trigger-is August 6, 2026 12:37 — with GitHub Actions Inactive
@rugpanov
rugpanov merged commit 168aa4d into main Aug 6, 2026
6 checks passed
@github-actions

github-actions Bot commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

If integration tests don't run automatically, an authorized user can run them manually by following the instructions below:

Trigger:
go/deco-tests-run/vscode

Inputs:

  • PR number: 2086
  • Commit SHA: da80a231be07b05b99a421e648a345f1032f0ed3

Checks will be approved automatically on success.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants