Skip to content

Commit f08993b

Browse files
Hook up kernel selection to the kernel finder (part 1) (#11505)
1 parent 09baf52 commit f08993b

File tree

11 files changed

+213
-25
lines changed

11 files changed

+213
-25
lines changed

src/client/datascience/interactive-common/interactiveBase.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1069,7 +1069,11 @@ export abstract class InteractiveBase extends WebViewHost<IInteractiveWindowMapp
10691069
// If we get an invalid kernel error, make sure to ask the user to switch
10701070
if (e instanceof JupyterInvalidKernelError && serverConnection && serverConnection.localLaunch) {
10711071
// Ask the user for a new local kernel
1072-
const newKernel = await this.switcher.askForLocalKernel(resource, e.kernelSpec);
1072+
const newKernel = await this.switcher.askForLocalKernel(
1073+
resource,
1074+
serverConnection.type,
1075+
e.kernelSpec
1076+
);
10731077
if (newKernel?.kernelSpec) {
10741078
// Update the notebook metadata
10751079
await this.updateNotebookOptions(newKernel.kernelSpec, newKernel.interpreter);

src/client/datascience/jupyter/jupyterExecution.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -231,6 +231,7 @@ export class JupyterExecutionBase implements IJupyterExecution {
231231
const sessionManager = await sessionManagerFactory.create(connection);
232232
const kernelInterpreter = await this.kernelSelector.selectLocalKernel(
233233
undefined,
234+
'jupyter',
234235
new StopWatch(),
235236
sessionManager,
236237
cancelToken,

src/client/datascience/jupyter/kernels/kernelSelections.ts

Lines changed: 39 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import { IFileSystem } from '../../../common/platform/types';
1010
import { IPathUtils, Resource } from '../../../common/types';
1111
import * as localize from '../../../common/utils/localize';
1212
import { IInterpreterSelector } from '../../../interpreter/configuration/types';
13+
import { IKernelFinder } from '../../kernel-launcher/types';
1314
import { IJupyterKernelSpec, IJupyterSessionManager } from '../../types';
1415
import { KernelService } from './kernelService';
1516
import { IKernelSelectionListProvider, IKernelSpecQuickPickItem, LiveKernelModel } from './types';
@@ -117,6 +118,20 @@ export class InstalledJupyterKernelSelectionListProvider implements IKernelSelec
117118
}
118119
}
119120

121+
// Provider for searching for installed kernelspecs on disk without using jupyter to search
122+
export class InstalledRawKernelSelectionListProvider implements IKernelSelectionListProvider {
123+
constructor(private readonly kernelFinder: IKernelFinder, private readonly pathUtils: IPathUtils) {}
124+
public async getKernelSelections(
125+
_resource: Resource,
126+
cancelToken?: CancellationToken
127+
): Promise<IKernelSpecQuickPickItem[]> {
128+
const items = await this.kernelFinder.listKernelSpecs(cancelToken);
129+
return items
130+
.filter((item) => (item.language || '').toLowerCase() === PYTHON_LANGUAGE.toLowerCase())
131+
.map((item) => getQuickPickItemForKernelSpec(item, this.pathUtils));
132+
}
133+
}
134+
120135
/**
121136
* Provider for interpreters to be treated as kernel specs.
122137
* I.e. return interpreters that are to be treated as kernel specs, and not yet installed as kernels.
@@ -157,7 +172,8 @@ export class KernelSelectionProvider {
157172
@inject(KernelService) private readonly kernelService: KernelService,
158173
@inject(IInterpreterSelector) private readonly interpreterSelector: IInterpreterSelector,
159174
@inject(IFileSystem) private readonly fileSystem: IFileSystem,
160-
@inject(IPathUtils) private readonly pathUtils: IPathUtils
175+
@inject(IPathUtils) private readonly pathUtils: IPathUtils,
176+
@inject(IKernelFinder) private readonly kernelFinder: IKernelFinder
161177
) {}
162178
/**
163179
* Gets a selection of kernel specs from a remote session.
@@ -206,15 +222,32 @@ export class KernelSelectionProvider {
206222
*/
207223
public async getKernelSelectionsForLocalSession(
208224
resource: Resource,
225+
type: 'raw' | 'jupyter' | 'noConnection',
209226
sessionManager?: IJupyterSessionManager,
210227
cancelToken?: CancellationToken
211228
): Promise<IKernelSpecQuickPickItem[]> {
212229
const getSelections = async () => {
213-
const installedKernelsPromise = new InstalledJupyterKernelSelectionListProvider(
214-
this.kernelService,
215-
this.pathUtils,
216-
sessionManager
217-
).getKernelSelections(resource, cancelToken);
230+
// For raw versus jupyter connections we need to use a different method for fetching installed kernelspecs
231+
// There is a possible unknown case for if we have a guest jupyter notebook that has not yet connected
232+
// in that case we don't use either method
233+
let installedKernelsPromise: Promise<IKernelSpecQuickPickItem[]> = Promise.resolve([]);
234+
switch (type) {
235+
case 'raw':
236+
installedKernelsPromise = new InstalledRawKernelSelectionListProvider(
237+
this.kernelFinder,
238+
this.pathUtils
239+
).getKernelSelections(resource, cancelToken);
240+
break;
241+
case 'jupyter':
242+
installedKernelsPromise = new InstalledJupyterKernelSelectionListProvider(
243+
this.kernelService,
244+
this.pathUtils,
245+
sessionManager
246+
).getKernelSelections(resource, cancelToken);
247+
break;
248+
default:
249+
break;
250+
}
218251
const interpretersPromise = new InterpreterKernelSelectionListProvider(
219252
this.interpreterSelector
220253
).getKernelSelections(resource, cancelToken);

src/client/datascience/jupyter/kernels/kernelSelector.ts

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,7 @@ export class KernelSelector {
104104
suggestions = suggestions.filter((item) => !this.kernelIdsToHide.has(item.selection.kernelModel?.id || ''));
105105
return this.selectKernel(
106106
resource,
107+
'jupyter',
107108
stopWatch,
108109
Telemetry.SelectRemoteJupyterKernel,
109110
suggestions,
@@ -122,19 +123,22 @@ export class KernelSelector {
122123
*/
123124
public async selectLocalKernel(
124125
resource: Resource,
126+
type: 'raw' | 'jupyter' | 'noConnection',
125127
stopWatch: StopWatch,
126128
session?: IJupyterSessionManager,
127129
cancelToken?: CancellationToken,
128130
currentKernel?: IJupyterKernelSpec | LiveKernelModel
129131
): Promise<KernelSpecInterpreter> {
130132
let suggestions = await this.selectionProvider.getKernelSelectionsForLocalSession(
131133
resource,
134+
type,
132135
session,
133136
cancelToken
134137
);
135138
suggestions = suggestions.filter((item) => !this.kernelIdsToHide.has(item.selection.kernelModel?.id || ''));
136139
return this.selectKernel(
137140
resource,
141+
type,
138142
stopWatch,
139143
Telemetry.SelectLocalJupyterKernel,
140144
suggestions,
@@ -169,7 +173,9 @@ export class KernelSelector {
169173
};
170174
// When this method is called, we know we've started a local jupyter server.
171175
// Lets pre-warm the list of local kernels.
172-
this.selectionProvider.getKernelSelectionsForLocalSession(resource, sessionManager, cancelToken).ignoreErrors();
176+
this.selectionProvider
177+
.getKernelSelectionsForLocalSession(resource, 'jupyter', sessionManager, cancelToken)
178+
.ignoreErrors();
173179

174180
let selection: KernelSpecInterpreter = {};
175181
if (notebookMetadata?.kernelspec) {
@@ -198,14 +204,21 @@ export class KernelSelector {
198204
selection = await this.useInterpreterAsKernel(
199205
resource,
200206
activeInterpreter,
207+
'jupyter',
201208
notebookMetadata.kernelspec.display_name,
202209
sessionManager,
203210
disableUI,
204211
cancelToken
205212
);
206213
} else {
207214
telemetryProps.promptedToSelect = true;
208-
selection = await this.selectLocalKernel(resource, stopWatch, sessionManager, cancelToken);
215+
selection = await this.selectLocalKernel(
216+
resource,
217+
'jupyter',
218+
stopWatch,
219+
sessionManager,
220+
cancelToken
221+
);
209222
}
210223
}
211224
} else {
@@ -302,6 +315,7 @@ export class KernelSelector {
302315
}
303316
private async selectKernel(
304317
resource: Resource,
318+
type: 'raw' | 'jupyter' | 'noConnection',
305319
stopWatch: StopWatch,
306320
telemetryEvent: Telemetry,
307321
suggestions: IKernelSpecQuickPickItem[],
@@ -323,6 +337,7 @@ export class KernelSelector {
323337
return this.useInterpreterAsKernel(
324338
resource,
325339
selection.selection.interpreter,
340+
type,
326341
undefined,
327342
session,
328343
false,
@@ -371,6 +386,7 @@ export class KernelSelector {
371386
private async useInterpreterAsKernel(
372387
resource: Resource,
373388
interpreter: PythonInterpreter,
389+
type: 'raw' | 'jupyter' | 'noConnection',
374390
displayNameOfKernelNotFound?: string,
375391
session?: IJupyterSessionManager,
376392
disableUI?: boolean,
@@ -426,7 +442,7 @@ export class KernelSelector {
426442

427443
// When this method is called, we know a new kernel may have been registered.
428444
// Lets pre-warm the list of local kernels (with the new list).
429-
this.selectionProvider.getKernelSelectionsForLocalSession(resource, session, cancelToken).ignoreErrors();
445+
this.selectionProvider.getKernelSelectionsForLocalSession(resource, type, session, cancelToken).ignoreErrors();
430446

431447
return { kernelSpec, interpreter };
432448
}

src/client/datascience/jupyter/kernels/kernelSwitcher.ts

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ export class KernelSwitcher {
4343

4444
public async askForLocalKernel(
4545
resource: Resource,
46+
type: 'raw' | 'jupyter' | 'noConnection',
4647
kernelSpec: IJupyterKernelSpec | LiveKernelModel | undefined
4748
): Promise<KernelSpecInterpreter | undefined> {
4849
const displayName = kernelSpec?.display_name || kernelSpec?.name || '';
@@ -51,7 +52,7 @@ export class KernelSwitcher {
5152
const cancel = Common.cancel();
5253
const selection = await this.appShell.showErrorMessage(message, selectKernel, cancel);
5354
if (selection === selectKernel) {
54-
return this.selectLocalJupyterKernel(resource, kernelSpec);
55+
return this.selectLocalJupyterKernel(resource, type, kernelSpec);
5556
}
5657
}
5758

@@ -64,7 +65,11 @@ export class KernelSwitcher {
6465
settings.datascience.jupyterServerURI.toLowerCase() === Settings.JupyterServerLocalLaunch;
6566

6667
if (isLocalConnection) {
67-
kernel = await this.selectLocalJupyterKernel(notebook.resource, notebook?.getKernelSpec());
68+
kernel = await this.selectLocalJupyterKernel(
69+
notebook.resource,
70+
notebook.connection?.type || 'noConnection',
71+
notebook?.getKernelSpec()
72+
);
6873
} else if (notebook) {
6974
const connInfo = notebook.connection;
7075
const currentKernel = notebook.getKernelSpec();
@@ -77,9 +82,17 @@ export class KernelSwitcher {
7782

7883
private async selectLocalJupyterKernel(
7984
resource: Resource,
85+
type: 'raw' | 'jupyter' | 'noConnection',
8086
currentKernel?: IJupyterKernelSpec | LiveKernelModel
8187
): Promise<KernelSpecInterpreter> {
82-
return this.kernelSelector.selectLocalKernel(resource, new StopWatch(), undefined, undefined, currentKernel);
88+
return this.kernelSelector.selectLocalKernel(
89+
resource,
90+
type,
91+
new StopWatch(),
92+
undefined,
93+
undefined,
94+
currentKernel
95+
);
8396
}
8497

8598
private async selectRemoteJupyterKernel(
@@ -119,6 +132,7 @@ export class KernelSwitcher {
119132
// At this point we have a valid jupyter server.
120133
const potential = await this.askForLocalKernel(
121134
notebook.resource,
135+
notebook.connection?.type || 'noConnection',
122136
kernel.kernelSpec || kernel.kernelModel
123137
);
124138
if (potential && Object.keys(potential).length > 0) {

src/client/datascience/kernel-launcher/kernelFinder.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,11 @@ export class KernelFinder implements IKernelFinder {
107107
return this.verifyIpyKernel(foundKernel, cancelToken);
108108
}
109109

110+
// Search all our local file system locations for installed kernel specs and return them
111+
public async listKernelSpecs(_cancelToken?: CancellationToken): Promise<IJupyterKernelSpec[]> {
112+
throw new Error('Not yet implmented');
113+
}
114+
110115
// For the given kernelspec return back the kernelspec with ipykernel installed into it or error
111116
private async verifyIpyKernel(
112117
kernelSpec: IJupyterKernelSpec,

src/client/datascience/kernel-launcher/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ export interface IKernelFinder {
4949
kernelName?: string,
5050
cancelToken?: CancellationToken
5151
): Promise<IJupyterKernelSpec>;
52+
listKernelSpecs(cancelToken?: CancellationToken): Promise<IJupyterKernelSpec[]>;
5253
}
5354

5455
/**

src/test/datascience/jupyter/kernels/kernelSelections.unit.test.ts

Lines changed: 44 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import { JupyterSessionManager } from '../../../../client/datascience/jupyter/ju
1616
import { KernelSelectionProvider } from '../../../../client/datascience/jupyter/kernels/kernelSelections';
1717
import { KernelService } from '../../../../client/datascience/jupyter/kernels/kernelService';
1818
import { IKernelSpecQuickPickItem } from '../../../../client/datascience/jupyter/kernels/types';
19+
import { IKernelFinder } from '../../../../client/datascience/kernel-launcher/types';
1920
import { IJupyterKernel, IJupyterKernelSpec, IJupyterSessionManager } from '../../../../client/datascience/types';
2021
import { InterpreterSelector } from '../../../../client/interpreter/configuration/interpreterSelector/interpreterSelector';
2122
import { IInterpreterQuickPickItem, IInterpreterSelector } from '../../../../client/interpreter/configuration/types';
@@ -25,6 +26,7 @@ import { InterpreterType } from '../../../../client/interpreter/contracts';
2526
suite('Data Science - KernelSelections', () => {
2627
let kernelSelectionProvider: KernelSelectionProvider;
2728
let kernelService: KernelService;
29+
let kernelFinder: IKernelFinder;
2830
let interpreterSelector: IInterpreterSelector;
2931
let pathUtils: IPathUtils;
3032
let fs: IFileSystem;
@@ -132,6 +134,7 @@ suite('Data Science - KernelSelections', () => {
132134
interpreterSelector = mock(InterpreterSelector);
133135
sessionManager = mock(JupyterSessionManager);
134136
kernelService = mock(KernelService);
137+
kernelFinder = mock<IKernelFinder>();
135138
fs = mock(FileSystem);
136139
pathUtils = mock(PathUtils);
137140
when(pathUtils.getDisplayName(anything())).thenReturn('<user friendly path>');
@@ -140,7 +143,8 @@ suite('Data Science - KernelSelections', () => {
140143
instance(kernelService),
141144
instance(interpreterSelector),
142145
instance(fs),
143-
instance(pathUtils)
146+
instance(pathUtils),
147+
instance(kernelFinder)
144148
);
145149
});
146150

@@ -216,7 +220,44 @@ suite('Data Science - KernelSelections', () => {
216220
verify(sessionManager.getKernelSpecs()).once();
217221
assert.deepEqual(items, expectedItems);
218222
});
219-
test('Should return a list of Local Kernels + Interpreters for local connection (excluding non-python kernels)', async () => {
223+
test('Should return a list of Local Kernels + Interpreters for local raw connection (excluding non-python kernels)', async () => {
224+
when(kernelFinder.listKernelSpecs(anything())).thenResolve(allSpecs);
225+
when(interpreterSelector.getSuggestions(undefined)).thenResolve(allInterpreters);
226+
227+
// Quick pick must contain
228+
// - kernel spec display name
229+
// - selection = kernel model + kernel spec
230+
// - description = last activity and # of connections.
231+
const expectedKernelItems: IKernelSpecQuickPickItem[] = [python1KernelSpecModel, python3KernelSpecModel].map(
232+
(item) => {
233+
return {
234+
label: item.display_name,
235+
detail: '<user friendly path>',
236+
selection: { interpreter: undefined, kernelModel: undefined, kernelSpec: item }
237+
};
238+
}
239+
);
240+
const expectedInterpreterItems: IKernelSpecQuickPickItem[] = allInterpreters.map((item) => {
241+
return {
242+
...item,
243+
label: item.label,
244+
detail: '<user friendly path>',
245+
description: '',
246+
selection: { kernelModel: undefined, interpreter: item.interpreter, kernelSpec: undefined }
247+
};
248+
});
249+
const expectedList = [...expectedKernelItems, ...expectedInterpreterItems];
250+
expectedList.sort((a, b) => (a.label === b.label ? 0 : a.label > b.label ? 1 : -1));
251+
252+
const items = await kernelSelectionProvider.getKernelSelectionsForLocalSession(
253+
undefined,
254+
'raw',
255+
instance(sessionManager)
256+
);
257+
258+
assert.deepEqual(items, expectedList);
259+
});
260+
test('Should return a list of Local Kernels + Interpreters for local jupyter connection (excluding non-python kernels)', async () => {
220261
when(sessionManager.getKernelSpecs()).thenResolve(allSpecs);
221262
when(kernelService.getKernelSpecs(anything(), anything())).thenResolve(allSpecs);
222263
when(interpreterSelector.getSuggestions(undefined)).thenResolve(allInterpreters);
@@ -248,6 +289,7 @@ suite('Data Science - KernelSelections', () => {
248289

249290
const items = await kernelSelectionProvider.getKernelSelectionsForLocalSession(
250291
undefined,
292+
'jupyter',
251293
instance(sessionManager)
252294
);
253295

0 commit comments

Comments
 (0)