Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for "conda run" in PythonExecutionFactory #8259

Merged
Merged
Show file tree
Hide file tree
Changes from 67 commits
Commits
Show all changes
70 commits
Select commit Hold shift + click to select a range
66468a0
Initial commit for conda run
kimadeline Oct 28, 2019
4fda3b0
Forgot djangoShellCodeExecution.ts
kimadeline Oct 28, 2019
73bee3c
Fix news file
kimadeline Oct 28, 2019
d68445e
Fix single workspace tests
kimadeline Oct 28, 2019
de9dddb
Don't use interpreter in terminal code exec
kimadeline Oct 29, 2019
16e5542
Merge branch 'master' into 7696-python-execution-service-conda-run
kimadeline Oct 29, 2019
6d91b33
Try increasing activation timeout time for smoke tests
kimadeline Oct 29, 2019
2646ea2
Try different timeouts
kimadeline Oct 29, 2019
8296bd0
try commenting conda in pythonExecutionFactory
kimadeline Oct 29, 2019
389bb59
Undo timeout changes
kimadeline Oct 30, 2019
a79252a
forgot await lol
kimadeline Oct 30, 2019
bdaa375
Uncomment things
kimadeline Oct 30, 2019
865a216
Merge branch 'master' into 7696-python-execution-service-conda-run
kimadeline Oct 30, 2019
e6b37c3
Small fixes + remove empty line
kimadeline Oct 30, 2019
e294b4d
Reduce PR noise by removing some autoformatting
kimadeline Oct 30, 2019
5d8a1d3
Update createActivatedEnv + minor refactoring
kimadeline Oct 30, 2019
82eafec
Add pythonExecutionFactory unit tests
kimadeline Oct 30, 2019
9b8f948
Add pythonExecutionFactory unit tests
kimadeline Oct 30, 2019
780538e
Merge branch '7696-python-execution-service-conda-run' of github.com:…
kimadeline Oct 31, 2019
0068e0d
Fix conda env name check + django terminal
kimadeline Oct 31, 2019
637be5f
Unit tests for terminalCodeExecution.ts
kimadeline Oct 31, 2019
ad42ee8
Fix linting functional tests
kimadeline Oct 31, 2019
5255e45
djangoShellCodeExecution tests + fix terminalCodeExec.unit.test.ts
kimadeline Oct 31, 2019
6a0ab56
Make conda path check more explicit
kimadeline Oct 31, 2019
2a43052
Add Windows test to PythonExecutionFactory
kimadeline Oct 31, 2019
3b304be
Add conda call verification
kimadeline Oct 31, 2019
1fea07e
Add CondaEnvironmentInfo type
kimadeline Nov 1, 2019
1361103
CondaExecutionService unit tests
kimadeline Nov 1, 2019
d47072d
Merge branch 'master' into 7696-python-execution-service-conda-run
kimadeline Nov 4, 2019
fe5601b
Add comment
kimadeline Nov 4, 2019
45160bf
Remove autformatting
kimadeline Nov 6, 2019
7319345
Make getExecutableInfo public
kimadeline Nov 6, 2019
3e972ac
Use CondaExecutionService in terminal code execution
kimadeline Nov 6, 2019
4f7cc14
Apply suggestions from code review
kimadeline Nov 7, 2019
1719adf
refactor CondaExecutionService instantiation
kimadeline Nov 7, 2019
1b3eb47
Merge branch '7696-python-execution-service-conda-run' of github.com:…
kimadeline Nov 7, 2019
2ffe187
getExecutableInfo unit tests + more concise conda getExecutableInfo
kimadeline Nov 7, 2019
1df9f8a
createCondaExecutionService unit tests
kimadeline Nov 7, 2019
faf1f39
Add conda version check
kimadeline Nov 7, 2019
6a772bf
Housekeeping
kimadeline Nov 8, 2019
938ebd6
Merge branch 'master' into 7696-python-execution-service-conda-run
kimadeline Nov 8, 2019
689bf1f
Move terminal changes to another PR
kimadeline Nov 8, 2019
42ba9ff
Typo
kimadeline Nov 8, 2019
923dea2
Revert execObservable and execModuleObservable
kimadeline Nov 8, 2019
365c92a
Merge branch 'master' into 7696-python-execution-service-conda-run
kimadeline Nov 8, 2019
77fac1f
Update GH link
kimadeline Nov 8, 2019
fd4ddd6
More housekeeping
kimadeline Nov 8, 2019
6edb1da
Fix compilation errors
kimadeline Nov 8, 2019
cf3ba57
Undo formatting changes
kimadeline Nov 8, 2019
5fb07a4
More formatting reverting
kimadeline Nov 8, 2019
61d685a
Merge branch 'master' into 7696-python-execution-service-conda-run
kimadeline Nov 12, 2019
55f96e9
Try creating a conda environment first
kimadeline Nov 12, 2019
77d94a1
Fix tests
kimadeline Nov 12, 2019
68216fa
Drop unnecessary command arg in getExecutionInfo
kimadeline Nov 12, 2019
ee42991
Fix PythonDaemon getExecutionInfo
kimadeline Nov 12, 2019
3442437
Undo formatting changes
kimadeline Nov 12, 2019
a1ae719
Merge branch 'master' into 7696-python-execution-service-conda-run
kimadeline Nov 12, 2019
6d176bc
Fix merge gone sideways
kimadeline Nov 12, 2019
eb7625b
Merge branch 'master' into 7696-python-execution-service-conda-run
kimadeline Nov 12, 2019
5dbd096
Fix linting functional tests
kimadeline Nov 14, 2019
9c5724e
Fix unit tests
kimadeline Nov 14, 2019
38c4321
Merge branch 'master' into 7696-python-execution-service-conda-run
kimadeline Nov 15, 2019
75076e4
Revert "Move terminal changes to another PR"
kimadeline Nov 15, 2019
a4f0da5
Fixes
kimadeline Nov 15, 2019
b6b3fd3
Fix tests
kimadeline Nov 16, 2019
610fb01
Merge branch 'master' into 7696-python-execution-service-conda-run
kimadeline Nov 19, 2019
eddcf15
Fix single workspace tests
kimadeline Nov 20, 2019
269c54b
Remove unused declaration
kimadeline Nov 20, 2019
394c85f
Move createConda around in createActivatedEnv
kimadeline Nov 20, 2019
0f93c43
Fix unit tests
kimadeline Nov 20, 2019
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
2 changes: 2 additions & 0 deletions news/3 Code Health/7696.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Use "conda run" (instead of using the "python.pythonPath" setting directly) when executing
Python and an Anaconda environment is selected.
26 changes: 26 additions & 0 deletions src/client/common/process/condaExecutionService.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
import { injectable } from 'inversify';
import { CondaEnvironmentInfo } from '../../interpreter/contracts';
import { IServiceContainer } from '../../ioc/types';
import { PythonExecutionService } from './pythonProcess';
import { IProcessService, PythonExecutionInfo } from './types';

