Skip to content

feat(python-setup): add the Python-setup preset picker (Full / DB Connect / Python) - #2179

Merged
rugpanov merged 3 commits into
mainfrom
rugpanov/python-setup-preset-picker
Sep 9, 2026
Merged

feat(python-setup): add the Python-setup preset picker (Full / DB Connect / Python)#2179
rugpanov merged 3 commits into
mainfrom
rugpanov/python-setup-preset-picker

Conversation

@rugpanov

@rugpanov rugpanov commented Sep 7, 2026

Copy link
Copy Markdown
Contributor

Why

The uv-native "set up Python environment" flow always ran the Full preset — it hardcoded a flag-free setup-local invocation. Users had no way to pick a lighter tier, even though the CLI already exposes the orthogonal --no-constraints / --no-dbconnect flags.

Separately, the two-value telemetry mode dimension (default | constraints-only, derived from skipDbconnect) cannot represent a --no-constraints-only run: a DB Connect run would report mode: default despite skipping the pins. A picker that sets the two axes orthogonally makes that gap actionable.

What

  • New pure util pythonSetupPresetPicker.ts:
    • SetupPreset = full | dbconnect | python and presetToFlags (full → no flags; dbconnect → --no-constraints; python → --no-constraints --no-dbconnect).
    • buildPresetPickItems — the three rows (Full starred and first), copy verbatim from the spec.
    • computeTargetLabelserverless v5 / Runtime 17.3 / a generic fallback when the runtime is unknown.
    • pickSetupPreset — single-select, explicit createQuickPick lifecycle (onDidAccept resolves the row, onDidHide = dismissed); createQuickPick injected as a seam for tests.
  • Controller: a pickSetupPreset dep is invoked after compute resolves (so the title names the target). Dismissing it bails out silently with no attempt recorded, mirroring a dismissed serverless-version prompt. The invocation's skip flags come from the chosen preset.
  • Wiring: the dep composes computeTargetLabel + the injected createQuickPick; extension.ts resolves the cluster's DBR (reusing the attached cluster, else fetching by id) so the title always carries the runtime.
  • Telemetry: a new categorical setupPreset dimension on python_env.setup.attempt, disambiguating the two orthogonal skip axes. The legacy mode field is retained, derived as before, for dashboard continuity.

Testing

  • test:unit: 1098 passing. New coverage: preset→flags mapping, verbatim row copy, compute-label formatting, picker accept/dismiss, controller flag-threading + dismissal + setupPreset/mode telemetry, wiring composition (title from serverless/cluster DBR), and the telemetry emitter allowlist.
  • tsc + eslint clean on changed files.

This pull request and its description were written by Isaac.

@rugpanov

rugpanov commented Sep 7, 2026

Copy link
Copy Markdown
Contributor Author

🤖 Integration tests running for 2462d6e2 — ⏳.
View run

@rugpanov
rugpanov force-pushed the rugpanov/python-setup-preset-picker branch from 2462d6e to 04b00a5 Compare September 7, 2026 15:30
…nect / Python)

*Why*
- The uv-native setup flow always ran the "Full" preset; users had no way to
  choose a lighter tier (matching Python + Databricks Connect without pins, or
  matching Python only), even though the CLI already supports the orthogonal
  --no-constraints / --no-dbconnect flags.
- The two-value telemetry `mode` dimension cannot represent a --no-constraints
  run, so a DB Connect run would misreport as `mode: default` despite skipping
  the pins. A picker that sets flags orthogonally makes that gap actionable.

*What*
- New pure util `pythonSetupPresetPicker.ts`: `SetupPreset`, `presetToFlags`
  (full -> no flags; dbconnect -> --no-constraints; python -> both),
  `buildPresetPickItems` (the three verbatim rows, Full starred/first),
  `computeTargetLabel` (serverless vN / Runtime X.Y / generic fallback), and
  `pickSetupPreset` using the explicit `createQuickPick` lifecycle
  (onDidAccept resolves the row, onDidHide = dismissed), createQuickPick
  injected as a required seam (no global default) for testability.
- Controller: a `pickSetupPreset` dep is called after compute resolves (so the
  title names the target); dismissing it bails out silently with no attempt
  recorded, mirroring a dismissed serverless-version prompt. The invocation's
  skip flags are built from the chosen preset.
