Prompt for Python installation after choosing a Jupyter kernel in notebook (#4453)

This commit is contained in:
Cory Rivera
2019-03-13 18:44:54 -07:00
committed by GitHub
parent cca84e6455
commit 34d36c1de1
6 changed files with 86 additions and 74 deletions

View File

@@ -30,7 +30,7 @@ import CodeAdapter from '../prompts/adapter';
let untitledCounter = 0;
export class JupyterController implements vscode.Disposable {
private _jupyterInstallation: Promise<JupyterServerInstallation>;
private _jupyterInstallation: JupyterServerInstallation;
private _notebookInstances: IServerInstance[] = [];
private outputChannel: vscode.OutputChannel;
@@ -55,31 +55,11 @@ export class JupyterController implements vscode.Disposable {
// PUBLIC METHODS //////////////////////////////////////////////////////
public async activate(): Promise<boolean> {
// Prompt for install if the python installation path is not defined
let jupyterInstaller = new JupyterServerInstallation(
this._jupyterInstallation = new JupyterServerInstallation(
this.extensionContext.extensionPath,
this.outputChannel,
this.apiWrapper);
if (JupyterServerInstallation.isPythonInstalled(this.apiWrapper)) {
this._jupyterInstallation = Promise.resolve(jupyterInstaller);
} else {
this._jupyterInstallation = new Promise(resolve => {
jupyterInstaller.onInstallComplete(err => {
if (!err) {
resolve(jupyterInstaller);
}
});
});
}
let notebookProvider = undefined;
notebookProvider = this.registerNotebookProvider();
azdata.nb.onDidOpenNotebookDocument(notebook => {
if (!JupyterServerInstallation.isPythonInstalled(this.apiWrapper)) {
this.doConfigurePython(jupyterInstaller);
}
});
// Add command/task handlers
this.apiWrapper.registerTaskHandler(constants.jupyterOpenNotebookTask, (profile: azdata.IConnectionProfile) => {
return this.handleOpenNotebookTask(profile);
@@ -96,11 +76,12 @@ export class JupyterController implements vscode.Disposable {
this.apiWrapper.registerCommand(constants.jupyterReinstallDependenciesCommand, () => { return this.handleDependenciesReinstallation(); });
this.apiWrapper.registerCommand(constants.jupyterInstallPackages, () => { return this.doManagePackages(); });
this.apiWrapper.registerCommand(constants.jupyterConfigurePython, () => { return this.doConfigurePython(jupyterInstaller); });
this.apiWrapper.registerCommand(constants.jupyterConfigurePython, () => { return this.doConfigurePython(this._jupyterInstallation); });
let supportedFileFilter: vscode.DocumentFilter[] = [
{ scheme: 'untitled', language: '*' }
];
let notebookProvider = this.registerNotebookProvider();
this.extensionContext.subscriptions.push(this.apiWrapper.registerCompletionItemProvider(supportedFileFilter, new NotebookCompletionItemProvider(notebookProvider)));
return true;
@@ -195,7 +176,7 @@ export class JupyterController implements vscode.Disposable {
private async handleDependenciesReinstallation(): Promise<void> {
if (await this.confirmReinstall()) {
this._jupyterInstallation = JupyterServerInstallation.getInstallation(
this._jupyterInstallation = await JupyterServerInstallation.getInstallation(
this.extensionContext.extensionPath,
this.outputChannel,
this.apiWrapper,
@@ -225,14 +206,11 @@ export class JupyterController implements vscode.Disposable {
}
}
public async doConfigurePython(jupyterInstaller: JupyterServerInstallation): Promise<void> {
try {
let pythonDialog = new ConfigurePythonDialog(this.appContext, this.outputChannel, jupyterInstaller);
await pythonDialog.showDialog();
} catch (error) {
let message = utils.getErrorMessage(error);
this.apiWrapper.showErrorMessage(message);
}
public doConfigurePython(jupyterInstaller: JupyterServerInstallation): void {
let pythonDialog = new ConfigurePythonDialog(this.apiWrapper, this.outputChannel, jupyterInstaller);
pythonDialog.showDialog().catch(err => {
this.apiWrapper.showErrorMessage(utils.getErrorMessage(err));
});
}
public getTextToSendToTerminal(shellType: any): string {

View File

@@ -16,7 +16,9 @@ import * as request from 'request';
import { ApiWrapper } from '../common/apiWrapper';
import * as constants from '../common/constants';
import * as utils from '../common/utils';
import { OutputChannel, ConfigurationTarget, Event, EventEmitter, window } from 'vscode';
import { OutputChannel, ConfigurationTarget, window } from 'vscode';
import { Deferred } from '../common/promise';
import { ConfigurePythonDialog } from '../dialog/configurePythonDialog';
const localize = nls.loadMessageBundle();
const msgPythonInstallationProgress = localize('msgPythonInstallationProgress', 'Python installation is in progress');
@@ -53,7 +55,7 @@ export default class JupyterServerInstallation {
private static readonly DefaultPythonLocation = path.join(utils.getUserHome(), 'azuredatastudio-python');
private _installCompleteEmitter = new EventEmitter<string>();
private _installReady: Deferred<void>;
constructor(extensionPath: string, outputChannel: OutputChannel, apiWrapper: ApiWrapper, pythonInstallationPath?: string, forceInstall?: boolean) {
this.extensionPath = extensionPath;
@@ -63,10 +65,15 @@ export default class JupyterServerInstallation {
this._forceInstall = !!forceInstall;
this.configurePackagePaths();
this._installReady = new Deferred<void>();
if (JupyterServerInstallation.isPythonInstalled(this.apiWrapper)) {
this._installReady.resolve();
}
}
public get onInstallComplete(): Event<string> {
return this._installCompleteEmitter.event;
public get installReady(): Promise<void> {
return this._installReady.promise;
}
public static async getInstallation(
@@ -232,7 +239,7 @@ export default class JupyterServerInstallation {
};
}
public async startInstallProcess(pythonInstallationPath?: string): Promise<void> {
public startInstallProcess(pythonInstallationPath?: string): Promise<void> {
if (pythonInstallationPath) {
this._pythonInstallationPath = pythonInstallationPath;
this.configurePackagePaths();
@@ -249,23 +256,31 @@ export default class JupyterServerInstallation {
operation: op => {
this.installDependencies(op)
.then(() => {
this._installCompleteEmitter.fire();
this._installReady.resolve();
updateConfig();
})
.catch(err => {
let errorMsg = msgDependenciesInstallationFailed(err);
op.updateStatus(azdata.TaskStatus.Failed, errorMsg);
this.apiWrapper.showErrorMessage(errorMsg);
this._installCompleteEmitter.fire(errorMsg);
this._installReady.reject(errorMsg);
});
}
});
} else {
// Python executable already exists, but the path setting wasn't defined,
// so update it here
this._installCompleteEmitter.fire();
this._installReady.resolve();
updateConfig();
}
return this._installReady.promise;
}
public async promptForPythonInstall(): Promise<void> {
if (!JupyterServerInstallation.isPythonInstalled(this.apiWrapper)) {
let pythonDialog = new ConfigurePythonDialog(this.apiWrapper, this.outputChannel, this);
return pythonDialog.showDialog(true);
}
}
private async installJupyterProsePackage(): Promise<void> {

View File

@@ -20,7 +20,7 @@ import { PerNotebookServerInstance, IInstanceOptions } from './serverInstance';
export interface IServerManagerOptions {
documentPath: string;
jupyterInstallation: Promise<JupyterServerInstallation>;
jupyterInstallation: JupyterServerInstallation;
extensionContext: vscode.ExtensionContext;
apiWrapper?: ApiWrapper;
factory?: ServerInstanceFactory;
@@ -62,7 +62,7 @@ export class LocalJupyterServerManager implements nb.ServerManager, vscode.Dispo
this._onServerStarted.fire();
} catch (error) {
this.apiWrapper.showErrorMessage(localize('startServerFailed', 'Starting local Notebook server failed with error {0}', utils.getErrorMessage(error)));
this.apiWrapper.showErrorMessage(localize('startServerFailed', 'Starting local Notebook server failed with error: {0}', utils.getErrorMessage(error)));
throw error;
}
}
@@ -102,7 +102,8 @@ export class LocalJupyterServerManager implements nb.ServerManager, vscode.Dispo
}
private async doStartServer(): Promise<IServerInstance> { // We can't find or create servers until the installation is complete
let installation = await this.options.jupyterInstallation;
let installation = this.options.jupyterInstallation;
await installation.promptForPythonInstall();
// Calculate the path to use as the notebook-dir for Jupyter based on the path of the uri of the
// notebook to open. This will be the workspace folder if the notebook uri is inside a workspace