Skip to content

Commit

Permalink
Refactor how we check whether interpreters are available (#3809)
Browse files Browse the repository at this point in the history
For #3369
  • Loading branch information
DonJayamanne authored Dec 27, 2018
1 parent 4f19099 commit 209edb6
Show file tree
Hide file tree
Showing 16 changed files with 92 additions and 34 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -54,9 +54,9 @@ export class InvalidPythonInterpreterService extends BaseDiagnosticsService {
}

const interpreterService = this.serviceContainer.get<IInterpreterService>(IInterpreterService);
const interpreters = await interpreterService.getInterpreters();
const hasInterpreters = await interpreterService.hasInterpreters;

if (interpreters.length === 0) {
if (!hasInterpreters) {
return [new InvalidPythonInterpreterDiagnostic(DiagnosticCodes.NoPythonInterpretersDiagnostic)];
}

Expand All @@ -77,6 +77,7 @@ export class InvalidPythonInterpreterService extends BaseDiagnosticsService {
if (!currentInterpreter || currentInterpreter.type !== InterpreterType.Unknown) {
return [];
}
const interpreters = await interpreterService.getInterpreters();
if (interpreters.filter(i => !helper.isMacDefaultPythonPath(i.path)).length === 0) {
return [new InvalidPythonInterpreterDiagnostic(DiagnosticCodes.MacInterpreterSelectedAndNoOtherInterpretersDiagnostic)];
}
Expand Down
2 changes: 2 additions & 0 deletions src/client/interpreter/contracts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ export const IInterpreterLocatorService = Symbol('IInterpreterLocatorService');

export interface IInterpreterLocatorService extends Disposable {
readonly onLocating: Event<Promise<PythonInterpreter[]>>;
readonly hasInterpreters: Promise<boolean>;
getInterpreters(resource?: Uri, ignoreCache?: boolean): Promise<PythonInterpreter[]>;
}

Expand Down Expand Up @@ -81,6 +82,7 @@ export type WorkspacePythonPath = {
export const IInterpreterService = Symbol('IInterpreterService');
export interface IInterpreterService {
onDidChangeInterpreter: Event<void>;
hasInterpreters: Promise<boolean>;
getInterpreters(resource?: Uri): Promise<PythonInterpreter[]>;
autoSetInterpreter(): Promise<void>;
getActiveInterpreter(resource?: Uri): Promise<PythonInterpreter | undefined>;
Expand Down
3 changes: 3 additions & 0 deletions src/client/interpreter/interpreterService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,9 @@ export class InterpreterService implements Disposable, IInterpreterService {
this.fs = this.serviceContainer.get<IFileSystem>(IFileSystem);
this.persistentStateFactory = this.serviceContainer.get<IPersistentStateFactory>(IPersistentStateFactory);
}
public get hasInterpreters(): Promise<boolean> {
return this.locator.hasInterpreters;
}

public async refresh(resource?: Uri) {
const interpreterDisplay = this.serviceContainer.get<IInterpreterDisplay>(IInterpreterDisplay);
Expand Down
14 changes: 14 additions & 0 deletions src/client/interpreter/locators/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { inject, injectable } from 'inversify';
import { Disposable, Event, EventEmitter, Uri } from 'vscode';
import { IPlatformService } from '../../common/platform/types';
import { IDisposableRegistry } from '../../common/types';
import { createDeferred, Deferred } from '../../common/utils/async';
import { OSType } from '../../common/utils/platform';
import { IServiceContainer } from '../../ioc/types';
import {
Expand All @@ -28,9 +29,11 @@ export class PythonInterpreterLocatorService implements IInterpreterLocatorServi
private readonly disposables: Disposable[] = [];
private readonly platform: IPlatformService;
private readonly interpreterLocatorHelper: IInterpreterLocatorHelper;
private readonly _hasInterpreters: Deferred<boolean>;
constructor(
@inject(IServiceContainer) private serviceContainer: IServiceContainer
) {
this._hasInterpreters = createDeferred<boolean>();
serviceContainer.get<Disposable[]>(IDisposableRegistry).push(this);
this.platform = serviceContainer.get<IPlatformService>(IPlatformService);
this.interpreterLocatorHelper = serviceContainer.get<IInterpreterLocatorHelper>(IInterpreterLocatorHelper);
Expand All @@ -46,6 +49,9 @@ export class PythonInterpreterLocatorService implements IInterpreterLocatorServi
public get onLocating(): Event<Promise<PythonInterpreter[]>> {
return new EventEmitter<Promise<PythonInterpreter[]>>().event;
}
public get hasInterpreters(): Promise<boolean> {
return this._hasInterpreters.promise;
}

/**
* Release any held resources.
Expand All @@ -65,11 +71,19 @@ export class PythonInterpreterLocatorService implements IInterpreterLocatorServi
public async getInterpreters(resource?: Uri): Promise<PythonInterpreter[]> {
const locators = this.getLocators();
const promises = locators.map(async provider => provider.getInterpreters(resource));
locators.forEach(locator => {
locator.hasInterpreters.then(found => {
if (found) {
this._hasInterpreters.resolve(true);
}
}).ignoreErrors();
});
const listOfInterpreters = await Promise.all(promises);

const items = flatten(listOfInterpreters)
.filter(item => !!item)
.map(item => item!);
this._hasInterpreters.resolve(items.length > 0);
return this.interpreterLocatorHelper.mergeInterpreters(items);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ export class KnownPathsService extends CacheableLocatorService {
if (!details) {
return;
}
this._hasInterpreters.resolve(true);
return {
...(details as PythonInterpreter),
path: interpreter,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ export class BaseVirtualEnvService extends CacheableLocatorService {
if (!details) {
return;
}
this._hasInterpreters.resolve(true);
return {
...(details as PythonInterpreter),
envName: virtualEnvName,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,18 +16,23 @@ import { IInterpreterLocatorService, IInterpreterWatcher, PythonInterpreter } fr

@injectable()
export abstract class CacheableLocatorService implements IInterpreterLocatorService {
protected readonly _hasInterpreters: Deferred<boolean>;
private readonly promisesPerResource = new Map<string, Deferred<PythonInterpreter[]>>();
private readonly handlersAddedToResource = new Set<string>();
private readonly cacheKeyPrefix: string;
private readonly locating = new EventEmitter<Promise<PythonInterpreter[]>>();
constructor(@unmanaged() name: string,
@unmanaged() protected readonly serviceContainer: IServiceContainer,
@unmanaged() private cachePerWorkspace: boolean = false) {
this._hasInterpreters = createDeferred<boolean>();
this.cacheKeyPrefix = `INTERPRETERS_CACHE_v2_${name}`;
}
public get onLocating(): Event<Promise<PythonInterpreter[]>> {
return this.locating.event;
}
public get hasInterpreters(): Promise<boolean> {
return this._hasInterpreters.promise;
}
public abstract dispose();
public async getInterpreters(resource?: Uri, ignoreCache?: boolean): Promise<PythonInterpreter[]> {
const cacheKey = this.getCacheKey(resource);
Expand All @@ -49,6 +54,10 @@ export abstract class CacheableLocatorService implements IInterpreterLocatorServ

this.locating.fire(deferred.promise);
}
deferred.promise
.then(items => this._hasInterpreters.resolve(items.length > 0))
.catch(_ => this._hasInterpreters.resolve(false));

if (deferred.completed) {
return deferred.promise;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ export class CondaEnvFileService extends CacheableLocatorService {
*
* This is used by CacheableLocatorService.getInterpreters().
*/
protected getInterpretersImplementation(resource?: Uri): Promise<PythonInterpreter[]> {
protected getInterpretersImplementation(_resource?: Uri): Promise<PythonInterpreter[]> {
return this.getSuggestionsFromConda();
}

Expand Down Expand Up @@ -103,6 +103,7 @@ export class CondaEnvFileService extends CacheableLocatorService {
return;
}
const envName = details.envName ? details.envName : path.basename(environmentPath);
this._hasInterpreters.resolve(true);
return {
...(details as PythonInterpreter),
path: interpreter,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ export class CondaEnvService extends CacheableLocatorService {
this.fileSystem,
this.helper
);
this._hasInterpreters.resolve(interpreters.length > 0);
const environments = await this.condaService.getCondaEnvironments(true);
if (Array.isArray(environments) && environments.length > 0) {
interpreters
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ export class CurrentPathService extends CacheableLocatorService {
if (!details) {
return;
}
this._hasInterpreters.resolve(true);
return {
...(details as PythonInterpreter),
path: pythonPath,
Expand Down
7 changes: 4 additions & 3 deletions src/client/interpreter/locators/services/pipEnvService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { inject, injectable } from 'inversify';
import * as path from 'path';
import { Uri } from 'vscode';
import { IApplicationShell, IWorkspaceService } from '../../../common/application/types';
import { traceError } from '../../../common/logger';
import { IFileSystem, IPlatformService } from '../../../common/platform/types';
import { IProcessServiceFactory } from '../../../common/process/types';
import { ICurrentProcess, ILogger } from '../../../common/types';
Expand Down Expand Up @@ -62,6 +63,7 @@ export class PipEnvService extends CacheableLocatorService implements IPipEnvSer
if (!details) {
return;
}
this._hasInterpreters.resolve(true);
return {
...(details as PythonInterpreter),
path: interpreterPath,
Expand All @@ -86,11 +88,10 @@ export class PipEnvService extends CacheableLocatorService implements IPipEnvSer
}
try {
const pythonPath = await this.invokePipenv('--py', cwd);
// TODO: Why do we need to do this?
return pythonPath && await this.fs.fileExists(pythonPath) ? pythonPath : undefined;
return (pythonPath && await this.fs.fileExists(pythonPath)) ? pythonPath : undefined;
// tslint:disable-next-line:no-empty
} catch (error) {
console.error(error);
traceError('PipEnv identification failed', error);
if (ignoreErrors) {
return;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,7 @@ export class WindowsRegistryService extends CacheableLocatorService {
return;
}
const version = interpreterInfo.version ? this.pathUtils.basename(interpreterInfo.version) : this.pathUtils.basename(tagKey);
this._hasInterpreters.resolve(true);
// tslint:disable-next-line:prefer-type-cast no-object-literal-type-assertion
return {
...(details as PythonInterpreter),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -108,8 +108,8 @@ suite('Application Diagnostics - Checks Python Interpreter', () => {
.returns(() => false)
.verifiable(typemoq.Times.once());
interpreterService
.setup(i => i.getInterpreters(typemoq.It.isAny()))
.returns(() => Promise.resolve([]))
.setup(i => i.hasInterpreters)
.returns(() => Promise.resolve(false))
.verifiable(typemoq.Times.once());

const diagnostics = await diagnosticService.diagnose();
Expand All @@ -122,10 +122,14 @@ suite('Application Diagnostics - Checks Python Interpreter', () => {
.setup(s => s.disableInstallationChecks)
.returns(() => false)
.verifiable(typemoq.Times.once());
interpreterService
.setup(i => i.hasInterpreters)
.returns(() => Promise.resolve(true))
.verifiable(typemoq.Times.once());
interpreterService
.setup(i => i.getInterpreters(typemoq.It.isAny()))
.returns(() => Promise.resolve([{} as any]))
.verifiable(typemoq.Times.once());
.verifiable(typemoq.Times.never());
interpreterService
.setup(i => i.getActiveInterpreter(typemoq.It.isAny()))
.returns(() => { return Promise.resolve({ type: InterpreterType.Unknown } as any); })
Expand All @@ -146,10 +150,14 @@ suite('Application Diagnostics - Checks Python Interpreter', () => {
.setup(s => s.disableInstallationChecks)
.returns(() => false)
.verifiable(typemoq.Times.once());
interpreterService
.setup(i => i.hasInterpreters)
.returns(() => Promise.resolve(true))
.verifiable(typemoq.Times.once());
interpreterService
.setup(i => i.getInterpreters(typemoq.It.isAny()))
.returns(() => Promise.resolve([{} as any]))
.verifiable(typemoq.Times.once());
.verifiable(typemoq.Times.never());
interpreterService
.setup(i => i.getActiveInterpreter(typemoq.It.isAny()))
.returns(() => { return Promise.resolve({ type: InterpreterType.Unknown } as any); })
Expand Down
Loading

0 comments on commit 209edb6

Please sign in to comment.