Skip to content

[6/?] feat(python-setup): setup orchestrator (first CLI-client caller) - #2046

Open
rugpanov wants to merge 1 commit into
rugpanov/python-setup-cli-clientfrom
rugpanov/python-setup-orchestrator
Open

[6/?] feat(python-setup): setup orchestrator (first CLI-client caller)#2046
rugpanov wants to merge 1 commit into
rugpanov/python-setup-cli-clientfrom
rugpanov/python-setup-orchestrator

Conversation

@rugpanov

Copy link
Copy Markdown
Contributor

Stacked on #2039 (base = rugpanov/python-setup-cli-client). Review after #2039; the diff of interest is the single controllers/ commit on top. Once #2039 merges, GitHub retargets this to main automatically.

Why

#2039 adds PythonSetupCliClient with no call site — as raised in review, the API is hard to judge without seeing it used. This PR adds the orchestrator that actually drives the client end-to-end, so the client's usage ships alongside it.

What

PythonSetupEnvironmentSetup (python-setup/controllers/) — the first real caller of PythonSetupCliClient:

  1. gate → resolve compute → cli.run(invocation, {cwd, onLog}) under a progress indicator,
  2. on success: adopt the provisioned venv interpreter + persist {envKey, pythonVersion} for drift detection,
  3. on failure: surface the mapped getPythonSetupErrorMessage copy,
  4. PythonSetupCancelledError → treated as a silent user action (no error toast).

It consumes the real client surface (run(invocation{compute}, {cwd, onLog}), isPythonSetupSuccess, getPythonSetupErrorMessage), and depends on the client structurally (CliRunner) so the flow is unit-testable without spawn/vscode.

The two not-yet-built pieces are injected seams, not hard deps

Per the agreed approach, the gate and serverless-version selection are stubbed behind injected functions with explicit TODOs, so this PR stacks only on the CLI-client change and doesn't pull in unbuilt tickets:

  • isVisible()TODO(DECO-27781 / [4/?] feat(python-setup): package-manager gating & live detection #2044): shouldShowPythonSetup(...). The wiring stub returns false, so the feature is inert end-to-end for now.
  • resolveCompute()TODO(DECO-27782): serverless-version picker (scoreServerlessVersions + pickServerlessVersion). The wiring stub returns undefined, so a serverless setup is a no-op until that ticket; the cluster case can already resolve.

The real gate and picker drop in through these seams in their own tickets (and the extension.ts wiring lands in DECO-27786). Nothing is wired into the extension in this PR.

Verification

  • yarn --cwd packages/databricks-vscode test:unit — 344 passing (6 new orchestrator cases: success→ready+adopt+saveState, gate-closed→no run, no-compute→no run, CLI failure→mapped error, cancel→silent, onDidChangeState fires).
  • test:lint — clean · build — clean.

This pull request and its description were written by Isaac.

@rugpanov

Copy link
Copy Markdown
Contributor Author

🤖 Integration tests triggered for 3508259b — ⏳ running.
View run

@rugpanov
rugpanov force-pushed the rugpanov/python-setup-orchestrator branch from 3508259 to b48d2ae Compare July 24, 2026 13:01
@rugpanov
rugpanov temporarily deployed to test-trigger-is July 24, 2026 13:01 — with GitHub Actions Inactive
@rugpanov

Copy link
Copy Markdown
Contributor Author

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

@rugpanov
rugpanov temporarily deployed to test-trigger-is July 24, 2026 13:02 — with GitHub Actions Inactive
@rugpanov
rugpanov force-pushed the rugpanov/python-setup-orchestrator branch from b48d2ae to fa992f3 Compare July 24, 2026 14:23
@rugpanov
rugpanov temporarily deployed to test-trigger-is July 24, 2026 14:23 — with GitHub Actions Inactive
@rugpanov
rugpanov temporarily deployed to test-trigger-is July 24, 2026 14:23 — with GitHub Actions Inactive
@rugpanov

rugpanov commented Jul 24, 2026

Copy link
Copy Markdown
Contributor Author

🤖 Integration tests ❌ 1 of 35 test jobs failed for fa992f32 (34 passed).
View run

Comment on lines +106 to +110
* This is the first real caller of {@link PythonSetupCliClient}. The visibility
* gate and serverless-version selection are injected seams (see
* {@link PythonSetupSetupDeps}) that the extension currently wires to stubs, so
* shipping this alongside the client shows the client's usage without depending
* on tickets that are not built yet.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

nit: I don't think this is useful as code comments

Suggested change
* This is the first real caller of {@link PythonSetupCliClient}. The visibility
* gate and serverless-version selection are injected seams (see
* {@link PythonSetupSetupDeps}) that the extension currently wires to stubs, so
* shipping this alongside the client shows the client's usage without depending
* on tickets that are not built yet.

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.

Agreed — trimmed the class doc down to just the flow summary and dropped the PR-narrative prose. Also did a pass over the rest of the file removing comments that only restated self-explanatory names. Done in f84e6ff.

Comment on lines +192 to +199
// A real-run success must carry everything the success path consumes:
// the provisioned venv to adopt, and the target/resolved fields we
// persist as the drift-detection baseline. If any is missing (a dry-run
// shape, or CLI/schema drift), we can't deliver a usable, tracked
// environment — treat it as a failure rather than reporting a hollow
// "ready" that either skips interpreter adoption or leaves drift
// detection with no baseline.
if (!result.venvPath || !result.target || !result.resolved) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Can we merge this into isPythonSetupSuccess since it's an error case?

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.

I kept isPythonSetupSuccess as the CLI-contract predicate (ok === true) because it is legitimately true for a successful --dry-run (which has no venvPath), and folding the stricter check in would break that contract and its existing consumers/test. Instead I extracted the check into a named orchestrator-level type guard, isLocalEnvironmentReady(), so the "this is an error case" intent is explicit without changing the shared predicate. f84e6ff.

return;
}