- Wiring: the dep is composed from `computeTargetLabel` + the injected
  `createQuickPick`; `extension.ts` resolves the cluster's DBR (from the
  attached cluster, else fetched by id) so the title always carries the runtime.
- Telemetry: a new categorical `setupPreset` dimension (full | dbconnect |
  python) on `python_env.setup.attempt`, disambiguating the two orthogonal skip
  axes. The legacy `mode` field is kept, derived as before, for dashboard
  continuity.

*Verification*
- Unit suite (test:unit): preset/wiring/telemetry tests green. The only failures
  are the pre-existing packageJsonUtils stale-bundled-CLI worktree artifact
  (`1.14.1` vs pinned `1.15.0`), which CI passes by fetching the pinned binary.
- tsc + eslint clean on all changed files (the one remaining tsc error is the
  known cross-worktree `Cluster` self-symlink artifact at extension.ts).

Co-authored-by: Isaac <no-reply@databricks.com>
@rugpanov
rugpanov force-pushed the rugpanov/python-setup-preset-picker branch from 04b00a5 to 83549f8 Compare September 7, 2026 15:31
@rugpanov

rugpanov commented Sep 7, 2026

Copy link
Copy Markdown
Contributor Author

🤖 Integration tests ❌ failed for 83549f8f.
View run

*Why*
- The picker added in this branch shows a mandatory single-select QuickPick
  after compute resolves. The uv setup-local e2e specs invoked setup and then
  waited for the .venv / ready toast without answering that new prompt, so the
  run would stall (the interpreter never appears) once it reaches the picker.
- The re-run spec awaited `rerunPythonEnv` *inside* `executeWorkbench`, which
  would deadlock: the command can't resolve until the picker is answered, but
  the test can't answer it while blocked inside the callback.

*What*
- After each `Databricks: Setup python environment` / `rerunPythonEnv` trigger,
  wait for the preset QuickPick and select the recommended Full tier
  (index 0 — starred and first), so the full environment is provisioned.
- Fire `rerunPythonEnv` fire-and-forget (`void executeCommand`) instead of
  awaiting it inside `executeWorkbench`, then drive the picker from the test.
- Full maps to no skip flags, so the spec keeps working against the pinned CLI
  even before the orthogonal-flag CLI ships.

*Verification*
- eslint + prettier clean on the spec; e2e tsconfig typecheck clean for this
  file (the only tsc error is the pre-existing wdio.conf import-assertion quirk).
- The current Integration Tests failure was an unrelated shared cluster-start
  infra flake ("Timeout while placing nodes") that aborted warm-up before these
  specs ran; this change makes them pass once the picker is actually reached.

Co-authored-by: Isaac <no-reply@databricks.com>
@rugpanov
rugpanov marked this pull request as ready for review September 8, 2026 10:11
@rugpanov

rugpanov commented Sep 8, 2026

Copy link
Copy Markdown
Contributor Author

🤖 Integration tests ✅ passed for 13007149.
View run

@rugpanov
rugpanov requested a review from misha-db September 8, 2026 10:46
@github-actions

github-actions Bot commented Sep 9, 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: 2179
  • Commit SHA: a09af409eef45cc63b760139be0b6d7e14486f8e

Checks will be approved automatically on success.

