Skip to content

Commit

Permalink
Add API to consume new Python API (but no used) (#8324)
Browse files Browse the repository at this point in the history
  • Loading branch information
DonJayamanne authored Nov 22, 2021
1 parent 0822bed commit 402f1fa
Show file tree
Hide file tree
Showing 6 changed files with 107 additions and 6 deletions.
1 change: 0 additions & 1 deletion .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -565,7 +565,6 @@ module.exports = {
'src/client/common/installer/productService.ts',
'src/client/common/installer/pipInstaller.ts',
'src/client/common/process/currentProcess.ts',
'src/client/common/process/serviceRegistry.ts',
'src/client/common/process/pythonToolService.ts',
'src/client/common/process/internal/scripts/testing_tools.ts',
'src/client/common/process/pythonDaemonPool.ts',
Expand Down
11 changes: 11 additions & 0 deletions src/client/api/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import { InterpreterUri } from '../common/installer/types';
import { InstallerResponse, Product, Resource } from '../common/types';
import { IInterpreterQuickPickItem } from '../interpreter/configuration/types';
import { PythonEnvironment } from '../pythonEnvironments/info';
import type { SemVer } from 'semver';

export type ILanguageServerConnection = Pick<
lsp.ProtocolConnection,
'sendRequest' | 'sendNotification' | 'onProgress' | 'sendProgress' | 'onNotification' | 'onRequest'
Expand Down Expand Up @@ -111,6 +113,15 @@ export type PythonApi = {
* Registers a visibility filter for the interpreter status bar.
*/
registerInterpreterStatusFilter(filter: IInterpreterStatusbarVisibilityFilter): void;
getCondaVersion?(): Promise<SemVer | undefined>;
/**
* Returns the conda executable.
*/
getCondaFile?(): Promise<string | undefined>;
getEnvironmentActivationShellCommands?(
resource: Resource,
interpreter?: PythonEnvironment
): Promise<string[] | undefined>;
};

export const IPythonInstaller = Symbol('IPythonInstaller');
Expand Down
89 changes: 89 additions & 0 deletions src/client/common/process/condaService.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

import { inject, injectable, named } from 'inversify';
import { SemVer } from 'semver';
import { Memento } from 'vscode';
import { IPythonApiProvider } from '../../api/types';
import { IFileSystem } from '../platform/types';
import { GLOBAL_MEMENTO, IMemento } from '../types';
import { createDeferredFromPromise } from '../utils/async';

const CACHEKEY_FOR_CONDA_INFO = 'CONDA_INFORMATION_CACHE';

@injectable()
export class CondaService {
private _file?: string;
private _version?: SemVer;
constructor(
@inject(IPythonApiProvider) private readonly pythonApi: IPythonApiProvider,
@inject(IMemento) @named(GLOBAL_MEMENTO) private readonly globalState: Memento,
@inject(IFileSystem) private readonly fs: IFileSystem
) {}
async getCondaVersion() {
if (this._version) {
return this._version;
}
const latestInfo = this.pythonApi
.getApi()
.then((api) => (api.getCondaVersion ? api.getCondaVersion() : undefined));
void latestInfo.then((version) => {
this._version = version;
void this.udpateCache();
});
const cachedInfo = createDeferredFromPromise(this.getCachedInformation());
await Promise.race([cachedInfo, latestInfo]);
if (cachedInfo.completed && cachedInfo.value?.version) {
return (this._version = cachedInfo.value.version);
}
return latestInfo;
}
async getCondaFile() {
if (this._file) {
return this._file;
}
const latestInfo = this.pythonApi.getApi().then((api) => (api.getCondaFile ? api.getCondaFile() : undefined));
void latestInfo.then((file) => {
this._file = file;
void this.udpateCache();
});
const cachedInfo = createDeferredFromPromise(this.getCachedInformation());
await Promise.race([cachedInfo, latestInfo]);
if (cachedInfo.completed && cachedInfo.value?.file) {
return (this._file = cachedInfo.value.file);
}
return latestInfo;
}
private async udpateCache() {
if (!this._file || !this._version) {
return;
}
const fileHash = await this.fs.getFileHash(this._file);
await this.globalState.update(CACHEKEY_FOR_CONDA_INFO, {
version: this._version.raw,
file: this._file,
fileHash
});
}
/**
* If the last modified date of the conda file is the same as when we last checked,
* then we can assume the version is the same.
* Even if not, we'll update this with the latest information.
*/
private async getCachedInformation(): Promise<{ version: SemVer; file: string } | undefined> {
const cachedInfo = this.globalState.get<{ version: string; file: string; fileHash: string } | undefined>(
CACHEKEY_FOR_CONDA_INFO,
undefined
);
if (!cachedInfo) {
return;
}
const fileHash = await this.fs.getFileHash(cachedInfo.file);
if (cachedInfo.fileHash === fileHash) {
return {
version: new SemVer(cachedInfo.version),
file: cachedInfo.file
};
}
}
}
2 changes: 1 addition & 1 deletion src/client/common/process/pythonExecutionFactory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ export class PythonExecutionFactory implements IPythonExecutionFactory {
);
}

@traceDecorators.verbose('Create daemon')
@traceDecorators.verbose('Create daemon', TraceOptions.BeforeCall | TraceOptions.Arguments)
public async createDaemon<T extends IPythonDaemonExecutionService | IDisposable>(
options: DaemonExecutionFactoryCreationOptions
): Promise<T | IPythonExecutionService> {
Expand Down
2 changes: 2 additions & 0 deletions src/client/common/process/serviceRegistry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// Licensed under the MIT License.

import { IServiceManager } from '../../ioc/types';
import { CondaService } from './condaService';
import { BufferDecoder } from './decoder';
import { ProcessServiceFactory } from './processFactory';
import { PythonExecutionFactory } from './pythonExecutionFactory';
Expand All @@ -11,4 +12,5 @@ export function registerTypes(serviceManager: IServiceManager) {
serviceManager.addSingleton<IBufferDecoder>(IBufferDecoder, BufferDecoder);
serviceManager.addSingleton<IProcessServiceFactory>(IProcessServiceFactory, ProcessServiceFactory);
serviceManager.addSingleton<IPythonExecutionFactory>(IPythonExecutionFactory, PythonExecutionFactory);
serviceManager.addSingleton<CondaService>(CondaService, CondaService);
}
8 changes: 4 additions & 4 deletions src/test/common/process/pythonEnvironment.unit.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -231,7 +231,7 @@ suite('CondaEnvironment', () => {
});

test('getExecutionInfo with a named environment should return execution info using the environment name', () => {
const condaInfo = { name: 'foo', path: 'bar' };
const condaInfo = { name: 'foo', path: 'bar', version: undefined };
const env = createCondaEnv(condaFile, condaInfo, pythonPath, processService.object, fileSystem.object);

const result = env.getExecutionInfo(args);
Expand All @@ -245,7 +245,7 @@ suite('CondaEnvironment', () => {
});

test('getExecutionInfo with a non-named environment should return execution info using the environment path', () => {
const condaInfo = { name: '', path: 'bar' };
const condaInfo = { name: '', path: 'bar', version: undefined };
const env = createCondaEnv(condaFile, condaInfo, pythonPath, processService.object, fileSystem.object);

const result = env.getExecutionInfo(args);
Expand All @@ -260,7 +260,7 @@ suite('CondaEnvironment', () => {

test('getExecutionObservableInfo with a named environment should return execution info using pythonPath only', () => {
const expected = { command: pythonPath, args, python: [pythonPath], pythonExecutable: pythonPath };
const condaInfo = { name: 'foo', path: 'bar' };
const condaInfo = { name: 'foo', path: 'bar', version: undefined };
const env = createCondaEnv(condaFile, condaInfo, pythonPath, processService.object, fileSystem.object);

const result = env.getExecutionObservableInfo(args);
Expand All @@ -270,7 +270,7 @@ suite('CondaEnvironment', () => {

test('getExecutionObservableInfo with a non-named environment should return execution info using pythonPath only', () => {
const expected = { command: pythonPath, args, python: [pythonPath], pythonExecutable: pythonPath };
const condaInfo = { name: '', path: 'bar' };
const condaInfo = { name: '', path: 'bar', version: undefined };
const env = createCondaEnv(condaFile, condaInfo, pythonPath, processService.object, fileSystem.object);

const result = env.getExecutionObservableInfo(args);
Expand Down

0 comments on commit 402f1fa

Please sign in to comment.