await this.deps.adoptInterpreter(result.venvPath);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Do we intentionally not show an error message when this fails? What is the consequence of the Python extension not being pointed at the provisioned venv interpreter?

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 — that was an oversight, not intentional. Consequence: the venv is provisioned on disk but the Python extension is not pointed at it, so imports/dbconnect will not resolve — and previously it failed silently. Fixed by wrapping adoptInterpreter(): a failure now surfaces the error and keeps the flow not-ready. Added a test (surfaces an error and stays not-ready when interpreter adoption fails). f84e6ff.

@rugpanov
rugpanov force-pushed the rugpanov/python-setup-orchestrator branch from fa992f3 to 1c238f1 Compare July 27, 2026 19:03
@rugpanov
rugpanov temporarily deployed to test-trigger-is July 27, 2026 19:03 — with GitHub Actions Inactive
@rugpanov
rugpanov temporarily deployed to test-trigger-is July 27, 2026 19:04 — with GitHub Actions Inactive
@rugpanov

Copy link
Copy Markdown
Contributor Author

🤖 Integration tests triggered for 1c238f1b — ⏳ running.
View run

@rugpanov
rugpanov force-pushed the rugpanov/python-setup-orchestrator branch from 1c238f1 to 5f6604a Compare July 27, 2026 19:09
@rugpanov
rugpanov temporarily deployed to test-trigger-is July 27, 2026 19:09 — with GitHub Actions Inactive
@rugpanov
rugpanov temporarily deployed to test-trigger-is July 27, 2026 19:10 — with GitHub Actions Inactive
@rugpanov
rugpanov force-pushed the rugpanov/python-setup-orchestrator branch from 5f6604a to 8353bcb Compare July 27, 2026 19:10
@rugpanov
rugpanov temporarily deployed to test-trigger-is July 27, 2026 19:10 — with GitHub Actions Inactive
@rugpanov

Copy link
Copy Markdown
Contributor Author

🤖 Integration tests triggered for 5f6604a9 — ⏳ running.
View run