@rugpanov
rugpanov merged commit f7e0004 into main Sep 9, 2026
8 of 9 checks passed
rugpanov added a commit that referenced this pull request Sep 9, 2026
…s-DB-Connect fallback (#2181)

## Why

The uv-native "set up Python environment" **Full** preset pins the
cluster's
dependencies, so `uv sync` can fail when those pins conflict with the
user's own
dependencies. Today that surfaces as the generic `E_PROVISION` toast
("uv could
not resolve the project's dependencies… adjust your dependencies"),
which leaves
the user to work out on their own that dropping the pins — the **DB
Connect**
preset (`--no-constraints`) — would resolve it while keeping matched
Python +
databricks-connect.

## What

- **`E_PROVISION_CONFLICT`** — a distinct result-model error code for a
genuine
pin-vs-local conflict (a generic sync failure stays `E_PROVISION`), with
its
own actionable copy: *"The cluster dependencies conflict with your local
  dependencies, so uv sync couldn't resolve the environment."*
- **Recovery UX** — on a Full-preset conflict the generic toast is
replaced with
two buttons: **Retry DB Connect setup** and **Open pyproject.toml**.
"Show
Logs" is dropped from this toast (it's self-service — the channel is
still
  revealed), and every other provision failure is unchanged.
- **Restore-then-retry** — the conflict fails *after* the runtime pins
were
merged into `pyproject.toml` (`diskMutated: true`, pre-merge file saved
to the
CLI's `backupPath`). So **Retry** first restores that backup, then
re-runs with
the `dbconnect` preset (`--no-constraints`) — `--no-constraints` alone
only
stops the retry from re-adding pins, it wouldn't remove the ones already
on
disk. The restore is atomic (temp sibling + rename) and refuses a
`backupPath`
outside the project. With no `backupPath`, Retry isn't offered and the
run
  falls back to the ordinary doc-link handling.
- **`run` action arm** — `PythonSetupErrorAction` gains an
in-process-callback
arm alongside `url`/`command`, so the two runtime-stateful buttons reuse
`showError`'s existing plumbing (label de-dup, log reveal); `showError`
gains an
  `includeShowLogs` option to drop the trailing button for this toast.
- **`runGuarded` / `runResolved`** extracted from `runSetup`, so the
retry
re-enters the flow with the `dbconnect` preset directly — no re-prompt
of the
compute or the preset picker, and guarded so a click can't race an
in-flight
  run (and the restore only fires when the retry actually runs). Because
`dbconnect` skips constraints, the retry can't itself surface a
recoverable
conflict (no loop), and a stale toast's Retry no-ops once the project is
ready.
- **Telemetry** — a `conflict_retry` value on the existing
`python_env.setup.attempt` `trigger` dimension marks recovery runs,
paired with
the `python_env.setup.result` outcome for the recovery success rate. No
new
event. A conflict is never report-worthy (it's the user's own
dependencies).

## Depends on the CLI conflict code (dormant until it ships)

`E_PROVISION_CONFLICT` is a CLI-emitted code that is **not yet in a
released
CLI** — the extension is coded against the contract, so the new recovery
path is
fully unit-tested but stays inert (a real conflict still arrives as
`E_PROVISION`
today) until the CLI emits the distinct code **and populates
`backupPath` on that
failure** (the restore-then-retry relies on it). This mirrors the
stacked-on
picker PR's own posture. No end-to-end test is added for that reason.

## Testing

- `test:unit`: 1126 passing. New coverage: the conflict copy + fallback
doc link,
the `run`-action dispatch, `openProjectFile`, the controller
conflict→retry
flow (restore-before-retry ordering, `--no-constraints` + adopt on
success,
`conflict_retry` telemetry paired with its outcome, open pyproject at
the run's
cwd, `reportOffered: false`, missing-`backupPath` and non-Full
fallbacks,
restore-failure aborts without a retry attempt), and the
atomic/in-project
  restore wiring.
- `tsc` + `eslint` + `prettier` clean on changed files.

Stacked on #2179 — review/merge that first.

This pull request and its description were written by Isaac.

---------

Co-authored-by: Isaac <no-reply@databricks.com>
@github-actions github-actions Bot mentioned this pull request Sep 10, 2026
rugpanov added a commit that referenced this pull request Sep 10, 2026
Release **v2.17.0** of the Databricks VS Code extension.

## What's included

- Add a Python environment setup preset picker to choose a Full, DB
Connect, or Python-only environment (#2179)
- Recover from a dependency constraint conflict during Python setup by
offering to retry as a DB Connect environment (#2186)
-   Add recovery UX for Python download failures (#2164)
- Show an actionable error when Python setup hits a merge conflict on a
`pyproject.toml` without a `[project]` table (#2180)
-   Preserve script stdout when an Upload-and-Run file fails (#2171)
- Update Databricks CLI to v1.16.0 (#2175, #2183) — see the [CLI release
notes](https://github.com/databricks/cli/releases) for changes

Changelog curated from the generated output (stripped auto-close refs,
dropped internal telemetry/CI-only items). Tracking: DECO-28498.

---------

Co-authored-by: releasebot <noreply@github.com>
Co-authored-by: @rugpanov <gripanov@gmail.com>
Co-authored-by: Isaac <no-reply@databricks.com>
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