mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-16 10:58:30 -05:00
Update Configure Python dialog to allow packages to be installed for specific kernels. (#10286)
This commit is contained in:
@@ -34,6 +34,13 @@ export const localhostName = 'localhost';
|
||||
export const localhostTitle = localize('managePackages.localhost', "localhost");
|
||||
export const PackageNotFoundError = localize('managePackages.packageNotFound', "Could not find the specified package");
|
||||
|
||||
export const python3DisplayName = 'Python 3';
|
||||
export const pysparkDisplayName = 'PySpark';
|
||||
export const sparkScalaDisplayName = 'Spark | Scala';
|
||||
export const sparkRDisplayName = 'Spark | R';
|
||||
export const powershellDisplayName = 'PowerShell';
|
||||
export const allKernelsName = 'All Kernels';
|
||||
|
||||
export const visitedNotebooksMementoKey = 'notebooks.visited';
|
||||
|
||||
export enum BuiltInCommands {
|
||||
|
||||
@@ -294,3 +294,7 @@ function decorate(decorator: (fn: Function, key: string) => Function): Function
|
||||
descriptor[fnKey] = decorator(fn, key);
|
||||
};
|
||||
}
|
||||
|
||||
export function getDropdownValue(dropdown: azdata.DropDownComponent): string {
|
||||
return (typeof dropdown.value === 'string') ? dropdown.value : dropdown.value.name;
|
||||
}
|
||||
|
||||
33
extensions/notebook/src/dialog/configurePython/basePage.ts
Normal file
33
extensions/notebook/src/dialog/configurePython/basePage.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as azdata from 'azdata';
|
||||
import { ConfigurePythonModel, ConfigurePythonWizard } from './configurePythonWizard';
|
||||
import { ApiWrapper } from '../../common/apiWrapper';
|
||||
|
||||
export abstract class BasePage {
|
||||
|
||||
constructor(protected readonly apiWrapper: ApiWrapper,
|
||||
protected readonly instance: ConfigurePythonWizard,
|
||||
protected readonly wizardPage: azdata.window.WizardPage,
|
||||
protected readonly model: ConfigurePythonModel,
|
||||
protected readonly view: azdata.ModelView) {
|
||||
}
|
||||
|
||||
/**
|
||||
* This method constructs all the elements of the page.
|
||||
*/
|
||||
public async abstract initialize(): Promise<boolean>;
|
||||
|
||||
/**
|
||||
* This method is called when the user is entering the page.
|
||||
*/
|
||||
public async abstract onPageEnter(): Promise<void>;
|
||||
|
||||
/**
|
||||
* This method is called when the user is leaving the page.
|
||||
*/
|
||||
public async abstract onPageLeave(): Promise<boolean>;
|
||||
}
|
||||
@@ -0,0 +1,188 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as vscode from 'vscode';
|
||||
import * as azdata from 'azdata';
|
||||
import { BasePage } from './basePage';
|
||||
import * as nls from 'vscode-nls';
|
||||
import { JupyterServerInstallation } from '../../jupyter/jupyterServerInstallation';
|
||||
import { PythonPathInfo } from '../pythonPathLookup';
|
||||
import * as utils from '../../common/utils';
|
||||
|
||||
const localize = nls.loadMessageBundle();
|
||||
|
||||
export class ConfigurePathPage extends BasePage {
|
||||
private readonly BrowseButtonText = localize('configurePython.browseButtonText', "Browse");
|
||||
private readonly LocationTextBoxTitle = localize('configurePython.locationTextBoxText', "Python Install Location");
|
||||
private readonly SelectFileLabel = localize('configurePython.selectFileLabel', "Select");
|
||||
|
||||
private pythonLocationDropdown: azdata.DropDownComponent;
|
||||
private pythonDropdownLoader: azdata.LoadingComponent;
|
||||
private browseButton: azdata.ButtonComponent;
|
||||
private newInstallButton: azdata.RadioButtonComponent;
|
||||
private existingInstallButton: azdata.RadioButtonComponent;
|
||||
|
||||
private usingCustomPath: boolean = false;
|
||||
|
||||
public async initialize(): Promise<boolean> {
|
||||
this.pythonLocationDropdown = this.view.modelBuilder.dropDown()
|
||||
.withProperties<azdata.DropDownProperties>({
|
||||
value: undefined,
|
||||
values: [],
|
||||
width: '100%'
|
||||
}).component();
|
||||
this.pythonDropdownLoader = this.view.modelBuilder.loadingComponent()
|
||||
.withItem(this.pythonLocationDropdown)
|
||||
.withProperties<azdata.LoadingComponentProperties>({
|
||||
loading: false
|
||||
})
|
||||
.component();
|
||||
|
||||
this.browseButton = this.view.modelBuilder.button()
|
||||
.withProperties<azdata.ButtonProperties>({
|
||||
label: this.BrowseButtonText,
|
||||
width: '70px'
|
||||
}).component();
|
||||
this.browseButton.onDidClick(() => this.handleBrowse());
|
||||
|
||||
this.createInstallRadioButtons(this.view.modelBuilder, this.model.useExistingPython);
|
||||
|
||||
let formModel = this.view.modelBuilder.formContainer()
|
||||
.withFormItems([{
|
||||
component: this.newInstallButton,
|
||||
title: localize('configurePython.installationType', "Installation Type")
|
||||
}, {
|
||||
component: this.existingInstallButton,
|
||||
title: ''
|
||||
}, {
|
||||
component: this.pythonDropdownLoader,
|
||||
title: this.LocationTextBoxTitle
|
||||
}, {
|
||||
component: this.browseButton,
|
||||
title: ''
|
||||
}]).component();
|
||||
|
||||
await this.view.initializeModel(formModel);
|
||||
|
||||
await this.updatePythonPathsDropdown(this.model.useExistingPython);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public async onPageEnter(): Promise<void> {
|
||||
}
|
||||
|
||||
public async onPageLeave(): Promise<boolean> {
|
||||
let pythonLocation = utils.getDropdownValue(this.pythonLocationDropdown);
|
||||
if (!pythonLocation || pythonLocation.length === 0) {
|
||||
this.instance.showErrorMessage(this.instance.InvalidLocationMsg);
|
||||
return false;
|
||||
}
|
||||
|
||||
this.model.pythonLocation = pythonLocation;
|
||||
this.model.useExistingPython = !!this.existingInstallButton.checked;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private async updatePythonPathsDropdown(useExistingPython: boolean): Promise<void> {
|
||||
this.pythonDropdownLoader.loading = true;
|
||||
try {
|
||||
let pythonPaths: PythonPathInfo[];
|
||||
let dropdownValues: azdata.CategoryValue[];
|
||||
if (useExistingPython) {
|
||||
pythonPaths = await this.model.pythonPathsPromise;
|
||||
if (pythonPaths && pythonPaths.length > 0) {
|
||||
dropdownValues = pythonPaths.map(path => {
|
||||
return {
|
||||
displayName: localize('configurePythyon.dropdownPathLabel', "{0} (Python {1})", path.installDir, path.version),
|
||||
name: path.installDir
|
||||
};
|
||||
});
|
||||
} else {
|
||||
dropdownValues = [{
|
||||
displayName: localize('configurePythyon.noVersionsFound', "No supported Python versions found."),
|
||||
name: ''
|
||||
}];
|
||||
}
|
||||
} else {
|
||||
let defaultPath = JupyterServerInstallation.DefaultPythonLocation;
|
||||
dropdownValues = [{
|
||||
displayName: localize('configurePythyon.defaultPathLabel', "{0} (Default)", defaultPath),
|
||||
name: defaultPath
|
||||
}];
|
||||
}
|
||||
|
||||
this.usingCustomPath = false;
|
||||
await this.pythonLocationDropdown.updateProperties({
|
||||
value: dropdownValues[0],
|
||||
values: dropdownValues
|
||||
});
|
||||
} finally {
|
||||
this.pythonDropdownLoader.loading = false;
|
||||
}
|
||||
}
|
||||
|
||||
private createInstallRadioButtons(modelBuilder: azdata.ModelBuilder, useExistingPython: boolean): void {
|
||||
let buttonGroup = 'installationType';
|
||||
this.newInstallButton = modelBuilder.radioButton()
|
||||
.withProperties<azdata.RadioButtonProperties>({
|
||||
name: buttonGroup,
|
||||
label: localize('configurePython.newInstall', "New Python installation"),
|
||||
checked: !useExistingPython
|
||||
}).component();
|
||||
this.newInstallButton.onDidClick(() => {
|
||||
this.updatePythonPathsDropdown(false)
|
||||
.catch(err => {
|
||||
this.instance.showErrorMessage(utils.getErrorMessage(err));
|
||||
});
|
||||
});
|
||||
|
||||
this.existingInstallButton = modelBuilder.radioButton()
|
||||
.withProperties<azdata.RadioButtonProperties>({
|
||||
name: buttonGroup,
|
||||
label: localize('configurePython.existingInstall', "Use existing Python installation"),
|
||||
checked: useExistingPython
|
||||
}).component();
|
||||
this.existingInstallButton.onDidClick(() => {
|
||||
this.updatePythonPathsDropdown(true)
|
||||
.catch(err => {
|
||||
this.instance.showErrorMessage(utils.getErrorMessage(err));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private async handleBrowse(): Promise<void> {
|
||||
let options: vscode.OpenDialogOptions = {
|
||||
defaultUri: vscode.Uri.file(utils.getUserHome()),
|
||||
canSelectFiles: false,
|
||||
canSelectFolders: true,
|
||||
canSelectMany: false,
|
||||
openLabel: this.SelectFileLabel
|
||||
};
|
||||
|
||||
let fileUris: vscode.Uri[] = await this.apiWrapper.showOpenDialog(options);
|
||||
if (fileUris?.length > 0 && fileUris[0]) {
|
||||
let existingValues = <azdata.CategoryValue[]>this.pythonLocationDropdown.values;
|
||||
let filePath = fileUris[0].fsPath;
|
||||
let newValue = {
|
||||
displayName: localize('configurePythyon.customPathLabel', "{0} (Custom)", filePath),
|
||||
name: filePath
|
||||
};
|
||||
|
||||
if (this.usingCustomPath) {
|
||||
existingValues[0] = newValue;
|
||||
} else {
|
||||
existingValues.unshift(newValue);
|
||||
this.usingCustomPath = true;
|
||||
}
|
||||
|
||||
await this.pythonLocationDropdown.updateProperties({
|
||||
value: existingValues[0],
|
||||
values: existingValues
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7,12 +7,12 @@ import * as vscode from 'vscode';
|
||||
import * as nls from 'vscode-nls';
|
||||
import * as azdata from 'azdata';
|
||||
import { promises as fs } from 'fs';
|
||||
import * as utils from '../common/utils';
|
||||
import * as utils from '../../common/utils';
|
||||
|
||||
import { JupyterServerInstallation } from '../jupyter/jupyterServerInstallation';
|
||||
import { ApiWrapper } from '../common/apiWrapper';
|
||||
import { Deferred } from '../common/promise';
|
||||
import { PythonPathLookup, PythonPathInfo } from './pythonPathLookup';
|
||||
import { JupyterServerInstallation } from '../../jupyter/jupyterServerInstallation';
|
||||
import { ApiWrapper } from '../../common/apiWrapper';
|
||||
import { Deferred } from '../../common/promise';
|
||||
import { PythonPathLookup, PythonPathInfo } from '../pythonPathLookup';
|
||||
|
||||
const localize = nls.loadMessageBundle();
|
||||
|
||||
@@ -0,0 +1,199 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as nls from 'vscode-nls';
|
||||
import * as azdata from 'azdata';
|
||||
import { BasePage } from './basePage';
|
||||
import { ConfigurePathPage } from './configurePathPage';
|
||||
import { PickPackagesPage } from './pickPackagesPage';
|
||||
import { JupyterServerInstallation, PythonPkgDetails, PythonInstallSettings } from '../../jupyter/jupyterServerInstallation';
|
||||
import * as utils from '../../common/utils';
|
||||
import { promises as fs } from 'fs';
|
||||
import { Deferred } from '../../common/promise';
|
||||
import { PythonPathInfo, PythonPathLookup } from '../pythonPathLookup';
|
||||
import { ApiWrapper } from '../../common/apiWrapper';
|
||||
|
||||
const localize = nls.loadMessageBundle();
|
||||
|
||||
export interface ConfigurePythonModel {
|
||||
kernelName: string;
|
||||
pythonLocation: string;
|
||||
useExistingPython: boolean;
|
||||
pythonPathsPromise: Promise<PythonPathInfo[]>;
|
||||
packagesToInstall: PythonPkgDetails[];
|
||||
installation: JupyterServerInstallation;
|
||||
}
|
||||
|
||||
export class ConfigurePythonWizard {
|
||||
private readonly InstallButtonText = localize('configurePython.okButtonText', "Install");
|
||||
public readonly InvalidLocationMsg = localize('configurePython.invalidLocationMsg', "The specified install location is invalid.");
|
||||
private readonly PythonNotFoundMsg = localize('configurePython.pythonNotFoundMsg', "No Python installation was found at the specified location.");
|
||||
|
||||
private _wizard: azdata.window.Wizard;
|
||||
private model: ConfigurePythonModel;
|
||||
|
||||
private _setupComplete: Deferred<void>;
|
||||
private pythonPathsPromise: Promise<PythonPathInfo[]>;
|
||||
|
||||
constructor(private apiWrapper: ApiWrapper, private jupyterInstallation: JupyterServerInstallation) {
|
||||
this._setupComplete = new Deferred<void>();
|
||||
this.pythonPathsPromise = (new PythonPathLookup()).getSuggestions();
|
||||
}
|
||||
|
||||
public get wizard(): azdata.window.Wizard {
|
||||
return this._wizard;
|
||||
}
|
||||
|
||||
public get setupComplete(): Promise<void> {
|
||||
return this._setupComplete.promise;
|
||||
}
|
||||
|
||||
public async start(kernelName?: string, rejectOnCancel?: boolean, ...args: any[]): Promise<void> {
|
||||
this.model = <ConfigurePythonModel>{
|
||||
kernelName: kernelName,
|
||||
pythonPathsPromise: this.pythonPathsPromise,
|
||||
installation: this.jupyterInstallation,
|
||||
useExistingPython: JupyterServerInstallation.getExistingPythonSetting(this.apiWrapper)
|
||||
};
|
||||
|
||||
let pages: Map<number, BasePage> = new Map<number, BasePage>();
|
||||
|
||||
let wizardTitle: string;
|
||||
if (kernelName) {
|
||||
wizardTitle = localize('configurePython.wizardNameWithKernel', 'Configure Python to run {0} kernel', kernelName);
|
||||
} else {
|
||||
wizardTitle = localize('configurePython.wizardNameWithoutKernel', 'Configure Python to run kernels');
|
||||
}
|
||||
this._wizard = azdata.window.createWizard(wizardTitle);
|
||||
let page0 = azdata.window.createWizardPage(localize('configurePython.page0Name', 'Configure Python Runtime'));
|
||||
let page1 = azdata.window.createWizardPage(localize('configurePython.page1Name', 'Install Dependencies'));
|
||||
|
||||
page0.registerContent(async (view) => {
|
||||
let configurePathPage = new ConfigurePathPage(this.apiWrapper, this, page0, this.model, view);
|
||||
pages.set(0, configurePathPage);
|
||||
await configurePathPage.initialize();
|
||||
await configurePathPage.onPageEnter();
|
||||
});
|
||||
|
||||
page1.registerContent(async (view) => {
|
||||
let pickPackagesPage = new PickPackagesPage(this.apiWrapper, this, page1, this.model, view);
|
||||
pages.set(1, pickPackagesPage);
|
||||
await pickPackagesPage.initialize();
|
||||
});
|
||||
|
||||
this._wizard.doneButton.label = this.InstallButtonText;
|
||||
this._wizard.cancelButton.onClick(() => {
|
||||
if (rejectOnCancel) {
|
||||
this._setupComplete.reject(localize('configurePython.pythonInstallDeclined', "Python installation was declined."));
|
||||
} else {
|
||||
this._setupComplete.resolve();
|
||||
}
|
||||
});
|
||||
|
||||
this._wizard.onPageChanged(async info => {
|
||||
let newPage = pages.get(info.newPage);
|
||||
if (newPage) {
|
||||
await newPage.onPageEnter();
|
||||
}
|
||||
});
|
||||
|
||||
this._wizard.registerNavigationValidator(async (info) => {
|
||||
let lastPage = pages.get(info.lastPage);
|
||||
let newPage = pages.get(info.newPage);
|
||||
|
||||
// Hit "next" on last page, so handle submit
|
||||
let nextOnLastPage = !newPage && lastPage instanceof PickPackagesPage;
|
||||
if (nextOnLastPage) {
|
||||
return await this.handlePackageInstall();
|
||||
}
|
||||
|
||||
if (lastPage) {
|
||||
let pageValid = await lastPage.onPageLeave();
|
||||
if (!pageValid) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
this.clearStatusMessage();
|
||||
return true;
|
||||
});
|
||||
|
||||
this._wizard.generateScriptButton.hidden = true;
|
||||
this._wizard.pages = [page0, page1];
|
||||
this._wizard.open();
|
||||
}
|
||||
|
||||
public async close(): Promise<void> {
|
||||
await this._wizard.close();
|
||||
}
|
||||
|
||||
public showErrorMessage(errorMsg: string) {
|
||||
this._wizard.message = <azdata.window.DialogMessage>{
|
||||
text: errorMsg,
|
||||
level: azdata.window.MessageLevel.Error
|
||||
};
|
||||
}
|
||||
|
||||
public clearStatusMessage() {
|
||||
this._wizard.message = undefined;
|
||||
}
|
||||
|
||||
private async handlePackageInstall(): Promise<boolean> {
|
||||
let pythonLocation = this.model.pythonLocation;
|
||||
let useExistingPython = this.model.useExistingPython;
|
||||
try {
|
||||
let isValid = await this.isFileValid(pythonLocation);
|
||||
if (!isValid) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (useExistingPython) {
|
||||
let exePath = JupyterServerInstallation.getPythonExePath(pythonLocation, true);
|
||||
let pythonExists = await utils.exists(exePath);
|
||||
if (!pythonExists) {
|
||||
this.showErrorMessage(this.PythonNotFoundMsg);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
this.showErrorMessage(utils.getErrorMessage(err));
|
||||
return false;
|
||||
}
|
||||
|
||||
// Don't wait on installation, since there's currently no Cancel functionality
|
||||
let installSettings: PythonInstallSettings = {
|
||||
installPath: pythonLocation,
|
||||
existingPython: useExistingPython,
|
||||
specificPackages: this.model.packagesToInstall
|
||||
};
|
||||
this.jupyterInstallation.startInstallProcess(false, installSettings)
|
||||
.then(() => {
|
||||
this._setupComplete.resolve();
|
||||
})
|
||||
.catch(err => {
|
||||
this._setupComplete.reject(utils.getErrorMessage(err));
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private async isFileValid(pythonLocation: string): Promise<boolean> {
|
||||
try {
|
||||
const stats = await fs.stat(pythonLocation);
|
||||
if (stats.isFile()) {
|
||||
this.showErrorMessage(this.InvalidLocationMsg);
|
||||
return false;
|
||||
}
|
||||
} catch (err) {
|
||||
// Ignore error if folder doesn't exist, since it will be
|
||||
// created during installation
|
||||
if (err.code !== 'ENOENT') {
|
||||
this.showErrorMessage(err.message);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,134 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as azdata from 'azdata';
|
||||
import * as nls from 'vscode-nls';
|
||||
import { BasePage } from './basePage';
|
||||
import { JupyterServerInstallation, PythonPkgDetails } from '../../jupyter/jupyterServerInstallation';
|
||||
import { python3DisplayName, pysparkDisplayName, sparkScalaDisplayName, sparkRDisplayName, powershellDisplayName, allKernelsName } from '../../common/constants';
|
||||
import { getDropdownValue } from '../../common/utils';
|
||||
|
||||
const localize = nls.loadMessageBundle();
|
||||
|
||||
export class PickPackagesPage extends BasePage {
|
||||
private kernelLabel: azdata.TextComponent | undefined;
|
||||
private kernelDropdown: azdata.DropDownComponent | undefined;
|
||||
private requiredPackagesTable: azdata.DeclarativeTableComponent;
|
||||
private packageTableSpinner: azdata.LoadingComponent;
|
||||
|
||||
private installedPackagesPromise: Promise<PythonPkgDetails[]>;
|
||||
private installedPackages: PythonPkgDetails[];
|
||||
|
||||
public async initialize(): Promise<boolean> {
|
||||
if (this.model.kernelName) {
|
||||
// Wizard was started for a specific kernel, so don't populate any other options
|
||||
this.kernelLabel = this.view.modelBuilder.text().withProperties<azdata.TextComponentProperties>({
|
||||
value: this.model.kernelName
|
||||
}).component();
|
||||
} else {
|
||||
let dropdownValues = [python3DisplayName, pysparkDisplayName, sparkScalaDisplayName, sparkRDisplayName, powershellDisplayName, allKernelsName];
|
||||
this.kernelDropdown = this.view.modelBuilder.dropDown().withProperties<azdata.DropDownProperties>({
|
||||
value: dropdownValues[0],
|
||||
values: dropdownValues,
|
||||
width: '300px'
|
||||
}).component();
|
||||
this.kernelDropdown.onValueChanged(async value => {
|
||||
await this.updateRequiredPackages(value.selected);
|
||||
});
|
||||
}
|
||||
|
||||
this.requiredPackagesTable = this.view.modelBuilder.declarativeTable().withProperties<azdata.DeclarativeTableProperties>({
|
||||
columns: [{
|
||||
displayName: localize('configurePython.pkgNameColumn', "Name"),
|
||||
valueType: azdata.DeclarativeDataType.string,
|
||||
isReadOnly: true,
|
||||
width: '200px'
|
||||
}, {
|
||||
displayName: localize('configurePython.existingVersionColumn', "Existing Version"),
|
||||
valueType: azdata.DeclarativeDataType.string,
|
||||
isReadOnly: true,
|
||||
width: '200px'
|
||||
}, {
|
||||
displayName: localize('configurePython.requiredVersionColumn', "Required Version"),
|
||||
valueType: azdata.DeclarativeDataType.string,
|
||||
isReadOnly: true,
|
||||
width: '200px'
|
||||
}],
|
||||
data: [[]]
|
||||
}).component();
|
||||
|
||||
this.packageTableSpinner = this.view.modelBuilder.loadingComponent().withItem(this.requiredPackagesTable).component();
|
||||
|
||||
let formModel = this.view.modelBuilder.formContainer()
|
||||
.withFormItems([{
|
||||
component: this.kernelDropdown ?? this.kernelLabel,
|
||||
title: localize('configurePython.kernelLabel', "Kernel")
|
||||
}, {
|
||||
component: this.packageTableSpinner,
|
||||
title: localize('configurePython.requiredDependencies', "Install required kernel dependencies")
|
||||
}]).component();
|
||||
await this.view.initializeModel(formModel);
|
||||
return true;
|
||||
}
|
||||
|
||||
public async onPageEnter(): Promise<void> {
|
||||
let pythonExe = JupyterServerInstallation.getPythonExePath(this.model.pythonLocation, this.model.useExistingPython);
|
||||
this.installedPackagesPromise = this.model.installation.getInstalledPipPackages(pythonExe);
|
||||
this.installedPackages = undefined;
|
||||
|
||||
if (this.kernelDropdown) {
|
||||
if (this.model.kernelName) {
|
||||
this.kernelDropdown.value = this.model.kernelName;
|
||||
} else {
|
||||
this.model.kernelName = getDropdownValue(this.kernelDropdown);
|
||||
}
|
||||
}
|
||||
await this.updateRequiredPackages(this.model.kernelName);
|
||||
}
|
||||
|
||||
public async onPageLeave(): Promise<boolean> {
|
||||
return true;
|
||||
}
|
||||
|
||||
private async updateRequiredPackages(kernelName: string): Promise<void> {
|
||||
this.packageTableSpinner.loading = true;
|
||||
try {
|
||||
let pkgVersionMap = new Map<string, { currentVersion: string, newVersion: string }>();
|
||||
|
||||
// Fetch list of required packages for the specified kernel
|
||||
let requiredPackages = JupyterServerInstallation.getRequiredPackagesForKernel(kernelName);
|
||||
requiredPackages.forEach(pkg => {
|
||||
pkgVersionMap.set(pkg.name, { currentVersion: undefined, newVersion: pkg.version });
|
||||
});
|
||||
|
||||
// For each required package, check if there is another version of that package already installed
|
||||
if (!this.installedPackages) {
|
||||
this.installedPackages = await this.installedPackagesPromise;
|
||||
}
|
||||
this.installedPackages.forEach(pkg => {
|
||||
let info = pkgVersionMap.get(pkg.name);
|
||||
if (info) {
|
||||
info.currentVersion = pkg.version;
|
||||
pkgVersionMap.set(pkg.name, info);
|
||||
}
|
||||
});
|
||||
|
||||
if (pkgVersionMap.size > 0) {
|
||||
let packageData = [];
|
||||
for (let [key, value] of pkgVersionMap.entries()) {
|
||||
packageData.push([key, value.currentVersion ?? '-', value.newVersion]);
|
||||
}
|
||||
this.requiredPackagesTable.data = packageData;
|
||||
this.model.packagesToInstall = requiredPackages;
|
||||
} else {
|
||||
this.instance.showErrorMessage(localize('msgUnsupportedKernel', "Could not retrieve packages for unsupported kernel {0}", kernelName));
|
||||
this.requiredPackagesTable.data = [['-', '-', '-']];
|
||||
this.model.packagesToInstall = undefined;
|
||||
}
|
||||
} finally {
|
||||
this.packageTableSpinner.loading = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -22,7 +22,7 @@ import { ApiWrapper } from '../common/apiWrapper';
|
||||
import { LocalJupyterServerManager, ServerInstanceFactory } from './jupyterServerManager';
|
||||
import { NotebookCompletionItemProvider } from '../intellisense/completionItemProvider';
|
||||
import { JupyterNotebookProvider } from './jupyterNotebookProvider';
|
||||
import { ConfigurePythonDialog } from '../dialog/configurePythonDialog';
|
||||
import { ConfigurePythonWizard } from '../dialog/configurePython/configurePythonWizard';
|
||||
import CodeAdapter from '../prompts/adapter';
|
||||
import { ManagePackagesDialog } from '../dialog/managePackages/managePackagesDialog';
|
||||
import { IPackageManageProvider } from '../types';
|
||||
@@ -30,6 +30,7 @@ import { LocalPipPackageManageProvider } from './localPipPackageManageProvider';
|
||||
import { LocalCondaPackageManageProvider } from './localCondaPackageManageProvider';
|
||||
import { ManagePackagesDialogModel, ManagePackageDialogOptions } from '../dialog/managePackages/managePackagesDialogModel';
|
||||
import { PiPyClient } from './pipyClient';
|
||||
import { ConfigurePythonDialog } from '../dialog/configurePython/configurePythonDialog';
|
||||
|
||||
let untitledCounter = 0;
|
||||
|
||||
@@ -250,10 +251,21 @@ export class JupyterController implements vscode.Disposable {
|
||||
}
|
||||
|
||||
public doConfigurePython(jupyterInstaller: JupyterServerInstallation): void {
|
||||
let pythonDialog = new ConfigurePythonDialog(this.apiWrapper, jupyterInstaller);
|
||||
pythonDialog.showDialog().catch((err: any) => {
|
||||
this.apiWrapper.showErrorMessage(utils.getErrorMessage(err));
|
||||
});
|
||||
let enablePreviewFeatures = this.apiWrapper.getConfiguration('workbench').get('enablePreviewFeatures');
|
||||
if (enablePreviewFeatures) {
|
||||
let pythonWizard = new ConfigurePythonWizard(this.apiWrapper, jupyterInstaller);
|
||||
pythonWizard.start().catch((err: any) => {
|
||||
this.apiWrapper.showErrorMessage(utils.getErrorMessage(err));
|
||||
});
|
||||
pythonWizard.setupComplete.catch((err: any) => {
|
||||
this.apiWrapper.showErrorMessage(utils.getErrorMessage(err));
|
||||
});
|
||||
} else {
|
||||
let pythonDialog = new ConfigurePythonDialog(this.apiWrapper, jupyterInstaller);
|
||||
pythonDialog.showDialog().catch((err: any) => {
|
||||
this.apiWrapper.showErrorMessage(utils.getErrorMessage(err));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public get jupyterInstallation() {
|
||||
|
||||
@@ -20,8 +20,8 @@ export class JupyterNotebookManager implements nb.NotebookManager, vscode.Dispos
|
||||
this._sessionManager = sessionManager || new JupyterSessionManager(pythonEnvVarPath);
|
||||
this._serverManager.onServerStarted(() => {
|
||||
this.setServerSettings(this._serverManager.serverSettings);
|
||||
this._sessionManager.installation = this._serverManager.instanceOptions.install;
|
||||
});
|
||||
|
||||
}
|
||||
public get contentManager(): nb.ContentManager {
|
||||
return undefined;
|
||||
|
||||
@@ -17,9 +17,10 @@ import * as constants from '../common/constants';
|
||||
import * as utils from '../common/utils';
|
||||
import { OutputChannel, ConfigurationTarget, window } from 'vscode';
|
||||
import { Deferred } from '../common/promise';
|
||||
import { ConfigurePythonDialog } from '../dialog/configurePythonDialog';
|
||||
import { ConfigurePythonWizard } from '../dialog/configurePython/configurePythonWizard';
|
||||
import { IPrompter, IQuestion, QuestionTypes } from '../prompts/question';
|
||||
import CodeAdapter from '../prompts/adapter';
|
||||
import { ConfigurePythonDialog } from '../dialog/configurePython/configurePythonDialog';
|
||||
|
||||
const localize = nls.loadMessageBundle();
|
||||
const msgInstallPkgProgress = localize('msgInstallPkgProgress', "Notebook dependencies installation is in progress");
|
||||
@@ -39,10 +40,15 @@ function msgDependenciesInstallationFailed(errorMessage: string): string { retur
|
||||
function msgDownloadPython(platform: string, pythonDownloadUrl: string): string { return localize('msgDownloadPython', "Downloading local python for platform: {0} to {1}", platform, pythonDownloadUrl); }
|
||||
function msgPackageRetrievalFailed(errorMessage: string): string { return localize('msgPackageRetrievalFailed', "Encountered an error when trying to retrieve list of installed packages: {0}", errorMessage); }
|
||||
|
||||
export interface PythonInstallSettings {
|
||||
installPath: string;
|
||||
existingPython: boolean;
|
||||
specificPackages?: PythonPkgDetails[];
|
||||
}
|
||||
export interface IJupyterServerInstallation {
|
||||
installCondaPackages(packages: PythonPkgDetails[], useMinVersion: boolean): Promise<void>;
|
||||
configurePackagePaths(): Promise<void>;
|
||||
startInstallProcess(forceInstall: boolean, installSettings?: { installPath: string, existingPython: boolean }): Promise<void>;
|
||||
startInstallProcess(forceInstall: boolean, installSettings?: PythonInstallSettings): Promise<void>;
|
||||
getInstalledPipPackages(): Promise<PythonPkgDetails[]>;
|
||||
getInstalledCondaPackages(): Promise<PythonPkgDetails[]>;
|
||||
uninstallCondaPackages(packages: PythonPkgDetails[]): Promise<void>;
|
||||
@@ -66,7 +72,6 @@ export class JupyterServerInstallation implements IJupyterServerInstallation {
|
||||
|
||||
private _pythonInstallationPath: string;
|
||||
private _pythonExecutable: string;
|
||||
private _pythonPackageDir: string;
|
||||
private _usingExistingPython: boolean;
|
||||
private _usingConda: boolean;
|
||||
|
||||
@@ -104,6 +109,8 @@ export class JupyterServerInstallation implements IJupyterServerInstallation {
|
||||
private readonly _expectedCondaPipPackages = this._commonPipPackages;
|
||||
private readonly _expectedCondaPackages: PythonPkgDetails[];
|
||||
|
||||
private _kernelSetupCache: Map<string, boolean>;
|
||||
|
||||
constructor(extensionPath: string, outputChannel: OutputChannel, apiWrapper: ApiWrapper, pythonInstallationPath?: string) {
|
||||
this.extensionPath = extensionPath;
|
||||
this.outputChannel = outputChannel;
|
||||
@@ -120,9 +127,11 @@ export class JupyterServerInstallation implements IJupyterServerInstallation {
|
||||
} else {
|
||||
this._expectedCondaPackages = this._commonPackages;
|
||||
}
|
||||
|
||||
this._kernelSetupCache = new Map<string, boolean>();
|
||||
}
|
||||
|
||||
private async installDependencies(backgroundOperation: azdata.BackgroundOperation, forceInstall: boolean): Promise<void> {
|
||||
private async installDependencies(backgroundOperation: azdata.BackgroundOperation, forceInstall: boolean, specificPackages?: PythonPkgDetails[]): Promise<void> {
|
||||
if (!(await utils.exists(this._pythonExecutable)) || forceInstall || this._usingExistingPython) {
|
||||
window.showInformationMessage(msgInstallPkgStart);
|
||||
|
||||
@@ -132,12 +141,7 @@ export class JupyterServerInstallation implements IJupyterServerInstallation {
|
||||
|
||||
try {
|
||||
await this.installPythonPackage(backgroundOperation, this._usingExistingPython, this._pythonInstallationPath, this.outputChannel);
|
||||
|
||||
if (this._usingExistingPython) {
|
||||
await this.upgradePythonPackages(false, forceInstall);
|
||||
} else {
|
||||
await this.installOfflinePipDependencies();
|
||||
}
|
||||
await this.upgradePythonPackages(false, forceInstall, specificPackages);
|
||||
} catch (err) {
|
||||
this.outputChannel.appendLine(msgDependenciesInstallationFailed(utils.getErrorMessage(err)));
|
||||
throw err;
|
||||
@@ -282,19 +286,13 @@ export class JupyterServerInstallation implements IJupyterServerInstallation {
|
||||
? this._pythonInstallationPath
|
||||
: path.join(this._pythonInstallationPath, constants.pythonBundleVersion);
|
||||
|
||||
if (this._usingExistingPython) {
|
||||
this._pythonPackageDir = undefined;
|
||||
} else {
|
||||
this._pythonPackageDir = path.join(pythonSourcePath, 'offlinePackages');
|
||||
}
|
||||
|
||||
// Update python paths and properties to reference user's local python.
|
||||
let pythonBinPathSuffix = process.platform === constants.winPlatform ? '' : 'bin';
|
||||
|
||||
this._pythonExecutable = JupyterServerInstallation.getPythonExePath(this._pythonInstallationPath, this._usingExistingPython);
|
||||
this.pythonBinPath = path.join(pythonSourcePath, pythonBinPathSuffix);
|
||||
|
||||
this._usingConda = this.checkCondaExists();
|
||||
this._usingConda = this.isCondaInstalled();
|
||||
|
||||
// Store paths to python libraries required to run jupyter.
|
||||
this.pythonEnvVarPath = process.env['PATH'];
|
||||
@@ -360,7 +358,7 @@ export class JupyterServerInstallation implements IJupyterServerInstallation {
|
||||
* @param installSettings Optional parameter that specifies where to install python, and whether the install targets an existing python install.
|
||||
* The previous python path (or the default) is used if a new path is not specified.
|
||||
*/
|
||||
public async startInstallProcess(forceInstall: boolean, installSettings?: { installPath: string, existingPython: boolean }): Promise<void> {
|
||||
public async startInstallProcess(forceInstall: boolean, installSettings?: PythonInstallSettings): Promise<void> {
|
||||
let isPythonRunning: boolean;
|
||||
if (installSettings) {
|
||||
isPythonRunning = await this.isPythonRunning(installSettings.installPath, installSettings.existingPython);
|
||||
@@ -399,7 +397,7 @@ export class JupyterServerInstallation implements IJupyterServerInstallation {
|
||||
description: msgTaskName,
|
||||
isCancelable: false,
|
||||
operation: op => {
|
||||
this.installDependencies(op, forceInstall)
|
||||
this.installDependencies(op, forceInstall, installSettings?.specificPackages)
|
||||
.then(async () => {
|
||||
await updateConfig();
|
||||
this._installCompletion.resolve();
|
||||
@@ -427,28 +425,45 @@ export class JupyterServerInstallation implements IJupyterServerInstallation {
|
||||
/**
|
||||
* Opens a dialog for configuring the installation path for the Notebook Python dependencies.
|
||||
*/
|
||||
public async promptForPythonInstall(): Promise<void> {
|
||||
public async promptForPythonInstall(kernelDisplayName: string): Promise<void> {
|
||||
if (!JupyterServerInstallation.isPythonInstalled(this.apiWrapper)) {
|
||||
let pythonDialog = new ConfigurePythonDialog(this.apiWrapper, this);
|
||||
return pythonDialog.showDialog(true);
|
||||
let enablePreviewFeatures = this.apiWrapper.getConfiguration('workbench').get('enablePreviewFeatures');
|
||||
if (enablePreviewFeatures) {
|
||||
let pythonWizard = new ConfigurePythonWizard(this.apiWrapper, this);
|
||||
await pythonWizard.start(kernelDisplayName, true);
|
||||
return pythonWizard.setupComplete;
|
||||
} else {
|
||||
let pythonDialog = new ConfigurePythonDialog(this.apiWrapper, this);
|
||||
return pythonDialog.showDialog(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Prompts user to upgrade certain python packages if they're below the minimum expected version.
|
||||
*/
|
||||
public async promptForPackageUpgrade(): Promise<void> {
|
||||
public async promptForPackageUpgrade(kernelName: string): Promise<void> {
|
||||
if (this._installInProgress) {
|
||||
this.apiWrapper.showInfoMessage(msgWaitingForInstall);
|
||||
return this._installCompletion.promise;
|
||||
}
|
||||
|
||||
let requiredPackages: PythonPkgDetails[];
|
||||
let enablePreviewFeatures = this.apiWrapper.getConfiguration('workbench').get('enablePreviewFeatures');
|
||||
if (enablePreviewFeatures) {
|
||||
if (this._kernelSetupCache.get(kernelName)) {
|
||||
return;
|
||||
}
|
||||
requiredPackages = JupyterServerInstallation.getRequiredPackagesForKernel(kernelName);
|
||||
}
|
||||
|
||||
this._installInProgress = true;
|
||||
this._installCompletion = new Deferred<void>();
|
||||
this.upgradePythonPackages(true, false)
|
||||
this.upgradePythonPackages(true, false, requiredPackages)
|
||||
.then(() => {
|
||||
this._installCompletion.resolve();
|
||||
this._installInProgress = false;
|
||||
this._kernelSetupCache.set(kernelName, true);
|
||||
})
|
||||
.catch(err => {
|
||||
let errorMsg = msgDependenciesInstallationFailed(utils.getErrorMessage(err));
|
||||
@@ -458,10 +473,14 @@ export class JupyterServerInstallation implements IJupyterServerInstallation {
|
||||
return this._installCompletion.promise;
|
||||
}
|
||||
|
||||
private async upgradePythonPackages(promptForUpgrade: boolean, forceInstall: boolean): Promise<void> {
|
||||
private async upgradePythonPackages(promptForUpgrade: boolean, forceInstall: boolean, specificPackages?: PythonPkgDetails[]): Promise<void> {
|
||||
let expectedCondaPackages: PythonPkgDetails[];
|
||||
let expectedPipPackages: PythonPkgDetails[];
|
||||
if (this._usingConda) {
|
||||
if (specificPackages) {
|
||||
// Always install generic packages with pip, since conda may not have them.
|
||||
expectedCondaPackages = [];
|
||||
expectedPipPackages = specificPackages;
|
||||
} else if (this._usingConda) {
|
||||
expectedCondaPackages = this._expectedCondaPackages;
|
||||
expectedPipPackages = this._expectedCondaPipPackages;
|
||||
} else {
|
||||
@@ -510,9 +529,12 @@ export class JupyterServerInstallation implements IJupyterServerInstallation {
|
||||
if (promptForUpgrade) {
|
||||
doUpgrade = await this._prompter.promptSingle<boolean>(<IQuestion>{
|
||||
type: QuestionTypes.confirm,
|
||||
message: localize('confirmPackageUpgrade', "Some installed python packages need to be upgraded. Would you like to upgrade them now?"),
|
||||
message: localize('confirmPackageUpgrade', "Some required python packages need to be installed. Would you like to install them now?"),
|
||||
default: true
|
||||
});
|
||||
if (!doUpgrade) {
|
||||
throw new Error(localize('configurePython.packageInstallDeclined', "Package installation was declined."));
|
||||
}
|
||||
} else {
|
||||
doUpgrade = true;
|
||||
}
|
||||
@@ -563,9 +585,17 @@ export class JupyterServerInstallation implements IJupyterServerInstallation {
|
||||
}
|
||||
}
|
||||
|
||||
public async getInstalledPipPackages(): Promise<PythonPkgDetails[]> {
|
||||
public async getInstalledPipPackages(pythonExePath?: string): Promise<PythonPkgDetails[]> {
|
||||
try {
|
||||
let cmd = `"${this.pythonExecutable}" -m pip list --format=json`;
|
||||
if (pythonExePath) {
|
||||
if (!fs.existsSync(pythonExePath)) {
|
||||
return [];
|
||||
}
|
||||
} else if (!JupyterServerInstallation.isPythonInstalled(this.apiWrapper)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
let cmd = `"${pythonExePath ?? this.pythonExecutable}" -m pip list --format=json`;
|
||||
let packagesInfo = await this.executeBufferedCommand(cmd);
|
||||
let packagesResult: PythonPkgDetails[] = [];
|
||||
if (packagesInfo) {
|
||||
@@ -598,6 +628,10 @@ export class JupyterServerInstallation implements IJupyterServerInstallation {
|
||||
|
||||
public async getInstalledCondaPackages(): Promise<PythonPkgDetails[]> {
|
||||
try {
|
||||
if (!this.isCondaInstalled()) {
|
||||
return [];
|
||||
}
|
||||
|
||||
let condaExe = this.getCondaExePath();
|
||||
let cmd = `"${condaExe}" list --json`;
|
||||
let packagesInfo = await this.executeBufferedCommand(cmd);
|
||||
@@ -637,32 +671,6 @@ export class JupyterServerInstallation implements IJupyterServerInstallation {
|
||||
return this.executeStreamedCommand(cmd);
|
||||
}
|
||||
|
||||
private async installOfflinePipDependencies(): Promise<void> {
|
||||
// Skip this step if using existing python, since this is for our provided package
|
||||
if (!this._usingExistingPython && process.platform === constants.winPlatform) {
|
||||
this.outputChannel.show(true);
|
||||
this.outputChannel.appendLine(localize('msgInstallStart', "Installing required packages to run Notebooks..."));
|
||||
|
||||
let requirements = path.join(this._pythonPackageDir, 'requirements.txt');
|
||||
let installJupyterCommand = `"${this._pythonExecutable}" -m pip install --no-index -r "${requirements}" --find-links "${this._pythonPackageDir}" --no-warn-script-location`;
|
||||
await this.executeStreamedCommand(installJupyterCommand);
|
||||
|
||||
// Force reinstall pip to update shebangs in pip*.exe files
|
||||
installJupyterCommand = `"${this._pythonExecutable}" -m pip install --force-reinstall --no-index pip --find-links "${this._pythonPackageDir}" --no-warn-script-location`;
|
||||
await this.executeStreamedCommand(installJupyterCommand);
|
||||
|
||||
fs.remove(this._pythonPackageDir, (err: Error) => {
|
||||
if (err) {
|
||||
this.outputChannel.appendLine(err.message);
|
||||
}
|
||||
});
|
||||
|
||||
this.outputChannel.appendLine(localize('msgJupyterInstallDone', "... Jupyter installation complete."));
|
||||
} else {
|
||||
return Promise.resolve();
|
||||
}
|
||||
}
|
||||
|
||||
public async executeStreamedCommand(command: string): Promise<void> {
|
||||
await utils.executeStreamedCommand(command, { env: this.execOptions.env }, this.outputChannel);
|
||||
}
|
||||
@@ -691,7 +699,7 @@ export class JupyterServerInstallation implements IJupyterServerInstallation {
|
||||
return this._usingConda;
|
||||
}
|
||||
|
||||
private checkCondaExists(): boolean {
|
||||
private isCondaInstalled(): boolean {
|
||||
if (!this._usingExistingPython) {
|
||||
return false;
|
||||
}
|
||||
@@ -800,6 +808,55 @@ export class JupyterServerInstallation implements IJupyterServerInstallation {
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
public static getRequiredPackagesForKernel(kernelName: string): PythonPkgDetails[] {
|
||||
let packages = [{
|
||||
name: 'jupyter',
|
||||
version: '1.0.0'
|
||||
}];
|
||||
switch (kernelName) {
|
||||
case constants.python3DisplayName:
|
||||
break;
|
||||
case constants.pysparkDisplayName:
|
||||
case constants.sparkScalaDisplayName:
|
||||
case constants.sparkRDisplayName:
|
||||
packages.push({
|
||||
name: 'sparkmagic',
|
||||
version: '0.12.9'
|
||||
}, {
|
||||
name: 'pandas',
|
||||
version: '0.24.2'
|
||||
}, {
|
||||
name: 'prose-codeaccelerator',
|
||||
version: '1.3.0'
|
||||
});
|
||||
break;
|
||||
case constants.powershellDisplayName:
|
||||
packages.push({
|
||||
name: 'powershell-kernel',
|
||||
version: '0.1.3'
|
||||
});
|
||||
break;
|
||||
case constants.allKernelsName:
|
||||
packages.push({
|
||||
name: 'sparkmagic',
|
||||
version: '0.12.9'
|
||||
}, {
|
||||
name: 'pandas',
|
||||
version: '0.24.2'
|
||||
}, {
|
||||
name: 'prose-codeaccelerator',
|
||||
version: '1.3.0'
|
||||
}, {
|
||||
name: 'powershell-kernel',
|
||||
version: '0.1.3'
|
||||
});
|
||||
break;
|
||||
default:
|
||||
return undefined;
|
||||
}
|
||||
return packages;
|
||||
}
|
||||
}
|
||||
|
||||
export interface PythonPkgDetails {
|
||||
|
||||
@@ -56,16 +56,15 @@ export class LocalJupyterServerManager implements nb.ServerManager, vscode.Dispo
|
||||
return this.options && this.options.jupyterInstallation;
|
||||
}
|
||||
|
||||
public async startServer(): Promise<void> {
|
||||
public async startServer(kernelSpec: nb.IKernelSpec): Promise<void> {
|
||||
try {
|
||||
if (!this._jupyterServer) {
|
||||
this._jupyterServer = await this.doStartServer();
|
||||
this._jupyterServer = await this.doStartServer(kernelSpec);
|
||||
this.options.extensionContext.subscriptions.push(this);
|
||||
let partialSettings = LocalJupyterServerManager.getLocalConnectionSettings(this._jupyterServer.uri);
|
||||
this._serverSettings = partialSettings;
|
||||
this._onServerStarted.fire();
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
// this is caught and notified up the stack, no longer showing a message here
|
||||
throw error;
|
||||
@@ -107,10 +106,10 @@ export class LocalJupyterServerManager implements nb.ServerManager, vscode.Dispo
|
||||
return this.options.documentPath;
|
||||
}
|
||||
|
||||
private async doStartServer(): Promise<IServerInstance> { // We can't find or create servers until the installation is complete
|
||||
private async doStartServer(kernelSpec: nb.IKernelSpec): Promise<IServerInstance> { // We can't find or create servers until the installation is complete
|
||||
let installation = this.options.jupyterInstallation;
|
||||
await installation.promptForPythonInstall();
|
||||
await installation.promptForPackageUpgrade();
|
||||
await installation.promptForPythonInstall(kernelSpec.display_name);
|
||||
await installation.promptForPackageUpgrade(kernelSpec.display_name);
|
||||
this._apiWrapper.setCommandContext(CommandContext.NotebookPythonInstalled, true);
|
||||
|
||||
// Calculate the path to use as the notebook-dir for Jupyter based on the path of the uri of the
|
||||
|
||||
@@ -15,6 +15,7 @@ const localize = nls.loadMessageBundle();
|
||||
|
||||
import { JupyterKernel } from './jupyterKernel';
|
||||
import { Deferred } from '../common/promise';
|
||||
import { JupyterServerInstallation } from './jupyterServerInstallation';
|
||||
|
||||
const configBase = {
|
||||
'kernel_python_credentials': {
|
||||
@@ -66,6 +67,7 @@ export class JupyterSessionManager implements nb.SessionManager {
|
||||
private _isReady: boolean;
|
||||
private _sessionManager: Session.IManager;
|
||||
private static _sessions: JupyterSession[] = [];
|
||||
private _installation: JupyterServerInstallation;
|
||||
|
||||
constructor(private _pythonEnvVarPath?: string) {
|
||||
this._isReady = false;
|
||||
@@ -84,6 +86,12 @@ export class JupyterSessionManager implements nb.SessionManager {
|
||||
});
|
||||
}
|
||||
|
||||
public set installation(installation: JupyterServerInstallation) {
|
||||
this._installation = installation;
|
||||
JupyterSessionManager._sessions.forEach(session => {
|
||||
session.installation = installation;
|
||||
});
|
||||
}
|
||||
public get isReady(): boolean {
|
||||
return this._isReady;
|
||||
}
|
||||
@@ -126,7 +134,7 @@ export class JupyterSessionManager implements nb.SessionManager {
|
||||
return Promise.reject(new Error(localize('errorStartBeforeReady', "Cannot start a session, the manager is not yet initialized")));
|
||||
}
|
||||
let sessionImpl = await this._sessionManager.startNew(options);
|
||||
let jupyterSession = new JupyterSession(sessionImpl, skipSettingEnvironmentVars, this._pythonEnvVarPath);
|
||||
let jupyterSession = new JupyterSession(sessionImpl, this._installation, skipSettingEnvironmentVars, this._pythonEnvVarPath);
|
||||
await jupyterSession.messagesComplete;
|
||||
let index = JupyterSessionManager._sessions.findIndex(session => session.path === options.path);
|
||||
if (index > -1) {
|
||||
@@ -173,7 +181,7 @@ export class JupyterSession implements nb.ISession {
|
||||
private _kernel: nb.IKernel;
|
||||
private _messagesComplete: Deferred<void> = new Deferred<void>();
|
||||
|
||||
constructor(private sessionImpl: Session.ISession, skipSettingEnvironmentVars?: boolean, private _pythonEnvVarPath?: string) {
|
||||
constructor(private sessionImpl: Session.ISession, private _installation: JupyterServerInstallation, skipSettingEnvironmentVars?: boolean, private _pythonEnvVarPath?: string) {
|
||||
this.setEnvironmentVars(skipSettingEnvironmentVars).catch(error => {
|
||||
console.error(`Unexpected exception setting Jupyter Session variables : ${error}`);
|
||||
// We don't want callers to hang forever waiting - it's better to continue on even if we weren't
|
||||
@@ -221,7 +229,20 @@ export class JupyterSession implements nb.ISession {
|
||||
return this._messagesComplete.promise;
|
||||
}
|
||||
|
||||
public set installation(installation: JupyterServerInstallation) {
|
||||
this._installation = installation;
|
||||
}
|
||||
|
||||
public async changeKernel(kernelInfo: nb.IKernelSpec): Promise<nb.IKernel> {
|
||||
if (this._installation) {
|
||||
try {
|
||||
await this._installation.promptForPackageUpgrade(kernelInfo.display_name);
|
||||
} catch (err) {
|
||||
// Have to swallow the error here to prevent hangs when changing back to the old kernel.
|
||||
console.error(err.toString());
|
||||
return this._kernel;
|
||||
}
|
||||
}
|
||||
// For now, Jupyter implementation handles disposal etc. so we can just
|
||||
// null out our kernel and let the changeKernel call handle this
|
||||
this._kernel = undefined;
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as azdata from 'azdata';
|
||||
import * as vscode from 'vscode';
|
||||
|
||||
import { IServerInstance } from '../jupyter/common';
|
||||
@@ -255,3 +256,160 @@ export class FutureStub implements Kernel.IFuture {
|
||||
}
|
||||
}
|
||||
//#endregion
|
||||
|
||||
//#region test modelView components
|
||||
class TestComponentBase implements azdata.Component {
|
||||
id: string = '';
|
||||
updateProperties(properties: { [key: string]: any; }): Thenable<void> {
|
||||
Object.assign(this, properties);
|
||||
return Promise.resolve();
|
||||
}
|
||||
updateProperty(key: string, value: any): Thenable<void> {
|
||||
throw new Error('Method not implemented');
|
||||
}
|
||||
updateCssStyles(cssStyles: { [key: string]: string; }): Thenable<void> {
|
||||
throw new Error('Method not implemented');
|
||||
}
|
||||
onValidityChanged: vscode.Event<boolean> = undefined;
|
||||
valid: boolean = true;
|
||||
validate(): Thenable<boolean> {
|
||||
return Promise.resolve(true);
|
||||
}
|
||||
focus(): Thenable<void> {
|
||||
return Promise.resolve();
|
||||
}
|
||||
}
|
||||
|
||||
class TestDropdownComponent extends TestComponentBase implements azdata.DropDownComponent {
|
||||
constructor(private onClick: vscode.EventEmitter<any>) {
|
||||
super();
|
||||
}
|
||||
onValueChanged: vscode.Event<any> = this.onClick.event;
|
||||
}
|
||||
|
||||
class TestDeclarativeTableComponent extends TestComponentBase implements azdata.DeclarativeTableComponent {
|
||||
constructor(private onClick: vscode.EventEmitter<any>) {
|
||||
super();
|
||||
}
|
||||
onDataChanged: vscode.Event<any> = this.onClick.event;
|
||||
data: any[][];
|
||||
columns: azdata.DeclarativeTableColumn[];
|
||||
}
|
||||
|
||||
class TestButtonComponent extends TestComponentBase implements azdata.ButtonComponent {
|
||||
constructor(private onClick: vscode.EventEmitter<any>) {
|
||||
super();
|
||||
}
|
||||
onDidClick: vscode.Event<any> = this.onClick.event;
|
||||
}
|
||||
|
||||
class TestRadioButtonComponent extends TestComponentBase implements azdata.RadioButtonComponent {
|
||||
constructor(private onClick: vscode.EventEmitter<any>) {
|
||||
super();
|
||||
}
|
||||
onDidClick: vscode.Event<any> = this.onClick.event;
|
||||
}
|
||||
|
||||
class TestTextComponent extends TestComponentBase implements azdata.TextComponent {
|
||||
}
|
||||
|
||||
class TestLoadingComponent extends TestComponentBase implements azdata.LoadingComponent {
|
||||
loading: boolean;
|
||||
component: azdata.Component;
|
||||
}
|
||||
|
||||
class TestFormContainer extends TestComponentBase implements azdata.FormContainer {
|
||||
items: azdata.Component[] = [];
|
||||
clearItems(): void {
|
||||
}
|
||||
addItems(itemConfigs: azdata.Component[], itemLayout?: azdata.FormItemLayout): void {
|
||||
}
|
||||
addItem(component: azdata.Component, itemLayout?: azdata.FormItemLayout): void {
|
||||
}
|
||||
insertItem(component: azdata.Component, index: number, itemLayout?: azdata.FormItemLayout): void {
|
||||
}
|
||||
removeItem(component: azdata.Component): boolean {
|
||||
return true;
|
||||
}
|
||||
setLayout(layout: azdata.FormLayout): void {
|
||||
}
|
||||
setItemLayout(component: azdata.Component, layout: azdata.FormItemLayout): void {
|
||||
}
|
||||
}
|
||||
|
||||
class TestComponentBuilder<T extends azdata.Component> implements azdata.ComponentBuilder<T> {
|
||||
constructor(private _component: T) {
|
||||
}
|
||||
component(): T {
|
||||
return this._component;
|
||||
}
|
||||
withProperties<U>(properties: U): azdata.ComponentBuilder<T> {
|
||||
this._component.updateProperties(properties);
|
||||
return this;
|
||||
}
|
||||
withValidation(validation: (component: T) => boolean): azdata.ComponentBuilder<T> {
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
class TestLoadingBuilder extends TestComponentBuilder<azdata.LoadingComponent> implements azdata.LoadingComponentBuilder {
|
||||
withItem(component: azdata.Component): azdata.LoadingComponentBuilder {
|
||||
this.component().component = component;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
export function createViewContext(): TestContext {
|
||||
let onClick: vscode.EventEmitter<any> = new vscode.EventEmitter<any>();
|
||||
|
||||
let form: azdata.FormContainer = new TestFormContainer();
|
||||
let textBuilder: azdata.ComponentBuilder<azdata.TextComponent> = new TestComponentBuilder(new TestTextComponent());
|
||||
let buttonBuilder: azdata.ComponentBuilder<azdata.ButtonComponent> = new TestComponentBuilder(new TestButtonComponent(onClick));
|
||||
let radioButtonBuilder: azdata.ComponentBuilder<azdata.ButtonComponent> = new TestComponentBuilder(new TestRadioButtonComponent(onClick));
|
||||
let declarativeTableBuilder: azdata.ComponentBuilder<azdata.DeclarativeTableComponent> = new TestComponentBuilder(new TestDeclarativeTableComponent(onClick));
|
||||
let loadingBuilder: azdata.LoadingComponentBuilder = new TestLoadingBuilder(new TestLoadingComponent());
|
||||
let dropdownBuilder: azdata.ComponentBuilder<azdata.DropDownComponent> = new TestComponentBuilder(new TestDropdownComponent(onClick));
|
||||
|
||||
let formBuilder: azdata.FormBuilder = Object.assign({}, {
|
||||
component: () => form,
|
||||
addFormItem: () => { },
|
||||
insertFormItem: () => { },
|
||||
removeFormItem: () => true,
|
||||
addFormItems: () => { },
|
||||
withFormItems: () => formBuilder,
|
||||
withProperties: () => formBuilder,
|
||||
withValidation: () => formBuilder,
|
||||
withItems: () => formBuilder,
|
||||
withLayout: () => formBuilder
|
||||
});
|
||||
|
||||
let view: azdata.ModelView = {
|
||||
onClosed: undefined!,
|
||||
connection: undefined!,
|
||||
serverInfo: undefined!,
|
||||
valid: true,
|
||||
onValidityChanged: undefined!,
|
||||
validate: undefined!,
|
||||
initializeModel: () => { return Promise.resolve(); },
|
||||
modelBuilder: <azdata.ModelBuilder>{
|
||||
radioButton: () => radioButtonBuilder,
|
||||
text: () => textBuilder,
|
||||
button: () => buttonBuilder,
|
||||
dropDown: () => dropdownBuilder,
|
||||
declarativeTable: () => declarativeTableBuilder,
|
||||
formContainer: () => formBuilder,
|
||||
loadingComponent: () => loadingBuilder
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
view: view,
|
||||
onClick: onClick,
|
||||
};
|
||||
}
|
||||
|
||||
export interface TestContext {
|
||||
view: azdata.ModelView;
|
||||
onClick: vscode.EventEmitter<any>;
|
||||
}
|
||||
//#endregion
|
||||
|
||||
133
extensions/notebook/src/test/configurePython.test.ts
Normal file
133
extensions/notebook/src/test/configurePython.test.ts
Normal file
@@ -0,0 +1,133 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as azdata from 'azdata';
|
||||
import * as TypeMoq from 'typemoq';
|
||||
import { ApiWrapper } from '../common/apiWrapper';
|
||||
import { ConfigurePythonWizard, ConfigurePythonModel } from '../dialog/configurePython/configurePythonWizard';
|
||||
import { JupyterServerInstallation } from '../jupyter/jupyterServerInstallation';
|
||||
import { ConfigurePathPage } from '../dialog/configurePython/configurePathPage';
|
||||
import * as should from 'should';
|
||||
import { PickPackagesPage } from '../dialog/configurePython/pickPackagesPage';
|
||||
import { python3DisplayName, allKernelsName } from '../common/constants';
|
||||
import { TestContext, createViewContext } from './common';
|
||||
|
||||
describe('Configure Python Wizard', function () {
|
||||
let apiWrapper: ApiWrapper = new ApiWrapper();
|
||||
let testWizard: ConfigurePythonWizard;
|
||||
let viewContext: TestContext;
|
||||
let testInstallation: JupyterServerInstallation;
|
||||
|
||||
beforeEach(() => {
|
||||
let mockInstall = TypeMoq.Mock.ofType(JupyterServerInstallation);
|
||||
mockInstall.setup(i => i.getInstalledPipPackages(TypeMoq.It.isAnyString())).returns(() => Promise.resolve([]));
|
||||
testInstallation = mockInstall.object;
|
||||
|
||||
let mockWizard = TypeMoq.Mock.ofType(ConfigurePythonWizard);
|
||||
mockWizard.setup(w => w.showErrorMessage(TypeMoq.It.isAnyString()));
|
||||
testWizard = mockWizard.object;
|
||||
|
||||
viewContext = createViewContext();
|
||||
});
|
||||
|
||||
// These wizard tests are disabled due to errors with disposable objects
|
||||
//
|
||||
// it('Start wizard test', async () => {
|
||||
// let wizard = new ConfigurePythonWizard(apiWrapper, testInstallation);
|
||||
// await wizard.start();
|
||||
// await wizard.close();
|
||||
// await should(wizard.setupComplete).be.resolved();
|
||||
// });
|
||||
|
||||
// it('Reject setup on cancel test', async () => {
|
||||
// let wizard = new ConfigurePythonWizard(apiWrapper, testInstallation);
|
||||
// await wizard.start(undefined, true);
|
||||
// await wizard.close();
|
||||
// await should(wizard.setupComplete).be.rejected();
|
||||
// });
|
||||
|
||||
// it('Error message test', async () => {
|
||||
// let wizard = new ConfigurePythonWizard(apiWrapper, testInstallation);
|
||||
// await wizard.start();
|
||||
|
||||
// should(wizard.wizard.message).be.undefined();
|
||||
|
||||
// let testMsg = 'Test message';
|
||||
// wizard.showErrorMessage(testMsg);
|
||||
// should(wizard.wizard.message.text).be.equal(testMsg);
|
||||
// should(wizard.wizard.message.level).be.equal(azdata.window.MessageLevel.Error);
|
||||
|
||||
// wizard.clearStatusMessage();
|
||||
// should(wizard.wizard.message).be.undefined();
|
||||
|
||||
// await wizard.close();
|
||||
// });
|
||||
|
||||
it('Configure Path Page test', async () => {
|
||||
let testPythonLocation = '/not/a/real/path';
|
||||
let model = <ConfigurePythonModel>{
|
||||
useExistingPython: true,
|
||||
pythonPathsPromise: Promise.resolve([{
|
||||
installDir: testPythonLocation,
|
||||
version: '4000'
|
||||
}])
|
||||
};
|
||||
|
||||
let page = azdata.window.createWizardPage('Page 1');
|
||||
let configurePathPage = new ConfigurePathPage(apiWrapper, testWizard, page, model, viewContext.view);
|
||||
|
||||
should(await configurePathPage.initialize()).be.true();
|
||||
|
||||
// First page, so onPageEnter should do nothing
|
||||
await should(configurePathPage.onPageEnter()).be.resolved();
|
||||
|
||||
should(await configurePathPage.onPageLeave()).be.true();
|
||||
should(model.useExistingPython).be.true();
|
||||
should(model.pythonLocation).be.equal(testPythonLocation);
|
||||
});
|
||||
|
||||
it('Pick Packages Page test', async () => {
|
||||
let model = <ConfigurePythonModel>{
|
||||
kernelName: allKernelsName,
|
||||
installation: testInstallation,
|
||||
pythonLocation: '/not/a/real/path',
|
||||
useExistingPython: true
|
||||
};
|
||||
|
||||
let page = azdata.window.createWizardPage('Page 2');
|
||||
let pickPackagesPage = new PickPackagesPage(apiWrapper, testWizard, page, model, viewContext.view);
|
||||
|
||||
should(await pickPackagesPage.initialize()).be.true();
|
||||
|
||||
should((<any>pickPackagesPage).kernelLabel).not.be.undefined();
|
||||
should((<any>pickPackagesPage).kernelDropdown).be.undefined();
|
||||
|
||||
// Last page, so onPageLeave should do nothing
|
||||
should(await pickPackagesPage.onPageLeave()).be.true();
|
||||
|
||||
await should(pickPackagesPage.onPageEnter()).be.resolved();
|
||||
should(model.packagesToInstall).be.deepEqual(JupyterServerInstallation.getRequiredPackagesForKernel(allKernelsName));
|
||||
});
|
||||
|
||||
it('Undefined kernel test', async () => {
|
||||
let model = <ConfigurePythonModel>{
|
||||
kernelName: undefined,
|
||||
installation: testInstallation,
|
||||
pythonLocation: '/not/a/real/path',
|
||||
useExistingPython: true
|
||||
};
|
||||
|
||||
let page = azdata.window.createWizardPage('Page 2');
|
||||
let pickPackagesPage = new PickPackagesPage(apiWrapper, testWizard, page, model, viewContext.view);
|
||||
|
||||
should(await pickPackagesPage.initialize()).be.true();
|
||||
|
||||
should((<any>pickPackagesPage).kernelLabel).be.undefined();
|
||||
should((<any>pickPackagesPage).kernelDropdown).not.be.undefined();
|
||||
|
||||
await should(pickPackagesPage.onPageEnter()).be.resolved();
|
||||
should(model.packagesToInstall).be.deepEqual(JupyterServerInstallation.getRequiredPackagesForKernel(python3DisplayName));
|
||||
});
|
||||
});
|
||||
@@ -8,7 +8,7 @@ import * as azdata from 'azdata';
|
||||
import * as should from 'should';
|
||||
import 'mocha';
|
||||
import * as TypeMoq from 'typemoq';
|
||||
import { JupyterServerInstallation, PythonPkgDetails, IJupyterServerInstallation } from '../../jupyter/jupyterServerInstallation';
|
||||
import { JupyterServerInstallation, PythonPkgDetails, IJupyterServerInstallation, PythonInstallSettings } from '../../jupyter/jupyterServerInstallation';
|
||||
import { LocalCondaPackageManageProvider } from '../../jupyter/localCondaPackageManageProvider';
|
||||
import * as constants from '../../common/constants';
|
||||
import { LocalPipPackageManageProvider } from '../../jupyter/localPipPackageManageProvider';
|
||||
@@ -188,7 +188,7 @@ describe('Manage Package Providers', () => {
|
||||
serverInstallation: {
|
||||
installCondaPackages: (packages: PythonPkgDetails[], useMinVersion: boolean) => { return Promise.resolve(); },
|
||||
configurePackagePaths: () => { return Promise.resolve(); },
|
||||
startInstallProcess: (forceInstall: boolean, installSettings?: { installPath: string, existingPython: boolean }) => { return Promise.resolve(); },
|
||||
startInstallProcess: (forceInstall: boolean, installSettings?: PythonInstallSettings) => { return Promise.resolve(); },
|
||||
getInstalledPipPackages: () => { return Promise.resolve([]); },
|
||||
installPipPackages: (packages: PythonPkgDetails[], useMinVersion: boolean) => { return Promise.resolve(); },
|
||||
uninstallPipPackages: (packages: PythonPkgDetails[]) => { return Promise.resolve(); },
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
import * as should from 'should';
|
||||
import * as TypeMoq from 'typemoq';
|
||||
import * as vscode from 'vscode';
|
||||
import * as azdata from 'azdata';
|
||||
import 'mocha';
|
||||
|
||||
import { JupyterServerInstanceStub } from '../common';
|
||||
@@ -18,6 +19,10 @@ import { IServerInstance } from '../../jupyter/common';
|
||||
import { MockExtensionContext } from '../common/stubs';
|
||||
|
||||
describe('Local Jupyter Server Manager', function (): void {
|
||||
const pythonKernelSpec: azdata.nb.IKernelSpec = {
|
||||
name: 'python3',
|
||||
display_name: 'Python 3'
|
||||
};
|
||||
let expectedPath = 'my/notebook.ipynb';
|
||||
let serverManager: LocalJupyterServerManager;
|
||||
let deferredInstall: Deferred<void>;
|
||||
@@ -33,7 +38,7 @@ describe('Local Jupyter Server Manager', function (): void {
|
||||
|
||||
deferredInstall = new Deferred<void>();
|
||||
let mockInstall = TypeMoq.Mock.ofType(JupyterServerInstallation, undefined, undefined, '/root');
|
||||
mockInstall.setup(j => j.promptForPythonInstall()).returns(() => deferredInstall.promise);
|
||||
mockInstall.setup(j => j.promptForPythonInstall(TypeMoq.It.isAny())).returns(() => deferredInstall.promise);
|
||||
mockInstall.object.execOptions = { env: Object.assign({}, process.env) };
|
||||
|
||||
serverManager = new LocalJupyterServerManager({
|
||||
@@ -53,7 +58,7 @@ describe('Local Jupyter Server Manager', function (): void {
|
||||
it('Should show error message on install failure', async function (): Promise<void> {
|
||||
let error = 'Error!!';
|
||||
deferredInstall.reject(error);
|
||||
await testUtils.assertThrowsAsync(() => serverManager.startServer(), undefined);
|
||||
await testUtils.assertThrowsAsync(() => serverManager.startServer(pythonKernelSpec), undefined);
|
||||
});
|
||||
|
||||
it('Should configure and start install', async function (): Promise<void> {
|
||||
@@ -65,7 +70,7 @@ describe('Local Jupyter Server Manager', function (): void {
|
||||
// When I start the server
|
||||
let notified = false;
|
||||
serverManager.onServerStarted(() => notified = true);
|
||||
await serverManager.startServer();
|
||||
await serverManager.startServer(pythonKernelSpec);
|
||||
|
||||
// Then I expect the port to be included in settings
|
||||
should(serverManager.serverSettings.baseUrl.indexOf('1234') > -1).be.true();
|
||||
@@ -89,7 +94,7 @@ describe('Local Jupyter Server Manager', function (): void {
|
||||
deferredInstall.resolve();
|
||||
|
||||
// When I start and then the server
|
||||
await serverManager.startServer();
|
||||
await serverManager.startServer(pythonKernelSpec);
|
||||
await serverManager.stopServer();
|
||||
|
||||
// Then I expect stop to have been called on the server instance
|
||||
@@ -104,7 +109,7 @@ describe('Local Jupyter Server Manager', function (): void {
|
||||
deferredInstall.resolve();
|
||||
|
||||
// When I start and then dispose the extension
|
||||
await serverManager.startServer();
|
||||
await serverManager.startServer(pythonKernelSpec);
|
||||
should(mockExtensionContext.subscriptions).have.length(1);
|
||||
mockExtensionContext.subscriptions[0].dispose();
|
||||
|
||||
|
||||
@@ -108,7 +108,7 @@ describe('Jupyter Session', function (): void {
|
||||
|
||||
beforeEach(() => {
|
||||
mockJupyterSession = TypeMoq.Mock.ofType(SessionStub);
|
||||
session = new JupyterSession(mockJupyterSession.object);
|
||||
session = new JupyterSession(mockJupyterSession.object, undefined);
|
||||
});
|
||||
|
||||
it('should always be able to change kernels', function (): void {
|
||||
|
||||
2
src/sql/azdata.d.ts
vendored
2
src/sql/azdata.d.ts
vendored
@@ -4590,7 +4590,7 @@ declare module 'azdata' {
|
||||
* Starts the server. Some server types may not support or require this.
|
||||
* Should no-op if server is already started
|
||||
*/
|
||||
startServer(): Thenable<void>;
|
||||
startServer(kernelSpec: IKernelSpec): Thenable<void>;
|
||||
|
||||
/**
|
||||
* Stops the server. Some server types may not support or require this
|
||||
|
||||
@@ -184,12 +184,12 @@ class ServerManagerWrapper implements azdata.nb.ServerManager {
|
||||
return this.onServerStartedEmitter.event;
|
||||
}
|
||||
|
||||
startServer(): Thenable<void> {
|
||||
return this.doStartServer();
|
||||
startServer(kernelSpec: azdata.nb.IKernelSpec): Thenable<void> {
|
||||
return this.doStartServer(kernelSpec);
|
||||
}
|
||||
|
||||
private async doStartServer(): Promise<void> {
|
||||
await this._proxy.ext.$doStartServer(this.handle);
|
||||
private async doStartServer(kernelSpec: azdata.nb.IKernelSpec): Promise<void> {
|
||||
await this._proxy.ext.$doStartServer(this.handle, kernelSpec);
|
||||
this._isStarted = true;
|
||||
this.onServerStartedEmitter.fire();
|
||||
}
|
||||
|
||||
@@ -54,8 +54,8 @@ export class ExtHostNotebook implements ExtHostNotebookShape {
|
||||
}
|
||||
}
|
||||
|
||||
$doStartServer(managerHandle: number): Thenable<void> {
|
||||
return this._withServerManager(managerHandle, (serverManager) => serverManager.startServer());
|
||||
$doStartServer(managerHandle: number, kernelSpec: azdata.nb.IKernelSpec): Thenable<void> {
|
||||
return this._withServerManager(managerHandle, (serverManager) => serverManager.startServer(kernelSpec));
|
||||
}
|
||||
|
||||
$doStopServer(managerHandle: number): Thenable<void> {
|
||||
|
||||
@@ -806,7 +806,7 @@ export interface ExtHostNotebookShape {
|
||||
$handleNotebookClosed(notebookUri: UriComponents): void;
|
||||
|
||||
// Server Manager APIs
|
||||
$doStartServer(managerHandle: number): Thenable<void>;
|
||||
$doStartServer(managerHandle: number, kernelSpec: azdata.nb.IKernelSpec): Thenable<void>;
|
||||
$doStopServer(managerHandle: number): Thenable<void>;
|
||||
|
||||
// Content Manager APIs
|
||||
|
||||
@@ -178,7 +178,7 @@ export class ServerManagerStub implements nb.ServerManager {
|
||||
calledEnd: boolean = false;
|
||||
result: Promise<void> = undefined;
|
||||
|
||||
startServer(): Promise<void> {
|
||||
startServer(kernelSpec: nb.IKernelSpec): Promise<void> {
|
||||
this.calledStart = true;
|
||||
return this.result;
|
||||
}
|
||||
|
||||
@@ -60,7 +60,7 @@ export class ClientSession implements IClientSession {
|
||||
|
||||
public async initialize(): Promise<void> {
|
||||
try {
|
||||
this._serverLoadFinished = this.startServer();
|
||||
this._serverLoadFinished = this.startServer(this.options.kernelSpec);
|
||||
await this._serverLoadFinished;
|
||||
await this.initializeSession();
|
||||
await this.updateCachedKernelSpec();
|
||||
@@ -75,10 +75,10 @@ export class ClientSession implements IClientSession {
|
||||
}
|
||||
}
|
||||
|
||||
private async startServer(): Promise<void> {
|
||||
private async startServer(kernelSpec: nb.IKernelSpec): Promise<void> {
|
||||
let serverManager = this.notebookManager.serverManager;
|
||||
if (serverManager) {
|
||||
await serverManager.startServer();
|
||||
await serverManager.startServer(kernelSpec);
|
||||
if (!serverManager.isStarted) {
|
||||
throw new Error(localize('ServerNotStarted', "Server did not start for unknown reason"));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user