From bdf5af774f92bcbe4cc30ce21483c2c06688c7aa Mon Sep 17 00:00:00 2001 From: eleanorjboyd <26030610+eleanorjboyd@users.noreply.github.com> Date: Tue, 6 Oct 2026 10:56:57 -0700 Subject: [PATCH 1/3] fix(api): expose last-known environment synchronously Allow compatibility consumers with synchronous getters to read the environment selection cache without triggering discovery or refresh.\n\nCo-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/extensionApi.ts | 4 +++ src/test/extensionApi.unit.test.ts | 40 ++++++++++++++++++++++++++++++ src/types.ts | 8 ++++++ 3 files changed, 52 insertions(+) diff --git a/src/extensionApi.ts b/src/extensionApi.ts index 337322ed1..61f0b0751 100644 --- a/src/extensionApi.ts +++ b/src/extensionApi.ts @@ -280,6 +280,10 @@ export class PythonEnvironmentApiImpl implements PythonEnvironmentApi { } return this.envManagers.getLastKnownEnvironment(currentScope); } + getEnvironmentSync(scope: GetEnvironmentScope): PythonEnvironment | undefined { + const currentScope = checkUri(scope) as GetEnvironmentScope; + return this.envManagers.getLastKnownEnvironment(currentScope); + } onDidChangeEnvironment: Event = this._onDidChangeEnvironment.event; async resolveEnvironment(context: ResolveEnvironmentContext): Promise { await waitForAllEnvManagers(); diff --git a/src/test/extensionApi.unit.test.ts b/src/test/extensionApi.unit.test.ts index 62459fbc3..e8a540338 100644 --- a/src/test/extensionApi.unit.test.ts +++ b/src/test/extensionApi.unit.test.ts @@ -241,3 +241,43 @@ suite('PythonEnvironmentApiImpl - package resolution', () => { assert.ok(manage.calledOnceWithExactly(environment, { install: ['example'] })); }); }); + +suite('PythonEnvironmentApiImpl - getEnvironmentSync', () => { + teardown(() => { + sinon.restore(); + }); + + test('returns the last-known environment without waiting for manager readiness', () => { + const workspaceUri = Uri.joinPath(Uri.file(process.cwd()), 'workspace'); + const environmentUri = Uri.joinPath(workspaceUri, '.venv'); + const scope = Uri.joinPath(workspaceUri, 'script.py'); + const lastKnown = { + envId: { id: 'selected', managerId: 'ms-python.python:venv' }, + name: 'selected', + displayName: 'selected', + displayPath: environmentUri.fsPath, + version: '3.12.0', + environmentPath: environmentUri, + execInfo: { run: { executable: Uri.joinPath(environmentUri, 'python').fsPath, args: [] } }, + sysPrefix: environmentUri.fsPath, + } as PythonEnvironment; + type ApiArgs = ConstructorParameters; + const getLastKnownEnvironment = sinon.stub().withArgs(scope).returns(lastKnown); + const api = new PythonEnvironmentApiImpl( + { + onDidChangeActiveEnvironment: new EventEmitter().event, + getLastKnownEnvironment, + } as unknown as ApiArgs[0], + { + getProjects: () => [], + onDidChangeProjects: new EventEmitter().event, + } as unknown as ApiArgs[1], + {} as unknown as ApiArgs[2], + {} as unknown as ApiArgs[3], + { onDidChangeEnvironmentVariables: new EventEmitter().event } as unknown as ApiArgs[4], + ); + + assert.strictEqual(api.getEnvironmentSync(scope), lastKnown); + sinon.assert.calledOnceWithExactly(getLastKnownEnvironment, scope); + }); +}); diff --git a/src/types.ts b/src/types.ts index 8699ce618..e5373b06d 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1127,6 +1127,14 @@ export interface PythonProjectEnvironmentApi { */ getEnvironment(scope: GetEnvironmentScope): Promise; + /** + * Synchronously retrieves the last-known Python environment within the specified scope. + * This does not trigger environment discovery or refresh. + * @param scope - The scope within which to retrieve the environment. + * @returns The last-known Python environment, or undefined if none has been resolved. + */ + getEnvironmentSync(scope: GetEnvironmentScope): PythonEnvironment | undefined; + /** * Event that is fired when the selected Python environment changes for Project, Folder or File. * @see {@link DidChangeEnvironmentEventArgs} From f386766cfa018d23272a55d5fa587c38a843df45 Mon Sep 17 00:00:00 2001 From: eleanorjboyd <26030610+eleanorjboyd@users.noreply.github.com> Date: Tue, 6 Oct 2026 11:01:22 -0700 Subject: [PATCH 2/3] test: update environment API mock Include the new upstream package-provider event in the synchronous environment API test fixture.\n\nCo-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/test/extensionApi.unit.test.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/test/extensionApi.unit.test.ts b/src/test/extensionApi.unit.test.ts index e8a540338..78b3dc769 100644 --- a/src/test/extensionApi.unit.test.ts +++ b/src/test/extensionApi.unit.test.ts @@ -266,6 +266,7 @@ suite('PythonEnvironmentApiImpl - getEnvironmentSync', () => { const api = new PythonEnvironmentApiImpl( { onDidChangeActiveEnvironment: new EventEmitter().event, + onDidChangePackageProviderPackages: new EventEmitter().event, getLastKnownEnvironment, } as unknown as ApiArgs[0], { From 580cd8a2828c961d5b1200143dd6a3de3081ee96 Mon Sep 17 00:00:00 2001 From: eleanorjboyd <26030610+eleanorjboyd@users.noreply.github.com> Date: Tue, 6 Oct 2026 11:35:59 -0700 Subject: [PATCH 3/3] test: cover synchronous environment edge cases Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/test/extensionApi.unit.test.ts | 61 ++++++++++++++++++++---------- 1 file changed, 42 insertions(+), 19 deletions(-) diff --git a/src/test/extensionApi.unit.test.ts b/src/test/extensionApi.unit.test.ts index 78b3dc769..ba6e536da 100644 --- a/src/test/extensionApi.unit.test.ts +++ b/src/test/extensionApi.unit.test.ts @@ -243,26 +243,26 @@ suite('PythonEnvironmentApiImpl - package resolution', () => { }); suite('PythonEnvironmentApiImpl - getEnvironmentSync', () => { - teardown(() => { - sinon.restore(); - }); + type ApiArgs = ConstructorParameters; + const workspaceUri = Uri.joinPath(Uri.file(process.cwd()), 'workspace'); + const environmentUri = Uri.joinPath(workspaceUri, '.venv'); + const scope = Uri.joinPath(workspaceUri, 'script.py'); + const lastKnown = { + envId: { id: 'selected', managerId: 'ms-python.python:venv' }, + name: 'selected', + displayName: 'selected', + displayPath: environmentUri.fsPath, + version: '3.12.0', + environmentPath: environmentUri, + execInfo: { run: { executable: Uri.joinPath(environmentUri, 'python').fsPath, args: [] } }, + sysPrefix: environmentUri.fsPath, + } as PythonEnvironment; - test('returns the last-known environment without waiting for manager readiness', () => { - const workspaceUri = Uri.joinPath(Uri.file(process.cwd()), 'workspace'); - const environmentUri = Uri.joinPath(workspaceUri, '.venv'); - const scope = Uri.joinPath(workspaceUri, 'script.py'); - const lastKnown = { - envId: { id: 'selected', managerId: 'ms-python.python:venv' }, - name: 'selected', - displayName: 'selected', - displayPath: environmentUri.fsPath, - version: '3.12.0', - environmentPath: environmentUri, - execInfo: { run: { executable: Uri.joinPath(environmentUri, 'python').fsPath, args: [] } }, - sysPrefix: environmentUri.fsPath, - } as PythonEnvironment; - type ApiArgs = ConstructorParameters; - const getLastKnownEnvironment = sinon.stub().withArgs(scope).returns(lastKnown); + function createApi(environment: PythonEnvironment | undefined): { + api: PythonEnvironmentApiImpl; + getLastKnownEnvironment: sinon.SinonStub; + } { + const getLastKnownEnvironment = sinon.stub().returns(environment); const api = new PythonEnvironmentApiImpl( { onDidChangeActiveEnvironment: new EventEmitter().event, @@ -277,8 +277,31 @@ suite('PythonEnvironmentApiImpl - getEnvironmentSync', () => { {} as unknown as ApiArgs[3], { onDidChangeEnvironmentVariables: new EventEmitter().event } as unknown as ApiArgs[4], ); + return { api, getLastKnownEnvironment }; + } + + teardown(() => { + sinon.restore(); + }); + + test('returns the last-known environment without waiting for manager readiness', () => { + const { api, getLastKnownEnvironment } = createApi(lastKnown); assert.strictEqual(api.getEnvironmentSync(scope), lastKnown); sinon.assert.calledOnceWithExactly(getLastKnownEnvironment, scope); }); + + test('returns the last-known global environment', () => { + const { api, getLastKnownEnvironment } = createApi(lastKnown); + + assert.strictEqual(api.getEnvironmentSync(undefined), lastKnown); + sinon.assert.calledOnceWithExactly(getLastKnownEnvironment, undefined); + }); + + test('returns undefined when no environment has been resolved', () => { + const { api, getLastKnownEnvironment } = createApi(undefined); + + assert.strictEqual(api.getEnvironmentSync(scope), undefined); + sinon.assert.calledOnceWithExactly(getLastKnownEnvironment, scope); + }); });