Refactor notebook dependency lists. Enable Manage Packages from command palette. (#24484)

This commit is contained in:
Cory Rivera 2023-09-21 10:43:41 -07:00 коммит произвёл GitHub
Родитель a63463e35b
Коммит e5e535475a
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
10 изменённых файлов: 113 добавлений и 83 удалений

Просмотреть файл

@ -370,7 +370,7 @@
},
{
"command": "jupyter.cmd.managePackages",
"when": "false"
"when": "notebook:pythonInstalled && !notebook:runningOnSAW"
},
{
"command": "notebook.command.saveBook",

Просмотреть файл

@ -38,7 +38,7 @@
"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.configurePython": "Configure Python for Notebooks",
"title.managePackages": "Manage Packages",
"title.managePackages": "Manage Notebook Packages",
"title.SQL19PreviewBook": "SQL Server 2019 Guide",
"books-preview-category": "Jupyter Books",
"title.saveJupyterBook": "Save Jupyter Book",

Просмотреть файл

@ -27,7 +27,7 @@ export class ManagePackagesDialog {
* Opens a dialog to manage packages used by notebooks.
*/
public showDialog(): void {
this.dialog = azdata.window.createModelViewDialog(localize('managePackages.dialogName', "Manage Packages"));
this.dialog = azdata.window.createModelViewDialog(localize('managePackages.dialogName', "Manage Notebook Packages"));
this.installedPkgTab = new InstalledPackagesTab(this, this.jupyterInstallation);
this.addNewPkgTab = new AddNewPackageTab(this, this.jupyterInstallation);

Просмотреть файл

@ -12,7 +12,7 @@ import { AppContext } from './common/appContext';
import { IExtensionApi, IPackageManageProvider } from './types';
import { CellType } from './contracts/content';
import { NotebookUriHandler } from './protocol/notebookUriHandler';
import { BuiltInCommands, unsavedBooksContextKey } from './common/constants';
import { BuiltInCommands, CommandContext, unsavedBooksContextKey } from './common/constants';
import { RemoteBookController } from './book/remoteBookController';
import { RemoteBookDialog } from './dialog/remoteBookDialog';
import { RemoteBookDialogModel } from './dialog/remoteBookDialogModel';
@ -22,6 +22,7 @@ import { BookTreeItem } from './book/bookTreeItem';
import Logger from './common/logger';
import { sendNotebookActionEvent, NbTelemetryView, NbTelemetryAction } from './telemetry';
import { TelemetryReporter } from './telemetry';
import { JupyterServerInstallation } from './jupyter/jupyterServerInstallation';
const localize = nls.loadMessageBundle();
@ -39,6 +40,11 @@ export async function activate(extensionContext: vscode.ExtensionContext): Promi
if (vscode.env.uiKind === vscode.UIKind.Web) {
await config.update('allowRoot', true, vscode.ConfigurationTarget.Global);
}
// Check if python is already installed in order to enable the Manage Notebook Packages dialog
let pythonInstalled = JupyterServerInstallation.isPythonInstalled();
await vscode.commands.executeCommand(BuiltInCommands.SetContext, CommandContext.NotebookPythonInstalled, pythonInstalled);
/**
* ***** IMPORTANT *****
* If changes are made to bookTreeView.openBook, please ensure backwards compatibility with its current state.
@ -154,7 +160,6 @@ export async function activate(extensionContext: vscode.ExtensionContext): Promi
return undefined;
}
const bookTreeViewProvider = appContext.bookTreeViewProvider;
await bookTreeViewProvider.initialized;
const providedBookTreeViewProvider = appContext.providedBookTreeViewProvider;

Просмотреть файл

@ -155,21 +155,26 @@ export class JupyterController {
}
public async doManagePackages(options?: ManagePackageDialogOptions | vscode.Uri): Promise<void> {
try {
if (!options || options instanceof vscode.Uri) {
options = {
defaultLocation: constants.localhostName,
defaultProviderId: LocalPipPackageManageProvider.ProviderId
};
}
let model = new ManagePackagesDialogModel(this._jupyterInstallation, this._packageManageProviders, options);
// Handle the edge case where python is installed and then deleted manually from the user settings.
if (!JupyterServerInstallation.isPythonInstalled()) {
await vscode.window.showErrorMessage(localize('pythonNotSetup', "Python is not currently configured for notebooks. The 'Configure Python for Notebooks' command must be run before being able to manage notebook packages."));
} else {
try {
if (!options || options instanceof vscode.Uri) {
options = {
defaultLocation: constants.localhostName,
defaultProviderId: LocalPipPackageManageProvider.ProviderId
};
}
let model = new ManagePackagesDialogModel(this._jupyterInstallation, this._packageManageProviders, options);
await model.init();
let packagesDialog = new ManagePackagesDialog(model, this.extensionContext);
packagesDialog.showDialog();
} catch (error) {
let message = utils.getErrorMessage(error);
void vscode.window.showErrorMessage(message);
await model.init();
let packagesDialog = new ManagePackagesDialog(model, this.extensionContext);
packagesDialog.showDialog();
} catch (error) {
let message = utils.getErrorMessage(error);
await vscode.window.showErrorMessage(message);
}
}
}

Просмотреть файл

@ -18,6 +18,7 @@ import * as utils from '../common/utils';
import { Deferred } from '../common/promise';
import { ConfigurePythonWizard } from '../dialog/configurePython/configurePythonWizard';
import { IKernelInfo } from '../contracts/content';
import { requiredJupyterPackages } from './requiredJupyterPackages';
const localize = nls.loadMessageBundle();
const msgInstallPkgProgress = localize('msgInstallPkgProgress', "Notebook dependencies installation is in progress");
@ -78,30 +79,6 @@ export interface IJupyterServerInstallation {
installedPythonVersion: string;
}
export const requiredJupyterPkg: PythonPkgDetails = {
name: 'jupyter',
version: '1.0.0'
};
// Require notebook 6.5.6 for https://github.com/jupyter/notebook/issues/7048
export const requiredNotebookPkg: PythonPkgDetails = {
name: 'notebook',
version: '6.5.6',
installExactVersion: true
};
// https://github.com/microsoft/azuredatastudio/issues/24405
export const requiredIpykernelPkg: PythonPkgDetails = {
name: 'ipykernel',
version: '5.5.5',
installExactVersion: true
};
export const requiredPowershellPkg: PythonPkgDetails = {
name: 'powershell-kernel',
version: '0.1.4'
};
export class JupyterServerInstallation implements IJupyterServerInstallation {
public extensionPath: string;
public pythonBinPath: string;
@ -121,14 +98,14 @@ export class JupyterServerInstallation implements IJupyterServerInstallation {
private _oldUserInstalledPipPackages: PythonPkgDetails[] = [];
private _upgradePrompted: boolean = false;
private _installInProgress: boolean;
private _installInProgress = false;
private _installCompletion: Deferred<void>;
public static readonly DefaultPythonLocation = path.join(utils.getUserHome(), 'azuredatastudio-python');
private _kernelSetupCache: Map<string, boolean>;
private readonly _requiredKernelPackages: Map<string, PythonPkgDetails[]>;
private readonly _requiredPackagesSet: Set<string>;
private readonly _kernelSetupCache = new Map<string, boolean>();
private readonly _requiredKernelPackages = new Map<string, PythonPkgDetails[]>();
private readonly _requiredPackageNames: Set<string>;
private readonly _runningOnSAW: boolean;
private readonly _tsgopsweb: boolean;
@ -152,22 +129,21 @@ export class JupyterServerInstallation implements IJupyterServerInstallation {
this._pythonInstallationPath = JupyterServerInstallation.getPythonInstallPath();
this._usingExistingPython = JupyterServerInstallation.getExistingPythonSetting();
}
this._installInProgress = false;
this._kernelSetupCache = new Map<string, boolean>();
this._requiredKernelPackages = new Map<string, PythonPkgDetails[]>();
let allPackages = requiredJupyterPackages.sharedPackages;
for (let kernelInfo of requiredJupyterPackages.kernels) {
// Add this kernel's specific dependencies to the running list of packages for All Kernels.
allPackages = allPackages.concat(kernelInfo.packages);
this._requiredKernelPackages.set(constants.ipykernelDisplayName, [requiredJupyterPkg, requiredNotebookPkg, requiredIpykernelPkg]);
this._requiredKernelPackages.set(constants.python3DisplayName, [requiredJupyterPkg, requiredNotebookPkg, requiredIpykernelPkg]);
this._requiredKernelPackages.set(constants.powershellDisplayName, [requiredJupyterPkg, requiredPowershellPkg, requiredNotebookPkg, requiredIpykernelPkg]);
// Combine this kernel's specific dependencies with the shared list of packages to get its
// full list of dependencies
let packages = requiredJupyterPackages.sharedPackages.concat(kernelInfo.packages);
this._requiredKernelPackages.set(kernelInfo.name, packages);
}
let allPackages = [requiredJupyterPkg, requiredNotebookPkg, requiredIpykernelPkg, requiredPowershellPkg];
this._requiredKernelPackages.set(constants.allKernelsName, allPackages);
this._requiredPackagesSet = new Set<string>();
allPackages.forEach(pkg => {
this._requiredPackagesSet.add(pkg.name);
});
this._requiredPackageNames = new Set<string>(allPackages.map(pkg => pkg.name));
}
private async installDependencies(backgroundOperation: azdata.BackgroundOperation, forceInstall: boolean, packages: PythonPkgDetails[]): Promise<void> {
@ -461,8 +437,10 @@ export class JupyterServerInstallation implements IJupyterServerInstallation {
await notebookConfig.update(constants.existingPythonConfigKey, this._usingExistingPython, vscode.ConfigurationTarget.Global);
await this.configurePackagePaths();
await vscode.commands.executeCommand(constants.BuiltInCommands.SetContext, constants.CommandContext.NotebookPythonInstalled, true);
this._installCompletion.resolve();
this._installInProgress = false;
if (this._upgradeInProcess) {
// Pass in false for restartJupyterServer parameter since the jupyter server has already been shutdown
// when removing the old Python version on Windows.
@ -635,7 +613,7 @@ export class JupyterServerInstallation implements IJupyterServerInstallation {
public uninstallPipPackages(packages: PythonPkgDetails[]): Promise<void> {
for (let pkg of packages) {
if (this._requiredPackagesSet.has(pkg.name)) {
if (this._requiredPackageNames.has(pkg.name)) {
this._kernelSetupCache.clear();
break;
}
@ -687,7 +665,7 @@ export class JupyterServerInstallation implements IJupyterServerInstallation {
public async uninstallCondaPackages(packages: PythonPkgDetails[]): Promise<void> {
if (this.condaExecutable) {
for (let pkg of packages) {
if (this._requiredPackagesSet.has(pkg.name)) {
if (this._requiredPackageNames.has(pkg.name)) {
this._kernelSetupCache.clear();
break;
}

Просмотреть файл

@ -14,7 +14,6 @@ import { JupyterServerInstallation } from './jupyterServerInstallation';
import * as utils from '../common/utils';
import { IServerInstance } from './common';
import { PerFolderServerInstance, IInstanceOptions } from './serverInstance';
import { CommandContext, BuiltInCommands } from '../common/constants';
export interface IServerManagerOptions {
documentPath: string;
@ -105,7 +104,6 @@ export class LocalJupyterServerManager implements nb.ServerManager, vscode.Dispo
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(kernelSpec.display_name);
void vscode.commands.executeCommand(BuiltInCommands.SetContext, CommandContext.NotebookPythonInstalled, true);
// Calculate the path to use as the notebook-dir for Jupyter based on the path of the uri of the
// notebook to open. This will be the workspace folder if the notebook uri is inside a workspace

Просмотреть файл

@ -0,0 +1,46 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { PythonPkgDetails } from './jupyterServerInstallation'
export interface RequiredPackagesInfo {
sharedPackages: PythonPkgDetails[];
kernels: {
name: string;
packages: PythonPkgDetails[];
}[];
}
export const requiredJupyterPackages: RequiredPackagesInfo = {
sharedPackages: [{
name: 'jupyter',
version: '1.0.0'
},
// Require notebook 6.5.6 for https://github.com/jupyter/notebook/issues/7048
{
name: 'notebook',
version: '6.5.6',
installExactVersion: true
},
// Require ipykernel 5.5.5 for https://github.com/microsoft/azuredatastudio/issues/24405
{
name: 'ipykernel',
version: '5.5.5',
installExactVersion: true
}],
kernels: [{
name: 'Python 3 (ipykernel)',
packages: []
}, {
name: 'Python 3',
packages: []
}, {
name: 'PowerShell',
packages: [{
name: 'powershell-kernel',
version: '0.1.4'
}]
}]
}

Просмотреть файл

@ -19,10 +19,8 @@ describe('Jupyter Controller', function () {
let appContext = new AppContext(mockExtensionContext);
let controller: JupyterController;
let connection: azdata.connection.ConnectionProfile = new azdata.connection.ConnectionProfile();
let showErrorMessageSpy: sinon.SinonSpy;
this.beforeEach(() => {
showErrorMessageSpy = sinon.spy(vscode.window, 'showErrorMessage');
sinon.stub(azdata.connection, 'getCurrentConnection').returns(Promise.resolve(connection));
sinon.stub(azdata.tasks, 'registerTask');
sinon.stub(vscode.commands, 'registerCommand');
@ -62,17 +60,6 @@ describe('Jupyter Controller', function () {
should(defaultConnection).deepEqual(connection, 'getDefaultConnection() did not return expected result');
});
it('should show error message for doManagePackages before activation', async () => {
await controller.doManagePackages();
should(showErrorMessageSpy.calledOnce).be.true('showErrorMessage should be called');
});
it('should not show error message for doManagePackages after activation', async () => {
await controller.activate();
await controller.doManagePackages();
should(showErrorMessageSpy.notCalled).be.true('showErrorMessage should not be called');
});
it('Returns expected values from notebook provider', async () => {
await controller.activate();
should(controller.executeProvider.providerId).equal('jupyter', 'Notebook provider should be jupyter');

Просмотреть файл

@ -11,8 +11,9 @@ import * as uuid from 'uuid';
import * as fs from 'fs-extra';
import * as request from 'request';
import * as utils from '../../common/utils';
import { requiredJupyterPkg, JupyterServerInstallation, requiredPowershellPkg, PythonInstallSettings, PythonPkgDetails, requiredNotebookPkg, requiredIpykernelPkg } from '../../jupyter/jupyterServerInstallation';
import { powershellDisplayName, python3DisplayName, winPlatform } from '../../common/constants';
import { JupyterServerInstallation, PythonInstallSettings, PythonPkgDetails } from '../../jupyter/jupyterServerInstallation';
import { allKernelsName, ipykernelDisplayName, powershellDisplayName, python3DisplayName, winPlatform } from '../../common/constants';
import { requiredJupyterPackages } from '../../jupyter/requiredJupyterPackages';
describe('Jupyter Server Installation', function () {
let outputChannelStub: TypeMoq.IMock<vscode.OutputChannel>;
@ -224,14 +225,24 @@ describe('Jupyter Server Installation', function () {
should(packages.length).be.equal(0);
});
it('Get required packages test - Python 3 kernel', async function () {
let packages = installation.getRequiredPackagesForKernel(python3DisplayName);
should(packages).be.deepEqual([requiredJupyterPkg, requiredNotebookPkg, requiredIpykernelPkg]);
const pythonKernels = [python3DisplayName, ipykernelDisplayName, powershellDisplayName];
pythonKernels.forEach(kernelName => {
it(`Get required packages test - ${kernelName} kernel`, async function () {
let packages = installation.getRequiredPackagesForKernel(kernelName);
let kernelInfo = requiredJupyterPackages.kernels.find(kernel => kernel.name === kernelName);
should(kernelInfo).not.be.undefined();
let expectedPackages = requiredJupyterPackages.sharedPackages.concat(kernelInfo.packages);
should(packages).be.deepEqual(expectedPackages);
});
});
it('Get required packages test - Powershell kernel', async function () {
let packages = installation.getRequiredPackagesForKernel(powershellDisplayName);
should(packages).be.deepEqual([requiredJupyterPkg, requiredPowershellPkg, requiredNotebookPkg, requiredIpykernelPkg]);
it('Get required packages test - All Kernels', async function () {
let packages = installation.getRequiredPackagesForKernel(allKernelsName);
let allPackages = requiredJupyterPackages.sharedPackages;
for (let kernel of requiredJupyterPackages.kernels) {
allPackages = allPackages.concat(kernel.packages);
}
should(packages).be.deepEqual(allPackages);
});
it('Install python test - Run install while Python is already running', async function () {