Skip to content

Commit 58b5c80

Browse files
author
David Kutugata
authored
Install jupyter if its not found (#6421)
* fixed issue 5682, promt user to install jupyter if its not found * moved the jupyter installation to codewatcher * changed the error handler to be its own separate object * removed serviceContainer from the data science error handler * added jupyter to the productService map * added jupyter to a function in the productInstaller * added the data science productType * excluded jupyter installation from a test * removed jupyter from product installer * added jupyter back to product installer * added a productPath test for dataScience * excluded jupyter from checking if its installed on the product installer * Added DataScienceProductPathService to 3 tests * added done(to two tests) * added a binding of IDataScienceErrorHandler * added IInstallationChannelManager binding * added jupyter as an exception to 'getNamesAndValues' test * added jupyter as an exception to the 'getNamesAndValues' test * localized a new string from productInstaller.ts
1 parent a4c6eed commit 58b5c80

24 files changed

Lines changed: 300 additions & 201 deletions

news/2 Fixes/5682.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
The extension will now prompt to auto install jupyter in case its not found.

package.nls.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,8 @@
143143
"Linter.enableLinter": "Enable {0}",
144144
"Linter.enablePylint": "You have a pylintrc file in your workspace. Do you want to enable pylint?",
145145
"Linter.replaceWithSelectedLinter": "Multiple linters are enabled in settings. Replace with '{0}'?",
146+
"DataScience.libraryNotInstalled": "Data Science library {0} is not installed. Install?",
147+
"DataScience.jupyterInstall": "Install",
146148
"DataScience.jupyterSelectURILaunchLocal": "Launch a local Jupyter server when needed",
147149
"DataScience.jupyterSelectURISpecifyURI": "Type in the URI to connect to a running Jupyter server",
148150
"DataScience.jupyterSelectURIPrompt": "Enter the URI of a Jupyter server",

src/client/common/installer/productInstaller.ts

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { inject, injectable, named } from 'inversify';
44
import * as os from 'os';
55
import { OutputChannel, Uri } from 'vscode';
66
import '../../common/extensions';
7+
import * as localize from '../../common/utils/localize';
78
import { IServiceContainer } from '../../ioc/types';
89
import { LinterId } from '../../linters/types';
910
import { sendTelemetryEvent } from '../../telemetry';
@@ -76,7 +77,7 @@ export abstract class BaseInstaller {
7677
}
7778

7879
public async isInstalled(product: Product, resource?: Uri): Promise<boolean | undefined> {
79-
if (product === Product.unittest) {
80+
if (product === Product.unittest || product === Product.jupyter) {
8081
return true;
8182
}
8283
// User may have customized the module name or provided the fully qualified path.
@@ -271,6 +272,14 @@ export class RefactoringLibraryInstaller extends BaseInstaller {
271272
}
272273
}
273274

275+
export class DataScienceInstaller extends BaseInstaller {
276+
protected async promptToInstallImplementation(product: Product, resource?: Uri): Promise<InstallerResponse> {
277+
const productName = ProductNames.get(product)!;
278+
const item = await this.appShell.showErrorMessage(localize.DataScience.libraryNotInstalled().format(productName), 'Yes', 'No');
279+
return item === 'Yes' ? this.install(product, resource) : InstallerResponse.Ignore;
280+
}
281+
}
282+
274283
@injectable()
275284
export class ProductInstaller implements IInstaller {
276285
private readonly productService: IProductService;
@@ -307,6 +316,8 @@ export class ProductInstaller implements IInstaller {
307316
return new TestFrameworkInstaller(this.serviceContainer, this.outputChannel);
308317
case ProductType.RefactoringLibrary:
309318
return new RefactoringLibraryInstaller(this.serviceContainer, this.outputChannel);
319+
case ProductType.DataScience:
320+
return new DataScienceInstaller(this.serviceContainer, this.outputChannel);
310321
default:
311322
break;
312323
}
@@ -333,6 +344,7 @@ function translateProductToModule(product: Product, purpose: ModuleNamePurpose):
333344
case Product.unittest: return 'unittest';
334345
case Product.rope: return 'rope';
335346
case Product.bandit: return 'bandit';
347+
case Product.jupyter: return 'jupyter';
336348
default: {
337349
throw new Error(`Product ${product} cannot be installed as a Python Module.`);
338350
}

src/client/common/installer/productNames.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,3 +19,4 @@ ProductNames.set(Product.pylint, 'pylint');
1919
ProductNames.set(Product.pytest, 'pytest');
2020
ProductNames.set(Product.yapf, 'yapf');
2121
ProductNames.set(Product.rope, 'rope');
22+
ProductNames.set(Product.jupyter, 'jupyter');

src/client/common/installer/productPath.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,3 +99,13 @@ export class RefactoringLibraryProductPathService extends BaseProductPathsServic
9999
return this.productInstaller.translateProductToModuleName(product, ModuleNamePurpose.run);
100100
}
101101
}
102+
103+
@injectable()
104+
export class DataScienceProductPathService extends BaseProductPathsService {
105+
constructor(@inject(IServiceContainer) serviceContainer: IServiceContainer) {
106+
super(serviceContainer);
107+
}
108+
public getExecutableNameFromSettings(product: Product, _?: Uri): string {
109+
return this.productInstaller.translateProductToModuleName(product, ModuleNamePurpose.run);
110+
}
111+
}

src/client/common/installer/productService.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ export class ProductService implements IProductService {
2828
this.ProductTypes.set(Product.black, ProductType.Formatter);
2929
this.ProductTypes.set(Product.yapf, ProductType.Formatter);
3030
this.ProductTypes.set(Product.rope, ProductType.RefactoringLibrary);
31+
this.ProductTypes.set(Product.jupyter, ProductType.DataScience);
3132
}
3233
public getProductType(product: Product): ProductType {
3334
return this.ProductTypes.get(product)!;

src/client/common/installer/serviceRegistry.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import { CondaInstaller } from './condaInstaller';
1111
import { PipEnvInstaller } from './pipEnvInstaller';
1212
import { PipInstaller } from './pipInstaller';
1313
import { PoetryInstaller } from './poetryInstaller';
14-
import { CTagsProductPathService, FormatterProductPathService, LinterProductPathService, RefactoringLibraryProductPathService, TestFrameworkProductPathService } from './productPath';
14+
import { CTagsProductPathService, DataScienceProductPathService, FormatterProductPathService, LinterProductPathService, RefactoringLibraryProductPathService, TestFrameworkProductPathService } from './productPath';
1515
import { ProductService } from './productService';
1616
import { IInstallationChannelManager, IModuleInstaller, IProductPathService, IProductService } from './types';
1717

@@ -28,5 +28,6 @@ export function registerTypes(serviceManager: IServiceManager) {
2828
serviceManager.addSingleton<IProductPathService>(IProductPathService, LinterProductPathService, ProductType.Linter);
2929
serviceManager.addSingleton<IProductPathService>(IProductPathService, TestFrameworkProductPathService, ProductType.TestFramework);
3030
serviceManager.addSingleton<IProductPathService>(IProductPathService, RefactoringLibraryProductPathService, ProductType.RefactoringLibrary);
31+
serviceManager.addSingleton<IProductPathService>(IProductPathService, DataScienceProductPathService, ProductType.DataScience);
3132
serviceManager.addSingleton<IWebPanelProvider>(IWebPanelProvider, WebPanelProvider);
3233
}

src/client/common/types.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,8 @@ export enum ProductType {
7474
Formatter = 'Formatter',
7575
TestFramework = 'TestFramework',
7676
RefactoringLibrary = 'RefactoringLibrary',
77-
WorkspaceSymbols = 'WorkspaceSymbols'
77+
WorkspaceSymbols = 'WorkspaceSymbols',
78+
DataScience = 'DataScience'
7879
}
7980

8081
export enum Product {
@@ -94,7 +95,8 @@ export enum Product {
9495
rope = 14,
9596
isort = 15,
9697
black = 16,
97-
bandit = 17
98+
bandit = 17,
99+
jupyter = 18
98100
}
99101

100102
export enum ModuleNamePurpose {

src/client/common/utils/localize.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,8 @@ export namespace DataScience {
104104
export const notebookCheckForImportYes = localize('DataScience.notebookCheckForImportYes', 'Import');
105105
export const notebookCheckForImportNo = localize('DataScience.notebookCheckForImportNo', 'Later');
106106
export const notebookCheckForImportDontAskAgain = localize('DataScience.notebookCheckForImportDontAskAgain', 'Don\'t Ask Again');
107+
export const libraryNotInstalled = localize('DataScience.libraryNotInstalled', 'Data Science library {0} is not installed. Install?');
108+
export const jupyterInstall = localize('DataScience.jupyterInstall', 'Install');
107109
export const jupyterNotSupported = localize('DataScience.jupyterNotSupported', 'Jupyter is not installed');
108110
export const jupyterNotSupportedBecauseOfEnvironment = localize('DataScience.jupyterNotSupportedBecauseOfEnvironment', 'Activating {0} to run Jupyter failed with {1}');
109111
export const jupyterNbConvertNotSupported = localize('DataScience.jupyterNbConvertNotSupported', 'Jupyter nbconvert is not installed');

src/client/datascience/editor-integration/codewatcher.ts

Lines changed: 27 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -4,17 +4,18 @@
44
import { inject, injectable } from 'inversify';
55
import { CodeLens, Position, Range, Selection, TextDocument, TextEditor, TextEditorRevealType } from 'vscode';
66

7-
import { IApplicationShell, IDocumentManager } from '../../common/application/types';
7+
import { IDocumentManager } from '../../common/application/types';
88
import { IFileSystem } from '../../common/platform/types';
9-
import { IConfigurationService, IDataScienceSettings, ILogger } from '../../common/types';
10-
import { noop } from '../../common/utils/misc';
9+
import { IConfigurationService, IDataScienceSettings } from '../../common/types';
10+
// import * as localize from '../../common/utils/localize';
1111
import { StopWatch } from '../../common/utils/stopWatch';
12+
import { IServiceContainer } from '../../ioc/types';
1213
import { captureTelemetry } from '../../telemetry';
1314
import { ICodeExecutionHelper } from '../../terminals/types';
1415
import { Commands, Telemetry } from '../constants';
15-
import { JupyterInstallError } from '../jupyter/jupyterInstallError';
16-
import { JupyterSelfCertsError } from '../jupyter/jupyterSelfCertsError';
17-
import { ICodeLensFactory, ICodeWatcher, IInteractiveWindowProvider } from '../types';
16+
// import { JupyterInstallError } from '../jupyter/jupyterInstallError';
17+
// import { JupyterSelfCertsError } from '../jupyter/jupyterSelfCertsError';
18+
import { ICodeLensFactory, ICodeWatcher, IDataScienceErrorHandler, IInteractiveWindowProvider } from '../types';
1819

1920
@injectable()
2021
export class CodeWatcher implements ICodeWatcher {
@@ -24,15 +25,15 @@ export class CodeWatcher implements ICodeWatcher {
2425
private codeLenses: CodeLens[] = [];
2526
private cachedSettings: IDataScienceSettings | undefined;
2627

27-
constructor(@inject(IApplicationShell) private applicationShell: IApplicationShell,
28-
@inject(ILogger) private logger: ILogger,
29-
@inject(IInteractiveWindowProvider) private interactiveWindowProvider : IInteractiveWindowProvider,
30-
@inject(IFileSystem) private fileSystem: IFileSystem,
31-
@inject(IConfigurationService) private configService: IConfigurationService,
32-
@inject(IDocumentManager) private documentManager : IDocumentManager,
33-
@inject(ICodeExecutionHelper) private executionHelper: ICodeExecutionHelper,
34-
@inject(ICodeLensFactory) private codeLensFactory: ICodeLensFactory
35-
) {
28+
constructor(@inject(IInteractiveWindowProvider) private interactiveWindowProvider: IInteractiveWindowProvider,
29+
@inject(IFileSystem) private fileSystem: IFileSystem,
30+
@inject(IConfigurationService) private configService: IConfigurationService,
31+
@inject(IDocumentManager) private documentManager: IDocumentManager,
32+
@inject(ICodeExecutionHelper) private executionHelper: ICodeExecutionHelper,
33+
@inject(IDataScienceErrorHandler) protected dataScienceErrorHandler: IDataScienceErrorHandler,
34+
@inject(ICodeLensFactory) private codeLensFactory: ICodeLensFactory,
35+
@inject(IServiceContainer) protected serviceContainer: IServiceContainer
36+
) {
3637
}
3738

3839
public setDocument(document: TextDocument) {
@@ -57,7 +58,7 @@ export class CodeWatcher implements ICodeWatcher {
5758
return this.version;
5859
}
5960

60-
public getCachedSettings() : IDataScienceSettings | undefined {
61+
public getCachedSettings(): IDataScienceSettings | undefined {
6162
return this.cachedSettings;
6263
}
6364

@@ -143,13 +144,13 @@ export class CodeWatcher implements ICodeWatcher {
143144
}
144145

145146
@captureTelemetry(Telemetry.RunSelectionOrLine)
146-
public async runSelectionOrLine(activeEditor : TextEditor | undefined) {
147+
public async runSelectionOrLine(activeEditor: TextEditor | undefined) {
147148
if (this.document && activeEditor &&
148149
this.fileSystem.arePathsSame(activeEditor.document.fileName, this.document.fileName)) {
149150
// Get just the text of the selection or the current line if none
150151
const codeToExecute = await this.executionHelper.getSelectedTextToExecute(activeEditor);
151152
if (!codeToExecute) {
152-
return ;
153+
return;
153154
}
154155
const normalizedCode = await this.executionHelper.normalizeLines(codeToExecute!);
155156
if (!normalizedCode || normalizedCode.trim().length === 0) {
@@ -184,7 +185,7 @@ export class CodeWatcher implements ICodeWatcher {
184185
}
185186

186187
@captureTelemetry(Telemetry.RunCell)
187-
public runCell(range: Range) : Promise<void> {
188+
public runCell(range: Range): Promise<void> {
188189
if (!this.documentManager.activeTextEditor || !this.documentManager.activeTextEditor.document) {
189190
return Promise.resolve();
190191
}
@@ -195,7 +196,7 @@ export class CodeWatcher implements ICodeWatcher {
195196
}
196197

197198
@captureTelemetry(Telemetry.DebugCurrentCell)
198-
public debugCell(range: Range) : Promise<void> {
199+
public debugCell(range: Range): Promise<void> {
199200
if (!this.documentManager.activeTextEditor || !this.documentManager.activeTextEditor.document) {
200201
return Promise.resolve();
201202
}
@@ -205,7 +206,7 @@ export class CodeWatcher implements ICodeWatcher {
205206
}
206207

207208
@captureTelemetry(Telemetry.RunCurrentCell)
208-
public runCurrentCell() : Promise<void> {
209+
public runCurrentCell(): Promise<void> {
209210
if (!this.documentManager.activeTextEditor || !this.documentManager.activeTextEditor.document) {
210211
return Promise.resolve();
211212
}
@@ -224,7 +225,7 @@ export class CodeWatcher implements ICodeWatcher {
224225
return this.runMatchingCell(this.documentManager.activeTextEditor.selection, true);
225226
}
226227

227-
public async addEmptyCellToBottom() : Promise<void> {
228+
public async addEmptyCellToBottom(): Promise<void> {
228229
const editor = this.documentManager.activeTextEditor;
229230
if (editor) {
230231
editor.edit((editBuilder) => {
@@ -236,7 +237,7 @@ export class CodeWatcher implements ICodeWatcher {
236237
}
237238
}
238239

239-
private async addCode(code: string, file: string, line: number, editor?: TextEditor, debug?: boolean) : Promise<void> {
240+
private async addCode(code: string, file: string, line: number, editor?: TextEditor, debug?: boolean): Promise<void> {
240241
try {
241242
const stopWatch = new StopWatch();
242243
const activeInteractiveWindow = await this.interactiveWindowProvider.getOrCreateActive();
@@ -246,7 +247,7 @@ export class CodeWatcher implements ICodeWatcher {
246247
await activeInteractiveWindow.addCode(code, file, line, editor, stopWatch);
247248
}
248249
} catch (err) {
249-
this.handleError(err);
250+
this.dataScienceErrorHandler.handleError(err);
250251
}
251252
}
252253

@@ -279,11 +280,11 @@ export class CodeWatcher implements ICodeWatcher {
279280
}
280281
}
281282

282-
private getCurrentCellLens(pos: Position) : CodeLens | undefined {
283+
private getCurrentCellLens(pos: Position): CodeLens | undefined {
283284
return this.codeLenses.find(l => l.range.contains(pos) && l.command !== undefined && l.command.command === Commands.RunCell);
284285
}
285286

286-
private getNextCellLens(pos: Position) : CodeLens | undefined {
287+
private getNextCellLens(pos: Position): CodeLens | undefined {
287288
const currentIndex = this.codeLenses.findIndex(l => l.range.contains(pos) && l.command !== undefined && l.command.command === Commands.RunCell);
288289
if (currentIndex >= 0) {
289290
return this.codeLenses.find((l: CodeLens, i: number) => l.command !== undefined && l.command.command === Commands.RunCell && i > currentIndex);
@@ -298,29 +299,6 @@ export class CodeWatcher implements ICodeWatcher {
298299
}
299300
}
300301

301-
// tslint:disable-next-line:no-any
302-
private handleError = (err : any) => {
303-
if (err instanceof JupyterInstallError) {
304-
const jupyterError = err as JupyterInstallError;
305-
306-
// This is a special error that shows a link to open for more help
307-
this.applicationShell.showErrorMessage(jupyterError.message, jupyterError.actionTitle).then(v => {
308-
// User clicked on the link, open it.
309-
if (v === jupyterError.actionTitle) {
310-
this.applicationShell.openUrl(jupyterError.action);
311-
}
312-
});
313-
} else if (err instanceof JupyterSelfCertsError) {
314-
// Don't show the message for self cert errors
315-
noop();
316-
} else if (err.message) {
317-
this.applicationShell.showErrorMessage(err.message);
318-
} else {
319-
this.applicationShell.showErrorMessage(err.toString());
320-
}
321-
this.logger.logError(err);
322-
}
323-
324302
// User has picked run and advance on the last cell of a document
325303
// Create a new cell at the bottom and put their selection there, ready to type
326304
private createNewCell(currentRange: Range): Range {

0 commit comments

Comments
 (0)