@injectable()
export class CondaExecutionService extends PythonExecutionService {
constructor(
serviceContainer: IServiceContainer,
procService: IProcessService,
pythonPath: string,
private readonly condaFile: string,
private readonly condaEnvironment: CondaEnvironmentInfo
) {
super(serviceContainer, procService, pythonPath);
}

public getExecutionInfo(args: string[]): PythonExecutionInfo {
const executionArgs = this.condaEnvironment.name !== '' ? ['-n', this.condaEnvironment.name] : ['-p', this.condaEnvironment.path];

return { command: this.condaFile, args: ['run', ...executionArgs, 'python', ...args] };
}
}
4 changes: 4 additions & 0 deletions src/client/common/process/pythonDaemon.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import {
IPythonExecutionService,
ObservableExecutionResult,
Output,
PythonExecutionInfo,
PythonVersionInfo,
SpawnOptions,
StdErrError
Expand Down Expand Up @@ -96,6 +97,9 @@ export class PythonDaemonExecutionService implements IPythonDaemonExecutionServi
return this.pythonExecutionService.getExecutablePath();
}
}
public getExecutionInfo(args: string[]): PythonExecutionInfo {
return this.pythonExecutionService.getExecutionInfo(args);
}
public async isModuleInstalled(moduleName: string): Promise<boolean> {
this.throwIfRPCConnectionIsDead();
try {
Expand Down
4 changes: 4 additions & 0 deletions src/client/common/process/pythonDaemonPool.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import {
IPythonDaemonExecutionService,
IPythonExecutionService,
ObservableExecutionResult,
PythonExecutionInfo,
SpawnOptions
} from './types';

Expand Down Expand Up @@ -71,6 +72,9 @@ export class PythonDaemonExecutionServicePool implements IPythonDaemonExecutionS
this.logger.logProcess(`${this.pythonPath} (daemon)`, ['getExecutablePath']);
return this.wrapCall(daemon => daemon.getExecutablePath());
}
public getExecutionInfo(args: string[]): PythonExecutionInfo {
return this.pythonExecutionService.getExecutionInfo(args);
}
public async isModuleInstalled(moduleName: string): Promise<boolean> {
this.logger.logProcess(`${this.pythonPath} (daemon)`, ['-m', moduleName]);
return this.wrapCall(daemon => daemon.isModuleInstalled(moduleName));
Expand Down
49 changes: 47 additions & 2 deletions src/client/common/process/pythonExecutionFactory.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,19 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
import { inject, injectable } from 'inversify';
import { gte } from 'semver';

import { Uri } from 'vscode';
import { IEnvironmentActivationService } from '../../interpreter/activation/types';
import { IInterpreterService } from '../../interpreter/contracts';
import { ICondaService, IInterpreterService } from '../../interpreter/contracts';
import { WindowsStoreInterpreter } from '../../interpreter/locators/services/windowsStoreInterpreter';
import { IWindowsStoreInterpreter } from '../../interpreter/locators/types';
import { IServiceContainer } from '../../ioc/types';
import { sendTelemetryEvent } from '../../telemetry';
import { EventName } from '../../telemetry/constants';
import { traceError } from '../logger';
import { IConfigurationService, IDisposableRegistry } from '../types';
import { CondaExecutionService } from './condaExecutionService';
import { ProcessService } from './proc';
import { PythonDaemonExecutionServicePool } from './pythonDaemonPool';
import { PythonExecutionService } from './pythonProcess';
Expand All @@ -28,6 +31,9 @@ import {
} from './types';
import { WindowsStorePythonProcess } from './windowsStorePythonProcess';

// Minimum version number of conda required to be able to use 'conda run'
export const CONDA_RUN_VERSION = '4.6.0';

@injectable()
export class PythonExecutionFactory implements IPythonExecutionFactory {
private readonly daemonsPerPythonService = new Map<string, Promise<IPythonDaemonExecutionService>>();
Expand All @@ -36,6 +42,7 @@ export class PythonExecutionFactory implements IPythonExecutionFactory {
@inject(IEnvironmentActivationService) private readonly activationHelper: IEnvironmentActivationService,
@inject(IProcessServiceFactory) private readonly processServiceFactory: IProcessServiceFactory,
@inject(IConfigurationService) private readonly configService: IConfigurationService,
@inject(ICondaService) private readonly condaService: ICondaService,
@inject(IBufferDecoder) private readonly decoder: IBufferDecoder,
@inject(WindowsStoreInterpreter) private readonly windowsStoreInterpreter: IWindowsStoreInterpreter
) {}
Expand All @@ -44,6 +51,18 @@ export class PythonExecutionFactory implements IPythonExecutionFactory {
const processService: IProcessService = await this.processServiceFactory.create(options.resource);
const processLogger = this.serviceContainer.get<IProcessLogger>(IProcessLogger);
processService.on('exec', processLogger.logProcess.bind(processLogger));

// Don't bother getting a conda execution service instance if we haven't fetched the list of interpreters yet.
// Also, without this hasInterpreters check smoke tests will time out
const interpreterService = this.serviceContainer.get<IInterpreterService>(IInterpreterService);
const hasInterpreters = await interpreterService.hasInterpreters;
if (hasInterpreters) {
const condaExecutionService = await this.createCondaExecutionService(pythonPath, processService);
if (condaExecutionService) {
return condaExecutionService;
}
}

if (this.windowsStoreInterpreter.isWindowsStoreInterpreter(pythonPath)) {
return new WindowsStorePythonProcess(this.serviceContainer, processService, pythonPath, this.windowsStoreInterpreter);
}
Expand Down Expand Up @@ -87,17 +106,43 @@ export class PythonExecutionFactory implements IPythonExecutionFactory {
});
}
public async createActivatedEnvironment(options: ExecutionFactoryCreateWithEnvironmentOptions): Promise<IPythonExecutionService> {
const pythonPath = options.interpreter ? options.interpreter.path : this.configService.getSettings(options.resource).pythonPath;
const condaExecutionService = await this.createCondaExecutionService(pythonPath, undefined, options.resource);
kimadeline marked this conversation as resolved.
Show resolved Hide resolved
if (condaExecutionService) {
return condaExecutionService;
}

const envVars = await this.activationHelper.getActivatedEnvironmentVariables(options.resource, options.interpreter, options.allowEnvironmentFetchExceptions);
const hasEnvVars = envVars && Object.keys(envVars).length > 0;
sendTelemetryEvent(EventName.PYTHON_INTERPRETER_ACTIVATION_ENVIRONMENT_VARIABLES, undefined, { hasEnvVars });
if (!hasEnvVars) {
return this.create({ resource: options.resource, pythonPath: options.interpreter ? options.interpreter.path : undefined });
}
const pythonPath = options.interpreter ? options.interpreter.path : this.configService.getSettings(options.resource).pythonPath;
const processService: IProcessService = new ProcessService(this.decoder, { ...envVars });
const processLogger = this.serviceContainer.get<IProcessLogger>(IProcessLogger);
processService.on('exec', processLogger.logProcess.bind(processLogger));
this.serviceContainer.get<IDisposableRegistry>(IDisposableRegistry).push(processService);
return new PythonExecutionService(this.serviceContainer, processService, pythonPath);
}
public async createCondaExecutionService(pythonPath: string, processService?: IProcessService, resource?: Uri): Promise<CondaExecutionService | undefined> {
const processServicePromise = processService ? Promise.resolve(processService) : this.processServiceFactory.create(resource);
const [condaVersion, condaEnvironment, condaFile, procService] = await Promise.all([
this.condaService.getCondaVersion(),
this.condaService.getCondaEnvironment(pythonPath),
this.condaService.getCondaFile(),
processServicePromise
]);

if (condaVersion && gte(condaVersion, CONDA_RUN_VERSION) && condaEnvironment && condaFile && procService) {
// Add logging to the newly created process service
if (!processService) {
const processLogger = this.serviceContainer.get<IProcessLogger>(IProcessLogger);
procService.on('exec', processLogger.logProcess.bind(processLogger));
this.serviceContainer.get<IDisposableRegistry>(IDisposableRegistry).push(procService);
}
return new CondaExecutionService(this.serviceContainer, procService, pythonPath, condaFile, condaEnvironment);
}

return Promise.resolve(undefined);
}
}
26 changes: 20 additions & 6 deletions src/client/common/process/pythonProcess.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import {
IProcessService,
IPythonExecutionService,
ObservableExecutionResult,
PythonExecutionInfo,
PythonVersionInfo,
SpawnOptions
} from './types';
Expand All @@ -41,7 +42,8 @@ export class PythonExecutionService implements IPythonExecutionService {
// See these two bugs:
// https://github.com/microsoft/vscode-python/issues/7569
// https://github.com/microsoft/vscode-python/issues/7760
const jsonValue = await waitForPromise(this.procService.exec(this.pythonPath, [file], { mergeStdOutErr: true }), 5000)
const { command, args } = this.getExecutionInfo([file]);
const jsonValue = await waitForPromise(this.procService.exec(command, args, { mergeStdOutErr: true }), 5000)
.then(output => output ? output.stdout.trim() : '--timed out--'); // --timed out-- should cause an exception

let json: { versionInfo: PythonVersionInfo; sysPrefix: string; sysVersion: string; is64Bit: boolean };
Expand Down Expand Up @@ -69,29 +71,41 @@ export class PythonExecutionService implements IPythonExecutionService {
if (await this.fileSystem.fileExists(this.pythonPath)) {
kimadeline marked this conversation as resolved.
Show resolved Hide resolved
return this.pythonPath;
kimadeline marked this conversation as resolved.
Show resolved Hide resolved
}
return this.procService.exec(this.pythonPath, ['-c', 'import sys;print(sys.executable)'], { throwOnStdErr: true })
.then(output => output.stdout.trim());

const { command, args } = this.getExecutionInfo(['-c', 'import sys;print(sys.executable)']);
return this.procService.exec(command, args, { throwOnStdErr: true }).then(output => output.stdout.trim());
}
public async isModuleInstalled(moduleName: string): Promise<boolean> {
return this.procService.exec(this.pythonPath, ['-c', `import ${moduleName}`], { throwOnStdErr: true })
const { command, args } = this.getExecutionInfo(['-c', `import ${moduleName}`]);
return this.procService.exec(command, args, { throwOnStdErr: true })
.then(() => true).catch(() => false);
}

public getExecutionInfo(args: string[]): PythonExecutionInfo {
return { command: this.pythonPath, args };
}

public execObservable(args: string[], options: SpawnOptions): ObservableExecutionResult<string> {
const opts: SpawnOptions = { ...options };
// Cannot use this.getExecutionInfo() until 'conda run' can be run without buffering output.
// See https://github.com/microsoft/vscode-python/issues/8473

Choose a reason for hiding this comment

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

Please ensure you use createActivatedEnvironment instead of procService.
Else this is totally useless.
When running conda we must either use conda run or at a minimium revert to the old hack, activate the environment then grab the env variables and run in the context of those variables.

Choose a reason for hiding this comment

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

Because today we use createActivatedEnvironment when running tools such as pytest, unittest, etc.
If we are going with conda run then createActivatedEnvironment becomes obsolete, BUT ONLY if this (above mentioned) change is added.

Copy link
Author

Choose a reason for hiding this comment

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

Where is that old hack? I could only find the following createActivatedEnvironment calls on master:
image

In this PR execObservable and execModuleObservable use the old code, I've only added a comment.

Choose a reason for hiding this comment

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

Further to this, createActivatedEnvironment was created to get around conda run. It allows code to execute python code within an activated python environment context (which is what conda run does).

So, anywhere createActivatedEnvironment is being used, we need to ensure it uses condaRun.

We have a couple of options:

  • Return conda execution service when calling createActivatedEnvironment
  • Drop createActivatedEnvironemnt (or make it private)
  • Anything that requires observable output, then the conda execution service will fallback to using the service returned by createActivatedEnvironment.
  • Please remember, conda run isn't the only special case. We could have pyenv and others. pyenv requires initializing an environment variable to run the correct python interpreter (they use shims).
  • Hence conda run is an implementation detail when it comes to createActivatedEnvironment.

Finally, i'd prefer if these were done as separate PRs (issues), as opposed to doing them all in one go in a large PR...I.e. first implement conda exec service, then another, then integrate in a few places, etc... Else it just feels too large (to review)

@karthiknadig /cc

Choose a reason for hiding this comment

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

Lets talk

return this.procService.execObservable(this.pythonPath, args, opts);
}
public execModuleObservable(moduleName: string, args: string[], options: SpawnOptions): ObservableExecutionResult<string> {
const opts: SpawnOptions = { ...options };
// Cannot use this.getExecutionInfo() until 'conda run' can be run without buffering output.
// See https://github.com/microsoft/vscode-python/issues/8473
return this.procService.execObservable(this.pythonPath, ['-m', moduleName, ...args], opts);
}
public async exec(args: string[], options: SpawnOptions): Promise<ExecutionResult<string>> {
const opts: SpawnOptions = { ...options };
return this.procService.exec(this.pythonPath, args, opts);
const executable = this.getExecutionInfo(args);
return this.procService.exec(executable.command, executable.args, opts);
}
public async execModule(moduleName: string, args: string[], options: SpawnOptions): Promise<ExecutionResult<string>> {
const opts: SpawnOptions = { ...options };
const result = await this.procService.exec(this.pythonPath, ['-m', moduleName, ...args], opts);
const executable = this.getExecutionInfo(['-m', moduleName, ...args]);
const result = await this.procService.exec(executable.command, executable.args, opts);

// If a module is not installed we'll have something in stderr.
if (moduleName && ErrorUtils.outputHasModuleNotInstalledError(moduleName!, result.stderr)) {
Expand Down
7 changes: 7 additions & 0 deletions src/client/common/process/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { Newable } from '../../ioc/types';
import { ExecutionInfo, IDisposable, Version } from '../types';
import { Architecture } from '../utils/platform';
import { EnvironmentVariables } from '../variables/types';
import { CondaExecutionService } from './condaExecutionService';

export const IBufferDecoder = Symbol('IBufferDecoder');
export interface IBufferDecoder {
Expand Down Expand Up @@ -115,6 +116,7 @@ export interface IPythonExecutionFactory {
*/
createDaemon(options: DaemonExecutionFactoryCreationOptions): Promise<IPythonExecutionService>;
createActivatedEnvironment(options: ExecutionFactoryCreateWithEnvironmentOptions): Promise<IPythonExecutionService>;
createCondaExecutionService(pythonPath: string, processService?: IProcessService, resource?: Uri): Promise<CondaExecutionService | undefined>;
}
export type ReleaseLevel = 'alpha' | 'beta' | 'candidate' | 'final' | 'unknown';
export type PythonVersionInfo = [number, number, number, ReleaseLevel];
Expand All @@ -132,6 +134,7 @@ export interface IPythonExecutionService {
getInterpreterInformation(): Promise<InterpreterInfomation | undefined>;
getExecutablePath(): Promise<string>;
isModuleInstalled(moduleName: string): Promise<boolean>;
getExecutionInfo(args: string[]): PythonExecutionInfo;

execObservable(args: string[], options: SpawnOptions): ObservableExecutionResult<string>;
execModuleObservable(moduleName: string, args: string[], options: SpawnOptions): ObservableExecutionResult<string>;
Expand All @@ -140,6 +143,10 @@ export interface IPythonExecutionService {
execModule(moduleName: string, args: string[], options: SpawnOptions): Promise<ExecutionResult<string>>;
}

export type PythonExecutionInfo = {
command: string;
args: string[];
};
/**
* Identical to the PythonExecutionService, but with a `dispose` method.
* This is a daemon process that lives on until it is disposed, hence the `IDisposable`.
Expand Down
9 changes: 7 additions & 2 deletions src/client/interpreter/contracts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,11 @@ export type CondaInfo = {
conda_version?: string;
};

export type CondaEnvironmentInfo = {
ericsnowcurrently marked this conversation as resolved.
Show resolved Hide resolved
kimadeline marked this conversation as resolved.
Show resolved Hide resolved
name: string;
path: string;
};

export const ICondaService = Symbol('ICondaService');

export interface ICondaService {
Expand All @@ -51,11 +56,11 @@ export interface ICondaService {
isCondaAvailable(): Promise<boolean>;
getCondaVersion(): Promise<SemVer | undefined>;
getCondaInfo(): Promise<CondaInfo | undefined>;
getCondaEnvironments(ignoreCache: boolean): Promise<({ name: string; path: string }[]) | undefined>;
getCondaEnvironments(ignoreCache: boolean): Promise<CondaEnvironmentInfo[] | undefined>;
getInterpreterPath(condaEnvironmentPath: string): string;
getCondaFileFromInterpreter(interpreterPath?: string, envName?: string): Promise<string | undefined>;
isCondaEnvironment(interpreterPath: string): Promise<boolean>;
getCondaEnvironment(interpreterPath: string): Promise<{ name: string; path: string } | undefined>;
getCondaEnvironment(interpreterPath: string): Promise<CondaEnvironmentInfo | undefined>;
}

export enum InterpreterType {
Expand Down
2 changes: 1 addition & 1 deletion src/client/interpreter/locators/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ export class PythonInterpreterLocatorService implements IInterpreterLocatorServi
return new EventEmitter<Promise<PythonInterpreter[]>>().event;
}
public get hasInterpreters(): Promise<boolean> {
return this._hasInterpreters.promise;
return this._hasInterpreters.completed ? this._hasInterpreters.promise : Promise.resolve(false);
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ export abstract class CacheableLocatorService implements IInterpreterLocatorServ
return this.locating.event;
}
public get hasInterpreters(): Promise<boolean> {
return this._hasInterpreters.promise;
return this._hasInterpreters.completed ? this._hasInterpreters.promise : Promise.resolve(false);
}
public abstract dispose(): void;
@traceDecorators.verbose('Get Interpreters in CacheableLocatorService')
Expand Down
5 changes: 3 additions & 2 deletions src/client/interpreter/locators/services/condaService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { IProcessServiceFactory } from '../../../common/process/types';
import { IConfigurationService, IDisposableRegistry, ILogger, IPersistentStateFactory } from '../../../common/types';
import { cache } from '../../../common/utils/decorators';
import {
CondaEnvironmentInfo,
CondaInfo,
ICondaService,
IInterpreterLocatorService,
Expand Down Expand Up @@ -219,10 +220,10 @@ export class CondaService implements ICondaService {
* Return the list of conda envs (by name, interpreter filename).
*/
@traceDecorators.verbose('Get Conda environments')
public async getCondaEnvironments(ignoreCache: boolean): Promise<({ name: string; path: string }[]) | undefined> {
public async getCondaEnvironments(ignoreCache: boolean): Promise<CondaEnvironmentInfo[] | undefined> {
// Global cache.
// tslint:disable-next-line:no-any
const globalPersistence = this.persistentStateFactory.createGlobalPersistentState<{ data: { name: string; path: string }[] | undefined }>('CONDA_ENVIRONMENTS', undefined as any);
const globalPersistence = this.persistentStateFactory.createGlobalPersistentState<{ data: CondaEnvironmentInfo[] | undefined }>('CONDA_ENVIRONMENTS', undefined as any);
if (!ignoreCache && globalPersistence.value) {
return globalPersistence.value.data;
}
Expand Down
Loading