mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-03-23 21:30:29 -04:00
Manage Package Dialog Refactor (#8473)
* Refactoring Manage Packages dialog so that other extensions can contribute to it by registering package mange providers for different location and package type
This commit is contained in:
@@ -25,6 +25,11 @@ import { JupyterNotebookProvider } from './jupyterNotebookProvider';
|
||||
import { ConfigurePythonDialog } from '../dialog/configurePythonDialog';
|
||||
import CodeAdapter from '../prompts/adapter';
|
||||
import { ManagePackagesDialog } from '../dialog/managePackages/managePackagesDialog';
|
||||
import { IPackageManageProvider } from '../types';
|
||||
import { LocalPipPackageManageProvider } from './localPipPackageManageProvider';
|
||||
import { LocalCondaPackageManageProvider } from './localCondaPackageManageProvider';
|
||||
import { ManagePackagesDialogModel, ManagePackageDialogOptions } from '../dialog/managePackages/managePackagesDialogModel';
|
||||
import { PiPyClient } from './pipyClient';
|
||||
|
||||
let untitledCounter = 0;
|
||||
|
||||
@@ -32,6 +37,7 @@ export class JupyterController implements vscode.Disposable {
|
||||
private _jupyterInstallation: JupyterServerInstallation;
|
||||
private _notebookInstances: IServerInstance[] = [];
|
||||
private _serverInstanceFactory: ServerInstanceFactory = new ServerInstanceFactory();
|
||||
private _packageManageProviders = new Map<string, IPackageManageProvider>();
|
||||
|
||||
private outputChannel: vscode.OutputChannel;
|
||||
private prompter: IPrompter;
|
||||
@@ -76,7 +82,7 @@ export class JupyterController implements vscode.Disposable {
|
||||
});
|
||||
|
||||
this.apiWrapper.registerCommand(constants.jupyterReinstallDependenciesCommand, () => { return this.handleDependenciesReinstallation(); });
|
||||
this.apiWrapper.registerCommand(constants.jupyterManagePackages, () => { return this.doManagePackages(); });
|
||||
this.apiWrapper.registerCommand(constants.jupyterManagePackages, async (args) => { return this.doManagePackages(args); });
|
||||
this.apiWrapper.registerCommand(constants.jupyterConfigurePython, () => { return this.doConfigurePython(this._jupyterInstallation); });
|
||||
|
||||
let supportedFileFilter: vscode.DocumentFilter[] = [
|
||||
@@ -85,6 +91,7 @@ export class JupyterController implements vscode.Disposable {
|
||||
let notebookProvider = this.registerNotebookProvider();
|
||||
this.extensionContext.subscriptions.push(this.apiWrapper.registerCompletionItemProvider(supportedFileFilter, new NotebookCompletionItemProvider(notebookProvider)));
|
||||
|
||||
this.registerDefaultPackageManageProviders();
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -110,7 +117,7 @@ export class JupyterController implements vscode.Disposable {
|
||||
|
||||
public deactivate(): void {
|
||||
// Shutdown any open notebooks
|
||||
this._notebookInstances.forEach(instance => { instance.stop(); });
|
||||
this._notebookInstances.forEach(async (instance) => { await instance.stop(); });
|
||||
}
|
||||
|
||||
// EVENT HANDLERS //////////////////////////////////////////////////////
|
||||
@@ -196,9 +203,19 @@ export class JupyterController implements vscode.Disposable {
|
||||
});
|
||||
}
|
||||
|
||||
public doManagePackages(): void {
|
||||
public async doManagePackages(options?: ManagePackageDialogOptions): Promise<void> {
|
||||
try {
|
||||
let packagesDialog = new ManagePackagesDialog(this._jupyterInstallation);
|
||||
if (!options) {
|
||||
options = {
|
||||
multiLocations: false,
|
||||
defaultLocation: constants.localhostName,
|
||||
defaultProviderId: LocalPipPackageManageProvider.ProviderId
|
||||
};
|
||||
}
|
||||
let model = new ManagePackagesDialogModel(this._jupyterInstallation, this._packageManageProviders, options);
|
||||
|
||||
await model.init();
|
||||
let packagesDialog = new ManagePackagesDialog(model);
|
||||
packagesDialog.showDialog();
|
||||
} catch (error) {
|
||||
let message = utils.getErrorMessage(error);
|
||||
@@ -206,6 +223,33 @@ export class JupyterController implements vscode.Disposable {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a package provider
|
||||
* @param providerId Provider Id
|
||||
* @param packageManageProvider Provider instance
|
||||
*/
|
||||
public registerPackageManager(providerId: string, packageManageProvider: IPackageManageProvider): void {
|
||||
if (packageManageProvider) {
|
||||
if (!this._packageManageProviders.has(providerId)) {
|
||||
this._packageManageProviders.set(providerId, packageManageProvider);
|
||||
} else {
|
||||
throw Error(`Package manager provider is already registered. provider id: ${providerId}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the list of registered providers
|
||||
*/
|
||||
public get packageManageProviders(): Map<string, IPackageManageProvider> {
|
||||
return this._packageManageProviders;
|
||||
}
|
||||
|
||||
private registerDefaultPackageManageProviders(): void {
|
||||
this.registerPackageManager(LocalPipPackageManageProvider.ProviderId, new LocalPipPackageManageProvider(this._jupyterInstallation, new PiPyClient()));
|
||||
this.registerPackageManager(LocalCondaPackageManageProvider.ProviderId, new LocalCondaPackageManageProvider(this._jupyterInstallation));
|
||||
}
|
||||
|
||||
public doConfigurePython(jupyterInstaller: JupyterServerInstallation): void {
|
||||
let pythonDialog = new ConfigurePythonDialog(this.apiWrapper, jupyterInstaller);
|
||||
pythonDialog.showDialog().catch((err: any) => {
|
||||
|
||||
@@ -38,7 +38,22 @@ 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 class JupyterServerInstallation {
|
||||
export interface IJupyterServerInstallation {
|
||||
installCondaPackages(packages: PythonPkgDetails[], useMinVersion: boolean): Promise<void>;
|
||||
configurePackagePaths(): Promise<void>;
|
||||
startInstallProcess(forceInstall: boolean, installSettings?: { installPath: string, existingPython: boolean }): Promise<void>;
|
||||
getInstalledPipPackages(): Promise<PythonPkgDetails[]>;
|
||||
getInstalledCondaPackages(): Promise<PythonPkgDetails[]>;
|
||||
uninstallCondaPackages(packages: PythonPkgDetails[]): Promise<void>;
|
||||
usingConda: boolean;
|
||||
getCondaExePath(): string;
|
||||
executeBufferedCommand(command: string): Promise<string>;
|
||||
executeStreamedCommand(command: string): Promise<void>;
|
||||
installPipPackages(packages: PythonPkgDetails[], useMinVersion: boolean): Promise<void>;
|
||||
uninstallPipPackages(packages: PythonPkgDetails[]): Promise<void>;
|
||||
pythonExecutable: string;
|
||||
}
|
||||
export class JupyterServerInstallation implements IJupyterServerInstallation {
|
||||
public apiWrapper: ApiWrapper;
|
||||
public extensionPath: string;
|
||||
public pythonBinPath: string;
|
||||
@@ -625,7 +640,7 @@ export class JupyterServerInstallation {
|
||||
}
|
||||
}
|
||||
|
||||
private async executeStreamedCommand(command: string): Promise<void> {
|
||||
public async executeStreamedCommand(command: string): Promise<void> {
|
||||
await utils.executeStreamedCommand(command, { env: this.execOptions.env }, this.outputChannel);
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,114 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { IPackageManageProvider, IPackageDetails, IPackageTarget, IPackageOverview } from '../types';
|
||||
import { IJupyterServerInstallation } from './jupyterServerInstallation';
|
||||
import * as constants from '../common/constants';
|
||||
import * as utils from '../common/utils';
|
||||
|
||||
export class LocalCondaPackageManageProvider implements IPackageManageProvider {
|
||||
|
||||
/**
|
||||
* Provider Id for Anaconda package manage provider
|
||||
*/
|
||||
public static ProviderId = 'localhost_Anaconda';
|
||||
|
||||
constructor(private jupyterInstallation: IJupyterServerInstallation) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns package target
|
||||
*/
|
||||
public get packageTarget(): IPackageTarget {
|
||||
return { location: constants.localhostName, packageType: constants.PythonPkgType.Anaconda };
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns provider Id
|
||||
*/
|
||||
public get providerId(): string {
|
||||
return LocalCondaPackageManageProvider.ProviderId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns list of packages
|
||||
*/
|
||||
public async listPackages(): Promise<IPackageDetails[]> {
|
||||
return await this.jupyterInstallation.getInstalledCondaPackages();
|
||||
}
|
||||
|
||||
/**
|
||||
* Installs given packages
|
||||
* @param packages Packages to install
|
||||
* @param useMinVersion minimum version
|
||||
*/
|
||||
installPackages(packages: IPackageDetails[], useMinVersion: boolean): Promise<void> {
|
||||
return this.jupyterInstallation.installCondaPackages(packages, useMinVersion);
|
||||
}
|
||||
|
||||
/**
|
||||
* Uninstalls given packages
|
||||
* @param packages Packages to uninstall
|
||||
*/
|
||||
uninstallPackages(packages: IPackageDetails[]): Promise<void> {
|
||||
return this.jupyterInstallation.uninstallCondaPackages(packages);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the provider can be used
|
||||
*/
|
||||
canUseProvider(): Promise<boolean> {
|
||||
return Promise.resolve(this.jupyterInstallation.usingConda);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns location title
|
||||
*/
|
||||
getLocationTitle(): Promise<string> {
|
||||
return Promise.resolve(constants.localhostTitle);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns package overview for given name
|
||||
* @param packageName Package Name
|
||||
*/
|
||||
getPackageOverview(packageName: string): Promise<IPackageOverview> {
|
||||
return this.fetchCondaPackage(packageName);
|
||||
}
|
||||
|
||||
private async fetchCondaPackage(packageName: string): Promise<IPackageOverview> {
|
||||
let condaExe = this.jupyterInstallation.getCondaExePath();
|
||||
let cmd = `"${condaExe}" search --json ${packageName}`;
|
||||
let packageResult: string;
|
||||
try {
|
||||
packageResult = await this.jupyterInstallation.executeBufferedCommand(cmd);
|
||||
} catch (err) {
|
||||
throw new Error(constants.PackageNotFoundError);
|
||||
}
|
||||
|
||||
if (packageResult) {
|
||||
let packageJson = JSON.parse(packageResult);
|
||||
if (packageJson) {
|
||||
if (packageJson.error) {
|
||||
throw new Error(packageJson.error);
|
||||
}
|
||||
|
||||
let packages = packageJson[packageName];
|
||||
if (Array.isArray(packages)) {
|
||||
let allVersions = packages.filter(pkg => pkg && pkg.version).map(pkg => pkg.version);
|
||||
let singletonVersions = new Set<string>(allVersions);
|
||||
let sortedVersions = utils.sortPackageVersions(Array.from(singletonVersions), false);
|
||||
return {
|
||||
name: packageName,
|
||||
versions: sortedVersions,
|
||||
summary: undefined
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
110
extensions/notebook/src/jupyter/localPipPackageManageProvider.ts
Normal file
110
extensions/notebook/src/jupyter/localPipPackageManageProvider.ts
Normal file
@@ -0,0 +1,110 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { IPackageManageProvider, IPackageDetails, IPackageTarget, IPackageOverview } from '../types';
|
||||
import { IJupyterServerInstallation } from './jupyterServerInstallation';
|
||||
import * as constants from '../common/constants';
|
||||
import * as utils from '../common/utils';
|
||||
import { IPiPyClient } from './pipyClient';
|
||||
|
||||
export class LocalPipPackageManageProvider implements IPackageManageProvider {
|
||||
|
||||
/**
|
||||
* Provider Id for Pip package manage provider
|
||||
*/
|
||||
public static ProviderId = 'localhost_Pip';
|
||||
|
||||
constructor(
|
||||
private jupyterInstallation: IJupyterServerInstallation,
|
||||
private pipyClient: IPiPyClient) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns provider Id
|
||||
*/
|
||||
public get providerId(): string {
|
||||
return LocalPipPackageManageProvider.ProviderId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns package target
|
||||
*/
|
||||
public get packageTarget(): IPackageTarget {
|
||||
return { location: constants.localhostName, packageType: constants.PythonPkgType.Pip };
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns list of packages
|
||||
*/
|
||||
public async listPackages(): Promise<IPackageDetails[]> {
|
||||
return await this.jupyterInstallation.getInstalledPipPackages();
|
||||
}
|
||||
|
||||
/**
|
||||
* Installs given packages
|
||||
* @param packages Packages to install
|
||||
* @param useMinVersion minimum version
|
||||
*/
|
||||
installPackages(packages: IPackageDetails[], useMinVersion: boolean): Promise<void> {
|
||||
return this.jupyterInstallation.installPipPackages(packages, useMinVersion);
|
||||
}
|
||||
|
||||
/**
|
||||
* Uninstalls given packages
|
||||
* @param packages Packages to uninstall
|
||||
*/
|
||||
uninstallPackages(packages: IPackageDetails[]): Promise<void> {
|
||||
return this.jupyterInstallation.uninstallPipPackages(packages);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the provider can be used
|
||||
*/
|
||||
canUseProvider(): Promise<boolean> {
|
||||
return Promise.resolve(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns location title
|
||||
*/
|
||||
getLocationTitle(): Promise<string> {
|
||||
return Promise.resolve(constants.localhostTitle);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns package overview for given name
|
||||
* @param packageName Package Name
|
||||
*/
|
||||
getPackageOverview(packageName: string): Promise<IPackageOverview> {
|
||||
return this.fetchPypiPackage(packageName);
|
||||
}
|
||||
|
||||
private async fetchPypiPackage(packageName: string): Promise<IPackageOverview> {
|
||||
let body = await this.pipyClient.fetchPypiPackage(packageName);
|
||||
let packagesJson = JSON.parse(body);
|
||||
let versionNums: string[] = [];
|
||||
let packageSummary = '';
|
||||
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;
|
||||
});
|
||||
versionNums = utils.sortPackageVersions(versionKeys, false);
|
||||
}
|
||||
|
||||
if (packagesJson.info && packagesJson.info.summary) {
|
||||
packageSummary = packagesJson.info.summary;
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
name: packageName,
|
||||
versions: versionNums,
|
||||
summary: packageSummary
|
||||
};
|
||||
}
|
||||
}
|
||||
46
extensions/notebook/src/jupyter/pipyClient.ts
Normal file
46
extensions/notebook/src/jupyter/pipyClient.ts
Normal file
@@ -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 * as nls from 'vscode-nls';
|
||||
import * as request from 'request';
|
||||
import * as constants from '../common/constants';
|
||||
|
||||
const localize = nls.loadMessageBundle();
|
||||
|
||||
export interface IPiPyClient {
|
||||
fetchPypiPackage(packageName: string): Promise<any>;
|
||||
}
|
||||
|
||||
export class PiPyClient implements IPiPyClient {
|
||||
|
||||
private readonly RequestTimeout = 10000;
|
||||
private getLink(packageName: string): string {
|
||||
return `https://pypi.org/pypi/${packageName}/json`;
|
||||
}
|
||||
|
||||
public async fetchPypiPackage(packageName: string): Promise<any> {
|
||||
return new Promise<any>((resolve, reject) => {
|
||||
request.get(this.getLink(packageName), { timeout: this.RequestTimeout }, (error, response, body) => {
|
||||
if (error) {
|
||||
return reject(error);
|
||||
}
|
||||
|
||||
if (response.statusCode === 404) {
|
||||
return reject(constants.PackageNotFoundError);
|
||||
}
|
||||
|
||||
if (response.statusCode !== 200) {
|
||||
return reject(
|
||||
localize('managePackages.packageRequestError',
|
||||
"Package info request failed with error: {0} {1}",
|
||||
response.statusCode,
|
||||
response.statusMessage));
|
||||
}
|
||||
|
||||
resolve(body);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user