Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions packages/databricks-vscode/src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1559,7 +1559,7 @@ export async function activate(
context.subscriptions.push(pythonSetupEntry);

// Once-per-session adoption gauge: for a project with a uv-native setup on
// record, whether its managed .venv still exists. Distinct from drift above
// record, whether its managed venv still exists. Distinct from drift above
// (which compares compute env keys) — drift never checks that the venv is
// actually present. Measurement only, best-effort, and it derives no env key
// of its own: drift already reports env-key mismatch via python_env.drift.
Expand All @@ -1585,8 +1585,8 @@ export async function activate(
: connectionManager.cluster
? "cluster"
: "none",
venvExists: (root) =>
existsSync(venvInterpreterPath(path.join(root, ".venv"))),
savedSetup: () => stateStorage.get("databricks.pythonSetup.setupState"),
venvExists: (venvDir) => existsSync(venvInterpreterPath(venvDir)),
record: (report) => telemetry.recordPythonSetupAdoption(report),
});
// A connect-time reading: report once the connection is CONNECTED (so the
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import {expect} from "chai";
import path from "node:path";
import {PythonSetupAdoption} from "../../telemetry/pythonSetupExtensions";
import {
PythonSetupAdoptionDeps,
Expand All @@ -15,6 +16,7 @@ function makeDeps(over: Partial<PythonSetupAdoptionDeps> = {}): {
isAttributable: () => true,
isVpexActive: () => true,
getTargetType: () => "serverless",
savedSetup: () => undefined,
venvExists: () => true,
record: (r) => recorded.push(r),
...over,
Expand All @@ -39,6 +41,52 @@ describe("PythonSetupAdoptionManager", () => {
]);
});

it("checks the venv folder that the last setup saved", () => {
// A uv workspace member shares the venv at the workspace root.
const checked: string[] = [];
const {deps} = makeDeps({
projectRoot: () => "/mono/bundles/sales",
savedSetup: () => ({
projectRoot: "/mono/bundles/sales",
venvPath: "/mono/.venv",
}),
venvExists: (venvDir) => {
checked.push(venvDir);
return true;
},
});
new PythonSetupAdoptionManager(deps).report();
expect(checked).to.deep.equal(["/mono/.venv"]);
});

it("does not emit while the saved setup belongs to another project", () => {
// Two bundles in one workspace folder: the state is from bundle A.
let root = "/ws/b";
const {deps, recorded} = makeDeps({
projectRoot: () => root,
savedSetup: () => ({projectRoot: "/ws/a", venvPath: "/ws/a/.venv"}),
});
const manager = new PythonSetupAdoptionManager(deps);
manager.report();
expect(recorded).to.deep.equal([]);
// Not latched: switching to A still emits.
root = "/ws/a";
manager.report();
expect(recorded).to.have.length(1);
});

it("checks <project>/.venv when the saved state has no venv folder", () => {
const checked: string[] = [];
const {deps} = makeDeps({
venvExists: (venvDir) => {
checked.push(venvDir);
return true;
},
});
new PythonSetupAdoptionManager(deps).report();
expect(checked).to.deep.equal([path.join("/ws/project", ".venv")]);
});

it("passes through the attached compute kind, including none", () => {
const {deps, recorded} = makeDeps({getTargetType: () => "none"});
new PythonSetupAdoptionManager(deps).report();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import path from "node:path";
import {PythonSetupState} from "../../vscode-objs/StateStorage";
import {TargetCompute} from "../../telemetry/constants";
import {PythonSetupAdoption} from "../../telemetry/pythonSetupExtensions";

Expand All @@ -8,7 +10,7 @@ export interface PythonSetupAdoptionDeps {
* Whether the persisted setup state can be attributed to the active project.
* `setupState` is a single workspace-scoped key with no per-project
* namespacing, so in a multi-root workspace it may belong to a sibling root:
* checking a different root's `.venv` against it would emit a spurious
* checking a different root's venv against it would emit a spurious
* `venvPresent: false` and inflate the denominator. False there suppresses the
* reading rather than emit an untrustworthy one.
*/
Expand All @@ -21,15 +23,22 @@ export interface PythonSetupAdoptionDeps {
isVpexActive: () => boolean;
/** The compute kind attached right now (cluster / serverless / none). */
getTargetType: () => TargetCompute;
/** Whether the project's managed `.venv` interpreter exists on disk. */
venvExists: (root: string) => boolean;
/**
* The project and venv folders in the persisted setup state; each is
* undefined in state saved by older versions.
*/
savedSetup: () =>
| Pick<PythonSetupState, "projectRoot" | "venvPath">
| undefined;
/** Whether the managed venv interpreter in `venvDir` exists on disk. */
venvExists: (venvDir: string) => boolean;
/** Emit the gauge. Wraps telemetry; must be best-effort (never throw). */
record: (report: PythonSetupAdoption) => void;
}

/**
* Emits the once-per-session {@link PythonSetupAdoption} gauge: for a project
* that has a uv-native Python setup on record, whether its managed `.venv` is
* that has a uv-native Python setup on record, whether its managed venv is
* still present and what compute is attached. Purely a measurement — it reads
* state and records, never changing the flow it observes.
*
Expand Down Expand Up @@ -69,8 +78,19 @@ export class PythonSetupAdoptionManager {
if (!this.deps.isVpexActive()) {
return;
}
// The single setupState key can hold the setup of another bundle in
// this workspace folder. Do NOT latch: switching to it still reports.
const saved = this.deps.savedSetup();
if (
saved?.projectRoot !== undefined &&
saved.projectRoot !== root
) {
return;
}
this.deps.record({
venvPresent: this.deps.venvExists(root),
venvPresent: this.deps.venvExists(
saved?.venvPath ?? path.join(root, ".venv")
),
currentTargetType: this.deps.getTargetType(),
});
this.reported.add(root);
Expand Down
Loading
Loading