Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions src/kernels/execution/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -642,8 +642,12 @@ export async function updateNotebookMetadataWithSelectedKernel(
// It's possible, such as with raw kernel and a default kernelspec to not have interpreter info
// for this case clear out old invalid language_info entries as they are related to the previous execution
// However we should clear previous language info only if language is python, else just leave it as is.
metadata.language_info = undefined;
changed = true;
// But for remote kernels, preserve the existing language_info since it contains valid info from the remote server
const isRemoteKernel = kernelConnection?.kind === 'startUsingRemoteKernelSpec' || kernelConnection?.kind === 'connectToLiveRemoteKernel';
if (!isRemoteKernel) {
metadata.language_info = undefined;
changed = true;
}
}
}

Expand Down
112 changes: 111 additions & 1 deletion src/kernels/execution/helpers.unit.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import {
getNotebookCellOutputMetadata,
updateNotebookMetadataWithSelectedKernel
} from './helpers';
import { IJupyterKernelSpec, PythonKernelConnectionMetadata } from '../types';
import { IJupyterKernelSpec, PythonKernelConnectionMetadata, RemoteKernelSpecConnectionMetadata, LiveRemoteKernelConnectionMetadata } from '../types';
import { PythonEnvironment } from '../../platform/pythonEnvironments/info';
import { PythonExtension } from '@vscode/python-extension';
import { instance, mock, when } from 'ts-mockito';
Expand Down Expand Up @@ -231,6 +231,116 @@ suite(`UpdateNotebookMetadata`, () => {
// Should be no change here
assert.strictEqual(value.changed, false);
});

test('Remote Kernel without interpreter should preserve existing language_info', async () => {
const notebookMetadata: nbformat.INotebookMetadata = {
orig_nbformat: 4,
kernelspec: { display_name: 'Python 3 (ipykernel)', language: 'python', name: 'python3' },
language_info: {
name: 'python',
version: '3.10.13',
codemirror_mode: {
name: 'ipython',
version: 3
},
file_extension: '.py',
mimetype: 'text/x-python',
nbconvert_exporter: 'python',
pygments_lexer: 'ipython3'
}
};
const remoteKernelSpec: IJupyterKernelSpec = {
argv: ['python', '-f', '{connection_file}'],
display_name: 'Python 3 (ipykernel)',
name: 'python3',
language: 'python',
executable: 'python'
};
// Create remote kernel connection without interpreter (typical for remote servers)
const kernelConnection = RemoteKernelSpecConnectionMetadata.create({
kernelSpec: remoteKernelSpec,
baseUrl: 'http://localhost:8888',
id: 'remote-python3',
serverProviderHandle: { handle: 'test', id: 'test', extensionId: 'test' }
// Note: no interpreter provided, which is typical for remote servers
});
const value = await updateNotebookMetadataWithSelectedKernel(notebookMetadata, kernelConnection);

// Verify language_info is preserved and not cleared to undefined
verifyMetadata(notebookMetadata, {
orig_nbformat: 4,
kernelspec: { display_name: 'Python 3 (ipykernel)', language: 'python', name: 'python3' },
language_info: {
name: 'python',
version: '3.10.13',
codemirror_mode: {
name: 'ipython',
version: 3
},
file_extension: '.py',
mimetype: 'text/x-python',
nbconvert_exporter: 'python',
pygments_lexer: 'ipython3'
}
});
// Should be no change since we're preserving existing metadata
assert.strictEqual(value.changed, false);
});

test('Live Remote Kernel without interpreter should preserve existing language_info', async () => {
const notebookMetadata: nbformat.INotebookMetadata = {
orig_nbformat: 4,
kernelspec: { display_name: 'Python 3 (ipykernel)', language: 'python', name: 'python3' },
language_info: {
name: 'python',
version: '3.10.13',
codemirror_mode: {
name: 'ipython',
version: 3
},
file_extension: '.py',
mimetype: 'text/x-python',
nbconvert_exporter: 'python',
pygments_lexer: 'ipython3'
}
};
const kernelModel = {
id: 'kernel-id',
name: 'python3',
display_name: 'python3',
language: 'python'
} as any;
// Create live remote kernel connection without interpreter (typical for remote servers)
const kernelConnection = LiveRemoteKernelConnectionMetadata.create({
kernelModel: kernelModel,
baseUrl: 'http://localhost:8888',
id: 'live-remote-python3',
serverProviderHandle: { handle: 'test', id: 'test', extensionId: 'test' }
// Note: no interpreter provided, which is typical for remote servers
});
const value = await updateNotebookMetadataWithSelectedKernel(notebookMetadata, kernelConnection);

// Verify language_info is preserved and not cleared to undefined
// Also verify that the kernelspec display_name gets updated to match the kernel model
verifyMetadata(notebookMetadata, {
orig_nbformat: 4,
kernelspec: { display_name: 'python3', language: 'python', name: 'python3' },
language_info: {
name: 'python',
version: '3.10.13',
codemirror_mode: {
name: 'ipython',
version: 3
},
file_extension: '.py',
mimetype: 'text/x-python',
nbconvert_exporter: 'python',
pygments_lexer: 'ipython3'
}
});
// Should have changed due to kernelspec update, but language_info should be preserved
assert.strictEqual(value.changed, true);
});
});

function verifyMetadata(actualMetadata: nbformat.INotebookMetadata, targetMetadata: nbformat.INotebookMetadata) {
Expand Down