Skip to content

Commit 150ef81

Browse files
authored
fix global python interpreter filtering in Project Wizard (#5502)
### Addresses - #5341 - #4270 ### Implementation - add new command `python.isGlobalPython` to the Python extension - Given a python interpreter path, return whether it is a global python interpreter - remove `RuntimeFilter`, `PythonRuntimeFilter` and `runtimeSource` filtering logic from the Project Wizard, which would require an update to the appropriate enum every time we want to recognize a specific `runtimeSource` as a permitted "Global Python" - rework `_getFilteredInterpreters()` to return all interpreters, only global interpreters, or undefined depending on the current state of the project wizard - add/update log messages in the Project Wizard state handling methods ### QA Notes On Windows, `MicrosoftStore`-installed Python versions should now show in the Project Wizard interpreter dropdown when creating a new Venv. On other operating systems, Global, Pyenv, Unknown and any other "Global" Python installations should show in the Project Wizard interpreter dropdown when creating a new Venv.
1 parent be40fbb commit 150ef81

File tree

5 files changed

+79
-71
lines changed

5 files changed

+79
-71
lines changed

extensions/positron-python/src/client/common/constants.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@ export namespace Commands {
8080
export const Get_Create_Environment_Providers = 'python.getCreateEnvironmentProviders';
8181
export const Is_Conda_Installed = 'python.isCondaInstalled';
8282
export const Get_Conda_Python_Versions = 'python.getCondaPythonVersions';
83+
export const Is_Global_Python = 'python.isGlobalPython';
8384
// --- End Positron ---
8485
export const InstallJupyter = 'python.installJupyter';
8586
export const InstallPython = 'python.installPython';

extensions/positron-python/src/client/positron/createEnvApi.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@ import {
1313
} from '../pythonEnvironments/creation/proposed.createEnvApis';
1414
import { handleCreateEnvironmentCommand } from '../pythonEnvironments/creation/createEnvironment';
1515
import { IPythonRuntimeManager } from './manager';
16+
import { getExtension } from '../common/vscodeApis/extensionsApi';
17+
import { PythonExtension } from '../api/types';
18+
import { PVSC_EXTENSION_ID } from '../common/constants';
1619

1720
/**
1821
* A simplified version of an environment provider that can be used in the Positron Project Wizard
@@ -70,3 +73,21 @@ export async function createEnvironmentAndRegister(
7073
}
7174
return result;
7275
}
76+
77+
/**
78+
* Checks if the given interpreter is a global python installation.
79+
* @param interpreterPath The interpreter path to check.
80+
* @returns True if the interpreter is a global python installation, false if it is not, and
81+
* undefined if the check could not be performed.
82+
* Implementation is based on isGlobalPythonSelected in extensions/positron-python/src/client/pythonEnvironments/creation/common/createEnvTriggerUtils.ts
83+
*/
84+
export async function isGlobalPython(interpreterPath: string): Promise<boolean | undefined> {
85+
const extension = getExtension<PythonExtension>(PVSC_EXTENSION_ID);
86+
if (!extension) {
87+
return undefined;
88+
}
89+
const extensionApi: PythonExtension = extension.exports as PythonExtension;
90+
const interpreterDetails = await extensionApi.environments.resolveEnvironment(interpreterPath);
91+
const isGlobal = interpreterDetails?.environment === undefined;
92+
return isGlobal;
93+
}

extensions/positron-python/src/client/pythonEnvironments/creation/createEnvApi.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,11 @@ import { CreateEnvironmentOptionsInternal } from './types';
2626
import { getCondaPythonVersions } from './provider/condaUtils';
2727
import { IPythonRuntimeManager } from '../../positron/manager';
2828
import { Conda } from '../common/environmentManagers/conda';
29-
import { createEnvironmentAndRegister, getCreateEnvironmentProviders } from '../../positron/createEnvApi';
29+
import {
30+
createEnvironmentAndRegister,
31+
getCreateEnvironmentProviders,
32+
isGlobalPython,
33+
} from '../../positron/createEnvApi';
3034
// --- End Positron ---
3135

3236
class CreateEnvironmentProviders {
@@ -109,6 +113,7 @@ export function registerCreateEnvironmentFeatures(
109113
},
110114
),
111115
registerCommand(Commands.Get_Conda_Python_Versions, () => getCondaPythonVersions()),
116+
registerCommand(Commands.Is_Global_Python, (interpreterPath: string) => isGlobalPython(interpreterPath)),
112117
// --- End Positron ---
113118
registerCreateEnvironmentProvider(new VenvCreationProvider(interpreterQuickPick)),
114119
registerCreateEnvironmentProvider(condaCreationProvider()),

src/vs/workbench/browser/positronNewProjectWizard/interfaces/newProjectWizardEnums.ts

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -35,11 +35,3 @@ export enum PythonEnvironmentProvider {
3535
Venv = 'Venv',
3636
Conda = 'Conda'
3737
}
38-
39-
/**
40-
* PythonRuntimeFilter enum.
41-
*/
42-
export enum PythonRuntimeFilter {
43-
Global = 'Global',
44-
Pyenv = 'Pyenv'
45-
}

src/vs/workbench/browser/positronNewProjectWizard/newProjectWizardState.ts

Lines changed: 51 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import { IFileDialogService } from 'vs/platform/dialogs/common/dialogs';
88
import { ILanguageRuntimeMetadata, ILanguageRuntimeService } from 'vs/workbench/services/languageRuntime/common/languageRuntimeService';
99
import { IRuntimeSessionService } from 'vs/workbench/services/runtimeSession/common/runtimeSessionService';
1010
import { IRuntimeStartupService, RuntimeStartupPhase } from 'vs/workbench/services/runtimeStartup/common/runtimeStartupService';
11-
import { EnvironmentSetupType, NewProjectWizardStep, PythonEnvironmentProvider, PythonRuntimeFilter } from 'vs/workbench/browser/positronNewProjectWizard/interfaces/newProjectWizardEnums';
11+
import { EnvironmentSetupType, NewProjectWizardStep, PythonEnvironmentProvider } from 'vs/workbench/browser/positronNewProjectWizard/interfaces/newProjectWizardEnums';
1212
import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService';
1313
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
1414
import { ILogService } from 'vs/platform/log/common/log';
@@ -86,12 +86,6 @@ export interface INewProjectWizardStateManager {
8686
readonly onUpdateProjectDirectory: Event<void>;
8787
}
8888

89-
/**
90-
* RuntimeFilter type.
91-
* More filters can be added here as needed.
92-
*/
93-
type RuntimeFilter = PythonRuntimeFilter;
94-
9589
/**
9690
* NewProjectWizardStateManager class.
9791
* This class is used to manage the state of the New Project Wizard.
@@ -174,8 +168,7 @@ export class NewProjectWizardStateManager
174168
.then(() => {
175169
this._runtimeStartupComplete = true;
176170
this._updateInterpreterRelatedState();
177-
}
178-
);
171+
});
179172
} else {
180173
// Register disposables.
181174
this._register(
@@ -607,30 +600,16 @@ export class NewProjectWizardStateManager
607600
return;
608601
}
609602

610-
const langId = this._getLangId();
611-
let runtimeSourceFilters: RuntimeFilter[] | undefined = undefined;
612-
613-
// Add runtime filters for new Venv Python environments.
614-
if (langId === LanguageIds.Python && this._pythonEnvSetupType === EnvironmentSetupType.NewEnvironment) {
615-
if (this._getEnvProviderName() === PythonEnvironmentProvider.Venv) {
616-
// TODO: instead of applying our own filters, we should use the filters provided by the
617-
// extension for Global python environments.
618-
// extensions/positron-python/src/client/pythonEnvironments/creation/common/createEnvTriggerUtils.ts
619-
// isGlobalPython
620-
runtimeSourceFilters = [PythonRuntimeFilter.Global, PythonRuntimeFilter.Pyenv];
621-
}
622-
}
623-
624603
// Update the interpreters list.
625-
this._interpreters = this._getFilteredInterpreters(runtimeSourceFilters);
604+
this._interpreters = await this._getFilteredInterpreters();
626605

627606
// Update the interpreter that should be selected in the dropdown.
628607
if (!this._selectedRuntime || !this._interpreters?.includes(this._selectedRuntime)) {
629608
this._resetSelectedRuntime();
630609
}
631610

632611
// For Python projects, check if ipykernel needs to be installed.
633-
if (langId === LanguageIds.Python) {
612+
if (this._getLangId() === LanguageIds.Python) {
634613
this._installIpykernel = await this._getInstallIpykernel();
635614
}
636615

@@ -753,7 +732,7 @@ export class NewProjectWizardStateManager
753732
this._condaPythonVersionInfo = EMPTY_CONDA_PYTHON_VERSION_INFO;
754733

755734
if (!this._pythonEnvProviders?.length) {
756-
this._services.logService.error('No Python environment providers found.');
735+
this._services.logService.error('[Project Wizard] No Python environment providers found.');
757736
return;
758737
}
759738

@@ -762,7 +741,7 @@ export class NewProjectWizardStateManager
762741
(provider) => provider.name === PythonEnvironmentProvider.Conda
763742
);
764743
if (!providersIncludeConda) {
765-
this._services.logService.info('Conda is not available as an environment provider.');
744+
this._services.logService.info('[Project Wizard] Conda is not available as an environment provider.');
766745
return;
767746
}
768747

@@ -772,7 +751,7 @@ export class NewProjectWizardStateManager
772751
);
773752
if (!this._isCondaInstalled) {
774753
this._services.logService.warn(
775-
'Conda is available as an environment provider, but it is not installed.'
754+
'[Project Wizard] Conda is available as an environment provider, but it is not installed.'
776755
);
777756
return;
778757
}
@@ -781,7 +760,7 @@ export class NewProjectWizardStateManager
781760
const pythonVersionInfo: CondaPythonVersionInfo | undefined =
782761
await this._services.commandService.executeCommand('python.getCondaPythonVersions');
783762
if (!pythonVersionInfo) {
784-
this._services.logService.warn('No Conda Python versions found.');
763+
this._services.logService.warn('[Project Wizard] No Conda Python versions found.');
785764
return;
786765
}
787766

@@ -821,15 +800,14 @@ export class NewProjectWizardStateManager
821800
}
822801

823802
/**
824-
* Retrieves the interpreters that match the language ID and runtime source filters. Sorts the
825-
* interpreters by runtime source.
826-
* @param runtimeSourceFilters Optional runtime source filters to apply.
827-
* @returns The filtered interpreters.
803+
* Retrieves the interpreters that match the current language ID and environment setup type if
804+
* applicable.
805+
* @returns The filtered interpreters sorted by runtime source, or undefined if runtime startup is
806+
* not complete or a Conda environment is being used.
828807
*/
829-
private _getFilteredInterpreters(runtimeSourceFilters?: RuntimeFilter[]): ILanguageRuntimeMetadata[] | undefined {
830-
const langId = this._getLangId();
831-
808+
private async _getFilteredInterpreters(): Promise<ILanguageRuntimeMetadata[] | undefined> {
832809
if (this._usesCondaEnv()) {
810+
this._services.logService.trace(`[Project Wizard] Conda environments do not have registered runtimes`);
833811
// Conda environments do not have registered runtimes. Instead, we have a list of Python
834812
// versions available for Conda environments, which is stored in condaPythonVersionInfo.
835813
return undefined;
@@ -838,41 +816,52 @@ export class NewProjectWizardStateManager
838816
// We don't want to return a partial list of interpreters if the runtime startup is not
839817
// complete, so we return undefined in that case.
840818
if (!this._runtimeStartupComplete) {
819+
this._services.logService.warn('[Project Wizard] Requested filtered interpreters before runtime startup is complete. Please come by later!');
841820
return undefined;
842821
}
843822

844823
// Once the runtime startup is complete, we can return the filtered list of interpreters.
845-
return this._services.languageRuntimeService.registeredRuntimes
846-
// Filter by language ID and runtime source.
847-
.filter(
848-
(runtime) =>
849-
runtime.languageId === langId &&
850-
this._includeRuntimeSource(runtime.runtimeSource, runtimeSourceFilters)
851-
)
852-
// Sort by runtime source.
824+
const langId = this._getLangId();
825+
let runtimesForLang = this._services.languageRuntimeService.registeredRuntimes
826+
.filter(runtime => runtime.languageId === langId);
827+
828+
// If we're creating a new Python environment, only return Global runtimes.
829+
if (langId === LanguageIds.Python
830+
&& this._pythonEnvSetupType === EnvironmentSetupType.NewEnvironment
831+
) {
832+
const globalRuntimes = [];
833+
for (const runtime of runtimesForLang) {
834+
const interpreterPath = runtime.extraRuntimeData.pythonPath as string ?? runtime.runtimePath;
835+
const isGlobal = await this.services.commandService.executeCommand(
836+
'python.isGlobalPython',
837+
interpreterPath
838+
) satisfies boolean | undefined;
839+
if (isGlobal === undefined) {
840+
this._services.logService.error(
841+
`[Project Wizard] Unable to determine if Python interpreter '${interpreterPath}' is global`
842+
);
843+
continue;
844+
}
845+
if (isGlobal) {
846+
globalRuntimes.push(runtime);
847+
} else {
848+
this._services.logService.trace(`[Project Wizard] Skipping non-global Python interpreter '${interpreterPath}'`);
849+
}
850+
}
851+
// If the global runtimes list is a different length than the original runtimes list,
852+
// then we only want to show the global runtimes.
853+
if (runtimesForLang.length !== globalRuntimes.length) {
854+
runtimesForLang = globalRuntimes;
855+
}
856+
}
857+
858+
// Return the runtimes, sorted by runtime source.
859+
return runtimesForLang
853860
.sort((left, right) =>
854861
left.runtimeSource.localeCompare(right.runtimeSource)
855862
);
856863
}
857864

858-
/**
859-
* Determines if the runtime source should be included based on the filters.
860-
* @param runtimeSource The runtime source to check.
861-
* @param filters The runtime source filters to apply.
862-
* @returns True if the runtime source should be included, false otherwise.
863-
* If no filters are provided, all runtime sources are included.
864-
*/
865-
private _includeRuntimeSource(
866-
runtimeSource: string,
867-
filters?: RuntimeFilter[]
868-
) {
869-
return (
870-
!filters ||
871-
!filters.length ||
872-
filters.find((rs) => rs === runtimeSource) !== undefined
873-
);
874-
}
875-
876865
/**
877866
* Resets the properties of the project configuration that should not be persisted when the
878867
* project type changes.

0 commit comments

Comments
 (0)