Skip to content

Commit c4d241b

Browse files
committed
feat: Enhance Deepnote server management with additional package support
- Added `additionalPackages` parameter to `startServer` and `startServerForEnvironment` methods in `DeepnoteServerStarter` to allow installation of extra packages. - Updated the `createDeepnoteServerConfigHandle` utility function for consistent server handle creation. - Refactored kernel connection metadata handling in `DeepnoteKernelAutoSelector` to utilize the new server config handle. - Cleaned up test cases to reflect changes in method signatures and ensure proper functionality. This commit improves the flexibility of the Deepnote server setup by enabling the installation of additional packages during server startup. Signed-off-by: Tomas Kislan <[email protected]>
1 parent 2682113 commit c4d241b

File tree

7 files changed

+64
-94
lines changed

7 files changed

+64
-94
lines changed

src/kernels/deepnote/deepnoteServerStarter.node.ts

Lines changed: 14 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,7 @@ export class DeepnoteServerStarter implements IDeepnoteServerStarter, IExtension
102102
public async startServer(
103103
interpreter: PythonEnvironment,
104104
venvPath: Uri,
105+
additionalPackages: string[],
105106
environmentId: string,
106107
deepnoteFileUri: Uri,
107108
token?: CancellationToken
@@ -110,9 +111,9 @@ export class DeepnoteServerStarter implements IDeepnoteServerStarter, IExtension
110111
const serverKey = `${fileKey}-${environmentId}`;
111112

112113
// Wait for any pending operations on this environment to complete
113-
let pendingOp = this.pendingOperations.get(fileKey);
114+
let pendingOp = this.pendingOperations.get(serverKey);
114115
if (pendingOp) {
115-
logger.info(`Waiting for pending operation on ${fileKey} to complete...`);
116+
logger.info(`Waiting for pending operation on ${serverKey} to complete...`);
116117
try {
117118
await pendingOp.promise;
118119
} catch {
@@ -131,7 +132,7 @@ export class DeepnoteServerStarter implements IDeepnoteServerStarter, IExtension
131132
}
132133

133134
// Start the operation if not already pending
134-
pendingOp = this.pendingOperations.get(fileKey);
135+
pendingOp = this.pendingOperations.get(serverKey);
135136

136137
if (pendingOp && pendingOp.type === 'start') {
137138
// TODO - check pending operation environment id ?
@@ -140,7 +141,7 @@ export class DeepnoteServerStarter implements IDeepnoteServerStarter, IExtension
140141
} else {
141142
// Stop the existing server
142143
logger.info(
143-
`Stopping existing server for ${fileKey} with environmentId ${existingEnvironmentId} to start new one with environmentId ${environmentId}...`
144+
`Stopping existing server for ${serverKey} with environmentId ${existingEnvironmentId} to start new one with environmentId ${environmentId}...`
144145
);
145146
await this.stopServerForEnvironment(existingContext, deepnoteFileUri, token);
146147
// TODO - Clear controllers for the notebook ?
@@ -157,39 +158,20 @@ export class DeepnoteServerStarter implements IDeepnoteServerStarter, IExtension
157158
existingContext = newContext;
158159
}
159160

160-
// if (existingContext == null) {
161-
// // TODO - solve with better typing
162-
// throw new Error('Invariant violation: existingContext should not be null here');
163-
// }
164-
165-
// // If server is already running for this environment, return existing info
166-
// // const existingServerInfo = this.serverInfos.get(environmentId);
167-
// const existingServerInfo = this.serverInfos.get(fileKey);
168-
// if (existingServerInfo && (await this.isServerRunning(existingServerInfo))) {
169-
// logger.info(`Deepnote server already running at ${existingServerInfo.url} for ${fileKey}`);
170-
// return existingServerInfo;
171-
// }
172-
173-
// // Start the operation if not already pending
174-
// pendingOp = this.pendingOperations.get(fileKey);
175-
176-
// if (pendingOp && pendingOp.type === 'start') {
177-
// return await pendingOp.promise;
178-
// }
179-
180161
// Start the operation and track it
181162
const operation = {
182163
type: 'start' as const,
183164
promise: this.startServerForEnvironment(
184165
existingContext,
185166
interpreter,
186167
venvPath,
168+
additionalPackages,
187169
environmentId,
188170
deepnoteFileUri,
189171
token
190172
)
191173
};
192-
this.pendingOperations.set(fileKey, operation);
174+
this.pendingOperations.set(serverKey, operation);
193175

194176
try {
195177
const result = await operation.promise;
@@ -199,8 +181,8 @@ export class DeepnoteServerStarter implements IDeepnoteServerStarter, IExtension
199181
return result;
200182
} finally {
201183
// Remove from pending operations when done
202-
if (this.pendingOperations.get(fileKey) === operation) {
203-
this.pendingOperations.delete(fileKey);
184+
if (this.pendingOperations.get(serverKey) === operation) {
185+
this.pendingOperations.delete(serverKey);
204186
}
205187
}
206188
}
@@ -253,6 +235,7 @@ export class DeepnoteServerStarter implements IDeepnoteServerStarter, IExtension
253235
projectContext: ProjectContext,
254236
interpreter: PythonEnvironment,
255237
venvPath: Uri,
238+
additionalPackages: string[],
256239
environmentId: string,
257240
deepnoteFileUri: Uri,
258241
token?: CancellationToken
@@ -272,6 +255,10 @@ export class DeepnoteServerStarter implements IDeepnoteServerStarter, IExtension
272255

273256
Cancellation.throwIfCanceled(token);
274257

258+
await this.toolkitInstaller.installAdditionalPackages(venvPath, additionalPackages, token);
259+
260+
Cancellation.throwIfCanceled(token);
261+
275262
// Allocate both ports with global lock to prevent race conditions
276263
// Note: allocatePorts reserves both ports immediately in serverInfos
277264
// const { jupyterPort, lspPort } = await this.allocatePorts(environmentId);

src/kernels/deepnote/environments/deepnoteEnvironmentsView.node.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import {
1919
} from '../../../platform/interpreter/helpers';
2020
import { getDisplayPath } from '../../../platform/common/platform/fs-paths';
2121
import { IKernelProvider } from '../../types';
22+
import { createDeepnoteServerConfigHandle } from '../../../platform/deepnote/deepnoteServerUtils.node';
2223

2324
/**
2425
* View controller for the Deepnote kernel environments tree view.
@@ -320,7 +321,7 @@ export class DeepnoteEnvironmentsView implements Disposable {
320321
const connectionMetadata = kernel.kernelConnectionMetadata;
321322
if (connectionMetadata.kind === 'startUsingDeepnoteKernel') {
322323
const deepnoteMetadata = connectionMetadata as DeepnoteKernelConnectionMetadata;
323-
const expectedHandle = `deepnote-config-server-${environmentId}-${notebook.uri.fsPath}`;
324+
const expectedHandle = createDeepnoteServerConfigHandle(environmentId, notebook.uri);
324325

325326
if (deepnoteMetadata.serverProviderHandle.handle === expectedHandle) {
326327
logger.info(

src/kernels/deepnote/environments/deepnoteEnvironmentsView.unit.test.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import { PythonEnvironment } from '../../../platform/pythonEnvironments/info';
1212
import { mockedVSCodeNamespaces, resetVSCodeMocks } from '../../../test/vscode-mock';
1313
import { DeepnoteEnvironmentTreeDataProvider } from './deepnoteEnvironmentTreeDataProvider.node';
1414
import * as interpreterHelpers from '../../../platform/interpreter/helpers';
15+
import { createDeepnoteServerConfigHandle } from '../../../platform/deepnote/deepnoteServerUtils.node';
1516

1617
suite('DeepnoteEnvironmentsView', () => {
1718
let view: DeepnoteEnvironmentsView;
@@ -501,7 +502,7 @@ suite('DeepnoteEnvironmentsView', () => {
501502
kernelConnectionMetadata: {
502503
kind: 'startUsingDeepnoteKernel',
503504
serverProviderHandle: {
504-
handle: `deepnote-config-server-${testEnvironmentId}`
505+
handle: createDeepnoteServerConfigHandle(testEnvironmentId, openNotebook1.uri)
505506
}
506507
},
507508
dispose: sinon.stub().resolves()
@@ -511,7 +512,7 @@ suite('DeepnoteEnvironmentsView', () => {
511512
kernelConnectionMetadata: {
512513
kind: 'startUsingDeepnoteKernel',
513514
serverProviderHandle: {
514-
handle: 'deepnote-config-server-different-env'
515+
handle: createDeepnoteServerConfigHandle('different-env-id', openNotebook3.uri)
515516
}
516517
},
517518
dispose: sinon.stub().resolves()

src/kernels/deepnote/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,7 @@ export interface IDeepnoteServerStarter {
138138
startServer(
139139
interpreter: PythonEnvironment,
140140
venvPath: vscode.Uri,
141+
additionalPackages: string[],
141142
environmentId: string,
142143
deepnoteFileUri: vscode.Uri,
143144
token?: vscode.CancellationToken

src/notebooks/deepnote/deepnoteKernelAutoSelector.node.ts

Lines changed: 21 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,11 @@ import {
88
workspace,
99
NotebookControllerAffinity,
1010
window,
11-
NotebookController,
1211
CancellationTokenSource,
1312
Disposable,
1413
Uri,
1514
l10n,
1615
env,
17-
notebooks,
1816
ProgressLocation,
1917
QuickPickItem,
2018
commands
@@ -50,6 +48,7 @@ import { DeepnoteKernelError } from '../../platform/errors/deepnoteKernelErrors'
5048
import { DeepnoteEnvironment } from '../../kernels/deepnote/environments/deepnoteEnvironment';
5149
import { STANDARD_OUTPUT_CHANNEL } from '../../platform/common/constants';
5250
import { IOutputChannel } from '../../platform/common/types';
51+
import { createDeepnoteServerConfigHandle } from '../../platform/deepnote/deepnoteServerUtils.node';
5352

5453
/**
5554
* Automatically selects and starts Deepnote kernel for .deepnote notebooks
@@ -62,8 +61,6 @@ export class DeepnoteKernelAutoSelector implements IDeepnoteKernelAutoSelector,
6261
private readonly notebookControllers = new Map<string, IVSCodeNotebookController>();
6362
// Track connection metadata per notebook file for reuse
6463
private readonly notebookConnectionMetadata = new Map<string, DeepnoteKernelConnectionMetadata>();
65-
// Track temporary loading controllers that get disposed when real controller is ready
66-
private readonly loadingControllers = new Map<string, NotebookController>();
6764
// Track projects where we need to run init notebook (set during controller setup)
6865
private readonly projectsPendingInitNotebook = new Map<
6966
string,
@@ -133,25 +130,26 @@ export class DeepnoteKernelAutoSelector implements IDeepnoteKernelAutoSelector,
133130
title: l10n.t('Auto-selecting Deepnote kernel...'),
134131
cancellable: false
135132
},
136-
async (progress, token) => {
133+
// async (progress, token) => {
134+
async () => {
137135
try {
138136
const result = await this.ensureKernelSelected(notebook);
139137
if (!result) {
140-
const selectedEnvironment = await this.pickEnvironment(notebook.uri);
141-
if (selectedEnvironment) {
142-
await this.notebookEnvironmentMapper.setEnvironmentForNotebook(
143-
notebook.uri,
144-
selectedEnvironment.id
145-
);
146-
await this.ensureKernelSelectedWithConfiguration(
147-
notebook,
148-
selectedEnvironment,
149-
notebook.uri,
150-
notebook.uri.fsPath,
151-
progress,
152-
token
153-
);
154-
}
138+
// const selectedEnvironment = await this.pickEnvironment(notebook.uri);
139+
// if (selectedEnvironment) {
140+
// await this.notebookEnvironmentMapper.setEnvironmentForNotebook(
141+
// notebook.uri,
142+
// selectedEnvironment.id
143+
// );
144+
// await this.ensureKernelSelectedWithConfiguration(
145+
// notebook,
146+
// selectedEnvironment,
147+
// notebook.uri,
148+
// notebook.uri.fsPath,
149+
// progress,
150+
// token
151+
// );
152+
// }
155153
}
156154
} catch (error) {
157155
logger.error(`Failed to auto-select Deepnote kernel for ${getDisplayPath(notebook.uri)}`, error);
@@ -459,6 +457,7 @@ export class DeepnoteKernelAutoSelector implements IDeepnoteKernelAutoSelector,
459457
const serverInfo = await this.serverStarter.startServer(
460458
configuration.pythonInterpreter,
461459
configuration.venvPath,
460+
configuration.packages ?? [],
462461
configuration.id,
463462
baseFileUri,
464463
progressToken
@@ -473,7 +472,7 @@ export class DeepnoteKernelAutoSelector implements IDeepnoteKernelAutoSelector,
473472
const serverProviderHandle: JupyterServerProviderHandle = {
474473
extensionId: JVSC_EXTENSION_ID,
475474
id: 'deepnote-server',
476-
handle: `deepnote-config-server-${configuration.id}-${baseFileUri.fsPath}`
475+
handle: createDeepnoteServerConfigHandle(configuration.id, baseFileUri)
477476
};
478477

479478
// Register the server with the provider
@@ -643,7 +642,7 @@ export class DeepnoteKernelAutoSelector implements IDeepnoteKernelAutoSelector,
643642
return;
644643
}
645644

646-
const expectedHandle = `deepnote-config-server-${environmentId}`;
645+
const expectedHandle = createDeepnoteServerConfigHandle(environmentId, notebook.uri);
647646

648647
if (selectedController.connection.serverProviderHandle.handle === expectedHandle) {
649648
// Unselect the controller by setting affinity to Default
@@ -654,31 +653,6 @@ export class DeepnoteKernelAutoSelector implements IDeepnoteKernelAutoSelector,
654653
}
655654
}
656655

657-
private createLoadingController(notebook: NotebookDocument, notebookKey: string): void {
658-
// Create a temporary controller that shows "Loading..." and prevents kernel selection prompt
659-
const loadingController = notebooks.createNotebookController(
660-
`deepnote-loading-${notebookKey}`,
661-
DEEPNOTE_NOTEBOOK_TYPE,
662-
l10n.t('Loading Deepnote Kernel...')
663-
);
664-
665-
// Set it as the preferred controller immediately
666-
loadingController.supportsExecutionOrder = false;
667-
loadingController.supportedLanguages = ['python'];
668-
669-
// Execution handler that does nothing - cells will just sit there until real kernel is ready
670-
loadingController.executeHandler = () => {
671-
// No-op: execution is blocked until the real controller takes over
672-
};
673-
674-
// Select this controller for the notebook
675-
loadingController.updateNotebookAffinity(notebook, NotebookControllerAffinity.Preferred);
676-
677-
// Store it so we can dispose it later
678-
this.loadingControllers.set(notebookKey, loadingController);
679-
logger.info(`Created loading controller for ${notebookKey}`);
680-
}
681-
682656
/**
683657
* Handle kernel selection errors with user-friendly messages and actions
684658
*/

0 commit comments

Comments
 (0)