Skip to content

Commit abdb271

Browse files
IanMatthewHuffIan Huff
and
Ian Huff
authored
push nb convert interpreter fix to vscode-jupyter (#181)
Co-authored-by: Ian Huff <[email protected]>
1 parent ffa6334 commit abdb271

35 files changed

+379
-346
lines changed

.eslintignore

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1397,7 +1397,6 @@ src/client/datascience/export/types.ts
13971397
src/client/datascience/export/exportToPDF.ts
13981398
src/client/datascience/export/exportManagerFilePicker.ts
13991399
src/client/datascience/export/exportBase.ts
1400-
src/client/datascience/export/exportDependencyChecker.ts
14011400
src/client/datascience/export/exportFileOpener.ts
14021401
src/client/datascience/notebookAndInteractiveTracker.ts
14031402
src/client/datascience/statusProvider.ts

news/2 Fixes/14143.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
For exporting, first check the notebook or interative window interpreter before the jupyter selected interpreter.

src/client/common/application/commands.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { CancellationToken, Position, TextDocument, Uri } from 'vscode';
77
import { Commands as DSCommands } from '../../datascience/constants';
88
import { KernelConnectionMetadata } from '../../datascience/jupyter/kernels/types';
99
import { INotebookModel, ISwitchKernelOptions } from '../../datascience/types';
10+
import { PythonEnvironment } from '../../pythonEnvironments/info';
1011
import { CommandSource } from '../../testing/common/constants';
1112
import { Commands } from '../constants';
1213
import { Channel } from './types';
@@ -123,10 +124,10 @@ export interface ICommandNameArgumentTypeMapping extends ICommandNameWithoutArgu
123124
[DSCommands.GotoPrevCellInFile]: [];
124125
[DSCommands.ScrollToCell]: [Uri, string];
125126
[DSCommands.ViewJupyterOutput]: [];
126-
[DSCommands.ExportAsPythonScript]: [INotebookModel];
127-
[DSCommands.ExportToHTML]: [INotebookModel, string | undefined];
128-
[DSCommands.ExportToPDF]: [INotebookModel, string | undefined];
129-
[DSCommands.Export]: [Uri | INotebookModel, string | undefined];
127+
[DSCommands.ExportAsPythonScript]: [INotebookModel, PythonEnvironment | undefined];
128+
[DSCommands.ExportToHTML]: [INotebookModel, string | undefined, PythonEnvironment | undefined];
129+
[DSCommands.ExportToPDF]: [INotebookModel, string | undefined, PythonEnvironment | undefined];
130+
[DSCommands.Export]: [Uri | INotebookModel, string | undefined, PythonEnvironment | undefined];
130131
[DSCommands.SetJupyterKernel]: [KernelConnectionMetadata, Uri, undefined | Uri];
131132
[DSCommands.SwitchJupyterKernel]: [ISwitchKernelOptions | undefined];
132133
[DSCommands.SelectJupyterCommandLine]: [undefined | Uri];

src/client/datascience/commands/exportCommands.ts

Lines changed: 31 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import { IApplicationShell, ICommandManager } from '../../common/application/typ
1212
import { IDisposable } from '../../common/types';
1313
import { DataScience } from '../../common/utils/localize';
1414
import { isUri } from '../../common/utils/misc';
15+
import { PythonEnvironment } from '../../pythonEnvironments/info';
1516
import { sendTelemetryEvent } from '../../telemetry';
1617
import { Commands, Telemetry } from '../constants';
1718
import { ExportManager } from '../export/exportManager';
@@ -33,15 +34,17 @@ export class ExportCommands implements IDisposable {
3334
@inject(IFileSystem) private readonly fs: IFileSystem
3435
) {}
3536
public register() {
36-
this.registerCommand(Commands.ExportAsPythonScript, (model) => this.export(model, ExportFormat.python));
37-
this.registerCommand(Commands.ExportToHTML, (model, defaultFileName?) =>
38-
this.export(model, ExportFormat.html, defaultFileName)
37+
this.registerCommand(Commands.ExportAsPythonScript, (model, interpreter?) =>
38+
this.export(model, ExportFormat.python, undefined, interpreter)
3939
);
40-
this.registerCommand(Commands.ExportToPDF, (model, defaultFileName?) =>
41-
this.export(model, ExportFormat.pdf, defaultFileName)
40+
this.registerCommand(Commands.ExportToHTML, (model, defaultFileName?, interpreter?) =>
41+
this.export(model, ExportFormat.html, defaultFileName, interpreter)
4242
);
43-
this.registerCommand(Commands.Export, (model, defaultFileName?) =>
44-
this.export(model, undefined, defaultFileName)
43+
this.registerCommand(Commands.ExportToPDF, (model, defaultFileName?, interpreter?) =>
44+
this.export(model, ExportFormat.pdf, defaultFileName, interpreter)
45+
);
46+
this.registerCommand(Commands.Export, (model, defaultFileName?, interpreter?) =>
47+
this.export(model, undefined, defaultFileName, interpreter)
4548
);
4649
}
4750

@@ -58,7 +61,12 @@ export class ExportCommands implements IDisposable {
5861
this.disposables.push(disposable);
5962
}
6063

61-
private async export(modelOrUri: Uri | INotebookModel, exportMethod?: ExportFormat, defaultFileName?: string) {
64+
private async export(
65+
modelOrUri: Uri | INotebookModel,
66+
exportMethod?: ExportFormat,
67+
defaultFileName?: string,
68+
interpreter?: PythonEnvironment
69+
) {
6270
defaultFileName = typeof defaultFileName === 'string' ? defaultFileName : undefined;
6371
let model: INotebookModel | undefined;
6472
if (modelOrUri && isUri(modelOrUri)) {
@@ -85,11 +93,13 @@ export class ExportCommands implements IDisposable {
8593
}
8694

8795
if (exportMethod) {
88-
await this.exportManager.export(exportMethod, model, defaultFileName);
96+
await this.exportManager.export(exportMethod, model, defaultFileName, interpreter);
8997
} else {
9098
// if we don't have an export method we need to ask for one and display the
9199
// quickpick menu
92-
const pickedItem = await this.showExportQuickPickMenu(model, defaultFileName).then((item) => item);
100+
const pickedItem = await this.showExportQuickPickMenu(model, defaultFileName, interpreter).then(
101+
(item) => item
102+
);
93103
if (pickedItem !== undefined) {
94104
pickedItem.handler();
95105
} else {
@@ -98,7 +108,11 @@ export class ExportCommands implements IDisposable {
98108
}
99109
}
100110

101-
private getExportQuickPickItems(model: INotebookModel, defaultFileName?: string): IExportQuickPickItem[] {
111+
private getExportQuickPickItems(
112+
model: INotebookModel,
113+
defaultFileName?: string,
114+
interpreter?: PythonEnvironment
115+
): IExportQuickPickItem[] {
102116
return [
103117
{
104118
label: DataScience.exportPythonQuickPickLabel(),
@@ -107,7 +121,7 @@ export class ExportCommands implements IDisposable {
107121
sendTelemetryEvent(Telemetry.ClickedExportNotebookAsQuickPick, undefined, {
108122
format: ExportFormat.python
109123
});
110-
this.commandManager.executeCommand(Commands.ExportAsPythonScript, model);
124+
this.commandManager.executeCommand(Commands.ExportAsPythonScript, model, interpreter);
111125
}
112126
},
113127
{
@@ -117,7 +131,7 @@ export class ExportCommands implements IDisposable {
117131
sendTelemetryEvent(Telemetry.ClickedExportNotebookAsQuickPick, undefined, {
118132
format: ExportFormat.html
119133
});
120-
this.commandManager.executeCommand(Commands.ExportToHTML, model, defaultFileName);
134+
this.commandManager.executeCommand(Commands.ExportToHTML, model, defaultFileName, interpreter);
121135
}
122136
},
123137
{
@@ -127,17 +141,18 @@ export class ExportCommands implements IDisposable {
127141
sendTelemetryEvent(Telemetry.ClickedExportNotebookAsQuickPick, undefined, {
128142
format: ExportFormat.pdf
129143
});
130-
this.commandManager.executeCommand(Commands.ExportToPDF, model, defaultFileName);
144+
this.commandManager.executeCommand(Commands.ExportToPDF, model, defaultFileName, interpreter);
131145
}
132146
}
133147
];
134148
}
135149

136150
private async showExportQuickPickMenu(
137151
model: INotebookModel,
138-
defaultFileName?: string
152+
defaultFileName?: string,
153+
interpreter?: PythonEnvironment
139154
): Promise<IExportQuickPickItem | undefined> {
140-
const items = this.getExportQuickPickItems(model, defaultFileName);
155+
const items = this.getExportQuickPickItems(model, defaultFileName, interpreter);
141156

142157
const options: QuickPickOptions = {
143158
ignoreFocusOut: false,

src/client/datascience/constants.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -554,7 +554,6 @@ export namespace LiveShare {
554554

555555
export namespace LiveShareCommands {
556556
export const isNotebookSupported = 'isNotebookSupported';
557-
export const getImportPackageVersion = 'getImportPackageVersion';
558557
export const connectToNotebookServer = 'connectToNotebookServer';
559558
export const getUsableJupyterPython = 'getUsableJupyterPython';
560559
export const executeObservable = 'executeObservable';

src/client/datascience/export/exportBase.ts

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import * as path from 'path';
33
import { CancellationToken, Uri } from 'vscode';
44

55
import { IPythonExecutionFactory, IPythonExecutionService } from '../../common/process/types';
6+
import { PythonEnvironment } from '../../pythonEnvironments/info';
67
import { reportAction } from '../progress/decorator';
78
import { ReportableAction } from '../progress/types';
89
import { IFileSystem, IJupyterSubCommandExecutionService, INotebookImporter } from '../types';
@@ -18,21 +19,27 @@ export class ExportBase implements IExport {
1819
@inject(INotebookImporter) protected readonly importer: INotebookImporter
1920
) {}
2021

21-
// tslint:disable-next-line: no-empty
22-
public async export(_source: Uri, _target: Uri, _token: CancellationToken): Promise<void> {}
22+
public async export(
23+
_source: Uri,
24+
_target: Uri,
25+
_interpreter: PythonEnvironment,
26+
_token: CancellationToken
27+
// tslint:disable-next-line: no-empty
28+
): Promise<void> {}
2329

2430
@reportAction(ReportableAction.PerformingExport)
2531
public async executeCommand(
2632
source: Uri,
2733
target: Uri,
2834
format: ExportFormat,
35+
interpreter: PythonEnvironment,
2936
token: CancellationToken
3037
): Promise<void> {
3138
if (token.isCancellationRequested) {
3239
return;
3340
}
3441

35-
const service = await this.getExecutionService(source);
42+
const service = await this.getExecutionService(source, interpreter);
3643
if (!service) {
3744
return;
3845
}
@@ -75,11 +82,10 @@ export class ExportBase implements IExport {
7582
}
7683
}
7784

78-
protected async getExecutionService(source: Uri): Promise<IPythonExecutionService | undefined> {
79-
const interpreter = await this.jupyterService.getSelectedInterpreter();
80-
if (!interpreter) {
81-
return;
82-
}
85+
protected async getExecutionService(
86+
source: Uri,
87+
interpreter: PythonEnvironment
88+
): Promise<IPythonExecutionService | undefined> {
8389
return this.pythonExecutionFactory.createActivatedEnvironment({
8490
resource: source,
8591
interpreter,

src/client/datascience/export/exportDependencyChecker.ts

Lines changed: 0 additions & 30 deletions
This file was deleted.
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
import { inject, injectable } from 'inversify';
2+
import * as localize from '../../common/utils/localize';
3+
import { PythonEnvironment } from '../../pythonEnvironments/info';
4+
import { JupyterInterpreterService } from '../jupyter/interpreter/jupyterInterpreterService';
5+
import { ProgressReporter } from '../progress/progressReporter';
6+
import { IJupyterInterpreterDependencyManager, INbConvertInterpreterDependencyChecker } from '../types';
7+
import { ExportFormat } from './types';
8+
9+
@injectable()
10+
export class ExportInterpreterFinder {
11+
constructor(
12+
@inject(IJupyterInterpreterDependencyManager)
13+
private readonly dependencyManager: IJupyterInterpreterDependencyManager,
14+
@inject(ProgressReporter) private readonly progressReporter: ProgressReporter,
15+
@inject(INbConvertInterpreterDependencyChecker)
16+
private readonly nbConvertDependencyChecker: INbConvertInterpreterDependencyChecker,
17+
@inject(JupyterInterpreterService) private readonly jupyterInterpreterService: JupyterInterpreterService
18+
) {}
19+
20+
// For the given ExportFormat and a possible candidateInterpreter return an interpreter capable of running nbconvert or throw
21+
public async getExportInterpreter(
22+
format: ExportFormat,
23+
candidateInterpreter?: PythonEnvironment
24+
): Promise<PythonEnvironment> {
25+
// Before we try the import, see if we don't support it, if we don't give a chance to install dependencies
26+
const reporter = this.progressReporter.createProgressIndicator(`Exporting to ${format}`);
27+
try {
28+
// If an interpreter was passed in, first see if that interpreter supports NB Convert
29+
// if it does, we are good to go, but don't install nbconvert into it
30+
if (candidateInterpreter && (await this.checkNotebookInterpreter(candidateInterpreter))) {
31+
return candidateInterpreter;
32+
}
33+
34+
// If an interpreter was not passed in, work with the main jupyter interperter
35+
const selectedJupyterInterpreter = await this.jupyterInterpreterService.getSelectedInterpreter();
36+
37+
if (selectedJupyterInterpreter) {
38+
if (await this.checkNotebookInterpreter(selectedJupyterInterpreter)) {
39+
return selectedJupyterInterpreter;
40+
} else {
41+
// Give the user a chance to install nbconvert into the selected jupyter interpreter
42+
await this.dependencyManager.installMissingDependencies();
43+
if (await this.checkNotebookInterpreter(selectedJupyterInterpreter)) {
44+
return selectedJupyterInterpreter;
45+
}
46+
}
47+
}
48+
49+
throw new Error(localize.DataScience.jupyterNbConvertNotSupported());
50+
} finally {
51+
reporter.dispose();
52+
}
53+
}
54+
55+
// For this specific interpreter associated with a notebook check to see if it supports import
56+
// and export with nbconvert
57+
private async checkNotebookInterpreter(interpreter: PythonEnvironment) {
58+
return this.nbConvertDependencyChecker.isNbConvertInstalled(interpreter);
59+
}
60+
}

0 commit comments

Comments
 (0)