Skip to content

Commit 02533df

Browse files
author
Kartik Raj
authored
Expose currently selected interpreter path using API (#11295)
* Expose interpreter path using API * News entry * Add flag in package.json * Update API description * API changes after discussion * Oops
1 parent 376d6b9 commit 02533df

File tree

5 files changed

+71
-5
lines changed

5 files changed

+71
-5
lines changed

news/1 Enhancements/11294.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Expose currently selected interpreter path using API.

package.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@
33
"displayName": "Python",
44
"description": "Linting, Debugging (multi-threaded, remote), Intellisense, Jupyter Notebooks, code formatting, refactoring, unit tests, snippets, and more.",
55
"version": "2020.5.0-dev",
6+
"featureFlags": {
7+
"usingNewInterpreterStorage": true
8+
},
69
"languageServerVersion": "0.5.30",
710
"publisher": "ms-python",
811
"enableProposedApi": false,

src/client/api.ts

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
import { isTestExecution } from './common/constants';
77
import { DebugAdapterNewPtvsd } from './common/experimentGroups';
88
import { traceError } from './common/logger';
9-
import { IExperimentsManager } from './common/types';
9+
import { IConfigurationService, IExperimentsManager, Resource } from './common/types';
1010
import { getDebugpyLauncherArgs, getPtvsdLauncherScriptArgs } from './debugger/extension/adapter/remoteLaunchers';
1111
import { IServiceContainer, IServiceManager } from './ioc/types';
1212

@@ -34,15 +34,36 @@ export interface IExtensionApi {
3434
*/
3535
getRemoteLauncherCommand(host: string, port: number, waitUntilDebuggerAttaches: boolean): Promise<string[]>;
3636
};
37+
/**
38+
* Return internal settings within the extension which are stored in VSCode storage
39+
*/
40+
settings: {
41+
/**
42+
* Returns the Python execution command corresponding to the specified resource, taking into account
43+
* any workspace-specific settings for the workspace to which this resource belongs.
44+
* E.g of execution commands returned could be,
45+
* * `['<path to the interpreter set in settings>']`
46+
* * `['<path to the interpreter selected by the extension when setting is not set>']`
47+
* * `['conda', 'run', 'python']` which is used to run from within Conda environments.
48+
* or something similar for some other Python environments.
49+
* @param {Resource} [resource] A resource for which the setting is asked for.
50+
* * When no resource is provided, the setting scoped to the first workspace folder is returned.
51+
* * If no folder is present, it returns the global setting.
52+
* @returns {(string[] | undefined)} When return value is `undefined`, it means no interpreter is set.
53+
* Otherwise, join the items returned using space to construct the full execution command.
54+
*/
55+
getExecutionCommand(resource?: Resource): string[] | undefined;
56+
};
3757
}
3858

3959
export function buildApi(
4060
// tslint:disable-next-line:no-any
4161
ready: Promise<any>,
4262
serviceManager: IServiceManager,
4363
serviceContainer: IServiceContainer
44-
) {
64+
): IExtensionApi {
4565
const experimentsManager = serviceContainer.get<IExperimentsManager>(IExperimentsManager);
66+
const configurationService = serviceContainer.get<IConfigurationService>(IConfigurationService);
4667
const api = {
4768
// 'ready' will propagate the exception, but we must log it here first.
4869
ready: ready.catch((ex) => {
@@ -71,6 +92,13 @@ export function buildApi(
7192
waitUntilDebuggerAttaches
7293
});
7394
}
95+
},
96+
settings: {
97+
getExecutionCommand(resource?: Resource) {
98+
const pythonPath = configurationService.getSettings(resource).pythonPath;
99+
// If pythonPath equals an empty string, no interpreter is set.
100+
return pythonPath === '' ? undefined : [pythonPath];
101+
}
74102
}
75103
};
76104

