Skip to content
This repository has been archived by the owner on Sep 27, 2022. It is now read-only.

Commit

Permalink
Merge pull request #1 from sadasant/web/9665-8-refactoring
Browse files Browse the repository at this point in the history
Refactoring
  • Loading branch information
sadasant authored Jul 19, 2022
2 parents 52b30e3 + d59afc3 commit 9ef5978
Show file tree
Hide file tree
Showing 8 changed files with 128 additions and 148 deletions.
4 changes: 2 additions & 2 deletions package.nls.json
Original file line number Diff line number Diff line change
Expand Up @@ -673,8 +673,8 @@
},
"DataScience.serverNotStarted": "Not Started",
"DataScience.localJupyterServer": "local",
"DataScience.pandasTooOldForViewingFormat": "Python package 'pandas' is version {0}. Version 0.20 or greater is required for viewing data.",
"DataScience.pandasRequiredForViewing": "Python package 'pandas' is required for viewing data. Please ensure you have installed 'pandas' version {0} or above.",
"DataScience.pandasTooOldForViewingFormat": "Python package 'pandas' is version {0}. Version {1} or greater is required for viewing data.",
"DataScience.pandasRequiredForViewing": "Python package 'pandas' version {0} (or above) is required for viewing data.",
"DataScience.failedToGetVersionOfPandas": "Failed to get version of Pandas to use the Data Viewer.",
"DataScience.valuesColumn": "values",
"DataScience.liveShareInvalid": "One or more guests in the session do not have the Jupyter [extension](https://marketplace.visualstudio.com/itemdetails?itemName=ms-toolsai.jupyter) installed.\r\nYour Live Share session cannot continue and will be closed.",
Expand Down
4 changes: 2 additions & 2 deletions src/platform/common/utils/localize.ts
Original file line number Diff line number Diff line change
Expand Up @@ -800,15 +800,15 @@ export namespace DataScience {
key: 'DataScience.pandasTooOldForViewingFormat',
comment: ["{Locked='pandas'", 'This is the name of the pandas package']
},
"Python package 'pandas' is version {0}. Version 0.20 or greater is required for viewing data."
"Python package 'pandas' is version {0}. Version {1} or greater is required for viewing data."
);
export const pandasRequiredForViewing = () =>
localize(
{
key: 'DataScience.pandasRequiredForViewing',
comment: ["{Locked='pandas'", 'This is the name of the pandas package']
},
"Python package 'pandas' is required for viewing data. Please ensure you have installed 'pandas' version {0} or above."
"Python package 'pandas' version {0} (or above) is required for viewing data."
);
export const valuesColumn = () => localize('DataScience.valuesColumn', 'values');
export const liveShareInvalid = () =>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ suite('DataScience - DataViewerDependencyService (IKernel, Web)', () => {
const resultPromise = dependencyService.checkAndInstallMissingDependencies(kernel);
await assert.isRejected(
resultPromise,
DataScience.pandasTooOldForViewingFormat().format('0.20.'),
DataScience.pandasTooOldForViewingFormat().format('0.20.', pandasMinimumVersionSupportedByVariableViewer),
'Failed to identify too old pandas'
);
assert.deepEqual(
Expand All @@ -98,7 +98,7 @@ suite('DataScience - DataViewerDependencyService (IKernel, Web)', () => {
const resultPromise = dependencyService.checkAndInstallMissingDependencies(kernel);
await assert.isRejected(
resultPromise,
DataScience.pandasTooOldForViewingFormat().format('0.10.'),
DataScience.pandasTooOldForViewingFormat().format('0.10.', pandasMinimumVersionSupportedByVariableViewer),
'Failed to identify too old pandas'
);
assert.deepEqual(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,10 @@ suite('DataScience - DataViewerDependencyService (PythonEnvironment, Node)', ()

const promise = dependencyService.checkAndInstallMissingDependencies(interpreter);

await assert.isRejected(promise, DataScience.pandasTooOldForViewingFormat().format('0.20.'));
await assert.isRejected(
promise,
DataScience.pandasTooOldForViewingFormat().format('0.20.', pandasMinimumVersionSupportedByVariableViewer)
);
});
test('Throw exception if pandas is installed and version is < 0.20', async () => {
when(
Expand All @@ -81,7 +84,10 @@ suite('DataScience - DataViewerDependencyService (PythonEnvironment, Node)', ()

const promise = dependencyService.checkAndInstallMissingDependencies(interpreter);

await assert.isRejected(promise, DataScience.pandasTooOldForViewingFormat().format('0.10.'));
await assert.isRejected(
promise,
DataScience.pandasTooOldForViewingFormat().format('0.10.', pandasMinimumVersionSupportedByVariableViewer)
);
});
test('Prompt to install pandas and install pandas', async () => {
when(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ suite('DataScience - DataViewerDependencyService (IKernel, Node)', () => {
const resultPromise = dependencyService.checkAndInstallMissingDependencies(kernel);
await assert.isRejected(
resultPromise,
DataScience.pandasTooOldForViewingFormat().format('0.20.'),
DataScience.pandasTooOldForViewingFormat().format('0.20.', pandasMinimumVersionSupportedByVariableViewer),
'Failed to identify too old pandas'
);
assert.deepEqual(
Expand All @@ -116,7 +116,7 @@ suite('DataScience - DataViewerDependencyService (IKernel, Node)', () => {
const resultPromise = dependencyService.checkAndInstallMissingDependencies(kernel);
await assert.isRejected(
resultPromise,
DataScience.pandasTooOldForViewingFormat().format('0.10.'),
DataScience.pandasTooOldForViewingFormat().format('0.10.', pandasMinimumVersionSupportedByVariableViewer),
'Failed to identify too old pandas'
);
assert.deepEqual(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,24 +9,86 @@ import { IKernel } from '../../../kernels/types';
import { IDataViewerDependencyService } from './types';
import { pandasMinimumVersionSupportedByVariableViewer } from './constants';
import { PythonEnvironment } from '../../../platform/pythonEnvironments/info';
import { parseSemVer } from '../../../platform/common/utils';
import { SemVer } from 'semver';
import { captureTelemetry, sendTelemetryEvent, Telemetry } from '../../../telemetry';
import { ProductNames } from '../../../kernels/installer/productNames';
import { Product } from '../../../kernels/installer/types';
import { CancellationToken, CancellationTokenSource } from 'vscode';
import { Cancellation } from '../../../platform/common/cancellation';
import { traceWarning } from '../../../platform/logging';

/**
* base class of the data viewer dependency implementation.
*/
export abstract class BaseDataViewerDependencyImplementation implements IDataViewerDependencyService {
export abstract class BaseDataViewerDependencyImplementation<TExecuter> implements IDataViewerDependencyService {
constructor(private readonly applicationShell: IApplicationShell, private isCodeSpace: boolean) {}

abstract checkAndInstallMissingDependencies(executionEnvironment: IKernel | PythonEnvironment): Promise<void>;

protected async promptInstall(): Promise<boolean> {
protected abstract _getVersion(executer: TExecuter, token: CancellationToken): Promise<string | undefined>;
protected abstract _doInstall(executer: TExecuter, tokenSource: CancellationTokenSource): Promise<void>;

protected async getVersion(executer: TExecuter, token: CancellationToken): Promise<SemVer | undefined> {
try {
const version = await this._getVersion(executer, token);
return typeof version === 'string' ? parseSemVer(version) : version;
} catch (e) {
traceWarning(DataScience.failedToGetVersionOfPandas(), e.message);
return;
}
}

@captureTelemetry(Telemetry.PythonModuleInstall, {
action: 'displayed',
moduleName: ProductNames.get(Product.pandas)!
})
protected async promptInstall(
executer: TExecuter,
tokenSource: CancellationTokenSource,
version?: string
): Promise<void> {
let message = version
? DataScience.pandasTooOldForViewingFormat().format(version, pandasMinimumVersionSupportedByVariableViewer)
: DataScience.pandasRequiredForViewing().format(pandasMinimumVersionSupportedByVariableViewer);

let selection = this.isCodeSpace
? Common.install()
: await this.applicationShell.showErrorMessage(
DataScience.pandasRequiredForViewing().format(pandasMinimumVersionSupportedByVariableViewer),
{ modal: true },
Common.install()
);
: await this.applicationShell.showErrorMessage(message, { modal: true }, Common.install());

if (selection === Common.install()) {
await this._doInstall(executer, tokenSource);
} else {
sendTelemetryEvent(Telemetry.UserDidNotInstallPandas);
throw new Error(message);
}
}

protected async checkOrInstall(executer: TExecuter): Promise<void> {
const tokenSource = new CancellationTokenSource();

try {
const pandasVersion = await this.getVersion(executer, tokenSource.token);

if (Cancellation.isCanceled(tokenSource.token)) {
sendTelemetryEvent(Telemetry.PandasInstallCanceled);
return;
}

return selection === Common.install();
if (pandasVersion) {
if (pandasVersion.compare(pandasMinimumVersionSupportedByVariableViewer) > 0) {
sendTelemetryEvent(Telemetry.PandasOK);
return;
}
sendTelemetryEvent(Telemetry.PandasTooOld);
// Warn user that we cannot start because pandas is too old.
const versionStr = `${pandasVersion.major}.${pandasVersion.minor}.${pandasVersion.build}`;
await this.promptInstall(executer, tokenSource, versionStr);
}
sendTelemetryEvent(Telemetry.PandasNotInstalled);
await this.promptInstall(executer, tokenSource);
} finally {
tokenSource.dispose();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,26 +3,20 @@

'use strict';

import { SemVer } from 'semver';
import { CancellationToken, CancellationTokenSource } from 'vscode';
import { ProductNames } from '../../../kernels/installer/productNames';
import { IInstaller, Product, InstallerResponse } from '../../../kernels/installer/types';
import { IApplicationShell } from '../../../platform/common/application/types';
import { Cancellation, createPromiseFromCancellation } from '../../../platform/common/cancellation';
import { traceWarning } from '../../../platform/logging';
import { IPythonExecutionFactory } from '../../../platform/common/process/types.node';
import { parseSemVer } from '../../../platform/common/utils';
import { DataScience } from '../../../platform/common/utils/localize';
import { IInterpreterService } from '../../../platform/interpreter/contracts';
import { PythonEnvironment } from '../../../platform/pythonEnvironments/info';
import { sendTelemetryEvent, Telemetry } from '../../../telemetry';
import { pandasMinimumVersionSupportedByVariableViewer } from './constants';
import { BaseDataViewerDependencyImplementation } from './baseDataViewerDependencyImplementation';

/**
* Uses the Python interpreter to manage dependencies of a Data Viewer.
*/
export class InterpreterDataViewerDependencyImplementation extends BaseDataViewerDependencyImplementation {
export class InterpreterDataViewerDependencyImplementation extends BaseDataViewerDependencyImplementation<PythonEnvironment> {
constructor(
private readonly installer: IInstaller,
private pythonFactory: IPythonExecutionFactory,
Expand All @@ -33,48 +27,23 @@ export class InterpreterDataViewerDependencyImplementation extends BaseDataViewe
super(applicationShell, isCodeSpace);
}

public async checkAndInstallMissingDependencies(interpreter: PythonEnvironment): Promise<void> {
sendTelemetryEvent(Telemetry.DataViewerUsingInterpreter);

const tokenSource = new CancellationTokenSource();
try {
const pandasVersion = await this.getVersion(interpreter, tokenSource.token);

if (Cancellation.isCanceled(tokenSource.token)) {
sendTelemetryEvent(Telemetry.PandasInstallCanceled);
return;
}

if (pandasVersion) {
if (pandasVersion.compare(pandasMinimumVersionSupportedByVariableViewer) > 0) {
sendTelemetryEvent(Telemetry.PandasOK);
return;
}
sendTelemetryEvent(Telemetry.PandasTooOld);
// Warn user that we cannot start because pandas is too old.
const versionStr = `${pandasVersion.major}.${pandasVersion.minor}.${pandasVersion.build}`;
throw new Error(DataScience.pandasTooOldForViewingFormat().format(versionStr));
}

sendTelemetryEvent(Telemetry.PandasNotInstalled);
await this.installMissingDependencies(interpreter, tokenSource);
} finally {
tokenSource.dispose();
}
}

private async installMissingDependencies(
protected async _getVersion(
interpreter: PythonEnvironment,
tokenSource: CancellationTokenSource
): Promise<void> {
sendTelemetryEvent(Telemetry.PythonModuleInstall, undefined, {
action: 'displayed',
moduleName: ProductNames.get(Product.pandas)!,
pythonEnvType: interpreter?.envType
token?: CancellationToken
): Promise<string | undefined> {
const launcher = await this.pythonFactory.createActivatedEnvironment({
resource: undefined,
interpreter,
allowEnvironmentFetchExceptions: true
});
const result = await launcher.exec(['-c', 'import pandas;print(pandas.__version__)'], {
throwOnStdErr: true,
token
});
return result.stdout;
}

const doInstall = await this.promptInstall();

protected async _doInstall(interpreter: PythonEnvironment, tokenSource: CancellationTokenSource): Promise<void> {
// All data science dependencies require an interpreter to be passed in
// Default to the active interpreter if no interpreter is available
const interpreterToInstallDependenciesInto =
Expand All @@ -84,44 +53,24 @@ export class InterpreterDataViewerDependencyImplementation extends BaseDataViewe
return;
}

if (doInstall) {
const cancellationPromise = createPromiseFromCancellation({
cancelAction: 'resolve',
defaultValue: InstallerResponse.Ignore,
token: tokenSource.token
});
// Always pass a cancellation token to `install`, to ensure it waits until the module is installed.
const response = await Promise.race([
this.installer.install(Product.pandas, interpreterToInstallDependenciesInto, tokenSource),
cancellationPromise
]);
if (response === InstallerResponse.Installed) {
sendTelemetryEvent(Telemetry.UserInstalledPandas);
}
} else {
sendTelemetryEvent(Telemetry.UserDidNotInstallPandas);
throw new Error(
DataScience.pandasRequiredForViewing().format(pandasMinimumVersionSupportedByVariableViewer)
);
const cancellationPromise = createPromiseFromCancellation({
cancelAction: 'resolve',
defaultValue: InstallerResponse.Ignore,
token: tokenSource.token
});
// Always pass a cancellation token to `install`, to ensure it waits until the module is installed.
const response = await Promise.race([
this.installer.install(Product.pandas, interpreterToInstallDependenciesInto, tokenSource),
cancellationPromise
]);
if (response === InstallerResponse.Installed) {
sendTelemetryEvent(Telemetry.UserInstalledPandas);
}
}

private async getVersion(interpreter: PythonEnvironment, token?: CancellationToken): Promise<SemVer | undefined> {
const launcher = await this.pythonFactory.createActivatedEnvironment({
resource: undefined,
interpreter,
allowEnvironmentFetchExceptions: true
});
try {
const result = await launcher.exec(['-c', 'import pandas;print(pandas.__version__)'], {
throwOnStdErr: true,
token
});
public async checkAndInstallMissingDependencies(interpreter: PythonEnvironment): Promise<void> {
sendTelemetryEvent(Telemetry.DataViewerUsingInterpreter);

return parseSemVer(result.stdout);
} catch (ex) {
traceWarning('Failed to get version of Pandas to use Data Viewer', ex);
return;
}
await this.checkOrInstall(interpreter);
}
}
Loading

0 comments on commit 9ef5978

Please sign in to comment.