@rugpanov
rugpanov temporarily deployed to test-trigger-is July 27, 2026 19:10 — with GitHub Actions Inactive
@rugpanov
rugpanov force-pushed the rugpanov/python-setup-orchestrator branch from 8353bcb to cade982 Compare July 27, 2026 19:11
@rugpanov
rugpanov temporarily deployed to test-trigger-is July 27, 2026 19:11 — with GitHub Actions Inactive
@rugpanov
rugpanov temporarily deployed to test-trigger-is July 27, 2026 19:11 — with GitHub Actions Inactive
*Why*
The CLI client (PythonSetupCliClient) shipped with no call site, which makes its
API hard to review in isolation. This adds the orchestrator that actually
drives it end-to-end, so the client's usage lands alongside the client.

*What*
- Add PythonSetupEnvironmentSetup (python-setup/controllers): decide whether to
  run, resolve the compute target, invoke cli.run() under a progress indicator,
  then on success adopt the provisioned venv interpreter and persist
  {envKey, pythonVersion} for drift detection; on failure surface the mapped
  getPythonSetupErrorMessage copy; treat PythonSetupCancelledError as a silent
  user action.
- Consumes the real client surface: run(invocation{compute}, {cwd, onLog, token}),
  isPythonSetupSuccess, getPythonSetupErrorMessage. The client is depended on
  structurally (CliRunner) so the flow is unit-testable without spawn/vscode.
- The two not-yet-built pieces are injected seams with explicit TODOs, not hard
  dependencies:
    * isVisible()  -> TODO(#2044): shouldShowPythonSetup(...). Wiring stub
      returns false, so the feature is inert end-to-end for now.
    * resolveCompute() -> TODO(#2052 / #2053): serverless-version picker. Wiring
      stub returns undefined, so a serverless setup is a no-op until then.

Review fixes (Claude + Codex + @rclarey):
- Cancellation is no longer dead code: ProgressTask now exposes a
  CancellationLike token alongside the log sink, and setup() threads it into
  cli.run({cwd, onLog, token}). The production withProgress passes through the
  progress notification's own token, so a user "Cancel" tears down the
  project-mutating CLI instead of being a silent no-op.
- Re-entrancy guard: overlapping setup() calls coalesce onto a single in-flight
  run (cleared in finally, incl. on rejection) so two setup-local processes
  can't race the same cwd's writes/backup.
- Strict real-run success via an isLocalEnvironmentReady() type guard: an ok:true
  result must carry venvPath AND target/resolved. Missing any (dry-run shape /
  CLI-schema drift) is treated as a failure. Kept separate from
  isPythonSetupSuccess (ok===true), which stays true for a --dry-run.
- Interpreter adoption is no longer a silent failure: adoptInterpreter() is now
  wrapped so a failure surfaces the error and stays not-ready, instead of
  rejecting with no user-facing message and a provisioned-but-unadopted venv.
- Trimmed doc/inline comments to the non-obvious "why"; dropped prose that only
  restated self-explanatory names.

*Verification*
yarn --cwd packages/databricks-vscode test:unit (352 passing; 14 orchestrator
cases, incl. success-without-venvPath, success-missing-target/resolved,
token threaded to cli.run, re-entrancy coalescing, in-flight guard cleared on
rejection, and adoption-failure surfaces error + stays not-ready). test:lint and
build (tsc --build) clean.

Co-authored-by: Isaac
@rugpanov
rugpanov force-pushed the rugpanov/python-setup-orchestrator branch from cade982 to f84e6ff Compare July 27, 2026 19:14
@rugpanov
rugpanov temporarily deployed to test-trigger-is July 27, 2026 19:14 — with GitHub Actions Inactive
@rugpanov
rugpanov temporarily deployed to test-trigger-is July 27, 2026 19:14 — with GitHub Actions Inactive
@rugpanov

rugpanov commented Jul 27, 2026

Copy link
Copy Markdown
Contributor Author

🤖 Integration tests ✅ all 35 test jobs passed for f84e6ff3.
View run

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