Skip to content

Commit 9c10e42

Browse files
authored
More useful error messages for jupyter not being installed (#3234)
1 parent e40ae84 commit 9c10e42

File tree

13 files changed

+164
-56
lines changed

13 files changed

+164
-56
lines changed

.travis.yml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,6 @@ script:
9595
fi
9696
- if [ $FUNCTIONAL_TEST == "true" ]; then
9797
python -m pip install --upgrade -r functionalTestRequirements.txt;
98-
npm run cover:enable;
9998
npm run test:functional;
10099
fi
101100
- if [ $SINGLE_WORKSPACE_TEST == "true" ]; then
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
# Trouble shooting the Python Interactive Window
2+
3+
This document is intended to help troubleshoot problems in the Python Interactive Window.
4+
5+
---
6+
## Jupyter Not Installed
7+
This error can happen when you
8+
9+
* Don't have Jupyter installed
10+
* Have picked the wrong Python environment (one that doesn't have Jupyter installed).
11+
12+
### The first step is to verify you are running the Python environment you want.
13+
14+
You can do this by either selecting it in the dropdown at the bottom of VS Code:
15+
16+
![selector](resources/PythonSelector.png)
17+
18+
Or by running some Python code in VS Code Python terminal:
19+
```python
20+
import sys
21+
sys.version
22+
```
23+
24+
### The second step (if changing the Python version doesn't work) is to install Jupyter
25+
26+
You can do this in a number of different ways:
27+
28+
#### Anaconda
29+
30+
Anaconda is a popular Python distribution. It makes it super easy to get Jupyter up and running.
31+
32+
If you're already using Anaconda, follow these steps to get jupyter
33+
1. Start anaconda environment
34+
1. Run 'conda install jupyter'
35+
1. Restart VS Code
36+
1. Pick the conda version of Python in the python selector
37+
38+
Otherwise you can install Anaconda and pick the default options
39+
https://www.anaconda.com/download
40+
41+
42+
#### Pip
43+
44+
You can also install Jupyter using pip. This is a little more work because you have to specify a number of sub packages too
45+
46+
1. python -m pip install jupyter
47+
1. python -m pip install notebook
48+
1. python -m pip install nbconvert
49+
1. Restart VS Code
50+
1. Pick the Python environment you did the pip install in

package.nls.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -75,8 +75,9 @@
7575
"DataScience.notebookCheckForImportNo": "Later",
7676
"DataScience.notebookCheckForImportDontAskAgain": "Don't Ask Again",
7777
"DataScience.notebookCheckForImportTitle": "Do you want to import the Jupyter Notebook into Python code?",
78-
"DataScience.jupyterNotSupported": "Jupyter is not installed",
79-
"DataScience.jupyterNbConvertNotSupported": "Jupyter nbconvert is not installed",
78+
"DataScience.jupyterNotSupported": "Running cells requires Jupyter notebooks to be installed.",
79+
"DataScience.jupyterNbConvertNotSupported": "Importing notebooks requires Jupyter nbconvert to be installed.",
80+
"DataScience.pythonInteractiveHelpLink" : "Get more help",
8081
"DataScience.importingFormat": "Importing {0}",
8182
"DataScience.startingJupyter": "Starting Jupyter Server",
8283
"Interpreters.RefreshingInterpreters": "Refreshing Python Interpreters",

resources/PythonSelector.png

6.3 KB
Loading

src/client/common/utils/localize.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ export namespace DataScience {
4444
export const notebookCheckForImportDontAskAgain = localize('DataScience.notebookCheckForImportDontAskAgain', 'Don\'t Ask Again');
4545
export const jupyterNotSupported = localize('DataScience.jupyterNotSupported', 'Jupyter is not installed');
4646
export const jupyterNbConvertNotSupported = localize('DataScience.jupyterNbConvertNotSupported', 'Jupyter nbconvert is not installed');
47+
export const pythonInteractiveHelpLink = localize('DataScience.pythonInteractiveHelpLink', 'See [https://aka.ms/pyaiinstall] for help on installing jupyter.');
4748
export const importingFormat = localize('DataScience.importingFormat', 'Importing {0}');
4849
export const startingJupyter = localize('DataScience.startingJupyter', 'Starting Jupyter Server');
4950
export const runAllCellsLensCommandTitle = localize('python.command.python.datascience.runallcells.title', 'Run all cells');

src/client/datascience/constants.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,3 +58,7 @@ export namespace Telemetry {
5858
export const ExpandAll = 'DATASCIENCE.EXPAND_ALL';
5959
export const CollapseAll = 'DATASCIENCE.COLLAPSE_ALL';
6060
}
61+
62+
export namespace HelpLinks {
63+
export const PythonInteractiveHelpLink = 'https://aka.ms/pyaiinstall';
64+
}

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

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,12 @@ import { CodeLens, Command, Position, Range, Selection, TextDocument, TextEditor
55

66
import { IApplicationShell, ICommandManager } from '../../common/application/types';
77
import { ContextKey } from '../../common/contextKey';
8+
import { ILogger } from '../../common/types';
89
import * as localize from '../../common/utils/localize';
910
import { IServiceContainer } from '../../ioc/types';
1011
import { captureTelemetry } from '../../telemetry';
1112
import { Commands, EditorContexts, RegExpValues, Telemetry } from '../constants';
13+
import { JupyterInstallError } from '../jupyterInstallError';
1214
import { ICodeWatcher, IHistoryProvider } from '../types';
1315

1416
export interface ICell {
@@ -24,11 +26,13 @@ export class CodeWatcher implements ICodeWatcher {
2426
private historyProvider: IHistoryProvider;
2527
private commandManager: ICommandManager;
2628
private applicationShell: IApplicationShell;
29+
private logger: ILogger;
2730

2831
constructor(serviceContainer: IServiceContainer, document: TextDocument) {
2932
this.historyProvider = serviceContainer.get<IHistoryProvider>(IHistoryProvider);
3033
this.commandManager = serviceContainer.get<ICommandManager>(ICommandManager);
3134
this.applicationShell = serviceContainer.get<IApplicationShell>(IApplicationShell);
35+
this.logger = serviceContainer.get<ILogger>(ILogger);
3236

3337
this.document = document;
3438

@@ -95,7 +99,7 @@ export class CodeWatcher implements ICodeWatcher {
9599
try {
96100
await activeHistory.addCode(code, this.getFileName(), range.start.line, window.activeTextEditor);
97101
} catch (err) {
98-
this.applicationShell.showErrorMessage(err);
102+
this.handleError(err);
99103
}
100104

101105
}
@@ -156,6 +160,26 @@ export class CodeWatcher implements ICodeWatcher {
156160
}
157161
}
158162

163+
// tslint:disable-next-line:no-any
164+
private handleError = (err : any) => {
165+
if ((<JupyterInstallError>err).actionTitle !== undefined) {
166+
const jupyterError = err as JupyterInstallError;
167+
168+
// This is a special error that shows a link to open for more help
169+
this.applicationShell.showErrorMessage(jupyterError.message, jupyterError.actionTitle).then(v => {
170+
// User clicked on the link, open it.
171+
if (v === jupyterError.actionTitle) {
172+
this.applicationShell.openUrl(jupyterError.action);
173+
}
174+
});
175+
} else if (err.message) {
176+
this.applicationShell.showErrorMessage(err.message);
177+
} else {
178+
this.applicationShell.showErrorMessage(err.toString());
179+
}
180+
this.logger.logError(err);
181+
}
182+
159183
// User has picked run and advance on the last cell of a document
160184
// Create a new cell at the bottom and put their selection there, ready to type
161185
private createNewCell(currentRange: Range): Range {

src/client/datascience/history.ts

Lines changed: 45 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -22,14 +22,14 @@ import * as localize from '../common/utils/localize';
2222
import { IInterpreterService } from '../interpreter/contracts';
2323
import { captureTelemetry, sendTelemetryEvent } from '../telemetry';
2424
import { HistoryMessages, Telemetry } from './constants';
25-
import { CellState, ICell, ICodeCssGenerator, IHistory, INotebookServer, IStatusProvider } from './types';
25+
import { JupyterInstallError } from './jupyterInstallError';
26+
import { CellState, ICell, ICodeCssGenerator, IHistory, IJupyterExecution, INotebookServer, IStatusProvider } from './types';
2627

2728
@injectable()
2829
export class History implements IWebPanelMessageListener, IHistory {
2930
private disposed : boolean = false;
3031
private webPanel : IWebPanel | undefined;
31-
// tslint:disable-next-line: no-any
32-
private loadPromise: Promise<any>;
32+
private loadPromise: Promise<void>;
3333
private settingsChangedDisposable : Disposable;
3434
private closedEvent : EventEmitter<IHistory>;
3535
private unfinishedCells: ICell[] = [];
@@ -44,7 +44,8 @@ export class History implements IWebPanelMessageListener, IHistory {
4444
@inject(IWebPanelProvider) private provider: IWebPanelProvider,
4545
@inject(IDisposableRegistry) private disposables: IDisposableRegistry,
4646
@inject(ICodeCssGenerator) private cssGenerator : ICodeCssGenerator,
47-
@inject(IStatusProvider) private statusProvider : IStatusProvider) {
47+
@inject(IStatusProvider) private statusProvider : IStatusProvider,
48+
@inject(IJupyterExecution) private jupyterExecution: IJupyterExecution) {
4849

4950
// Sign up for configuration changes
5051
this.settingsChangedDisposable = this.interpreterService.onDidChangeInterpreter(this.onSettingsChanged);
@@ -63,7 +64,7 @@ export class History implements IWebPanelMessageListener, IHistory {
6364
await this.loadPromise;
6465

6566
// Then show our web panel.
66-
if (this.webPanel) {
67+
if (this.webPanel && this.jupyterServer) {
6768
await this.webPanel.show();
6869
}
6970
}
@@ -77,29 +78,38 @@ export class History implements IWebPanelMessageListener, IHistory {
7778
// Start a status item
7879
const status = this.setStatus(localize.DataScience.executingCode());
7980

80-
// Make sure we're loaded first.
81-
await this.loadPromise;
81+
try {
82+
// Make sure we're loaded first.
83+
await this.loadPromise;
8284

83-
// Then show our webpanel
84-
await this.show();
85+
// Then show our webpanel
86+
await this.show();
8587

86-
if (this.jupyterServer) {
87-
// Attempt to evaluate this cell in the jupyter notebook
88-
const observable = this.jupyterServer.executeObservable(code, file, line);
89-
90-
// Sign up for cell changes
91-
observable.subscribe(
92-
(cells: ICell[]) => {
93-
this.onAddCodeEvent(cells, editor);
94-
},
95-
(error) => {
96-
status.dispose();
97-
this.applicationShell.showErrorMessage(error);
98-
},
99-
() => {
100-
// Indicate executing until this cell is done.
101-
status.dispose();
102-
});
88+
if (this.jupyterServer) {
89+
// Attempt to evaluate this cell in the jupyter notebook
90+
const observable = this.jupyterServer.executeObservable(code, file, line);
91+
92+
// Sign up for cell changes
93+
observable.subscribe(
94+
(cells: ICell[]) => {
95+
this.onAddCodeEvent(cells, editor);
96+
},
97+
(error) => {
98+
status.dispose();
99+
this.applicationShell.showErrorMessage(error);
100+
},
101+
() => {
102+
// Indicate executing until this cell is done.
103+
status.dispose();
104+
});
105+
}
106+
} catch (err) {
107+
status.dispose();
108+
109+
// We failed, dispose of ourselves too so that nobody uses us again
110+
this.dispose();
111+
112+
throw err;
103113
}
104114
}
105115

@@ -366,10 +376,14 @@ export class History implements IWebPanelMessageListener, IHistory {
366376
this.webPanel = this.provider.create(this, localize.DataScience.historyTitle(), mainScriptPath, css);
367377
}
368378

369-
private load = () : Promise<[void, void]> => {
370-
return Promise.all([
371-
this.loadWebPanel(),
372-
this.loadJupyterServer()
373-
]);
379+
private load = async () : Promise<void> => {
380+
381+
// Check to see if we support jupyter or not. If not quick fail
382+
if (!(await this.jupyterExecution.isImportSupported())) {
383+
throw new JupyterInstallError(localize.DataScience.jupyterNotSupported(), localize.DataScience.pythonInteractiveHelpLink());
384+
}
385+
386+
// Otherwise wait for both
387+
await Promise.all([this.loadJupyterServer(), this.loadWebPanel()]);
374388
}
375389
}

src/client/datascience/jupyterImporter.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ export class JupyterImporter implements INotebookImporter {
5858
return result.stdout;
5959
}
6060

61-
throw localize.DataScience.jupyterNotSupported();
61+
throw new Error(localize.DataScience.jupyterNbConvertNotSupported());
6262
}
6363

6464
public dispose = () => {
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License.
3+
'use strict';
4+
import '../common/extensions';
5+
import { HelpLinks } from './constants';
6+
7+
export class JupyterInstallError extends Error {
8+
public action: string;
9+
public actionTitle: string;
10+
11+
constructor(message: string, actionFormatString: string) {
12+
super(message);
13+
this.action = HelpLinks.PythonInteractiveHelpLink;
14+
this.actionTitle = actionFormatString.format(HelpLinks.PythonInteractiveHelpLink);
15+
}
16+
}

0 commit comments

Comments
 (0)