Skip to content

Commit 2e129b4

Browse files
Port NB Convert Fix to point release branch (#14200)
1 parent f276e07 commit 2e129b4

18 files changed

+204
-65
lines changed

CHANGELOG.md

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,67 @@
11
# Changelog
22

3+
## 2020.9.2 (1 October 2020)
4+
5+
### Fixes
6+
7+
1. Support nbconvert version 6+ for exporting notebooks to python code.
8+
([#14169](https://github.com/Microsoft/vscode-python/issues/14169))
9+
10+
### Thanks
11+
12+
Thanks to the following projects which we fully rely on to provide some of
13+
our features:
14+
15+
- [debugpy](https://pypi.org/project/debugpy/)
16+
- [isort](https://pypi.org/project/isort/)
17+
- [jedi](https://pypi.org/project/jedi/)
18+
and [parso](https://pypi.org/project/parso/)
19+
- [Microsoft Python Language Server](https://github.com/microsoft/python-language-server)
20+
- [Pylance](https://github.com/microsoft/pylance-release)
21+
- [exuberant ctags](http://ctags.sourceforge.net/) (user-installed)
22+
- [rope](https://pypi.org/project/rope/) (user-installed)
23+
24+
Also thanks to the various projects we provide integrations with which help
25+
make this extension useful:
26+
27+
- Debugging support:
28+
[Django](https://pypi.org/project/Django/),
29+
[Flask](https://pypi.org/project/Flask/),
30+
[gevent](https://pypi.org/project/gevent/),
31+
[Jinja](https://pypi.org/project/Jinja/),
32+
[Pyramid](https://pypi.org/project/pyramid/),
33+
[PySpark](https://pypi.org/project/pyspark/),
34+
[Scrapy](https://pypi.org/project/Scrapy/),
35+
[Watson](https://pypi.org/project/Watson/)
36+
- Formatting:
37+
[autopep8](https://pypi.org/project/autopep8/),
38+
[black](https://pypi.org/project/black/),
39+
[yapf](https://pypi.org/project/yapf/)
40+
- Interpreter support:
41+
[conda](https://conda.io/),
42+
[direnv](https://direnv.net/),
43+
[pipenv](https://pypi.org/project/pipenv/),
44+
[pyenv](https://github.com/pyenv/pyenv),
45+
[venv](https://docs.python.org/3/library/venv.html#module-venv),
46+
[virtualenv](https://pypi.org/project/virtualenv/)
47+
- Linting:
48+
[bandit](https://pypi.org/project/bandit/),
49+
[flake8](https://pypi.org/project/flake8/),
50+
[mypy](https://pypi.org/project/mypy/),
51+
[prospector](https://pypi.org/project/prospector/),
52+
[pylint](https://pypi.org/project/pylint/),
53+
[pydocstyle](https://pypi.org/project/pydocstyle/),
54+
[pylama](https://pypi.org/project/pylama/)
55+
- Testing:
56+
[nose](https://pypi.org/project/nose/),
57+
[pytest](https://pypi.org/project/pytest/),
58+
[unittest](https://docs.python.org/3/library/unittest.html#module-unittest)
59+
60+
And finally thanks to the [Python](https://www.python.org/) development team and
61+
community for creating a fantastic programming language and community to be a
62+
part of!
63+
64+
365
## 2020.9.1 (29 September 2020)
466

567
### Fixes

package.nls.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@
3131
"DataScience.openExportFileYes": "Yes",
3232
"DataScience.openExportFileNo": "No",
3333
"DataScience.failedExportMessage": "Export failed.",
34-
"DataScience.exportFailedGeneralMessage": "Export failed. Please check the 'Python' [output](command:python.viewOutput) panel for further details.",
34+
"DataScience.exportFailedGeneralMessage": "Please check the 'Python' [output](command:python.viewOutput) panel for further details.",
3535
"DataScience.exportToPDFDependencyMessage": "If you have not installed xelatex (TeX) you will need to do so before you can export to PDF, for further instructions go to https://nbconvert.readthedocs.io/en/latest/install.html#installing-tex. \r\nTo avoid installing xelatex (TeX) you might want to try exporting to HTML and using your browsers \"Print to PDF\" feature.",
3636
"DataScience.launchNotebookTrustPrompt": "A notebook could execute harmful code when opened. Some outputs have been hidden. Do you trust this notebook? [Learn more.](https://aka.ms/trusted-notebooks)",
3737
"DataScience.launchNotebookTrustPrompt.yes": "Trust",

src/client/common/utils/localize.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -892,7 +892,7 @@ export namespace DataScience {
892892
export const openExportFileNo = localize('DataScience.openExportFileNo', 'No');
893893
export const exportFailedGeneralMessage = localize(
894894
'DataScience.exportFailedGeneralMessage',
895-
`Export failed. Please check the 'Python' [output](command:python.viewOutput) panel for further details.`
895+
`Please check the 'Python' [output](command:python.viewOutput) panel for further details.`
896896
);
897897
export const exportToPDFDependencyMessage = localize(
898898
'DataScience.exportToPDFDependencyMessage',

src/client/datascience/common.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
'use strict';
44
import type { nbformat } from '@jupyterlab/coreutils';
55
import * as os from 'os';
6+
import { parse, SemVer } from 'semver';
67
import { Memento, Uri } from 'vscode';
78
import { splitMultilineString } from '../../datascience-ui/common';
89
import { traceError, traceInfo } from '../common/logger';
@@ -188,3 +189,14 @@ export async function getRealPath(
188189
}
189190
}
190191
}
192+
193+
// For the given string parse it out to a SemVer or return undefined
194+
export function parseSemVer(versionString: string): SemVer | undefined {
195+
const versionMatch = /^\s*(\d+)\.(\d+)\.(.+)\s*$/.exec(versionString);
196+
if (versionMatch && versionMatch.length > 2) {
197+
const major = parseInt(versionMatch[1], 10);
198+
const minor = parseInt(versionMatch[2], 10);
199+
const build = parseInt(versionMatch[3], 10);
200+
return parse(`${major}.${minor}.${build}`, true) ?? undefined;
201+
}
202+
}

src/client/datascience/constants.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -569,7 +569,7 @@ export namespace LiveShare {
569569

570570
export namespace LiveShareCommands {
571571
export const isNotebookSupported = 'isNotebookSupported';
572-
export const isImportSupported = 'isImportSupported';
572+
export const getImportPackageVersion = 'getImportPackageVersion';
573573
export const connectToNotebookServer = 'connectToNotebookServer';
574574
export const getUsableJupyterPython = 'getUsableJupyterPython';
575575
export const executeObservable = 'executeObservable';

src/client/datascience/data-viewing/dataViewerDependencyService.ts

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
'use strict';
55

66
import { inject, injectable } from 'inversify';
7-
import { parse, SemVer } from 'semver';
7+
import { SemVer } from 'semver';
88
import { CancellationToken } from 'vscode';
99
import { IApplicationShell } from '../../common/application/types';
1010
import { Cancellation, createPromiseFromCancellation, wrapCancellationTokens } from '../../common/cancellation';
@@ -14,6 +14,7 @@ import { IInstaller, InstallerResponse, Product } from '../../common/types';
1414
import { Common, DataScience } from '../../common/utils/localize';
1515
import { PythonEnvironment } from '../../pythonEnvironments/info';
1616
import { sendTelemetryEvent } from '../../telemetry';
17+
import { parseSemVer } from '../common';
1718
import { Telemetry } from '../constants';
1819

1920
const minimumSupportedPandaVersion = '0.20.0';
@@ -104,13 +105,8 @@ export class DataViewerDependencyService {
104105
throwOnStdErr: true,
105106
token
106107
});
107-
const versionMatch = /^\s*(\d+)\.(\d+)\.(.+)\s*$/.exec(result.stdout);
108-
if (versionMatch && versionMatch.length > 2) {
109-
const major = parseInt(versionMatch[1], 10);
110-
const minor = parseInt(versionMatch[2], 10);
111-
const build = parseInt(versionMatch[3], 10);
112-
return parse(`${major}.${minor}.${build}`, true) ?? undefined;
113-
}
108+
109+
return parseSemVer(result.stdout);
114110
} catch (ex) {
115111
traceWarning('Failed to get version of Pandas to use Data Viewer', ex);
116112
return;

src/client/datascience/export/exportDependencyChecker.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,9 @@ export class ExportDependencyChecker {
1717
// Before we try the import, see if we don't support it, if we don't give a chance to install dependencies
1818
const reporter = this.progressReporter.createProgressIndicator(`Exporting to ${format}`);
1919
try {
20-
if (!(await this.jupyterExecution.isImportSupported())) {
20+
if (!(await this.jupyterExecution.getImportPackageVersion())) {
2121
await this.dependencyManager.installMissingDependencies();
22-
if (!(await this.jupyterExecution.isImportSupported())) {
22+
if (!(await this.jupyterExecution.getImportPackageVersion())) {
2323
throw new Error(localize.DataScience.jupyterNbConvertNotSupported());
2424
}
2525
}

src/client/datascience/jupyter/interpreter/jupyterInterpreterDependencyService.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
'use strict';
55

66
import { inject, injectable } from 'inversify';
7+
import { SemVer } from 'semver';
78
import { CancellationToken } from 'vscode';
89
import { IApplicationShell } from '../../../common/application/types';
910
import { Cancellation, createPromiseFromCancellation, wrapCancellationTokens } from '../../../common/cancellation';
@@ -14,6 +15,7 @@ import { Common, DataScience } from '../../../common/utils/localize';
1415
import { noop } from '../../../common/utils/misc';
1516
import { PythonEnvironment } from '../../../pythonEnvironments/info';
1617
import { sendTelemetryEvent } from '../../../telemetry';
18+
import { parseSemVer } from '../../common';
1719
import { HelpLinks, JupyterCommands, Telemetry } from '../../constants';
1820
import { reportAction } from '../../progress/decorator';
1921
import { ReportableAction } from '../../progress/types';
@@ -241,6 +243,23 @@ export class JupyterInterpreterDependencyService {
241243
return installed;
242244
}
243245

246+
public async getNbConvertVersion(
247+
interpreter: PythonEnvironment,
248+
_token?: CancellationToken
249+
): Promise<SemVer | undefined> {
250+
const command = this.commandFactory.createInterpreterCommand(
251+
JupyterCommands.ConvertCommand,
252+
'jupyter',
253+
['-m', 'jupyter', 'nbconvert'],
254+
interpreter,
255+
false
256+
);
257+
258+
const result = await command.exec(['--version'], { throwOnStdErr: true });
259+
260+
return parseSemVer(result.stdout);
261+
}
262+
244263
/**
245264
* Gets a list of the dependencies not installed, dependencies that are required to launch the jupyter notebook server.
246265
*

src/client/datascience/jupyter/interpreter/jupyterInterpreterSubCommandExecutionService.ts

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
import { inject, injectable, named } from 'inversify';
77
import * as path from 'path';
8+
import { SemVer } from 'semver';
89
import { CancellationToken, Uri } from 'vscode';
910
import { Cancellation } from '../../../common/cancellation';
1011
import { traceError, traceInfo, traceWarning } from '../../../common/logger';
@@ -76,12 +77,16 @@ export class JupyterInterpreterSubCommandExecutionService
7677
}
7778
return this.jupyterDependencyService.areDependenciesInstalled(interpreter, token);
7879
}
79-
public async isExportSupported(token?: CancellationToken): Promise<boolean> {
80+
public async getExportPackageVersion(token?: CancellationToken): Promise<SemVer | undefined> {
8081
const interpreter = await this.jupyterInterpreter.getSelectedInterpreter(token);
8182
if (!interpreter) {
82-
return false;
83+
return;
84+
}
85+
86+
// If nbconvert is there check and return the version
87+
if (await this.jupyterDependencyService.isExportSupported(interpreter, token)) {
88+
return this.jupyterDependencyService.getNbConvertVersion(interpreter, token);
8389
}
84-
return this.jupyterDependencyService.isExportSupported(interpreter, token);
8590
}
8691
public async getReasonForJupyterNotebookNotBeingSupported(token?: CancellationToken): Promise<string> {
8792
let interpreter = await this.jupyterInterpreter.getSelectedInterpreter(token);
@@ -176,11 +181,21 @@ export class JupyterInterpreterSubCommandExecutionService
176181
const args = template
177182
? [file.fsPath, '--to', 'python', '--stdout', '--template', template]
178183
: [file.fsPath, '--to', 'python', '--stdout'];
184+
179185
// Ignore stderr, as nbconvert writes conversion result to stderr.
180186
// stdout contains the generated python code.
181187
return daemon
182188
.execModule('jupyter', ['nbconvert'].concat(args), { throwOnStdErr: false, encoding: 'utf8', token })
183-
.then((output) => output.stdout);
189+
.then((output) => {
190+
// We can't check stderr (as nbconvert puts diag output there) but we need to verify here that we actually
191+
// converted something. If it's zero size then just raise an error
192+
if (output.stdout === '') {
193+
traceError('nbconvert zero size output');
194+
throw new Error(output.stderr);
195+
} else {
196+
return output.stdout;
197+
}
198+
});
184199
}
185200
public async openNotebook(notebookFile: string): Promise<void> {
186201
const interpreter = await this.getSelectedInterpreterAndThrowIfNotAvailable();

src/client/datascience/jupyter/jupyterExecution.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// Licensed under the MIT License.
33
'use strict';
44
import * as path from 'path';
5+
import { SemVer } from 'semver';
56
import * as uuid from 'uuid/v4';
67
import { CancellationToken, CancellationTokenSource, Event, EventEmitter, Uri } from 'vscode';
78

@@ -123,9 +124,9 @@ export class JupyterExecutionBase implements IJupyterExecution {
123124
}
124125

125126
@reportAction(ReportableAction.CheckingIfImportIsSupported)
126-
public async isImportSupported(cancelToken?: CancellationToken): Promise<boolean> {
127+
public async getImportPackageVersion(cancelToken?: CancellationToken): Promise<SemVer | undefined> {
127128
// See if we can find the command nbconvert
128-
return this.jupyterInterpreterService.isExportSupported(cancelToken);
129+
return this.jupyterInterpreterService.getExportPackageVersion(cancelToken);
129130
}
130131

131132
public isSpawnSupported(cancelToken?: CancellationToken): Promise<boolean> {

0 commit comments

Comments
 (0)