forked from DonJayamanne/pythonVSCode
-
Notifications
You must be signed in to change notification settings - Fork 1.2k
Add Kernel selection picker for local and remote sessions #8873
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
DonJayamanne
merged 25 commits into
microsoft:master
from
DonJayamanne:kernelSpecPart7_LocalKernelSelectorDropDown
Dec 4, 2019
Merged
Changes from all commits
Commits
Show all changes
25 commits
Select commit
Hold shift + click to select a range
783e95c
Support installing ipykernel as a product
DonJayamanne e891f99
No need
DonJayamanne b090b76
Linter fixes
DonJayamanne a8d8c90
Address code review comments
DonJayamanne a5e3f8f
Oops
DonJayamanne 3dae2cb
Fixes to cheking if its a type
DonJayamanne fbd3b78
Oops
DonJayamanne da9177c
Revert changes
DonJayamanne 3fc2f14
Add tests
DonJayamanne b1be561
Linter issues
DonJayamanne 0fa51bd
Initial
DonJayamanne 741db2c
Fixes after merging
DonJayamanne b2d6310
Misc
DonJayamanne 9c44a98
More fixes
DonJayamanne 3bec944
Fixes
DonJayamanne ce3c003
Fixes
DonJayamanne 56e1066
Misc
DonJayamanne e3dc41f
Fixes
DonJayamanne a8b552d
Kernel selectors
DonJayamanne 3f553ad
Add tests
DonJayamanne 1631df9
Add tests
DonJayamanne 103a898
Oops
DonJayamanne b265cff
Oops
DonJayamanne 7ce7a24
Renamed
DonJayamanne f189caa
Aaddress code review comments
DonJayamanne File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
143 changes: 143 additions & 0 deletions
143
src/client/datascience/jupyter/kernels/kernelSelections.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,143 @@ | ||
// Copyright (c) Microsoft Corporation. All rights reserved. | ||
// Licensed under the MIT License. | ||
|
||
'use strict'; | ||
|
||
import { inject, injectable } from 'inversify'; | ||
import { CancellationToken } from 'vscode'; | ||
import * as localize from '../../../common/utils/localize'; | ||
import { IInterpreterSelector } from '../../../interpreter/configuration/types'; | ||
import { IJupyterKernel, IJupyterKernelSpec, IJupyterSessionManager } from '../../types'; | ||
import { KernelService } from './kernelService'; | ||
import { IKernelSelectionListProvider, IKernelSpecQuickPickItem } from './types'; | ||
|
||
// Small classes, hence all put into one file. | ||
// tslint:disable: max-classes-per-file | ||
|
||
/** | ||
* Given a kernel spec, this will return a quick pick item with appropriate display names and the like. | ||
* | ||
* @param {IJupyterKernelSpec} kernelSpec | ||
* @returns {IKernelSpecQuickPickItem} | ||
*/ | ||
function getQuickPickItemForKernelSpec(kernelSpec: IJupyterKernelSpec): IKernelSpecQuickPickItem { | ||
return { | ||
label: kernelSpec.display_name, | ||
// tslint:disable-next-line: no-suspicious-comment | ||
// TODO: Localize & fix as per spec. | ||
description: '(kernel)', | ||
selection: { kernelModel: undefined, kernelSpec: kernelSpec, interpreter: undefined } | ||
}; | ||
} | ||
|
||
/** | ||
* Given an active kernel, this will return a quick pick item with appropriate display names and the like. | ||
* | ||
* @param {(IJupyterKernel & Partial<IJupyterKernelSpec>)} kernel | ||
* @returns {IKernelSpecQuickPickItem} | ||
*/ | ||
function getQuickPickItemForActiveKernel(kernel: IJupyterKernel & Partial<IJupyterKernelSpec>): IKernelSpecQuickPickItem { | ||
return { | ||
label: kernel.display_name || kernel.name || '', | ||
description: localize.DataScience.jupyterSelectURIRunningDetailFormat().format(kernel.lastActivityTime.toLocaleString(), kernel.numberOfConnections.toString()), | ||
selection: { kernelModel: kernel, kernelSpec: undefined, interpreter: undefined } | ||
}; | ||
} | ||
|
||
/** | ||
* Provider for active kernel specs in a jupyter session. | ||
* | ||
* @export | ||
* @class ActiveJupyterSessionKernelSelectionListProvider | ||
* @implements {IKernelSelectionListProvider} | ||
*/ | ||
export class ActiveJupyterSessionKernelSelectionListProvider implements IKernelSelectionListProvider { | ||
DonJayamanne marked this conversation as resolved.
Show resolved
Hide resolved
|
||
constructor(private readonly sessionManager: IJupyterSessionManager) {} | ||
public async getKernelSelections(_cancelToken?: CancellationToken | undefined): Promise<IKernelSpecQuickPickItem[]> { | ||
const [activeKernels, kernelSpecs] = await Promise.all([this.sessionManager.getRunningKernels(), this.sessionManager.getKernelSpecs()]); | ||
const items = activeKernels.map(item => { | ||
const matchingSpec: Partial<IJupyterKernelSpec> = kernelSpecs.find(spec => spec.name === item.name) || {}; | ||
return { | ||
...item, | ||
...matchingSpec | ||
}; | ||
}); | ||
return items.filter(item => item.display_name || item.name).map(getQuickPickItemForActiveKernel); | ||
} | ||
} | ||
|
||
/** | ||
* Provider for installed kernel specs (`python -m jupyter kernelspec list`). | ||
* | ||
* @export | ||
* @class InstalledJupyterKernelSelectionListProvider | ||
* @implements {IKernelSelectionListProvider} | ||
*/ | ||
export class InstalledJupyterKernelSelectionListProvider implements IKernelSelectionListProvider { | ||
constructor(private readonly kernelService: KernelService, private readonly sessionManager?: IJupyterSessionManager) {} | ||
public async getKernelSelections(cancelToken?: CancellationToken | undefined): Promise<IKernelSpecQuickPickItem[]> { | ||
const items = await this.kernelService.getKernelSpecs(this.sessionManager, cancelToken); | ||
return items.map(getQuickPickItemForKernelSpec); | ||
} | ||
} | ||
|
||
/** | ||
* Provider for interpreters to be treated as kernel specs. | ||
* I.e. return interpreters that are to be treated as kernel specs, and not yet installed as kernels. | ||
* | ||
* @export | ||
* @class InterpreterKernelSelectionListProvider | ||
* @implements {IKernelSelectionListProvider} | ||
*/ | ||
export class InterpreterKernelSelectionListProvider implements IKernelSelectionListProvider { | ||
constructor(private readonly interpreterSelector: IInterpreterSelector) {} | ||
public async getKernelSelections(_cancelToken?: CancellationToken | undefined): Promise<IKernelSpecQuickPickItem[]> { | ||
const items = await this.interpreterSelector.getSuggestions(undefined); | ||
return items.map(item => { | ||
return { | ||
...item, | ||
// tslint:disable-next-line: no-suspicious-comment | ||
// TODO: Localize & fix as per spec. | ||
description: '(register and use interpreter as kernel)', | ||
DonJayamanne marked this conversation as resolved.
Show resolved
Hide resolved
|
||
selection: { kernelModel: undefined, interpreter: item.interpreter, kernelSpec: undefined } | ||
}; | ||
}); | ||
} | ||
} | ||
|
||
/** | ||
* Provides a list of kernel specs for selection, for both local and remote sessions. | ||
* | ||
* @export | ||
* @class KernelSelectionProviderFactory | ||
*/ | ||
@injectable() | ||
export class KernelSelectionProvider { | ||
constructor(@inject(KernelService) private readonly kernelService: KernelService, @inject(IInterpreterSelector) private readonly interpreterSelector: IInterpreterSelector) {} | ||
/** | ||
* Gets a selection of kernel specs from a remote session. | ||
* | ||
* @param {IJupyterSessionManager} sessionManager | ||
* @param {CancellationToken} [cancelToken] | ||
* @returns {Promise<IKernelSpecQuickPickItem[]>} | ||
* @memberof KernelSelectionProvider | ||
*/ | ||
public async getKernelSelectionsForRemoteSession(sessionManager: IJupyterSessionManager, cancelToken?: CancellationToken): Promise<IKernelSpecQuickPickItem[]> { | ||
DonJayamanne marked this conversation as resolved.
Show resolved
Hide resolved
|
||
return new ActiveJupyterSessionKernelSelectionListProvider(sessionManager).getKernelSelections(cancelToken); | ||
} | ||
/** | ||
* Gets a selection of kernel specs for a local session. | ||
* | ||
* @param {IJupyterSessionManager} [sessionManager] | ||
* @param {CancellationToken} [cancelToken] | ||
* @returns {Promise<IKernelSelectionListProvider>} | ||
* @memberof KernelSelectionProvider | ||
*/ | ||
public async getKernelSelectionsForLocalSession(sessionManager?: IJupyterSessionManager, cancelToken?: CancellationToken): Promise<IKernelSpecQuickPickItem[]> { | ||
const activeKernelsPromise = sessionManager ? new ActiveJupyterSessionKernelSelectionListProvider(sessionManager).getKernelSelections(cancelToken) : Promise.resolve([]); | ||
const jupyterKernelsPromise = new InstalledJupyterKernelSelectionListProvider(this.kernelService).getKernelSelections(cancelToken); | ||
const interpretersPromise = new InterpreterKernelSelectionListProvider(this.interpreterSelector).getKernelSelections(cancelToken); | ||
const [activeKernels, jupyterKernels, interprters] = await Promise.all([activeKernelsPromise, jupyterKernelsPromise, interpretersPromise]); | ||
return [...jupyterKernels!, ...activeKernels!, ...interprters]; | ||
} | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,82 @@ | ||
// Copyright (c) Microsoft Corporation. All rights reserved. | ||
// Licensed under the MIT License. | ||
|
||
'use strict'; | ||
|
||
import { inject, injectable } from 'inversify'; | ||
import { CancellationToken } from 'vscode-jsonrpc'; | ||
import { IApplicationShell } from '../../../common/application/types'; | ||
import { Cancellation } from '../../../common/cancellation'; | ||
import { traceInfo, traceWarning } from '../../../common/logger'; | ||
import { IInstaller, InstallerResponse, Product } from '../../../common/types'; | ||
import { PythonInterpreter } from '../../../interpreter/contracts'; | ||
import { IJupyterKernelSpec, IJupyterSessionManager } from '../../types'; | ||
import { KernelSelectionProvider } from './kernelSelections'; | ||
import { KernelService } from './kernelService'; | ||
|
||
@injectable() | ||
export class KernelSelector { | ||
constructor( | ||
@inject(KernelSelectionProvider) private readonly selectionProvider: KernelSelectionProvider, | ||
@inject(IApplicationShell) private readonly applicationShell: IApplicationShell, | ||
@inject(KernelService) private readonly kernelService: KernelService, | ||
@inject(IInstaller) private readonly installer: IInstaller | ||
) {} | ||
public async selectRemoteKernel(session: IJupyterSessionManager, cancelToken?: CancellationToken): Promise<IJupyterKernelSpec | undefined> { | ||
const suggestions = this.selectionProvider.getKernelSelectionsForRemoteSession(session, cancelToken); | ||
const selection = await this.applicationShell.showQuickPick(suggestions, undefined, cancelToken); | ||
if (!selection) { | ||
return; | ||
} | ||
|
||
if (selection.selection.kernelSpec) { | ||
return selection.selection.kernelSpec; | ||
} | ||
// This is not possible (remote kernels selector can only display remote kernels). | ||
throw new Error('Invalid Selection in kernel spec (somehow a local kernel/interpreter has been selected for a remote session!'); | ||
} | ||
|
||
public async selectLocalKernel(session?: IJupyterSessionManager, cancelToken?: CancellationToken): Promise<IJupyterKernelSpec | undefined> { | ||
const suggestions = this.selectionProvider.getKernelSelectionsForLocalSession(session, cancelToken); | ||
const selection = await this.applicationShell.showQuickPick(suggestions, undefined, cancelToken); | ||
if (!selection) { | ||
return; | ||
} | ||
|
||
// Check if ipykernel is installed in this kernel. | ||
const interpreter = selection.selection.interpreter; | ||
if (interpreter) { | ||
const isValid = await this.isSelectionValid(interpreter, cancelToken); | ||
if (isValid) { | ||
// Find the kernel associated with this interpter. | ||
const kernelSpec = await this.kernelService.findMatchingKernelSpec(interpreter, session, cancelToken); | ||
if (kernelSpec){ | ||
traceInfo(`ipykernel installed in ${interpreter.path}, and matching found.`); | ||
return kernelSpec; | ||
} | ||
traceInfo(`ipykernel installed in ${interpreter.path}, no matching kernel found. Will register kernel.`); | ||
} | ||
|
||
// Try an install this interpreter as a kernel. | ||
return this.kernelService.registerKernel(interpreter, cancelToken); | ||
} else { | ||
return selection.selection.kernelSpec; | ||
} | ||
} | ||
|
||
private async isSelectionValid(interpreter: PythonInterpreter, cancelToken?: CancellationToken): Promise<boolean> { | ||
// Is ipykernel installed in this environment. | ||
if (await this.installer.isInstalled(Product.ipykernel, interpreter)) { | ||
return true; | ||
} | ||
if (Cancellation.isCanceled(cancelToken)) { | ||
return false; | ||
} | ||
const response = await this.installer.promptToInstall(Product.ipykernel, interpreter); | ||
if (response === InstallerResponse.Installed) { | ||
return true; | ||
} | ||
traceWarning(`Prompted to install ipykernel, however ipykernel not installed in the interpreter ${interpreter.path}`); | ||
return false; | ||
} | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
// Copyright (c) Microsoft Corporation. All rights reserved. | ||
// Licensed under the MIT License. | ||
|
||
'use strict'; | ||
|
||
import { CancellationToken, QuickPickItem } from 'vscode'; | ||
import { PythonInterpreter } from '../../../interpreter/contracts'; | ||
import { IJupyterKernel, IJupyterKernelSpec } from '../../types'; | ||
|
||
export interface IKernelSpecQuickPickItem extends QuickPickItem { | ||
/** | ||
* Whether a | ||
* - Kernel spec (IJupyterKernelSpec) | ||
* - Active kernel (IJupyterKernel) or | ||
* - Interpreter has been selected. | ||
* If interpreter is selected, then we might need to install this as a kernel to get the kernel spec. | ||
* | ||
* @type {({ kernelModel: IJupyterKernel; kernelSpec: IJupyterKernelSpec; interpreter: undefined } | ||
* | { kernelModel: undefined; kernelSpec: IJupyterKernelSpec; interpreter: undefined } | ||
* | { kernelModel: undefined; kernelSpec: undefined; interpreter: PythonInterpreter })} | ||
* @memberof IKernelSpecQuickPickItem | ||
*/ | ||
selection: | ||
| { kernelModel: IJupyterKernel & Partial<IJupyterKernelSpec>; kernelSpec: undefined; interpreter: undefined } | ||
| { kernelModel: undefined; kernelSpec: IJupyterKernelSpec; interpreter: undefined } | ||
| { kernelModel: undefined; kernelSpec: undefined; interpreter: PythonInterpreter }; | ||
} | ||
|
||
export interface IKernelSelectionListProvider { | ||
getKernelSelections(cancelToken?: CancellationToken): Promise<IKernelSpecQuickPickItem[]>; | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.