mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-17 02:51:36 -05:00
Add dialog to notebooks for managing Pip packages (#5944)
This commit is contained in:
@@ -111,8 +111,8 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"command": "jupyter.cmd.installPackages",
|
"command": "jupyter.cmd.managePackages",
|
||||||
"title": "%title.installPackages%",
|
"title": "%title.managePackages%",
|
||||||
"icon": {
|
"icon": {
|
||||||
"dark": "resources/dark/manage_inverse.svg",
|
"dark": "resources/dark/manage_inverse.svg",
|
||||||
"light": "resources/light/manage.svg"
|
"light": "resources/light/manage.svg"
|
||||||
@@ -188,7 +188,7 @@
|
|||||||
"when": "false"
|
"when": "false"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"command": "jupyter.cmd.installPackages",
|
"command": "jupyter.cmd.managePackages",
|
||||||
"when": "false"
|
"when": "false"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
@@ -223,7 +223,7 @@
|
|||||||
],
|
],
|
||||||
"notebook/toolbar": [
|
"notebook/toolbar": [
|
||||||
{
|
{
|
||||||
"command": "jupyter.cmd.installPackages",
|
"command": "jupyter.cmd.managePackages",
|
||||||
"when": "providerId == jupyter"
|
"when": "providerId == jupyter"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -24,5 +24,5 @@
|
|||||||
"config.jupyter.kernelConfigValuesDescription": "Configuration options for Jupyter kernels. This is automatically managed and not recommended to be manually edited.",
|
"config.jupyter.kernelConfigValuesDescription": "Configuration options for Jupyter kernels. This is automatically managed and not recommended to be manually edited.",
|
||||||
"title.reinstallNotebookDependencies": "Reinstall Notebook dependencies",
|
"title.reinstallNotebookDependencies": "Reinstall Notebook dependencies",
|
||||||
"title.configurePython": "Configure Python for Notebooks",
|
"title.configurePython": "Configure Python for Notebooks",
|
||||||
"title.installPackages": "Install Packages"
|
"title.managePackages": "Manage Packages"
|
||||||
}
|
}
|
||||||
@@ -39,7 +39,7 @@ export const jupyterCommandSetContext = 'jupyter.setContext';
|
|||||||
export const jupyterCommandSetKernel = 'jupyter.setKernel';
|
export const jupyterCommandSetKernel = 'jupyter.setKernel';
|
||||||
export const jupyterReinstallDependenciesCommand = 'jupyter.reinstallDependencies';
|
export const jupyterReinstallDependenciesCommand = 'jupyter.reinstallDependencies';
|
||||||
export const jupyterAnalyzeCommand = 'jupyter.cmd.analyzeNotebook';
|
export const jupyterAnalyzeCommand = 'jupyter.cmd.analyzeNotebook';
|
||||||
export const jupyterInstallPackages = 'jupyter.cmd.installPackages';
|
export const jupyterManagePackages = 'jupyter.cmd.managePackages';
|
||||||
export const jupyterConfigurePython = 'jupyter.cmd.configurePython';
|
export const jupyterConfigurePython = 'jupyter.cmd.configurePython';
|
||||||
|
|
||||||
export enum BuiltInCommands {
|
export enum BuiltInCommands {
|
||||||
|
|||||||
@@ -0,0 +1,285 @@
|
|||||||
|
/*---------------------------------------------------------------------------------------------
|
||||||
|
* 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 * as request from 'request';
|
||||||
|
|
||||||
|
import JupyterServerInstallation from '../../jupyter/jupyterServerInstallation';
|
||||||
|
import * as utils from '../../common/utils';
|
||||||
|
import { ManagePackagesDialog } from './managePackagesDialog';
|
||||||
|
|
||||||
|
const localize = nls.loadMessageBundle();
|
||||||
|
|
||||||
|
export class AddNewPackageTab {
|
||||||
|
private addNewPkgTab: azdata.window.DialogTab;
|
||||||
|
|
||||||
|
private newPackagesSearchBar: azdata.InputBoxComponent;
|
||||||
|
private packagesSearchButton: azdata.ButtonComponent;
|
||||||
|
private newPackagesName: azdata.TextComponent;
|
||||||
|
private newPackagesNameLoader: azdata.LoadingComponent;
|
||||||
|
private newPackagesVersions: azdata.DropDownComponent;
|
||||||
|
private newPackagesVersionsLoader: azdata.LoadingComponent;
|
||||||
|
private newPackagesSummary: azdata.TextComponent;
|
||||||
|
private newPackagesSummaryLoader: azdata.LoadingComponent;
|
||||||
|
private packageInstallButton: azdata.ButtonComponent;
|
||||||
|
|
||||||
|
private readonly InvalidTextPlaceholder = localize('managePackages.invalidTextPlaceholder', "N/A");
|
||||||
|
|
||||||
|
constructor(private dialog: ManagePackagesDialog, private jupyterInstallation: JupyterServerInstallation) {
|
||||||
|
this.addNewPkgTab = azdata.window.createTab(localize('managePackages.addNewTabTitle', "Add new"));
|
||||||
|
|
||||||
|
this.addNewPkgTab.registerContent(async view => {
|
||||||
|
this.newPackagesSearchBar = view.modelBuilder.inputBox()
|
||||||
|
.withProperties<azdata.InputBoxProperties>({
|
||||||
|
placeHolder: localize('managePackages.searchBarPlaceholder', "Search for packages")
|
||||||
|
}).component();
|
||||||
|
|
||||||
|
this.packagesSearchButton = view.modelBuilder.button()
|
||||||
|
.withProperties<azdata.ButtonProperties>({
|
||||||
|
label: localize('managePackages.searchButtonLabel', "Search"),
|
||||||
|
width: '80px'
|
||||||
|
}).component();
|
||||||
|
this.packagesSearchButton.onDidClick(() => {
|
||||||
|
this.loadNewPackageInfo();
|
||||||
|
});
|
||||||
|
|
||||||
|
this.newPackagesName = view.modelBuilder.text().withProperties({
|
||||||
|
value: this.InvalidTextPlaceholder
|
||||||
|
}).component();
|
||||||
|
this.newPackagesNameLoader = view.modelBuilder.loadingComponent()
|
||||||
|
.withItem(this.newPackagesName)
|
||||||
|
.withProperties({ loading: false })
|
||||||
|
.component();
|
||||||
|
|
||||||
|
this.newPackagesVersions = view.modelBuilder.dropDown().withProperties({
|
||||||
|
value: this.InvalidTextPlaceholder,
|
||||||
|
values: [this.InvalidTextPlaceholder]
|
||||||
|
}).component();
|
||||||
|
this.newPackagesVersionsLoader = view.modelBuilder.loadingComponent()
|
||||||
|
.withItem(this.newPackagesVersions)
|
||||||
|
.withProperties({ loading: false })
|
||||||
|
.component();
|
||||||
|
|
||||||
|
this.newPackagesSummary = view.modelBuilder.text().withProperties({
|
||||||
|
value: this.InvalidTextPlaceholder
|
||||||
|
}).component();
|
||||||
|
this.newPackagesSummaryLoader = view.modelBuilder.loadingComponent()
|
||||||
|
.withItem(this.newPackagesSummary)
|
||||||
|
.withProperties({ loading: false })
|
||||||
|
.component();
|
||||||
|
|
||||||
|
this.packageInstallButton = view.modelBuilder.button().withProperties({
|
||||||
|
label: localize('managePackages.installButtonText', "Install"),
|
||||||
|
enabled: false,
|
||||||
|
width: '80px'
|
||||||
|
}).component();
|
||||||
|
this.packageInstallButton.onDidClick(() => {
|
||||||
|
this.doPackageInstall();
|
||||||
|
});
|
||||||
|
|
||||||
|
let formModel = view.modelBuilder.formContainer()
|
||||||
|
.withFormItems([{
|
||||||
|
component: this.newPackagesSearchBar,
|
||||||
|
title: ''
|
||||||
|
}, {
|
||||||
|
component: this.packagesSearchButton,
|
||||||
|
title: ''
|
||||||
|
}, {
|
||||||
|
component: this.newPackagesNameLoader,
|
||||||
|
title: localize('managePackages.packageNameTitle', "Package Name")
|
||||||
|
}, {
|
||||||
|
component: this.newPackagesVersionsLoader,
|
||||||
|
title: localize('managePackages.packageVersionTitle', "Package Version")
|
||||||
|
}, {
|
||||||
|
component: this.newPackagesSummaryLoader,
|
||||||
|
title: localize('managePackages.packageSummaryTitle', "Package Summary")
|
||||||
|
}, {
|
||||||
|
component: this.packageInstallButton,
|
||||||
|
title: ''
|
||||||
|
}]).component();
|
||||||
|
|
||||||
|
await view.initializeModel(formModel);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public get tab(): azdata.window.DialogTab {
|
||||||
|
return this.addNewPkgTab;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async toggleNewPackagesFields(enable: boolean): Promise<void> {
|
||||||
|
await this.packagesSearchButton.updateProperties({ enabled: enable });
|
||||||
|
await this.newPackagesNameLoader.updateProperties({ loading: !enable });
|
||||||
|
await this.newPackagesVersionsLoader.updateProperties({ loading: !enable });
|
||||||
|
await this.newPackagesSummaryLoader.updateProperties({ loading: !enable });
|
||||||
|
}
|
||||||
|
|
||||||
|
private async loadNewPackageInfo(): Promise<void> {
|
||||||
|
await this.packageInstallButton.updateProperties({ enabled: false });
|
||||||
|
await this.toggleNewPackagesFields(false);
|
||||||
|
try {
|
||||||
|
let packageName = this.newPackagesSearchBar.value;
|
||||||
|
if (!packageName || packageName.length === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let pipPackage = await this.fetchPypiPackage(packageName);
|
||||||
|
if (!pipPackage.versions || pipPackage.versions.length === 0) {
|
||||||
|
this.dialog.showErrorMessage(
|
||||||
|
localize('managePackages.noVersionsFound',
|
||||||
|
"Could not find any valid versions for the specified package"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.newPackagesName.updateProperties({
|
||||||
|
value: packageName
|
||||||
|
});
|
||||||
|
await this.newPackagesVersions.updateProperties({
|
||||||
|
values: pipPackage.versions,
|
||||||
|
value: pipPackage.versions[0],
|
||||||
|
});
|
||||||
|
await this.newPackagesSummary.updateProperties({
|
||||||
|
value: pipPackage.summary
|
||||||
|
});
|
||||||
|
|
||||||
|
// Only re-enable install on success
|
||||||
|
await this.packageInstallButton.updateProperties({ enabled: true });
|
||||||
|
} catch (err) {
|
||||||
|
this.dialog.showErrorMessage(utils.getErrorMessage(err));
|
||||||
|
|
||||||
|
await this.newPackagesName.updateProperties({
|
||||||
|
value: this.InvalidTextPlaceholder
|
||||||
|
});
|
||||||
|
await this.newPackagesVersions.updateProperties({
|
||||||
|
value: this.InvalidTextPlaceholder,
|
||||||
|
values: [this.InvalidTextPlaceholder],
|
||||||
|
});
|
||||||
|
await this.newPackagesSummary.updateProperties({
|
||||||
|
value: this.InvalidTextPlaceholder
|
||||||
|
});
|
||||||
|
} finally {
|
||||||
|
await this.toggleNewPackagesFields(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async fetchPypiPackage(packageName: string): Promise<PipPackageData> {
|
||||||
|
return new Promise<PipPackageData>((resolve, reject) => {
|
||||||
|
request.get(`https://pypi.org/pypi/${packageName}/json`, { timeout: 10000 }, (error, response, body) => {
|
||||||
|
if (error) {
|
||||||
|
return reject(error);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (response.statusCode === 404) {
|
||||||
|
return reject(localize('managePackages.packageNotFound', "Could not find the specified package"));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (response.statusCode !== 200) {
|
||||||
|
return reject(
|
||||||
|
localize('managePackages.packageRequestError',
|
||||||
|
"Package info request failed with error: {0} {1}",
|
||||||
|
response.statusCode,
|
||||||
|
response.statusMessage));
|
||||||
|
}
|
||||||
|
|
||||||
|
let versionNums: string[] = [];
|
||||||
|
let packageSummary = '';
|
||||||
|
|
||||||
|
let packagesJson = JSON.parse(body);
|
||||||
|
if (packagesJson) {
|
||||||
|
if (packagesJson.releases) {
|
||||||
|
let versionKeys = Object.keys(packagesJson.releases);
|
||||||
|
versionKeys = versionKeys.filter(versionKey => {
|
||||||
|
let releaseInfo = packagesJson.releases[versionKey];
|
||||||
|
return Array.isArray(releaseInfo) && releaseInfo.length > 0;
|
||||||
|
});
|
||||||
|
versionKeys.sort((first, second) => {
|
||||||
|
// sort in descending order
|
||||||
|
let firstVersion = first.split('.').map(numStr => Number.parseInt(numStr));
|
||||||
|
let secondVersion = second.split('.').map(numStr => Number.parseInt(numStr));
|
||||||
|
|
||||||
|
// If versions have different lengths, then append zeroes to the shorter one
|
||||||
|
if (firstVersion.length > secondVersion.length) {
|
||||||
|
let diff = firstVersion.length - secondVersion.length;
|
||||||
|
secondVersion = secondVersion.concat(new Array(diff).fill(0));
|
||||||
|
} else if (secondVersion.length > firstVersion.length) {
|
||||||
|
let diff = secondVersion.length - firstVersion.length;
|
||||||
|
firstVersion = firstVersion.concat(new Array(diff).fill(0));
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let i = 0; i < firstVersion.length; ++i) {
|
||||||
|
if (firstVersion[i] > secondVersion[i]) {
|
||||||
|
return -1;
|
||||||
|
} else if (firstVersion[i] < secondVersion[i]) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
});
|
||||||
|
versionNums = versionKeys;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (packagesJson.info && packagesJson.info.summary) {
|
||||||
|
packageSummary = packagesJson.info.summary;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
resolve({
|
||||||
|
name: packageName,
|
||||||
|
versions: versionNums,
|
||||||
|
summary: packageSummary
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private async doPackageInstall(): Promise<void> {
|
||||||
|
let packageName = this.newPackagesName.value;
|
||||||
|
let packageVersion = this.newPackagesVersions.value as string;
|
||||||
|
if (!packageName || packageName.length === 0 ||
|
||||||
|
!packageVersion || packageVersion.length === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let taskName = localize('managePackages.backgroundInstallStarted',
|
||||||
|
"Installing {0} {1}",
|
||||||
|
packageName,
|
||||||
|
packageVersion);
|
||||||
|
this.jupyterInstallation.apiWrapper.startBackgroundOperation({
|
||||||
|
displayName: taskName,
|
||||||
|
description: taskName,
|
||||||
|
isCancelable: false,
|
||||||
|
operation: op => {
|
||||||
|
this.jupyterInstallation.installPipPackage(packageName, packageVersion)
|
||||||
|
.then(async () => {
|
||||||
|
let installMsg = localize('managePackages.backgroundInstallComplete',
|
||||||
|
"Completed install for {0} {1}",
|
||||||
|
packageName,
|
||||||
|
packageVersion);
|
||||||
|
|
||||||
|
op.updateStatus(azdata.TaskStatus.Succeeded, installMsg);
|
||||||
|
this.jupyterInstallation.outputChannel.appendLine(installMsg);
|
||||||
|
|
||||||
|
await this.dialog.refreshInstalledPackages();
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
let installFailedMsg = localize('managePackages.backgroundInstallFailed',
|
||||||
|
"Failed to install {0} {1}. Error: {2}",
|
||||||
|
packageName,
|
||||||
|
packageVersion,
|
||||||
|
utils.getErrorMessage(err));
|
||||||
|
|
||||||
|
op.updateStatus(azdata.TaskStatus.Failed, installFailedMsg);
|
||||||
|
this.jupyterInstallation.outputChannel.appendLine(installFailedMsg);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
interface PipPackageData {
|
||||||
|
name: string;
|
||||||
|
versions: string[];
|
||||||
|
summary: string;
|
||||||
|
}
|
||||||
@@ -0,0 +1,193 @@
|
|||||||
|
/*---------------------------------------------------------------------------------------------
|
||||||
|
* 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 JupyterServerInstallation, { PythonPkgDetails } from '../../jupyter/jupyterServerInstallation';
|
||||||
|
import * as utils from '../../common/utils';
|
||||||
|
import { ManagePackagesDialog } from './managePackagesDialog';
|
||||||
|
import CodeAdapter from '../../prompts/adapter';
|
||||||
|
import { QuestionTypes, IQuestion } from '../../prompts/question';
|
||||||
|
|
||||||
|
const localize = nls.loadMessageBundle();
|
||||||
|
|
||||||
|
export class InstalledPackagesTab {
|
||||||
|
private prompter: CodeAdapter;
|
||||||
|
|
||||||
|
private installedPkgTab: azdata.window.DialogTab;
|
||||||
|
|
||||||
|
private installedPackageCount: azdata.TextComponent;
|
||||||
|
private installedPackagesTable: azdata.TableComponent;
|
||||||
|
private installedPackagesLoader: azdata.LoadingComponent;
|
||||||
|
private uninstallPackageButton: azdata.ButtonComponent;
|
||||||
|
|
||||||
|
constructor(private dialog: ManagePackagesDialog, private jupyterInstallation: JupyterServerInstallation) {
|
||||||
|
this.prompter = new CodeAdapter();
|
||||||
|
|
||||||
|
this.installedPkgTab = azdata.window.createTab(localize('managePackages.installedTabTitle', "Installed"));
|
||||||
|
|
||||||
|
this.installedPkgTab.registerContent(async view => {
|
||||||
|
this.installedPackageCount = view.modelBuilder.text().withProperties({
|
||||||
|
value: ''
|
||||||
|
}).component();
|
||||||
|
|
||||||
|
this.installedPackagesTable = view.modelBuilder.table()
|
||||||
|
.withProperties({
|
||||||
|
columns: [
|
||||||
|
localize('managePackages.pkgNameColumn', "Name"),
|
||||||
|
localize('managePackages.newPkgVersionColumn', "Version")
|
||||||
|
],
|
||||||
|
data: [[]],
|
||||||
|
height: '600px',
|
||||||
|
width: '400px'
|
||||||
|
}).component();
|
||||||
|
|
||||||
|
this.uninstallPackageButton = view.modelBuilder.button()
|
||||||
|
.withProperties({
|
||||||
|
label: localize('managePackages.uninstallButtonText', "Uninstall selected packages"),
|
||||||
|
width: '200px'
|
||||||
|
}).component();
|
||||||
|
this.uninstallPackageButton.onDidClick(() => this.doUninstallPackage());
|
||||||
|
|
||||||
|
let formModel = view.modelBuilder.formContainer()
|
||||||
|
.withFormItems([{
|
||||||
|
component: this.installedPackageCount,
|
||||||
|
title: ''
|
||||||
|
}, {
|
||||||
|
component: this.installedPackagesTable,
|
||||||
|
title: ''
|
||||||
|
}, {
|
||||||
|
component: this.uninstallPackageButton,
|
||||||
|
title: ''
|
||||||
|
}]).component();
|
||||||
|
|
||||||
|
this.installedPackagesLoader = view.modelBuilder.loadingComponent()
|
||||||
|
.withItem(formModel)
|
||||||
|
.withProperties({
|
||||||
|
loading: true
|
||||||
|
}).component();
|
||||||
|
|
||||||
|
await view.initializeModel(this.installedPackagesLoader);
|
||||||
|
|
||||||
|
await this.loadInstalledPackagesInfo();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public get tab(): azdata.window.DialogTab {
|
||||||
|
return this.installedPkgTab;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async loadInstalledPackagesInfo(): Promise<void> {
|
||||||
|
let pythonPackages: PythonPkgDetails[];
|
||||||
|
let packagesLocation: string;
|
||||||
|
|
||||||
|
await this.installedPackagesLoader.updateProperties({ loading: true });
|
||||||
|
await this.uninstallPackageButton.updateProperties({ enabled: false });
|
||||||
|
try {
|
||||||
|
pythonPackages = await this.jupyterInstallation.getInstalledPipPackages();
|
||||||
|
packagesLocation = await this.jupyterInstallation.getPythonPackagesPath();
|
||||||
|
} catch (err) {
|
||||||
|
this.dialog.showErrorMessage(utils.getErrorMessage(err));
|
||||||
|
} finally {
|
||||||
|
await this.installedPackagesLoader.updateProperties({ loading: false });
|
||||||
|
}
|
||||||
|
|
||||||
|
let packageData: string[][];
|
||||||
|
let packageCount: number;
|
||||||
|
if (pythonPackages) {
|
||||||
|
packageCount = pythonPackages.length;
|
||||||
|
packageData = pythonPackages.map(pkg => [pkg.name, pkg.version]);
|
||||||
|
} else {
|
||||||
|
packageCount = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
let countMsg: string;
|
||||||
|
if (packagesLocation && packagesLocation.length > 0) {
|
||||||
|
countMsg = localize('managePackages.packageCount', "{0} packages found in '{1}'",
|
||||||
|
packageCount,
|
||||||
|
packagesLocation);
|
||||||
|
} else {
|
||||||
|
countMsg = localize('managePackages.packageCountNoPath', "{0} packages found",
|
||||||
|
packageCount);
|
||||||
|
}
|
||||||
|
await this.installedPackageCount.updateProperties({
|
||||||
|
value: countMsg
|
||||||
|
});
|
||||||
|
|
||||||
|
if (packageData && packageData.length > 0) {
|
||||||
|
await this.installedPackagesTable.updateProperties({
|
||||||
|
data: packageData,
|
||||||
|
selectedRows: [0]
|
||||||
|
});
|
||||||
|
await this.uninstallPackageButton.updateProperties({ enabled: true });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async doUninstallPackage(): Promise<void> {
|
||||||
|
let rowNums = this.installedPackagesTable.selectedRows;
|
||||||
|
if (!rowNums || rowNums.length === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.uninstallPackageButton.updateProperties({ enabled: false });
|
||||||
|
let doUninstall = await this.prompter.promptSingle<boolean>(<IQuestion>{
|
||||||
|
type: QuestionTypes.confirm,
|
||||||
|
message: localize('managePackages.confirmUninstall', 'Are you sure you want to uninstall the specified packages?'),
|
||||||
|
default: false
|
||||||
|
});
|
||||||
|
|
||||||
|
if (doUninstall) {
|
||||||
|
try {
|
||||||
|
let packages: PythonPkgDetails[] = rowNums.map(rowNum => {
|
||||||
|
let row = this.installedPackagesTable.data[rowNum];
|
||||||
|
return {
|
||||||
|
name: row[0],
|
||||||
|
version: row[1]
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
let packagesStr = packages.map(pkg => {
|
||||||
|
return `${pkg.name} ${pkg.version}`;
|
||||||
|
}).join(', ');
|
||||||
|
let taskName = localize('managePackages.backgroundUninstallStarted',
|
||||||
|
"Uninstalling {0}",
|
||||||
|
packagesStr);
|
||||||
|
|
||||||
|
this.jupyterInstallation.apiWrapper.startBackgroundOperation({
|
||||||
|
displayName: taskName,
|
||||||
|
description: taskName,
|
||||||
|
isCancelable: false,
|
||||||
|
operation: op => {
|
||||||
|
this.jupyterInstallation.uninstallPipPackages(packages)
|
||||||
|
.then(async () => {
|
||||||
|
let uninstallMsg = localize('managePackages.backgroundUninstallComplete',
|
||||||
|
"Completed uninstall for {0}",
|
||||||
|
packagesStr);
|
||||||
|
|
||||||
|
op.updateStatus(azdata.TaskStatus.Succeeded, uninstallMsg);
|
||||||
|
this.jupyterInstallation.outputChannel.appendLine(uninstallMsg);
|
||||||
|
|
||||||
|
await this.loadInstalledPackagesInfo();
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
let uninstallFailedMsg = localize('managePackages.backgroundUninstallFailed',
|
||||||
|
"Failed to uninstall {0}. Error: {1}",
|
||||||
|
packagesStr,
|
||||||
|
utils.getErrorMessage(err));
|
||||||
|
|
||||||
|
op.updateStatus(azdata.TaskStatus.Failed, uninstallFailedMsg);
|
||||||
|
this.jupyterInstallation.outputChannel.appendLine(uninstallFailedMsg);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
this.dialog.showErrorMessage(utils.getErrorMessage(err));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.uninstallPackageButton.updateProperties({ enabled: true });
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
/*---------------------------------------------------------------------------------------------
|
||||||
|
* 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 JupyterServerInstallation from '../../jupyter/jupyterServerInstallation';
|
||||||
|
import { InstalledPackagesTab } from './installedPackagesTab';
|
||||||
|
import { AddNewPackageTab } from './addNewPackageTab';
|
||||||
|
|
||||||
|
const localize = nls.loadMessageBundle();
|
||||||
|
|
||||||
|
export class ManagePackagesDialog {
|
||||||
|
private dialog: azdata.window.Dialog;
|
||||||
|
private installedPkgTab: InstalledPackagesTab;
|
||||||
|
private addNewPkgTab: AddNewPackageTab;
|
||||||
|
|
||||||
|
constructor(private jupyterInstallation: JupyterServerInstallation) {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Opens a dialog to manage packages used by notebooks.
|
||||||
|
*/
|
||||||
|
public showDialog(): void {
|
||||||
|
this.dialog = azdata.window.createModelViewDialog(localize('managePackages.dialogName', "Manage Packages"));
|
||||||
|
|
||||||
|
this.installedPkgTab = new InstalledPackagesTab(this, this.jupyterInstallation);
|
||||||
|
this.addNewPkgTab = new AddNewPackageTab(this, this.jupyterInstallation);
|
||||||
|
|
||||||
|
this.dialog.okButton.hidden = true;
|
||||||
|
this.dialog.cancelButton.label = localize('managePackages.cancelButtonText', "Close");
|
||||||
|
|
||||||
|
this.dialog.content = [this.installedPkgTab.tab, this.addNewPkgTab.tab];
|
||||||
|
|
||||||
|
azdata.window.openDialog(this.dialog);
|
||||||
|
}
|
||||||
|
|
||||||
|
public refreshInstalledPackages(): Promise<void> {
|
||||||
|
return this.installedPkgTab.loadInstalledPackagesInfo();
|
||||||
|
}
|
||||||
|
|
||||||
|
public showInfoMessage(message: string): void {
|
||||||
|
this.dialog.message = {
|
||||||
|
text: message,
|
||||||
|
level: azdata.window.MessageLevel.Information
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public showErrorMessage(message: string): void {
|
||||||
|
this.dialog.message = {
|
||||||
|
text: message,
|
||||||
|
level: azdata.window.MessageLevel.Error
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -10,7 +10,7 @@ import * as path from 'path';
|
|||||||
import 'mocha';
|
import 'mocha';
|
||||||
|
|
||||||
import { JupyterController } from '../jupyter/jupyterController';
|
import { JupyterController } from '../jupyter/jupyterController';
|
||||||
import JupyterServerInstallation from '../jupyter/jupyterServerInstallation';
|
import JupyterServerInstallation, { PythonPkgDetails } from '../jupyter/jupyterServerInstallation';
|
||||||
import { pythonBundleVersion } from '../common/constants';
|
import { pythonBundleVersion } from '../common/constants';
|
||||||
import { executeStreamedCommand } from '../common/utils';
|
import { executeStreamedCommand } from '../common/utils';
|
||||||
|
|
||||||
@@ -80,4 +80,28 @@ describe('Notebook Extension Python Installation', function () {
|
|||||||
should(JupyterServerInstallation.getExistingPythonSetting(apiWrapper)).be.false();
|
should(JupyterServerInstallation.getExistingPythonSetting(apiWrapper)).be.false();
|
||||||
console.log('Existing Python Installation is done');
|
console.log('Existing Python Installation is done');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('Python Install Utilities Test', async function () {
|
||||||
|
let install = jupyterController.jupyterInstallation;
|
||||||
|
|
||||||
|
let packagePath = await install.getPythonPackagesPath();
|
||||||
|
should(packagePath).not.be.undefined();
|
||||||
|
should(packagePath.length).be.greaterThan(0);
|
||||||
|
|
||||||
|
let testPkg = 'pandas';
|
||||||
|
let testPkgVersion = '0.24.2';
|
||||||
|
let expectedPkg: PythonPkgDetails = { name: testPkg, version: testPkgVersion };
|
||||||
|
|
||||||
|
await install.installPipPackage(testPkg, testPkgVersion);
|
||||||
|
let packages = await install.getInstalledPipPackages();
|
||||||
|
should(packages).containEql(expectedPkg);
|
||||||
|
|
||||||
|
await install.uninstallPipPackages([{ name: testPkg, version: testPkgVersion}]);
|
||||||
|
packages = await install.getInstalledPipPackages();
|
||||||
|
should(packages).not.containEql(expectedPkg);
|
||||||
|
|
||||||
|
await install.installPipPackage(testPkg, testPkgVersion);
|
||||||
|
packages = await install.getInstalledPipPackages();
|
||||||
|
should(packages).containEql(expectedPkg);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ import { NotebookCompletionItemProvider } from '../intellisense/completionItemPr
|
|||||||
import { JupyterNotebookProvider } from './jupyterNotebookProvider';
|
import { JupyterNotebookProvider } from './jupyterNotebookProvider';
|
||||||
import { ConfigurePythonDialog } from '../dialog/configurePythonDialog';
|
import { ConfigurePythonDialog } from '../dialog/configurePythonDialog';
|
||||||
import CodeAdapter from '../prompts/adapter';
|
import CodeAdapter from '../prompts/adapter';
|
||||||
|
import { ManagePackagesDialog } from '../dialog/managePackages/managePackagesDialog';
|
||||||
|
|
||||||
let untitledCounter = 0;
|
let untitledCounter = 0;
|
||||||
|
|
||||||
@@ -73,7 +74,7 @@ export class JupyterController implements vscode.Disposable {
|
|||||||
});
|
});
|
||||||
|
|
||||||
this.apiWrapper.registerCommand(constants.jupyterReinstallDependenciesCommand, () => { return this.handleDependenciesReinstallation(); });
|
this.apiWrapper.registerCommand(constants.jupyterReinstallDependenciesCommand, () => { return this.handleDependenciesReinstallation(); });
|
||||||
this.apiWrapper.registerCommand(constants.jupyterInstallPackages, () => { return this.doManagePackages(); });
|
this.apiWrapper.registerCommand(constants.jupyterManagePackages, () => { return this.doManagePackages(); });
|
||||||
this.apiWrapper.registerCommand(constants.jupyterConfigurePython, () => { return this.doConfigurePython(this._jupyterInstallation); });
|
this.apiWrapper.registerCommand(constants.jupyterConfigurePython, () => { return this.doConfigurePython(this._jupyterInstallation); });
|
||||||
|
|
||||||
let supportedFileFilter: vscode.DocumentFilter[] = [
|
let supportedFileFilter: vscode.DocumentFilter[] = [
|
||||||
@@ -194,10 +195,8 @@ export class JupyterController implements vscode.Disposable {
|
|||||||
|
|
||||||
public doManagePackages(): void {
|
public doManagePackages(): void {
|
||||||
try {
|
try {
|
||||||
let terminal = this.apiWrapper.createTerminalWithOptions({ cwd: this.getPythonBinDir() });
|
let packagesDialog = new ManagePackagesDialog(this._jupyterInstallation);
|
||||||
terminal.show(true);
|
packagesDialog.showDialog();
|
||||||
let shellType = this.apiWrapper.getConfiguration().get('terminal.integrated.shell.windows');
|
|
||||||
terminal.sendText(this.getTextToSendToTerminal(shellType), true);
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
let message = utils.getErrorMessage(error);
|
let message = utils.getErrorMessage(error);
|
||||||
this.apiWrapper.showErrorMessage(message);
|
this.apiWrapper.showErrorMessage(message);
|
||||||
@@ -225,10 +224,6 @@ export class JupyterController implements vscode.Disposable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private getPythonBinDir(): string {
|
|
||||||
return JupyterServerInstallation.getPythonBinPath(this.apiWrapper);
|
|
||||||
}
|
|
||||||
|
|
||||||
public get jupyterInstallation() {
|
public get jupyterInstallation() {
|
||||||
return this._jupyterInstallation;
|
return this._jupyterInstallation;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -347,6 +347,28 @@ export default class JupyterServerInstallation {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async getInstalledPipPackages(): Promise<PythonPkgDetails[]> {
|
||||||
|
let cmd = `"${this.pythonExecutable}" -m pip list --format=json`;
|
||||||
|
let packagesInfo = await this.executeBufferedCommand(cmd);
|
||||||
|
|
||||||
|
let packagesResult: PythonPkgDetails[] = [];
|
||||||
|
if (packagesInfo) {
|
||||||
|
packagesResult = <PythonPkgDetails[]>JSON.parse(packagesInfo);
|
||||||
|
}
|
||||||
|
return packagesResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
public installPipPackage(packageName: string, version: string): Promise<void> {
|
||||||
|
let cmd = `"${this.pythonExecutable}" -m pip install ${packageName}==${version}`;
|
||||||
|
return this.executeStreamedCommand(cmd);
|
||||||
|
}
|
||||||
|
|
||||||
|
public uninstallPipPackages(packages: PythonPkgDetails[]): Promise<void> {
|
||||||
|
let packagesStr = packages.map(pkg => `${pkg.name}==${pkg.version}`).join(' ');
|
||||||
|
let cmd = `"${this.pythonExecutable}" -m pip uninstall -y ${packagesStr}`;
|
||||||
|
return this.executeStreamedCommand(cmd);
|
||||||
|
}
|
||||||
|
|
||||||
private async installOfflinePipPackages(): Promise<void> {
|
private async installOfflinePipPackages(): Promise<void> {
|
||||||
let installJupyterCommand: string;
|
let installJupyterCommand: string;
|
||||||
if (process.platform === constants.winPlatform) {
|
if (process.platform === constants.winPlatform) {
|
||||||
@@ -357,7 +379,7 @@ export default class JupyterServerInstallation {
|
|||||||
if (installJupyterCommand) {
|
if (installJupyterCommand) {
|
||||||
this.outputChannel.show(true);
|
this.outputChannel.show(true);
|
||||||
this.outputChannel.appendLine(localize('msgInstallStart', "Installing required packages to run Notebooks..."));
|
this.outputChannel.appendLine(localize('msgInstallStart', "Installing required packages to run Notebooks..."));
|
||||||
await this.executeCommand(installJupyterCommand);
|
await this.executeStreamedCommand(installJupyterCommand);
|
||||||
this.outputChannel.appendLine(localize('msgJupyterInstallDone', "... Jupyter installation complete."));
|
this.outputChannel.appendLine(localize('msgJupyterInstallDone', "... Jupyter installation complete."));
|
||||||
} else {
|
} else {
|
||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
@@ -378,7 +400,7 @@ export default class JupyterServerInstallation {
|
|||||||
if (installSparkMagic) {
|
if (installSparkMagic) {
|
||||||
this.outputChannel.show(true);
|
this.outputChannel.show(true);
|
||||||
this.outputChannel.appendLine(localize('msgInstallingSpark', "Installing SparkMagic..."));
|
this.outputChannel.appendLine(localize('msgInstallingSpark', "Installing SparkMagic..."));
|
||||||
await this.executeCommand(installSparkMagic);
|
await this.executeStreamedCommand(installSparkMagic);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -387,10 +409,10 @@ export default class JupyterServerInstallation {
|
|||||||
this.outputChannel.appendLine(localize('msgInstallStart', "Installing required packages to run Notebooks..."));
|
this.outputChannel.appendLine(localize('msgInstallStart', "Installing required packages to run Notebooks..."));
|
||||||
|
|
||||||
let installCommand = `"${this._pythonExecutable}" -m pip install jupyter==1.0.0 pandas==0.24.2`;
|
let installCommand = `"${this._pythonExecutable}" -m pip install jupyter==1.0.0 pandas==0.24.2`;
|
||||||
await this.executeCommand(installCommand);
|
await this.executeStreamedCommand(installCommand);
|
||||||
|
|
||||||
installCommand = `"${this._pythonExecutable}" -m pip install prose-codeaccelerator==1.3.0 --extra-index-url https://prose-python-packages.azurewebsites.net`;
|
installCommand = `"${this._pythonExecutable}" -m pip install prose-codeaccelerator==1.3.0 --extra-index-url https://prose-python-packages.azurewebsites.net`;
|
||||||
await this.executeCommand(installCommand);
|
await this.executeStreamedCommand(installCommand);
|
||||||
|
|
||||||
this.outputChannel.appendLine(localize('msgJupyterInstallDone', "... Jupyter installation complete."));
|
this.outputChannel.appendLine(localize('msgJupyterInstallDone', "... Jupyter installation complete."));
|
||||||
}
|
}
|
||||||
@@ -403,18 +425,22 @@ export default class JupyterServerInstallation {
|
|||||||
if (process.platform !== constants.winPlatform) {
|
if (process.platform !== constants.winPlatform) {
|
||||||
installCommand = `${installCommand} pykerberos==1.2.1`;
|
installCommand = `${installCommand} pykerberos==1.2.1`;
|
||||||
}
|
}
|
||||||
await this.executeCommand(installCommand);
|
await this.executeStreamedCommand(installCommand);
|
||||||
|
|
||||||
installCommand = `"${this._pythonExecutable}" -m pip install prose-codeaccelerator==1.3.0 --extra-index-url https://prose-python-packages.azurewebsites.net`;
|
installCommand = `"${this._pythonExecutable}" -m pip install prose-codeaccelerator==1.3.0 --extra-index-url https://prose-python-packages.azurewebsites.net`;
|
||||||
await this.executeCommand(installCommand);
|
await this.executeStreamedCommand(installCommand);
|
||||||
|
|
||||||
this.outputChannel.appendLine(localize('msgJupyterInstallDone', "... Jupyter installation complete."));
|
this.outputChannel.appendLine(localize('msgJupyterInstallDone', "... Jupyter installation complete."));
|
||||||
}
|
}
|
||||||
|
|
||||||
private async executeCommand(command: string): Promise<void> {
|
private async executeStreamedCommand(command: string): Promise<void> {
|
||||||
await utils.executeStreamedCommand(command, { env: this.execOptions.env }, this.outputChannel);
|
await utils.executeStreamedCommand(command, { env: this.execOptions.env }, this.outputChannel);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async executeBufferedCommand(command: string): Promise<string> {
|
||||||
|
return await utils.executeBufferedCommand(command, { env: this.execOptions.env });
|
||||||
|
}
|
||||||
|
|
||||||
public get pythonExecutable(): string {
|
public get pythonExecutable(): string {
|
||||||
return this._pythonExecutable;
|
return this._pythonExecutable;
|
||||||
}
|
}
|
||||||
@@ -500,10 +526,20 @@ export default class JupyterServerInstallation {
|
|||||||
pythonBinPathSuffix);
|
pythonBinPathSuffix);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async getPythonPackagesPath(): Promise<string> {
|
||||||
|
let cmd = `"${this.pythonExecutable}" -c "import site; print(site.getsitepackages()[0])"`;
|
||||||
|
return await this.executeBufferedCommand(cmd);
|
||||||
|
}
|
||||||
|
|
||||||
public static getPythonExePath(pythonInstallPath: string, useExistingInstall: boolean): string {
|
public static getPythonExePath(pythonInstallPath: string, useExistingInstall: boolean): string {
|
||||||
return path.join(
|
return path.join(
|
||||||
pythonInstallPath,
|
pythonInstallPath,
|
||||||
useExistingInstall ? '' : constants.pythonBundleVersion,
|
useExistingInstall ? '' : constants.pythonBundleVersion,
|
||||||
process.platform === constants.winPlatform ? 'python.exe' : 'bin/python3');
|
process.platform === constants.winPlatform ? 'python.exe' : 'bin/python3');
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PythonPkgDetails {
|
||||||
|
name: string;
|
||||||
|
version: string;
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user