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 API to consume new Python API (but no used) #8324

Merged
merged 2 commits into from
Nov 22, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
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?(
Copy link
Contributor Author

Choose a reason for hiding this comment

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

New API exposed by python extension. Possible users don't have the latest python extenison hence we need to take that into account.

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 {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Exposes the CondaVersion, CondaFile behind a class with caching & wraps the Python API call (possible its not available, etc)

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> {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Comments added how & why we cache

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