src/client/common/configSettings.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -630,6 +630,7 @@ export class PythonSettings implements IPythonSettings {
630630
}
631631
}
632632
if (inExperiment && this.pythonPath === DEFAULT_INTERPRETER_SETTING) {
633+
// If no interpreter is selected, set pythonPath to an empty string.
633634
// This is to ensure that we ask users to select an interpreter in case auto selected interpreter is not safe to select
634635
this.pythonPath = '';
635636
}

src/test/api.functional.test.ts

Lines changed: 36 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,18 +5,20 @@
55

66
// tslint:disable:no-any max-func-body-length
77

8-
import { expect } from 'chai';
8+
import { assert, expect } from 'chai';
99
import * as path from 'path';
1010
import { anyString, instance, mock, when } from 'ts-mockito';
11+
import { Uri } from 'vscode';
1112
import { buildApi } from '../client/api';
13+
import { ConfigurationService } from '../client/common/configuration/service';
1214
import { EXTENSION_ROOT_DIR } from '../client/common/constants';
1315
import { ExperimentsManager } from '../client/common/experiments';
14-
import { IExperimentsManager } from '../client/common/types';
16+
import { IConfigurationService, IExperimentsManager } from '../client/common/types';
1517
import { ServiceContainer } from '../client/ioc/container';
1618
import { ServiceManager } from '../client/ioc/serviceManager';
1719
import { IServiceContainer, IServiceManager } from '../client/ioc/types';
1820

19-
suite('Extension API - Debugger', () => {
21+
suite('Extension API', () => {
2022
const expectedLauncherPath = `${EXTENSION_ROOT_DIR.fileToCommandArgument()}/pythonFiles/ptvsd_launcher.py`;
2123
const ptvsdPath = path.join(EXTENSION_ROOT_DIR, 'pythonFiles', 'lib', 'python', 'debugpy', 'no_wheels', 'debugpy');
2224
const ptvsdHost = 'somehost';
@@ -25,15 +27,46 @@ suite('Extension API - Debugger', () => {
2527
let serviceContainer: IServiceContainer;
2628
let serviceManager: IServiceManager;
2729
let experimentsManager: IExperimentsManager;
30+
let configurationService: IConfigurationService;
2831

2932
setup(() => {
3033
serviceContainer = mock(ServiceContainer);
3134
serviceManager = mock(ServiceManager);
3235
experimentsManager = mock(ExperimentsManager);
36+
configurationService = mock(ConfigurationService);
3337

38+
when(serviceContainer.get<IConfigurationService>(IConfigurationService)).thenReturn(
39+
instance(configurationService)
40+
);
3441
when(serviceContainer.get<IExperimentsManager>(IExperimentsManager)).thenReturn(instance(experimentsManager));
3542
});
3643

44+
test('Execution command settings API returns expected array if interpreter is set', async () => {
45+
const resource = Uri.parse('a');
46+
when(configurationService.getSettings(resource)).thenReturn({ pythonPath: 'settingValue' } as any);
47+
48+
const interpreterPath = buildApi(
49+
Promise.resolve(),
50+
instance(serviceManager),
51+
instance(serviceContainer)
52+
).settings.getExecutionCommand(resource);
53+
54+
assert.deepEqual(interpreterPath, ['settingValue']);
55+
});
56+
57+
test('Execution command settings API returns `undefined` if interpreter is set', async () => {
58+
const resource = Uri.parse('a');
59+
when(configurationService.getSettings(resource)).thenReturn({ pythonPath: '' } as any);
60+
61+
const interpreterPath = buildApi(
62+
Promise.resolve(),
63+
instance(serviceManager),
64+
instance(serviceContainer)
65+
).settings.getExecutionCommand(resource);
66+
67+
expect(interpreterPath).to.equal(undefined, '');
68+
});
69+
3770
test('Test debug launcher args (no-wait and not in experiment)', async () => {
3871
const waitForAttach = false;
3972
when(experimentsManager.inExperiment(anyString())).thenReturn(false);

0 commit comments

Comments
 (0)