From 8c4449023bd44af94ab0a5e3bebb22c00d76e64f Mon Sep 17 00:00:00 2001 From: Ian Huff Date: Thu, 17 Sep 2020 13:43:08 -0700 Subject: [PATCH 01/24] Port ipykernel install fix to release (#13975) * Fix installing ipykernel into interpreters for raw kernel (#13959) * update news Co-authored-by: Ian Huff --- CHANGELOG.md | 2 + package.nls.json | 3 +- src/client/common/utils/localize.ts | 4 ++ .../jupyter/kernels/kernelSelector.ts | 29 +++++++++++++- .../kernel-launcher/kernelFinder.ts | 38 ++----------------- .../kernels/kernelSelector.unit.test.ts | 5 ++- .../datascience/kernelFinder.unit.test.ts | 8 +--- 7 files changed, 45 insertions(+), 44 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f89e1e35e763..3a6881c00fdb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -57,6 +57,8 @@ ([#13612](https://github.com/Microsoft/vscode-python/issues/13612)) 1. Fix the behavior of the 'python.showStartPage' setting. ([#13706](https://github.com/Microsoft/vscode-python/issues/13706)) +1. Correctly install ipykernel when launching from an interpreter. + ([#13956](https://github.com/Microsoft/vscode-python/issues/13956)) ### Code Health diff --git a/package.nls.json b/package.nls.json index ec6378814166..16a537b3a669 100644 --- a/package.nls.json +++ b/package.nls.json @@ -595,5 +595,6 @@ "DataScience.interactiveWindowModeBannerTitle": "Do you want to open a new Python Interactive window for this file? [More Information](command:workbench.action.openSettings?%5B%22python.dataScience.interactiveWindowMode%22%5D).", "DataScience.interactiveWindowModeBannerSwitchYes": "Yes", "DataScience.interactiveWindowModeBannerSwitchAlways": "Always", - "DataScience.interactiveWindowModeBannerSwitchNo": "No" + "DataScience.interactiveWindowModeBannerSwitchNo": "No", + "DataScience.ipykernelNotInstalled": "IPyKernel not installed into interpreter {0}" } diff --git a/src/client/common/utils/localize.ts b/src/client/common/utils/localize.ts index 8207de65015f..4a4981f8ae63 100644 --- a/src/client/common/utils/localize.ts +++ b/src/client/common/utils/localize.ts @@ -1116,6 +1116,10 @@ export namespace DataScience { ); export const connected = localize('DataScience.connected', 'Connected'); export const disconnected = localize('DataScience.disconnected', 'Disconnected'); + export const ipykernelNotInstalled = localize( + 'DataScience.ipykernelNotInstalled', + 'IPyKernel not installed into interpreter {0}' + ); } export namespace StartPage { diff --git a/src/client/datascience/jupyter/kernels/kernelSelector.ts b/src/client/datascience/jupyter/kernels/kernelSelector.ts index d2623d2df616..e398ded46d5f 100644 --- a/src/client/datascience/jupyter/kernels/kernelSelector.ts +++ b/src/client/datascience/jupyter/kernels/kernelSelector.ts @@ -27,7 +27,8 @@ import { IJupyterSessionManagerFactory, IKernelDependencyService, INotebookMetadataLive, - INotebookProviderConnection + INotebookProviderConnection, + KernelInterpreterDependencyResponse } from '../../types'; import { createDefaultKernelSpec, getDisplayNameOrNameOfKernelConnection } from './helpers'; import { KernelSelectionProvider } from './kernelSelections'; @@ -504,6 +505,8 @@ export class KernelSelector implements IKernelSelectionUsage { if (!kernelSpec && !activeInterpreter) { return; } else if (!kernelSpec && activeInterpreter) { + await this.installDependenciesIntoInterpreter(activeInterpreter, ignoreDependencyCheck, cancelToken); + // Return current interpreter. return { kind: 'startUsingPythonInterpreter', @@ -512,6 +515,11 @@ export class KernelSelector implements IKernelSelectionUsage { } else if (kernelSpec) { // Locate the interpreter that matches our kernelspec const interpreter = await this.kernelService.findMatchingInterpreter(kernelSpec, cancelToken); + + if (interpreter) { + await this.installDependenciesIntoInterpreter(interpreter, ignoreDependencyCheck, cancelToken); + } + return { kind: 'startUsingKernelSpec', kernelSpec, interpreter }; } } @@ -545,6 +553,25 @@ export class KernelSelector implements IKernelSelectionUsage { return { kernelSpec, interpreter, kind: 'startUsingPythonInterpreter' }; } + // If we need to install our dependencies now (for non-native scenarios) + // then install ipykernel into the interpreter or throw error + private async installDependenciesIntoInterpreter( + interpreter: PythonEnvironment, + ignoreDependencyCheck?: boolean, + cancelToken?: CancellationToken + ) { + if (!ignoreDependencyCheck) { + if ( + (await this.kernelDependencyService.installMissingDependencies(interpreter, cancelToken)) !== + KernelInterpreterDependencyResponse.ok + ) { + throw new Error( + localize.DataScience.ipykernelNotInstalled().format(interpreter.displayName || interpreter.path) + ); + } + } + } + /** * Use the provided interpreter as a kernel. * If `displayNameOfKernelNotFound` is provided, then display a message indicating we're using the `current interpreter`. diff --git a/src/client/datascience/kernel-launcher/kernelFinder.ts b/src/client/datascience/kernel-launcher/kernelFinder.ts index 4958ea42216d..5315c09caf05 100644 --- a/src/client/datascience/kernel-launcher/kernelFinder.ts +++ b/src/client/datascience/kernel-launcher/kernelFinder.ts @@ -5,13 +5,12 @@ import type { nbformat } from '@jupyterlab/coreutils'; import { inject, injectable, named } from 'inversify'; import * as path from 'path'; -import { CancellationToken, CancellationTokenSource } from 'vscode'; +import { CancellationToken } from 'vscode'; import { IWorkspaceService } from '../../common/application/types'; -import { wrapCancellationTokens } from '../../common/cancellation'; import { traceError, traceInfo } from '../../common/logger'; import { IPlatformService } from '../../common/platform/types'; import { IPythonExecutionFactory } from '../../common/process/types'; -import { IExtensionContext, IInstaller, InstallerResponse, IPathUtils, Product, Resource } from '../../common/types'; +import { IExtensionContext, IPathUtils, Resource } from '../../common/types'; import { IEnvironmentVariablesProvider } from '../../common/variables/types'; import { IInterpreterLocatorService, IInterpreterService, KNOWN_PATH_SERVICE } from '../../interpreter/contracts'; import { captureTelemetry } from '../../telemetry'; @@ -20,7 +19,6 @@ import { Telemetry } from '../constants'; import { defaultKernelSpecName } from '../jupyter/kernels/helpers'; import { JupyterKernelSpec } from '../jupyter/kernels/jupyterKernelSpec'; import { IDataScienceFileSystem, IJupyterKernelSpec } from '../types'; -import { getKernelInterpreter } from './helpers'; import { IKernelFinder } from './types'; // tslint:disable-next-line:no-require-imports no-var-requires const flatten = require('lodash/flatten') as typeof import('lodash/flatten'); @@ -56,7 +54,6 @@ export class KernelFinder implements IKernelFinder { @inject(IPlatformService) private platformService: IPlatformService, @inject(IDataScienceFileSystem) private fs: IDataScienceFileSystem, @inject(IPathUtils) private readonly pathUtils: IPathUtils, - @inject(IInstaller) private installer: IInstaller, @inject(IExtensionContext) private readonly context: IExtensionContext, @inject(IWorkspaceService) private readonly workspaceService: IWorkspaceService, @inject(IPythonExecutionFactory) private readonly exeFactory: IPythonExecutionFactory, @@ -65,9 +62,7 @@ export class KernelFinder implements IKernelFinder { @captureTelemetry(Telemetry.KernelFinderPerf) public async findKernelSpec( resource: Resource, - kernelSpecMetadata?: nbformat.IKernelspecMetadata, - cancelToken?: CancellationToken, - ignoreDependencyCheck?: boolean + kernelSpecMetadata?: nbformat.IKernelspecMetadata ): Promise { await this.readCache(); let foundKernel: IJupyterKernelSpec | undefined; @@ -108,8 +103,7 @@ export class KernelFinder implements IKernelFinder { this.writeCache().ignoreErrors(); - // Verify that ipykernel is installed into the given kernelspec interpreter - return ignoreDependencyCheck || !foundKernel ? foundKernel : this.verifyIpyKernel(foundKernel, cancelToken); + return foundKernel; } // Search all our local file system locations for installed kernel specs and return them @@ -318,30 +312,6 @@ export class KernelFinder implements IKernelFinder { return flatten(fullPathResults); } - // For the given kernelspec return back the kernelspec with ipykernel installed into it or error - private async verifyIpyKernel( - kernelSpec: IJupyterKernelSpec, - cancelToken?: CancellationToken - ): Promise { - const interpreter = await getKernelInterpreter(kernelSpec, this.interpreterService); - - if (await this.installer.isInstalled(Product.ipykernel, interpreter)) { - return kernelSpec; - } else { - const token = new CancellationTokenSource(); - const response = await this.installer.promptToInstall( - Product.ipykernel, - interpreter, - wrapCancellationTokens(cancelToken, token.token) - ); - if (response === InstallerResponse.Installed) { - return kernelSpec; - } - } - - throw new Error(`IPyKernel not installed into interpreter ${interpreter.displayName}`); - } - private async getKernelSpecFromActiveInterpreter( kernelName: string, resource: Resource diff --git a/src/test/datascience/jupyter/kernels/kernelSelector.unit.test.ts b/src/test/datascience/jupyter/kernels/kernelSelector.unit.test.ts index 515e6c4984de..2b15157b520b 100644 --- a/src/test/datascience/jupyter/kernels/kernelSelector.unit.test.ts +++ b/src/test/datascience/jupyter/kernels/kernelSelector.unit.test.ts @@ -31,7 +31,7 @@ import { LiveKernelModel } from '../../../../client/datascience/jupyter/kernels/types'; import { IKernelFinder } from '../../../../client/datascience/kernel-launcher/types'; -import { IJupyterSessionManager } from '../../../../client/datascience/types'; +import { IJupyterSessionManager, KernelInterpreterDependencyResponse } from '../../../../client/datascience/types'; import { IInterpreterService } from '../../../../client/interpreter/contracts'; import { InterpreterService } from '../../../../client/interpreter/interpreterService'; import { EnvironmentType, PythonEnvironment } from '../../../../client/pythonEnvironments/info'; @@ -72,6 +72,9 @@ suite('DataScience - KernelSelector', () => { kernelSelectionProvider = mock(KernelSelectionProvider); appShell = mock(ApplicationShell); dependencyService = mock(KernelDependencyService); + when(dependencyService.installMissingDependencies(anything(), anything())).thenResolve( + KernelInterpreterDependencyResponse.ok + ); interpreterService = mock(InterpreterService); kernelFinder = mock(); const jupyterSessionManagerFactory = mock(JupyterSessionManagerFactory); diff --git a/src/test/datascience/kernelFinder.unit.test.ts b/src/test/datascience/kernelFinder.unit.test.ts index 696864406153..bf37540a375c 100644 --- a/src/test/datascience/kernelFinder.unit.test.ts +++ b/src/test/datascience/kernelFinder.unit.test.ts @@ -11,7 +11,7 @@ import { Uri } from 'vscode'; import { IWorkspaceService } from '../../client/common/application/types'; import { IPlatformService } from '../../client/common/platform/types'; import { PythonExecutionFactory } from '../../client/common/process/pythonExecutionFactory'; -import { IExtensionContext, IInstaller, IPathUtils, Resource } from '../../client/common/types'; +import { IExtensionContext, IPathUtils, Resource } from '../../client/common/types'; import { Architecture } from '../../client/common/utils/platform'; import { IEnvironmentVariablesProvider } from '../../client/common/variables/types'; import { defaultKernelSpecName } from '../../client/datascience/jupyter/kernels/helpers'; @@ -30,7 +30,6 @@ suite('Kernel Finder', () => { let pathUtils: typemoq.IMock; let context: typemoq.IMock; let envVarsProvider: typemoq.IMock; - let installer: IInstaller; let workspaceService: IWorkspaceService; let kernelFinder: IKernelFinder; let activeInterpreter: PythonEnvironment; @@ -83,9 +82,6 @@ suite('Kernel Finder', () => { context.setup((c) => c.globalStoragePath).returns(() => './'); fileSystem = typemoq.Mock.ofType(); - installer = mock(); - when(installer.isInstalled(anything(), anything())).thenResolve(true); - platformService = typemoq.Mock.ofType(); platformService.setup((ps) => ps.isWindows).returns(() => true); platformService.setup((ps) => ps.isMac).returns(() => true); @@ -325,7 +321,6 @@ suite('Kernel Finder', () => { platformService.object, fileSystem.object, pathUtils.object, - instance(installer), context.object, instance(workspaceService), instance(executionFactory), @@ -408,7 +403,6 @@ suite('Kernel Finder', () => { platformService.object, fileSystem.object, pathUtils.object, - instance(installer), context.object, instance(workspaceService), instance(executionFactory), From b92203aae441d1b264a23ed1db584dc698f0c643 Mon Sep 17 00:00:00 2001 From: Rich Chiodo Date: Thu, 17 Sep 2020 14:40:12 -0700 Subject: [PATCH 02/24] Merge in changes to release (#13976) * Up release version for new release (#13928) * Up release version * Update changelog * Update changelog * Workaround test issue (#13930) * Try different version of VS code in release * Change to make it use the actual variable * Use a real version * More tests failing with gpu error (#13935) * Try different version of VS code in release * Change to make it use the actual variable * Use a real version * Two more version changes * Fix kernel and server name missing in certain situations (#13974) * Fix kernel name and server name * Fixup server name for remote situations * Add some functional tests * Add news entry * Delete news file --- CHANGELOG.md | 5 ++ build/ci/templates/globals.yml | 1 + .../interactive-common/interactiveBase.ts | 80 ++++++++++++------- .../interactive-ipynb/nativeEditor.ts | 4 +- .../datascience/jupyter/kernels/helpers.ts | 7 +- .../history-react/interactivePanel.tsx | 3 +- .../interactive-common/jupyterInfo.tsx | 42 +--------- .../interactive-common/mainState.ts | 8 +- .../redux/reducers/kernel.ts | 4 +- .../interactive-common/redux/store.ts | 4 +- src/datascience-ui/native-editor/toolbar.tsx | 1 - .../interactivePanel.functional.test.tsx | 4 +- .../nativeEditor.functional.test.tsx | 9 ++- .../nativeEditor.toolbar.functional.test.tsx | 8 +- src/test/datascience/testHelpers.tsx | 9 +++ src/test/debuggerTest.ts | 3 +- src/test/multiRootTest.ts | 4 +- src/test/standardTest.ts | 4 +- 18 files changed, 108 insertions(+), 92 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3a6881c00fdb..cae1f6150600 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -84,6 +84,11 @@ ([#13729](https://github.com/Microsoft/vscode-python/issues/13729)) 1. Fix nighly failure with beakerx. ([#13734](https://github.com/Microsoft/vscode-python/issues/13734)) +## 2020.8.6 (15 September 2020) + +### Fixes + +1. Workaround problem caused by https://github.com/microsoft/vscode/issues/106547 ### Thanks diff --git a/build/ci/templates/globals.yml b/build/ci/templates/globals.yml index 03457023e99e..98bd51685dd0 100644 --- a/build/ci/templates/globals.yml +++ b/build/ci/templates/globals.yml @@ -11,3 +11,4 @@ variables: npm_config_cache: $(Pipeline.Workspace)/.npm vmImageMacOS: 'macOS-10.15' TS_NODE_FILES: true # Temporarily enabled to allow using types from vscode.proposed.d.ts from ts-node (for tests). + VSC_PYTHON_CI_TEST_VSC_CHANNEL: '1.48.0' # Enforce this until https://github.com/microsoft/vscode-test/issues/73 is fixed diff --git a/src/client/datascience/interactive-common/interactiveBase.ts b/src/client/datascience/interactive-common/interactiveBase.ts index 1290190964d3..5e0c1d11e16c 100644 --- a/src/client/datascience/interactive-common/interactiveBase.ts +++ b/src/client/datascience/interactive-common/interactiveBase.ts @@ -47,7 +47,7 @@ import { captureTelemetry, sendTelemetryEvent } from '../../telemetry'; import { generateCellRangesFromDocument } from '../cellFactory'; import { CellMatcher } from '../cellMatcher'; import { addToUriList, translateKernelLanguageToMonaco } from '../common'; -import { Commands, Identifiers, Telemetry } from '../constants'; +import { Commands, Identifiers, Settings, Telemetry } from '../constants'; import { ColumnWarningSize, IDataViewerFactory } from '../data-viewing/types'; import { IAddedSysInfo, @@ -865,9 +865,54 @@ export abstract class InteractiveBase extends WebviewPanelHost { + // If we don't have a server connection, make one if remote. We need the remote connection in order + // to compute the display name. However only do this if the user is allowing auto start. + if ( + !serverConnection && + this.configService.getSettings(this.owningResource).datascience.jupyterServerURI !== + Settings.JupyterServerLocalLaunch && + !this.configService.getSettings(this.owningResource).datascience.disableJupyterAutoStart + ) { + serverConnection = await this.notebookProvider.connect({ disableUI: true }); + } + + let displayName = + serverConnection?.displayName || + (!serverConnection?.localLaunch ? serverConnection?.url : undefined) || + (this.configService.getSettings().datascience.jupyterServerURI === Settings.JupyterServerLocalLaunch + ? localize.DataScience.localJupyterServer() + : localize.DataScience.serverNotStarted()); + + if (serverConnection) { + // Determine the connection URI of the connected server to display + if (serverConnection.localLaunch) { + displayName = localize.DataScience.localJupyterServer(); + } else { + // Log this remote URI into our MRU list + addToUriList( + this.globalStorage, + !isNil(serverConnection.url) ? serverConnection.url : serverConnection.displayName, + Date.now(), + serverConnection.displayName + ); + } + } + + return displayName; + } + private combineData( oldData: nbformat.ICodeCell | nbformat.IRawCell | nbformat.IMarkdownCell | undefined, cell: ICell @@ -1154,27 +1199,6 @@ export abstract class InteractiveBase extends WebviewPanelHost { const statusChangeHandler = async (status: ServerStatus) => { const connectionMetadata = notebook.getKernelConnection(); @@ -1182,8 +1206,8 @@ export abstract class InteractiveBase extends WebviewPanelHost } private renderKernelSelection() { - if (this.props.kernel.localizedUri === getLocString('DataScience.localJupyterServer', 'local')) { + if (this.props.kernel.serverName === getLocString('DataScience.localJupyterServer', 'local')) { return; } @@ -235,7 +235,6 @@ ${buildSettingsCss(this.props.settings)}`} selectServer={this.props.selectServer} selectKernel={this.props.selectKernel} shouldShowTrustMessage={false} - settings={this.props.settings} /> ); } diff --git a/src/datascience-ui/interactive-common/jupyterInfo.tsx b/src/datascience-ui/interactive-common/jupyterInfo.tsx index ec94902757f3..62d16d111053 100644 --- a/src/datascience-ui/interactive-common/jupyterInfo.tsx +++ b/src/datascience-ui/interactive-common/jupyterInfo.tsx @@ -1,9 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. 'use strict'; -import { isEmpty, isNil } from 'lodash'; import * as React from 'react'; -import { IDataScienceExtraSettings } from '../../client/datascience/types'; import { Image, ImageName } from '../react-common/image'; import { getLocString } from '../react-common/locReactSide'; import { IFont, IServerState, ServerStatus } from './mainState'; @@ -16,7 +14,6 @@ export interface IJupyterInfoProps { kernel: IServerState; isNotebookTrusted?: boolean; shouldShowTrustMessage: boolean; - settings?: IDataScienceExtraSettings | undefined; selectServer(): void; launchNotebookTrustPrompt?(): void; // Native editor-specific selectKernel(): void; @@ -37,17 +34,10 @@ export class JupyterInfo extends React.Component { } public render() { - let jupyterServerDisplayName: string = this.props.kernel.localizedUri; - if (!isNil(this.props.settings) && isEmpty(jupyterServerDisplayName)) { - const jupyterServerUriSetting: string = this.props.settings.jupyterServerURI; - if (!isEmpty(jupyterServerUriSetting) && this.isUriOfComputeInstance(jupyterServerUriSetting)) { - jupyterServerDisplayName = this.getComputeInstanceNameFromId(jupyterServerUriSetting); - } - } - + const jupyterServerDisplayName: string = this.props.kernel.serverName; const serverTextSize = getLocString('DataScience.jupyterServer', 'Jupyter Server').length + jupyterServerDisplayName.length + 4; // plus 4 for the icon - const displayNameTextSize = this.props.kernel.displayName.length + this.props.kernel.jupyterServerStatus.length; + const displayNameTextSize = this.props.kernel.kernelName.length + this.props.kernel.jupyterServerStatus.length; const dynamicFont: React.CSSProperties = { fontSize: 'var(--vscode-font-size)', // Use the same font and size as the menu fontFamily: 'var(--vscode-font-family)', @@ -98,11 +88,11 @@ export class JupyterInfo extends React.Component { role="button" aria-disabled={ariaDisabled} > - {this.props.kernel.displayName}: {this.props.kernel.jupyterServerStatus} + {this.props.kernel.kernelName}: {this.props.kernel.jupyterServerStatus} ); } else { - const displayName = this.props.kernel.displayName ?? getLocString('DataScience.noKernel', 'No Kernel'); + const displayName = this.props.kernel.kernelName ?? getLocString('DataScience.noKernel', 'No Kernel'); return (
{displayName}: {this.props.kernel.jupyterServerStatus} @@ -138,30 +128,6 @@ export class JupyterInfo extends React.Component { : getLocString('DataScience.connected', 'Connected'); } - private isUriOfComputeInstance(uri: string): boolean { - try { - const parsedUrl: URL = new URL(uri); - return parsedUrl.searchParams.get('id') === 'azureml_compute_instances'; - } catch (e) { - return false; - } - } - - private getComputeInstanceNameFromId(id: string | undefined): string { - if (isNil(id)) { - return ''; - } - - const res: string[] | null = id.match( - /\/providers\/Microsoft.MachineLearningServices\/workspaces\/[^\/]+\/computes\/([^\/]+)(\/)?/ - ); - if (isNil(res) || res.length < 2) { - return ''; - } - - return res[1]; - } - private selectServer(): void { this.props.selectServer(); } diff --git a/src/datascience-ui/interactive-common/mainState.ts b/src/datascience-ui/interactive-common/mainState.ts index 250831ac5c3a..f84713746568 100644 --- a/src/datascience-ui/interactive-common/mainState.ts +++ b/src/datascience-ui/interactive-common/mainState.ts @@ -134,8 +134,8 @@ export interface IFont { export interface IServerState { jupyterServerStatus: ServerStatus; - localizedUri: string; - displayName: string; + serverName: string; + kernelName: string; language: string; } @@ -192,8 +192,8 @@ export function generateTestState(filePath: string = '', editable: boolean = fal loaded: false, testMode: true, kernel: { - localizedUri: 'No Kernel', - displayName: 'Python', + serverName: '', + kernelName: 'Python', jupyterServerStatus: ServerStatus.NotStarted, language: PYTHON_LANGUAGE }, diff --git a/src/datascience-ui/interactive-common/redux/reducers/kernel.ts b/src/datascience-ui/interactive-common/redux/reducers/kernel.ts index 7f8f2a8ad95c..c4f3f1de1387 100644 --- a/src/datascience-ui/interactive-common/redux/reducers/kernel.ts +++ b/src/datascience-ui/interactive-common/redux/reducers/kernel.ts @@ -40,9 +40,9 @@ export namespace Kernel { return { ...arg.prevState, kernel: { - localizedUri: arg.payload.data.localizedUri, + serverName: arg.payload.data.serverName, jupyterServerStatus: arg.payload.data.jupyterServerStatus, - displayName: arg.payload.data.displayName, + kernelName: arg.payload.data.kernelName, language: arg.payload.data.language } }; diff --git a/src/datascience-ui/interactive-common/redux/store.ts b/src/datascience-ui/interactive-common/redux/store.ts index 345be932aa1f..7ee0e58627ee 100644 --- a/src/datascience-ui/interactive-common/redux/store.ts +++ b/src/datascience-ui/interactive-common/redux/store.ts @@ -68,8 +68,8 @@ function generateDefaultState( monacoReady: testMode, // When testing, monaco starts out ready loaded: false, kernel: { - displayName: getLocString('DataScience.noKernel', 'No Kernel'), - localizedUri: getLocString('DataScience.serverNotStarted', 'Not Started'), + kernelName: getLocString('DataScience.noKernel', 'No Kernel'), + serverName: getLocString('DataScience.serverNotStarted', 'Not Started'), jupyterServerStatus: ServerStatus.NotStarted, language: PYTHON_LANGUAGE }, diff --git a/src/datascience-ui/native-editor/toolbar.tsx b/src/datascience-ui/native-editor/toolbar.tsx index 63ffab3abf18..ecba9b191e02 100644 --- a/src/datascience-ui/native-editor/toolbar.tsx +++ b/src/datascience-ui/native-editor/toolbar.tsx @@ -268,7 +268,6 @@ export class Toolbar extends React.PureComponent { shouldShowTrustMessage={true} isNotebookTrusted={this.props.isNotebookTrusted} launchNotebookTrustPrompt={launchNotebookTrustPrompt} - settings={this.props.settings} />
diff --git a/src/test/datascience/interactivePanel.functional.test.tsx b/src/test/datascience/interactivePanel.functional.test.tsx index 0ef60f1e23cd..8f2e1179bc41 100644 --- a/src/test/datascience/interactivePanel.functional.test.tsx +++ b/src/test/datascience/interactivePanel.functional.test.tsx @@ -76,9 +76,9 @@ suite('DataScience Interactive Panel', () => { interruptKernel: noopAny, isAtBottom: false, kernel: { - displayName: '', + kernelName: '', jupyterServerStatus: ServerStatus.Busy, - localizedUri: '', + serverName: '', language: PYTHON_LANGUAGE }, knownDark: false, diff --git a/src/test/datascience/nativeEditor.functional.test.tsx b/src/test/datascience/nativeEditor.functional.test.tsx index 041b71198905..f2ec387bd364 100644 --- a/src/test/datascience/nativeEditor.functional.test.tsx +++ b/src/test/datascience/nativeEditor.functional.test.tsx @@ -83,7 +83,8 @@ import { typeCode, verifyCellIndex, verifyCellSource, - verifyHtmlOnCell + verifyHtmlOnCell, + verifyServerStatus } from './testHelpers'; import { ITestNativeEditorProvider } from './testNativeEditorProvider'; @@ -713,6 +714,8 @@ df.head()`; // Make sure it has a server assert.ok(editor.editor.notebook, 'Notebook did not start with a server'); + // Make sure it does have a name though + verifyServerStatus(editor.mount.wrapper, 'local'); } else { context.skip(); } @@ -721,6 +724,7 @@ df.head()`; runMountedTest('Server load skipped', async (context) => { if (ioc.mockJupyter) { ioc.getSettings().datascience.disableJupyterAutoStart = true; + ioc.getSettings().datascience.jupyterServerURI = 'https://remotetest'; await ioc.activate(); // Create an editor so something is listening to messages @@ -731,6 +735,9 @@ df.head()`; // Make sure it does not have a server assert.notOk(editor.editor.notebook, 'Notebook should not start with a server'); + + // Make sure it does have a name though + verifyServerStatus(editor.mount.wrapper, 'Not Started'); } else { context.skip(); } diff --git a/src/test/datascience/nativeEditor.toolbar.functional.test.tsx b/src/test/datascience/nativeEditor.toolbar.functional.test.tsx index b1e86f026daa..e51a44826a41 100644 --- a/src/test/datascience/nativeEditor.toolbar.functional.test.tsx +++ b/src/test/datascience/nativeEditor.toolbar.functional.test.tsx @@ -45,9 +45,9 @@ suite('DataScience Native Toolbar', () => { font: { family: '', size: 1 }, interruptKernel: sinon.stub(), kernel: { - displayName: '', + kernelName: '', jupyterServerStatus: ServerStatus.Busy, - localizedUri: '', + serverName: '', language: PYTHON_LANGUAGE }, restartKernel: sinon.stub(), @@ -245,9 +245,9 @@ suite('DataScience Native Toolbar', () => { font: { family: '', size: 1 }, interruptKernel: sinon.stub(), kernel: { - displayName: '', + kernelName: '', jupyterServerStatus: ServerStatus.Busy, - localizedUri: '', + serverName: '', language: PYTHON_LANGUAGE }, restartKernel: sinon.stub(), diff --git a/src/test/datascience/testHelpers.tsx b/src/test/datascience/testHelpers.tsx index d8c07d1236b6..88798f4334f9 100644 --- a/src/test/datascience/testHelpers.tsx +++ b/src/test/datascience/testHelpers.tsx @@ -221,6 +221,15 @@ export function verifyCellSource( assert.deepStrictEqual(inst.state.model?.getValue(), source, 'Source does not match on cell'); } +export function verifyServerStatus(wrapper: ReactWrapper, React.Component>, statusText: string) { + wrapper.update(); + + const foundResult = wrapper.find('div.kernel-status-server'); + assert.ok(foundResult.length >= 1, "Didn't find server status"); + const html = foundResult.html(); + assert.ok(html.includes(statusText), `${statusText} not found in server status`); +} + export function verifyHtmlOnCell( wrapper: ReactWrapper, React.Component>, cellType: 'NativeCell' | 'InteractiveCell', diff --git a/src/test/debuggerTest.ts b/src/test/debuggerTest.ts index c7fc3e1058d1..3720b0e662ed 100644 --- a/src/test/debuggerTest.ts +++ b/src/test/debuggerTest.ts @@ -10,6 +10,7 @@ import { EXTENSION_ROOT_DIR_FOR_TESTS } from './constants'; const workspacePath = path.join(__dirname, '..', '..', 'src', 'testMultiRootWkspc', 'multi.code-workspace'); process.env.IS_CI_SERVER_TEST_DEBUGGER = '1'; process.env.VSC_PYTHON_CI_TEST = '1'; +const channel = process.env.VSC_PYTHON_CI_TEST_VSC_CHANNEL || 'stable'; function start() { console.log('*'.repeat(100)); @@ -18,7 +19,7 @@ function start() { extensionDevelopmentPath: EXTENSION_ROOT_DIR_FOR_TESTS, extensionTestsPath: path.join(EXTENSION_ROOT_DIR_FOR_TESTS, 'out', 'test', 'index'), launchArgs: [workspacePath], - version: 'stable', + version: channel, extensionTestsEnv: { ...process.env, UITEST_DISABLE_INSIDERS: '1' } }).catch((ex) => { console.error('End Debugger tests (with errors)', ex); diff --git a/src/test/multiRootTest.ts b/src/test/multiRootTest.ts index 5859708a8e99..04631bd1b2ca 100644 --- a/src/test/multiRootTest.ts +++ b/src/test/multiRootTest.ts @@ -11,6 +11,8 @@ process.env.VSC_PYTHON_CI_TEST = '1'; initializeLogger(); +const channel = process.env.VSC_PYTHON_CI_TEST_VSC_CHANNEL || 'stable'; + function start() { console.log('*'.repeat(100)); console.log('Start Multiroot tests'); @@ -18,7 +20,7 @@ function start() { extensionDevelopmentPath: EXTENSION_ROOT_DIR_FOR_TESTS, extensionTestsPath: path.join(EXTENSION_ROOT_DIR_FOR_TESTS, 'out', 'test', 'index'), launchArgs: [workspacePath], - version: 'stable', + version: channel, extensionTestsEnv: { ...process.env, UITEST_DISABLE_INSIDERS: '1' } }).catch((ex) => { console.error('End Multiroot tests (with errors)', ex); diff --git a/src/test/standardTest.ts b/src/test/standardTest.ts index 4a6dd9f2e287..76739ae4b667 100644 --- a/src/test/standardTest.ts +++ b/src/test/standardTest.ts @@ -16,9 +16,7 @@ const extensionDevelopmentPath = process.env.CODE_EXTENSIONS_PATH ? process.env.CODE_EXTENSIONS_PATH : EXTENSION_ROOT_DIR_FOR_TESTS; -const channel = (process.env.VSC_PYTHON_CI_TEST_VSC_CHANNEL || '').toLowerCase().includes('insiders') - ? 'insiders' - : 'stable'; +const channel = process.env.VSC_PYTHON_CI_TEST_VSC_CHANNEL || 'stable'; function start() { console.log('*'.repeat(100)); From 8c4a0ec1ac0bfac255f9664c3a6201a7f2d28c28 Mon Sep 17 00:00:00 2001 From: Rich Chiodo Date: Fri, 18 Sep 2020 12:01:43 -0700 Subject: [PATCH 03/24] Port two fixes to the release branch (#13995) * Disable split views of custom editors (#13985) * Fix backup storage by looking at the options correctly (#13983) * Fix backup storage by looking at the options correctly * Fix backup by being more explicit * Only linux tests are failing. Hopefully fix them * Fixup changelog Co-authored-by: Don Jayamanne --- CHANGELOG.md | 2 + src/client/datascience/export/exportUtil.ts | 4 +- .../nativeEditorProviderOld.ts | 4 +- .../interactive-ipynb/trustCommandHandler.ts | 2 +- .../interactive-window/interactiveWindow.ts | 2 +- .../datascience/notebook/contentProvider.ts | 15 ++-- .../notebook/notebookEditorProvider.ts | 2 +- .../notebookStorage/nativeEditorProvider.ts | 37 +++++---- .../notebookStorage/nativeEditorStorage.ts | 82 ++++++------------- .../notebookStorageProvider.ts | 34 ++------ src/client/datascience/types.ts | 22 ++--- .../datascience/export/exportUtil.test.ts | 2 +- .../nativeEditorStorage.unit.test.ts | 20 ++--- .../datascience/mockCustomEditorService.ts | 2 +- .../notebook/contentProvider.ds.test.ts | 2 +- .../notebook/contentProvider.unit.test.ts | 8 +- 16 files changed, 95 insertions(+), 145 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cae1f6150600..38b5f71f1330 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -59,6 +59,8 @@ ([#13706](https://github.com/Microsoft/vscode-python/issues/13706)) 1. Correctly install ipykernel when launching from an interpreter. ([#13956](https://github.com/Microsoft/vscode-python/issues/13956)) +1. Backup on custom editors is being ignored. + ([#13981](https://github.com/Microsoft/vscode-python/issues/13981)) ### Code Health diff --git a/src/client/datascience/export/exportUtil.ts b/src/client/datascience/export/exportUtil.ts index 237b93ff4ca1..9e9f2c9c97c8 100644 --- a/src/client/datascience/export/exportUtil.ts +++ b/src/client/datascience/export/exportUtil.ts @@ -57,7 +57,7 @@ export class ExportUtil { await this.jupyterExporter.exportToFile(cells, tempFile.filePath, false); const newPath = path.join(tempDir.path, '.ipynb'); await this.fs.copyLocal(tempFile.filePath, newPath); - model = await this.notebookStorage.getOrCreateModel(Uri.file(newPath)); + model = await this.notebookStorage.getOrCreateModel({ file: Uri.file(newPath) }); } finally { tempFile.dispose(); tempDir.dispose(); @@ -67,7 +67,7 @@ export class ExportUtil { } public async removeSvgs(source: Uri) { - const model = await this.notebookStorage.getOrCreateModel(source); + const model = await this.notebookStorage.getOrCreateModel({ file: source }); const newCells: ICell[] = []; for (const cell of model.cells) { diff --git a/src/client/datascience/interactive-ipynb/nativeEditorProviderOld.ts b/src/client/datascience/interactive-ipynb/nativeEditorProviderOld.ts index 9d7b37ffcd3d..99a857a61834 100644 --- a/src/client/datascience/interactive-ipynb/nativeEditorProviderOld.ts +++ b/src/client/datascience/interactive-ipynb/nativeEditorProviderOld.ts @@ -299,7 +299,7 @@ export class NativeEditorProviderOld extends NativeEditorProvider { this.activeEditors.set(e.file.fsPath, e); // Remove backup storage - this.loadModel(Uri.file(oldPath)) + this.loadModel({ file: Uri.file(oldPath) }) .then((m) => this.storage.deleteBackup(m)) .ignoreErrors(); } @@ -347,7 +347,7 @@ export class NativeEditorProviderOld extends NativeEditorProvider { * I.e. document is already opened in a VSC Notebook. */ private async isDocumentOpenedInVSCodeNotebook(document: TextDocument): Promise { - const model = await this.loadModel(document.uri); + const model = await this.loadModel({ file: document.uri }); // This is temporary code. return model instanceof VSCodeNotebookModel; } diff --git a/src/client/datascience/interactive-ipynb/trustCommandHandler.ts b/src/client/datascience/interactive-ipynb/trustCommandHandler.ts index 3718379cc804..b325032c8fc4 100644 --- a/src/client/datascience/interactive-ipynb/trustCommandHandler.ts +++ b/src/client/datascience/interactive-ipynb/trustCommandHandler.ts @@ -42,7 +42,7 @@ export class TrustCommandHandler implements IExtensionSingleActivationService { return; } - const model = await this.storageProvider.getOrCreateModel(uri); + const model = await this.storageProvider.getOrCreateModel({ file: uri }); if (model.isTrusted) { return; } diff --git a/src/client/datascience/interactive-window/interactiveWindow.ts b/src/client/datascience/interactive-window/interactiveWindow.ts index fd1b2388a161..3dd40ec874cd 100644 --- a/src/client/datascience/interactive-window/interactiveWindow.ts +++ b/src/client/datascience/interactive-window/interactiveWindow.ts @@ -256,7 +256,7 @@ export class InteractiveWindow extends InteractiveBase implements IInteractiveWi break; case InteractiveWindowMessages.ExportNotebookAs: - this.handleMessage(message, payload, this.exportAs); + this.handleMessage(message, payload, this.exportAs.bind); break; case InteractiveWindowMessages.HasCellResponse: diff --git a/src/client/datascience/notebook/contentProvider.ts b/src/client/datascience/notebook/contentProvider.ts index 12e3e0e9d41f..a94bc091a61c 100644 --- a/src/client/datascience/notebook/contentProvider.ts +++ b/src/client/datascience/notebook/contentProvider.ts @@ -70,9 +70,12 @@ export class NotebookContentProvider implements INotebookContentProvider { }; } // If there's no backup id, then skip loading dirty contents. - const model = await (openContext.backupId - ? this.notebookStorage.getOrCreateModel(uri, undefined, openContext.backupId, true) - : this.notebookStorage.getOrCreateModel(uri, undefined, true, true)); + const model = await this.notebookStorage.getOrCreateModel({ + file: uri, + backupId: openContext.backupId, + isNative: true, + skipLoadingDirtyContents: openContext.backupId === undefined + }); if (!(model instanceof VSCodeNotebookModel)) { throw new Error('Incorrect NotebookModel, expected VSCodeNotebookModel'); } @@ -82,7 +85,7 @@ export class NotebookContentProvider implements INotebookContentProvider { } @captureTelemetry(Telemetry.Save, undefined, true) public async saveNotebook(document: NotebookDocument, cancellation: CancellationToken) { - const model = await this.notebookStorage.getOrCreateModel(document.uri, undefined, undefined, true); + const model = await this.notebookStorage.getOrCreateModel({ file: document.uri, isNative: true }); if (cancellation.isCancellationRequested) { return; } @@ -94,7 +97,7 @@ export class NotebookContentProvider implements INotebookContentProvider { document: NotebookDocument, cancellation: CancellationToken ): Promise { - const model = await this.notebookStorage.getOrCreateModel(document.uri, undefined, undefined, true); + const model = await this.notebookStorage.getOrCreateModel({ file: document.uri, isNative: true }); if (!cancellation.isCancellationRequested) { await this.notebookStorage.saveAs(model, targetResource); } @@ -104,7 +107,7 @@ export class NotebookContentProvider implements INotebookContentProvider { _context: NotebookDocumentBackupContext, cancellation: CancellationToken ): Promise { - const model = await this.notebookStorage.getOrCreateModel(document.uri, undefined, undefined, true); + const model = await this.notebookStorage.getOrCreateModel({ file: document.uri, isNative: true }); const id = this.notebookStorage.generateBackupId(model); await this.notebookStorage.backup(model, cancellation, id); return { diff --git a/src/client/datascience/notebook/notebookEditorProvider.ts b/src/client/datascience/notebook/notebookEditorProvider.ts index 6ed78c1a62fa..1cc4b962ff96 100644 --- a/src/client/datascience/notebook/notebookEditorProvider.ts +++ b/src/client/datascience/notebook/notebookEditorProvider.ts @@ -145,7 +145,7 @@ export class NotebookEditorProvider implements INotebookEditorProvider { return; } const uri = doc.uri; - const model = await this.storage.getOrCreateModel(uri, undefined, undefined, true); + const model = await this.storage.getOrCreateModel({ file: uri, isNative: true }); if (model instanceof VSCodeNotebookModel) { model.associateNotebookDocument(doc); } diff --git a/src/client/datascience/notebookStorage/nativeEditorProvider.ts b/src/client/datascience/notebookStorage/nativeEditorProvider.ts index 5d166c119789..23fcaa04da53 100644 --- a/src/client/datascience/notebookStorage/nativeEditorProvider.ts +++ b/src/client/datascience/notebookStorage/nativeEditorProvider.ts @@ -51,6 +51,7 @@ import { IJupyterDebugger, IJupyterVariableDataProviderFactory, IJupyterVariables, + IModelLoadOptions, INotebookEditor, INotebookEditorProvider, INotebookExporter, @@ -119,7 +120,7 @@ export class NativeEditorProvider implements INotebookEditorProvider, CustomEdit enableFindWidget: true, retainContextWhenHidden: true }, - supportsMultipleEditorsPerDocument: true + supportsMultipleEditorsPerDocument: false }); } @@ -128,22 +129,26 @@ export class NativeEditorProvider implements INotebookEditorProvider, CustomEdit context: CustomDocumentOpenContext, // This has info about backups. right now we use our own data. _cancellation: CancellationToken ): Promise { - const model = await this.loadModel(uri, undefined, context.backupId); + const model = await this.loadModel({ + file: uri, + backupId: context.backupId, + skipLoadingDirtyContents: context.backupId === undefined + }); return { uri, dispose: () => model.dispose() }; } public async saveCustomDocument(document: CustomDocument, cancellation: CancellationToken): Promise { - const model = await this.loadModel(document.uri); + const model = await this.loadModel({ file: document.uri }); return this.storage.save(model, cancellation); } public async saveCustomDocumentAs(document: CustomDocument, targetResource: Uri): Promise { - const model = await this.loadModel(document.uri); + const model = await this.loadModel({ file: document.uri }); return this.storage.saveAs(model, targetResource); } public async revertCustomDocument(document: CustomDocument, cancellation: CancellationToken): Promise { - const model = await this.loadModel(document.uri); + const model = await this.loadModel({ file: document.uri }); return this.storage.revert(model, cancellation); } public async backupCustomDocument( @@ -151,7 +156,7 @@ export class NativeEditorProvider implements INotebookEditorProvider, CustomEdit _context: CustomDocumentBackupContext, cancellation: CancellationToken ): Promise { - const model = await this.loadModel(document.uri); + const model = await this.loadModel({ file: document.uri }); const id = this.storage.generateBackupId(model); await this.storage.backup(model, cancellation, id); return { @@ -167,7 +172,7 @@ export class NativeEditorProvider implements INotebookEditorProvider, CustomEdit public async resolveCustomDocument(document: CustomDocument): Promise { this.customDocuments.set(document.uri.fsPath, document); - await this.loadModel(document.uri); + await this.loadModel({ file: document.uri }); } public async open(file: Uri): Promise { @@ -199,30 +204,26 @@ export class NativeEditorProvider implements INotebookEditorProvider, CustomEdit } @captureTelemetry(Telemetry.CreateNewNotebook, undefined, false) - public async createNew(contents?: string, title?: string): Promise { + public async createNew(possibleContents?: string, title?: string): Promise { // Create a new URI for the dummy file using our root workspace path const uri = this.getNextNewNotebookUri(title); // Set these contents into the storage before the file opens. Make sure not // load from the memento storage though as this is an entirely brand new file. - await this.loadModel(uri, contents, true); + await this.loadModel({ file: uri, possibleContents, skipLoadingDirtyContents: true }); return this.open(uri); } - public async loadModel(file: Uri, contents?: string, skipDirtyContents?: boolean): Promise; - // tslint:disable-next-line: unified-signatures - public async loadModel(file: Uri, contents?: string, backupId?: string): Promise; - // tslint:disable-next-line: no-any - public async loadModel(file: Uri, contents?: string, options?: any): Promise { + public async loadModel(options: IModelLoadOptions): Promise { // Get the model that may match this file - let model = [...this.models.values()].find((m) => this.fs.arePathsSame(m.file, file)); + let model = [...this.models.values()].find((m) => this.fs.arePathsSame(m.file, options.file)); if (!model) { // Every time we load a new untitled file, up the counter past the max value for this counter - this.untitledCounter = getNextUntitledCounter(file, this.untitledCounter); + this.untitledCounter = getNextUntitledCounter(options.file, this.untitledCounter); // Load our model from our storage object. - model = await this.storage.getOrCreateModel(file, contents, options); + model = await this.storage.getOrCreateModel(options); // Make sure to listen to events on the model this.trackModel(model); @@ -273,7 +274,7 @@ export class NativeEditorProvider implements INotebookEditorProvider, CustomEdit protected async loadNotebookEditor(resource: Uri, panel?: WebviewPanel) { try { // Get the model - const model = await this.loadModel(resource); + const model = await this.loadModel({ file: resource }); // Load it (should already be visible) return this.createNotebookEditor(model, panel); diff --git a/src/client/datascience/notebookStorage/nativeEditorStorage.ts b/src/client/datascience/notebookStorage/nativeEditorStorage.ts index 0ca96a90bf9e..f3e71c597de0 100644 --- a/src/client/datascience/notebookStorage/nativeEditorStorage.ts +++ b/src/client/datascience/notebookStorage/nativeEditorStorage.ts @@ -17,6 +17,7 @@ import { CellState, IDataScienceFileSystem, IJupyterExecution, + IModelLoadOptions, INotebookModel, INotebookStorage, ITrustService @@ -75,27 +76,8 @@ export class NativeEditorStorage implements INotebookStorage { public get(_file: Uri): INotebookModel | undefined { return undefined; } - public getOrCreateModel( - file: Uri, - possibleContents?: string, - backupId?: string, - forVSCodeNotebook?: boolean - ): Promise; - public getOrCreateModel( - file: Uri, - possibleContents?: string, - // tslint:disable-next-line: unified-signatures - skipDirtyContents?: boolean, - forVSCodeNotebook?: boolean - ): Promise; - public getOrCreateModel( - file: Uri, - possibleContents?: string, - // tslint:disable-next-line: no-any - options?: any, - forVSCodeNotebook?: boolean - ): Promise { - return this.loadFromFile(file, possibleContents, options, forVSCodeNotebook); + public getOrCreateModel(options: IModelLoadOptions): Promise { + return this.loadFromFile(options); } public async save(model: INotebookModel, _cancellation: CancellationToken): Promise { const contents = model.getContent(); @@ -154,8 +136,8 @@ export class NativeEditorStorage implements INotebookStorage { } public async revert(model: INotebookModel, _cancellation: CancellationToken): Promise { - // Revert to what is in the hot exit file - await this.loadFromFile(model.file); + // Revert to what is in the real file. This is only used for the custom editor + await this.loadFromFile({ file: model.file, skipLoadingDirtyContents: true }); } public async deleteBackup(model: INotebookModel, backupId: string): Promise { @@ -253,54 +235,44 @@ export class NativeEditorStorage implements INotebookStorage { noop(); } } - private loadFromFile( - file: Uri, - possibleContents?: string, - backupId?: string, - forVSCodeNotebook?: boolean - ): Promise; - private loadFromFile( - file: Uri, - possibleContents?: string, - // tslint:disable-next-line: unified-signatures - skipDirtyContents?: boolean, - forVSCodeNotebook?: boolean - ): Promise; - private async loadFromFile( - file: Uri, - possibleContents?: string, - options?: boolean | string, - forVSCodeNotebook?: boolean - ): Promise { + private async loadFromFile(options: IModelLoadOptions): Promise { try { // Attempt to read the contents if a viable file - const contents = NativeEditorStorage.isUntitledFile(file) ? possibleContents : await this.fs.readFile(file); + const contents = NativeEditorStorage.isUntitledFile(options.file) + ? options.possibleContents + : await this.fs.readFile(options.file); - const skipDirtyContents = typeof options === 'boolean' ? options : !!options; - // Use backupId provided, else use static storage key. - const backupId = - typeof options === 'string' ? options : skipDirtyContents ? undefined : this.getStaticStorageKey(file); + // Get backup id from the options if available. + const backupId = options.backupId ? options.backupId : this.getStaticStorageKey(options.file); // If skipping dirty contents, delete the dirty hot exit file now - if (skipDirtyContents) { - await this.clearHotExit(file, backupId); + if (options.skipLoadingDirtyContents) { + await this.clearHotExit(options.file, backupId); } // See if this file was stored in storage prior to shutdown - const dirtyContents = skipDirtyContents ? undefined : await this.getStoredContents(file, backupId); + const dirtyContents = options.skipLoadingDirtyContents + ? undefined + : await this.getStoredContents(options.file, backupId); if (dirtyContents) { // This means we're dirty. Indicate dirty and load from this content - return this.loadContents(file, dirtyContents, true, forVSCodeNotebook); + return this.loadContents(options.file, dirtyContents, true, options.isNative); } else { // Load without setting dirty - return this.loadContents(file, contents, undefined, forVSCodeNotebook); + return this.loadContents(options.file, contents, undefined, options.isNative); } } catch (ex) { // May not exist at this time. Should always have a single cell though - traceError(`Failed to load notebook file ${file.toString()}`, ex); + traceError(`Failed to load notebook file ${options.file.toString()}`, ex); return this.factory.createModel( - { trusted: true, file, cells: [], crypto: this.crypto, globalMemento: this.globalStorage }, - forVSCodeNotebook + { + trusted: true, + file: options.file, + cells: [], + crypto: this.crypto, + globalMemento: this.globalStorage + }, + options.isNative ); } } diff --git a/src/client/datascience/notebookStorage/notebookStorageProvider.ts b/src/client/datascience/notebookStorage/notebookStorageProvider.ts index 4067751614cc..48e28f048c89 100644 --- a/src/client/datascience/notebookStorage/notebookStorageProvider.ts +++ b/src/client/datascience/notebookStorage/notebookStorageProvider.ts @@ -9,7 +9,7 @@ import { CancellationToken } from 'vscode-jsonrpc'; import { IWorkspaceService } from '../../common/application/types'; import { IDisposable, IDisposableRegistry } from '../../common/types'; import { generateNewNotebookUri } from '../common'; -import { INotebookModel, INotebookStorage } from '../types'; +import { IModelLoadOptions, INotebookModel, INotebookStorage } from '../types'; import { getNextUntitledCounter } from './nativeEditorStorage'; import { VSCodeNotebookModel } from './vscNotebookModel'; @@ -66,35 +66,15 @@ export class NotebookStorageProvider implements INotebookStorageProvider { return this.resolvedStorageAndModels.get(file.toString()); } - public getOrCreateModel( - file: Uri, - contents?: string, - backupId?: string, - forVSCodeNotebook?: boolean - ): Promise; - public getOrCreateModel( - file: Uri, - contents?: string, - // tslint:disable-next-line: unified-signatures - skipDirtyContents?: boolean, - forVSCodeNotebook?: boolean - ): Promise; - - public getOrCreateModel( - file: Uri, - contents?: string, - // tslint:disable-next-line: no-any - options?: any, - forVSCodeNotebook?: boolean - ): Promise { - const key = file.toString(); + public getOrCreateModel(options: IModelLoadOptions): Promise { + const key = options.file.toString(); if (!this.storageAndModels.has(key)) { // Every time we load a new untitled file, up the counter past the max value for this counter NotebookStorageProvider.untitledCounter = getNextUntitledCounter( - file, + options.file, NotebookStorageProvider.untitledCounter ); - const promise = this.storage.getOrCreateModel(file, contents, options, forVSCodeNotebook); + const promise = this.storage.getOrCreateModel(options); this.storageAndModels.set(key, promise.then(this.trackModel.bind(this))); } return this.storageAndModels.get(key)!; @@ -105,12 +85,12 @@ export class NotebookStorageProvider implements INotebookStorageProvider { } } - public async createNew(contents?: string, forVSCodeNotebooks?: boolean): Promise { + public async createNew(possibleContents?: string, forVSCodeNotebooks?: boolean): Promise { // Create a new URI for the dummy file using our root workspace path const uri = this.getNextNewNotebookUri(forVSCodeNotebooks); // Always skip loading from the hot exit file. When creating a new file we want a new file. - return this.getOrCreateModel(uri, contents, true, forVSCodeNotebooks); + return this.getOrCreateModel({ file: uri, possibleContents, skipLoadingDirtyContents: true }); } private getNextNewNotebookUri(forVSCodeNotebooks?: boolean): Uri { diff --git a/src/client/datascience/types.ts b/src/client/datascience/types.ts index 3a8b406a24ec..b651af2b7750 100644 --- a/src/client/datascience/types.ts +++ b/src/client/datascience/types.ts @@ -1106,6 +1106,14 @@ export interface INotebookModel { trust(): void; } +export interface IModelLoadOptions { + isNative?: boolean; + file: Uri; + possibleContents?: string; + backupId?: string; + skipLoadingDirtyContents?: boolean; +} + export const INotebookStorage = Symbol('INotebookStorage'); export interface INotebookStorage { @@ -1114,19 +1122,7 @@ export interface INotebookStorage { saveAs(model: INotebookModel, targetResource: Uri): Promise; backup(model: INotebookModel, cancellation: CancellationToken, backupId?: string): Promise; get(file: Uri): INotebookModel | undefined; - getOrCreateModel( - file: Uri, - contents?: string, - backupId?: string, - forVSCodeNotebook?: boolean - ): Promise; - getOrCreateModel( - file: Uri, - contents?: string, - // tslint:disable-next-line: unified-signatures - skipDirtyContents?: boolean, - forVSCodeNotebook?: boolean - ): Promise; + getOrCreateModel(options: IModelLoadOptions): Promise; revert(model: INotebookModel, cancellation: CancellationToken): Promise; deleteBackup(model: INotebookModel, backupId?: string): Promise; } diff --git a/src/test/datascience/export/exportUtil.test.ts b/src/test/datascience/export/exportUtil.test.ts index c59d133f3feb..1e80292e1829 100644 --- a/src/test/datascience/export/exportUtil.test.ts +++ b/src/test/datascience/export/exportUtil.test.ts @@ -37,7 +37,7 @@ suite('DataScience - Export Util', () => { ); await exportUtil.removeSvgs(file); - const model = await notebookStorage.getOrCreateModel(file); + const model = await notebookStorage.getOrCreateModel({ file }); // make sure no svg exists in model const SVG = 'image/svg+xml'; diff --git a/src/test/datascience/interactive-ipynb/nativeEditorStorage.unit.test.ts b/src/test/datascience/interactive-ipynb/nativeEditorStorage.unit.test.ts index ad5093ca7361..103d2d40b8ca 100644 --- a/src/test/datascience/interactive-ipynb/nativeEditorStorage.unit.test.ts +++ b/src/test/datascience/interactive-ipynb/nativeEditorStorage.unit.test.ts @@ -425,7 +425,7 @@ suite('DataScience - Native Editor Storage', () => { } test('Create new editor and add some cells', async () => { - model = await storage.getOrCreateModel(baseUri); + model = await storage.getOrCreateModel({ file: baseUri }); insertCell(0, '1'); const cells = model.cells; expect(cells).to.be.lengthOf(4); @@ -434,7 +434,7 @@ suite('DataScience - Native Editor Storage', () => { }); test('Move cells around', async () => { - model = await storage.getOrCreateModel(baseUri); + model = await storage.getOrCreateModel({ file: baseUri }); swapCells('NotebookImport#0', 'NotebookImport#1'); const cells = model.cells; expect(cells).to.be.lengthOf(3); @@ -443,7 +443,7 @@ suite('DataScience - Native Editor Storage', () => { }); test('Edit/delete cells', async () => { - model = await storage.getOrCreateModel(baseUri); + model = await storage.getOrCreateModel({ file: baseUri }); expect(model.isDirty).to.be.equal(false, 'Editor should not be dirty'); editCell( [ @@ -483,7 +483,7 @@ suite('DataScience - Native Editor Storage', () => { test('Editing a file and closing will keep contents', async () => { await filesConfig?.update('autoSave', 'off'); - model = await storage.getOrCreateModel(baseUri); + model = await storage.getOrCreateModel({ file: baseUri }); expect(model.isDirty).to.be.equal(false, 'Editor should not be dirty'); editCell( [ @@ -512,7 +512,7 @@ suite('DataScience - Native Editor Storage', () => { // Recreate storage = createStorage(); - model = await storage.getOrCreateModel(baseUri); + model = await storage.getOrCreateModel({ file: baseUri }); const cells = model.cells; expect(cells).to.be.lengthOf(3); @@ -522,7 +522,7 @@ suite('DataScience - Native Editor Storage', () => { }); test('Editing a new file and closing will keep contents', async () => { - model = await storage.getOrCreateModel(untiledUri, undefined, true); + model = await storage.getOrCreateModel({ file: untiledUri, skipLoadingDirtyContents: true }); expect(model.isDirty).to.be.equal(false, 'Editor should not be dirty'); insertCell(0, 'a=1'); @@ -531,7 +531,7 @@ suite('DataScience - Native Editor Storage', () => { // Recreate storage = createStorage(); - model = await storage.getOrCreateModel(untiledUri); + model = await storage.getOrCreateModel({ file: untiledUri }); const cells = model.cells; expect(cells).to.be.lengthOf(2); @@ -550,7 +550,7 @@ suite('DataScience - Native Editor Storage', () => { // Put the regular file into the local storage await localMemento.update(`notebook-storage-${file.toString()}`, differentFile); - model = await storage.getOrCreateModel(file); + model = await storage.getOrCreateModel({ file }); // It should load with that value const cells = model.cells; @@ -571,7 +571,7 @@ suite('DataScience - Native Editor Storage', () => { contents: differentFile, lastModifiedTimeMs: Date.now() }); - model = await storage.getOrCreateModel(file); + model = await storage.getOrCreateModel({ file }); // It should load with that value const cells = model.cells; @@ -601,7 +601,7 @@ suite('DataScience - Native Editor Storage', () => { lastModifiedTimeMs: Date.now() }); - model = await storage.getOrCreateModel(file); + model = await storage.getOrCreateModel({ file }); // It should load with that value const cells = model.cells; diff --git a/src/test/datascience/mockCustomEditorService.ts b/src/test/datascience/mockCustomEditorService.ts index 03ceee2b8c25..fa59c744d1b0 100644 --- a/src/test/datascience/mockCustomEditorService.ts +++ b/src/test/datascience/mockCustomEditorService.ts @@ -138,7 +138,7 @@ export class MockCustomEditorService implements ICustomEditorService { private async getModel(file: Uri): Promise { const nativeProvider = this.provider as NativeEditorProvider; if (nativeProvider) { - return (nativeProvider.loadModel(file) as unknown) as Promise; + return (nativeProvider.loadModel({ file }) as unknown) as Promise; } return undefined; } diff --git a/src/test/datascience/notebook/contentProvider.ds.test.ts b/src/test/datascience/notebook/contentProvider.ds.test.ts index d2a64d11ef61..fa283388337f 100644 --- a/src/test/datascience/notebook/contentProvider.ds.test.ts +++ b/src/test/datascience/notebook/contentProvider.ds.test.ts @@ -60,7 +60,7 @@ suite('DataScience - VSCode Notebook - (Open)', function () { 'notebook', 'testJsonContents.ipynb' ); - const model = await storageProvider.getOrCreateModel(Uri.file(file)); + const model = await storageProvider.getOrCreateModel({ file: Uri.file(file) }); disposables.push(model); model.trust(); const jsonStr = fs.readFileSync(file, { encoding: 'utf8' }); diff --git a/src/test/datascience/notebook/contentProvider.unit.test.ts b/src/test/datascience/notebook/contentProvider.unit.test.ts index 39fd8e5b3464..5ccae9026d79 100644 --- a/src/test/datascience/notebook/contentProvider.unit.test.ts +++ b/src/test/datascience/notebook/contentProvider.unit.test.ts @@ -55,9 +55,7 @@ suite('DataScience - NativeNotebook ContentProvider', () => { ] } ); - when(storageProvider.getOrCreateModel(anything(), anything(), anything(), anything())).thenResolve( - model - ); + when(storageProvider.getOrCreateModel(anything())).thenResolve(model); const notebook = await contentProvider.openNotebook(fileUri, {}); @@ -135,9 +133,7 @@ suite('DataScience - NativeNotebook ContentProvider', () => { ] } ); - when(storageProvider.getOrCreateModel(anything(), anything(), anything(), anything())).thenResolve( - model - ); + when(storageProvider.getOrCreateModel(anything())).thenResolve(model); const notebook = await contentProvider.openNotebook(fileUri, {}); From 6cb43d5eba89da9112a52f2b744583a53b361585 Mon Sep 17 00:00:00 2001 From: David Kutugata Date: Fri, 18 Sep 2020 13:05:45 -0700 Subject: [PATCH 04/24] add jedi-language-server to 3rd party notices (#13977) * add jedi-language-server to 3rd party notices * move license from distribution to repository file --- ThirdPartyNotices-Distribution.txt | 1 - ThirdPartyNotices-Repository.txt | 30 +++++++++++++++++++++++++++++- 2 files changed, 29 insertions(+), 2 deletions(-) diff --git a/ThirdPartyNotices-Distribution.txt b/ThirdPartyNotices-Distribution.txt index 2e62e34b8564..06d2e73f3084 100644 --- a/ThirdPartyNotices-Distribution.txt +++ b/ThirdPartyNotices-Distribution.txt @@ -15141,4 +15141,3 @@ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. You just DO WHAT THE FUCK YOU WANT TO. --------------------------------------------------------- - diff --git a/ThirdPartyNotices-Repository.txt b/ThirdPartyNotices-Repository.txt index 1162db48bc9c..98c64072ca51 100644 --- a/ThirdPartyNotices-Repository.txt +++ b/ThirdPartyNotices-Repository.txt @@ -1167,4 +1167,32 @@ trademarks does not indicate endorsement of the trademark holder by Font Awesome, nor vice versa. **Please do not use brand logos for any purpose except to represent the company, product, or service to which they refer.** ========================================= -END OF font-awesome NOTICES, INFORMATION, AND LICENSE \ No newline at end of file +END OF font-awesome NOTICES, INFORMATION, AND LICENSE + +%% jedi-language-server NOTICES, INFORMATION, AND LICENSE BEGIN HERE +========================================= +jedi-language-server + +MIT License + +Copyright (c) 2019 Sam Roeca + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +========================================= +END OF jedi-language-server NOTICES, INFORMATION, AND LICENSE \ No newline at end of file From 832025ac4011678c88ef9ba9397de607780d6f52 Mon Sep 17 00:00:00 2001 From: David Kutugata Date: Tue, 22 Sep 2020 10:34:09 -0700 Subject: [PATCH 05/24] disable test_discover_complex_default and (#14024) test_discover_complex_doctest --- pythonFiles/tests/testing_tools/adapter/test_functional.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pythonFiles/tests/testing_tools/adapter/test_functional.py b/pythonFiles/tests/testing_tools/adapter/test_functional.py index bd6c6b200314..86ca7275fd56 100644 --- a/pythonFiles/tests/testing_tools/adapter/test_functional.py +++ b/pythonFiles/tests/testing_tools/adapter/test_functional.py @@ -141,6 +141,7 @@ def test_discover_simple(self): ], ) + @pytest.mark.skip(reason="https://github.com/microsoft/vscode-python/issues/14023") def test_discover_complex_default(self): projroot, testroot = resolve_testroot("complex") expected = self.complex(projroot) @@ -161,6 +162,7 @@ def test_discover_complex_default(self): self.maxDiff = None self.assertEqual(result, expected) + @pytest.mark.skip(reason="https://github.com/microsoft/vscode-python/issues/14023") def test_discover_complex_doctest(self): projroot, _ = resolve_testroot("complex") expected = self.complex(projroot) From 074024c92852b06eaa20cc702ac384cdec3a2c37 Mon Sep 17 00:00:00 2001 From: Kartik Raj Date: Wed, 23 Sep 2020 01:59:22 +0530 Subject: [PATCH 06/24] Upgrade isort to 5.5.3 (#14035) (#14037) --- news/1 Enhancements/14027.md | 1 + requirements.in | 2 +- requirements.txt | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) create mode 100644 news/1 Enhancements/14027.md diff --git a/news/1 Enhancements/14027.md b/news/1 Enhancements/14027.md new file mode 100644 index 000000000000..810963c7b177 --- /dev/null +++ b/news/1 Enhancements/14027.md @@ -0,0 +1 @@ +Upgraded to isort `5.5.3`. diff --git a/requirements.in b/requirements.in index b2d0327ce838..f8f1eaf05370 100644 --- a/requirements.in +++ b/requirements.in @@ -9,6 +9,6 @@ git+git://github.com/vscode-python/jedi-language-server@42823a2598d4b6369e9273c5ad237a48c5d67553; python_version >= '3.6' pygls==0.9.0; python_version >= '3.6' # Sort Imports -isort==5.5.2; python_version >= '3.6' +isort==5.5.3; python_version >= '3.6' # DS Python daemon python-jsonrpc-server==0.2.0 diff --git a/requirements.txt b/requirements.txt index c309b6ab206b..3a7efb00e04e 100644 --- a/requirements.txt +++ b/requirements.txt @@ -6,7 +6,7 @@ # click==7.1.2 # via jedi-language-server future==0.18.2 # via python-jsonrpc-server -isort==5.5.2 ; python_version >= "3.6" # via -r requirements.in +isort==5.5.3 ; python_version >= "3.6" # via -r requirements.in git+git://github.com/vscode-python/jedi-language-server@42823a2598d4b6369e9273c5ad237a48c5d67553 ; python_version >= "3.6" # via -r requirements.in jedi==0.17.2 # via jedi-language-server parso==0.7.0 # via jedi From c1258f025c23556eb35423e51366db2e2ff68e6a Mon Sep 17 00:00:00 2001 From: David Kutugata Date: Tue, 22 Sep 2020 16:58:04 -0700 Subject: [PATCH 07/24] prepare release (#14042) --- CHANGELOG.md | 6 +- ThirdPartyNotices-Distribution.txt | 133 +++++++++++++++-------------- news/1 Enhancements/14027.md | 1 - package-lock.json | 2 +- package.json | 2 +- 5 files changed, 73 insertions(+), 71 deletions(-) delete mode 100644 news/1 Enhancements/14027.md diff --git a/CHANGELOG.md b/CHANGELOG.md index 38b5f71f1330..826423317b57 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # Changelog -## 2020.9.0 (14 September 2020) +## 2020.9.0 (22 September 2020) ### Enhancements @@ -19,6 +19,8 @@ ([#13831](https://github.com/Microsoft/vscode-python/issues/13831)) 1. Enable custom editor support in stable VS code at 20%. ([#13890](https://github.com/Microsoft/vscode-python/issues/13890)) +1. Upgraded to isort `5.5.3`. + ([#14027](https://github.com/Microsoft/vscode-python/issues/14027)) ### Fixes @@ -60,7 +62,7 @@ 1. Correctly install ipykernel when launching from an interpreter. ([#13956](https://github.com/Microsoft/vscode-python/issues/13956)) 1. Backup on custom editors is being ignored. - ([#13981](https://github.com/Microsoft/vscode-python/issues/13981)) + ([#13981](https://github.com/Microsoft/vscode-python/issues/13981)) ### Code Health diff --git a/ThirdPartyNotices-Distribution.txt b/ThirdPartyNotices-Distribution.txt index 06d2e73f3084..ddb4327c18dd 100644 --- a/ThirdPartyNotices-Distribution.txt +++ b/ThirdPartyNotices-Distribution.txt @@ -3265,12 +3265,13 @@ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND --------------------------------------------------------- -estraverse 1.5.1 - BSD-2-Clause -https://github.com/Constellation/estraverse +estraverse 4.3.0 - BSD-2-Clause +https://github.com/estools/estraverse +Copyright (c) 2014 Yusuke Suzuki Copyright (c) 2012 Ariya Hidayat Copyright (c) 2012-2013 Yusuke Suzuki -Copyright (c) 2012-2013 Yusuke Suzuki (http://github.com/Constellation) +Copyright (c) 2012-2016 Yusuke Suzuki (http://github.com/Constellation) Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: @@ -3297,13 +3298,12 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. --------------------------------------------------------- -estraverse 4.3.0 - BSD-2-Clause -https://github.com/estools/estraverse +estraverse 1.5.1 - BSD-2-Clause +https://github.com/Constellation/estraverse -Copyright (c) 2014 Yusuke Suzuki Copyright (c) 2012 Ariya Hidayat Copyright (c) 2012-2013 Yusuke Suzuki -Copyright (c) 2012-2016 Yusuke Suzuki (http://github.com/Constellation) +Copyright (c) 2012-2013 Yusuke Suzuki (http://github.com/Constellation) Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: @@ -4436,7 +4436,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. --------------------------------------------------------- -hoist-non-react-statics 3.3.0 - BSD-3-Clause +hoist-non-react-statics 3.3.1 - BSD-3-Clause https://github.com/mridgway/hoist-non-react-statics#readme Copyright 2015, Yahoo! Inc. @@ -4477,7 +4477,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. --------------------------------------------------------- -hoist-non-react-statics 3.3.1 - BSD-3-Clause +hoist-non-react-statics 3.3.0 - BSD-3-Clause https://github.com/mridgway/hoist-non-react-statics#readme Copyright 2015, Yahoo! Inc. @@ -4597,7 +4597,7 @@ The complete list of contributors can be found at: https://github.com/hapijs/qs/ --------------------------------------------------------- -source-map 0.5.7 - BSD-3-Clause +source-map 0.6.1 - BSD-3-Clause https://github.com/mozilla/source-map Copyright 2011 The Closure Compiler @@ -4640,7 +4640,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. --------------------------------------------------------- -source-map 0.6.1 - BSD-3-Clause +source-map 0.5.7 - BSD-3-Clause https://github.com/mozilla/source-map Copyright 2011 The Closure Compiler @@ -5502,8 +5502,8 @@ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. --------------------------------------------------------- -@babel/runtime 7.8.3 - MIT -https://babeljs.io/docs/en/next/babel-runtime +@babel/runtime 7.5.4 - MIT + Copyright (c) 2014-present Sebastian McKenzie and other contributors @@ -5535,8 +5535,8 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. --------------------------------------------------------- -@babel/runtime 7.5.4 - MIT - +@babel/runtime 7.8.3 - MIT +https://babeljs.io/docs/en/next/babel-runtime Copyright (c) 2014-present Sebastian McKenzie and other contributors @@ -7420,7 +7420,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. --------------------------------------------------------- -date-format 3.0.0 - MIT +date-format 2.1.0 - MIT https://github.com/nomiddlename/date-format#readme Copyright (c) 2013 Gareth Jones @@ -7451,7 +7451,7 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. --------------------------------------------------------- -date-format 2.1.0 - MIT +date-format 3.0.0 - MIT https://github.com/nomiddlename/date-format#readme Copyright (c) 2013 Gareth Jones @@ -7482,7 +7482,7 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. --------------------------------------------------------- -debug 4.1.1 - MIT +debug 3.1.0 - MIT https://github.com/visionmedia/debug#readme Copyright (c) 2014 TJ Holowaychuk @@ -7575,7 +7575,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. --------------------------------------------------------- -debug 3.1.0 - MIT +debug 4.1.1 - MIT https://github.com/visionmedia/debug#readme Copyright (c) 2014 TJ Holowaychuk @@ -8928,7 +8928,7 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI --------------------------------------------------------- -got 8.3.2 - MIT +got 9.6.0 - MIT https://github.com/sindresorhus/got#readme Copyright (c) Sindre Sorhus (sindresorhus.com) @@ -8948,7 +8948,7 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI --------------------------------------------------------- -got 9.6.0 - MIT +got 8.3.2 - MIT https://github.com/sindresorhus/got#readme Copyright (c) Sindre Sorhus (sindresorhus.com) @@ -9431,7 +9431,7 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. --------------------------------------------------------- -isarray 1.0.0 - MIT +isarray 0.0.1 - MIT https://github.com/juliangruber/isarray Copyright (c) 2013 Julian Gruber @@ -9450,7 +9450,7 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI --------------------------------------------------------- -isarray 0.0.1 - MIT +isarray 1.0.0 - MIT https://github.com/juliangruber/isarray Copyright (c) 2013 Julian Gruber @@ -9638,7 +9638,7 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI --------------------------------------------------------- -isort 5.5.2 - MIT +isort 5.5.3 - MIT Copyright 2017 Jack Evans @@ -11173,10 +11173,9 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. --------------------------------------------------------- -normalize-url 2.0.1 - MIT +normalize-url 4.5.0 - MIT https://github.com/sindresorhus/normalize-url#readme -(c) Sindre Sorhus (https://sindresorhus.com) Copyright (c) Sindre Sorhus (sindresorhus.com) MIT License @@ -11194,9 +11193,10 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI --------------------------------------------------------- -normalize-url 4.5.0 - MIT +normalize-url 2.0.1 - MIT https://github.com/sindresorhus/normalize-url#readme +(c) Sindre Sorhus (https://sindresorhus.com) Copyright (c) Sindre Sorhus (sindresorhus.com) MIT License @@ -11558,7 +11558,7 @@ SOFTWARE. --------------------------------------------------------- -p-cancelable 0.4.1 - MIT +p-cancelable 1.1.0 - MIT https://github.com/sindresorhus/p-cancelable#readme (c) Sindre Sorhus (https://sindresorhus.com) @@ -11579,7 +11579,7 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI --------------------------------------------------------- -p-cancelable 1.1.0 - MIT +p-cancelable 0.4.1 - MIT https://github.com/sindresorhus/p-cancelable#readme (c) Sindre Sorhus (https://sindresorhus.com) @@ -11931,7 +11931,7 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI --------------------------------------------------------- -process-nextick-args 2.0.1 - MIT +process-nextick-args 1.0.7 - MIT https://github.com/calvinmetcalf/process-nextick-args Copyright (c) 2015 Calvin Metcalf @@ -11961,7 +11961,7 @@ SOFTWARE.** --------------------------------------------------------- -process-nextick-args 1.0.7 - MIT +process-nextick-args 2.0.1 - MIT https://github.com/calvinmetcalf/process-nextick-args Copyright (c) 2015 Calvin Metcalf @@ -12128,7 +12128,7 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI --------------------------------------------------------- -pump 3.0.0 - MIT +pump 2.0.1 - MIT https://github.com/mafintosh/pump#readme Copyright (c) 2014 Mathias Buus @@ -12159,7 +12159,7 @@ THE SOFTWARE. --------------------------------------------------------- -pump 2.0.1 - MIT +pump 3.0.0 - MIT https://github.com/mafintosh/pump#readme Copyright (c) 2014 Mathias Buus @@ -12429,7 +12429,7 @@ IN THE SOFTWARE. --------------------------------------------------------- -readable-stream 2.3.7 - MIT +readable-stream 2.3.6 - MIT https://github.com/nodejs/readable-stream#readme Copyright Joyent, Inc. and other Node contributors. @@ -12487,7 +12487,7 @@ IN THE SOFTWARE. --------------------------------------------------------- -readable-stream 2.3.6 - MIT +readable-stream 2.3.7 - MIT https://github.com/nodejs/readable-stream#readme Copyright Joyent, Inc. and other Node contributors. @@ -12703,8 +12703,8 @@ SOFTWARE. --------------------------------------------------------- -resolve 1.1.7 - MIT -https://github.com/substack/node-resolve#readme +resolve 1.11.1 - MIT +https://github.com/browserify/resolve#readme This software is released under the MIT license: @@ -12731,8 +12731,8 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. --------------------------------------------------------- -resolve 1.11.1 - MIT -https://github.com/browserify/resolve#readme +resolve 1.1.7 - MIT +https://github.com/substack/node-resolve#readme This software is released under the MIT license: @@ -13134,7 +13134,7 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. --------------------------------------------------------- -static-module 2.2.5 - MIT +static-module 3.0.3 - MIT https://github.com/substack/static-module @@ -13162,7 +13162,7 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. --------------------------------------------------------- -static-module 3.0.3 - MIT +static-module 2.2.5 - MIT https://github.com/substack/static-module @@ -14067,7 +14067,7 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI --------------------------------------------------------- -unicode-trie 2.0.0 - MIT +unicode-trie 1.0.0 - MIT https://github.com/devongovett/unicode-trie Copyright 2018 @@ -14086,7 +14086,7 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI --------------------------------------------------------- -unicode-trie 1.0.0 - MIT +unicode-trie 2.0.0 - MIT https://github.com/devongovett/unicode-trie Copyright 2018 @@ -14320,6 +14320,27 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +--------------------------------------------------------- + +--------------------------------------------------------- + +uuid 8.2.0 - MIT +https://github.com/uuidjs/uuid#readme + +Copyright 2011, Sebastian Tschan https://blueimp.net +Copyright (c) Paul Johnston 1999 - 2009 Other contributors Greg Holt, Andrew Kepert, Ydnar, Lostinet + +The MIT License (MIT) + +Copyright (c) 2010-2020 Robert Kieffer and other contributors + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + --------------------------------------------------------- --------------------------------------------------------- @@ -14354,27 +14375,6 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---------------------------------------------------------- - ---------------------------------------------------------- - -uuid 8.2.0 - MIT -https://github.com/uuidjs/uuid#readme - -Copyright 2011, Sebastian Tschan https://blueimp.net -Copyright (c) Paul Johnston 1999 - 2009 Other contributors Greg Holt, Andrew Kepert, Ydnar, Lostinet - -The MIT License (MIT) - -Copyright (c) 2010-2020 Robert Kieffer and other contributors - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - - --------------------------------------------------------- --------------------------------------------------------- @@ -14825,7 +14825,7 @@ SOFTWARE. --------------------------------------------------------- -xml2js 0.4.19 - MIT +xml2js 0.2.8 - MIT https://github.com/Leonidas-from-XIV/node-xml2js Copyright 2010, 2011, 2012, 2013. @@ -14855,7 +14855,7 @@ IN THE SOFTWARE. --------------------------------------------------------- -xml2js 0.2.8 - MIT +xml2js 0.4.19 - MIT https://github.com/Leonidas-from-XIV/node-xml2js Copyright 2010, 2011, 2012, 2013. @@ -15141,3 +15141,4 @@ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. You just DO WHAT THE FUCK YOU WANT TO. --------------------------------------------------------- + diff --git a/news/1 Enhancements/14027.md b/news/1 Enhancements/14027.md deleted file mode 100644 index 810963c7b177..000000000000 --- a/news/1 Enhancements/14027.md +++ /dev/null @@ -1 +0,0 @@ -Upgraded to isort `5.5.3`. diff --git a/package-lock.json b/package-lock.json index 3c3d66258cc9..02c8932d6c64 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "python", - "version": "2020.9.0-rc", + "version": "2020.9.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index a91c5a5be59d..b34f0ee31e6b 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "python", "displayName": "Python", "description": "Linting, Debugging (multi-threaded, remote), Intellisense, Jupyter Notebooks, code formatting, refactoring, unit tests, snippets, and more.", - "version": "2020.9.0-rc", + "version": "2020.9.0", "featureFlags": { "usingNewInterpreterStorage": true }, From 0b41a8631fbd7eaf5024d3d0ed31185ee5edc69b Mon Sep 17 00:00:00 2001 From: David Kutugata Date: Tue, 22 Sep 2020 20:54:44 -0700 Subject: [PATCH 08/24] fixed annoying warnings (#14049) --- CHANGELOG.md | 2 +- package.nls.json | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 826423317b57..dc0ec9e5af07 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # Changelog -## 2020.9.0 (22 September 2020) +## 2020.9.0 (23 September 2020) ### Enhancements diff --git a/package.nls.json b/package.nls.json index 16a537b3a669..b1b7007f43d8 100644 --- a/package.nls.json +++ b/package.nls.json @@ -59,6 +59,8 @@ "python.command.python.datascience.debugFileInteractive.title": "Debug Current File in Python Interactive Window", "python.command.python.datascience.runallcells.title": "Run All Cells", "python.command.python.datascience.notebookeditor.runallcells.title": "Run All Notebook Cells", + "python.command.python.datascience.notebookeditor.expandallcells.title": "Expand All Notebook Cells", + "python.command.python.datascience.notebookeditor.collapseallcells.title": "Collapse All Notebook Cells", "python.command.python.datascience.runallcellsabove.title": "Run Above", "python.command.python.datascience.runcellandallbelow.title": "Run Below", "python.command.python.datascience.runallcellsabove.palette.title": "Run Cells Above Current Cell", From 8d337a7b785e191f3a3a34e00cf178d9ab61e8f1 Mon Sep 17 00:00:00 2001 From: Karthik Nadig Date: Mon, 28 Sep 2020 02:40:00 -0700 Subject: [PATCH 09/24] Cherry pick to address path issues. (#14125) * Do not quote isolated in exec module (#14108) * Do not quote isolated in exec module * Revert "Do not quote isolated in exec module" This reverts commit b9fa04c06be0876861017eff5ee032ca71acda3d. * Revert "IPyKernel install issue with windows paths (#13667)" This reverts commit 23725abd4249811f24475e215c2d78ed5e508e79. * Fix unit test broken by recent revert (#14122) --- src/client/common/process/internal/python.ts | 2 +- src/client/common/process/internal/scripts/index.ts | 2 +- src/test/common/installer/moduleInstaller.unit.test.ts | 2 +- src/test/common/moduleInstaller.test.ts | 2 +- src/test/common/process/pythonProcess.unit.test.ts | 2 +- .../common/terminals/synchronousTerminalService.unit.test.ts | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/client/common/process/internal/python.ts b/src/client/common/process/internal/python.ts index d553e54293c1..86123367f852 100644 --- a/src/client/common/process/internal/python.ts +++ b/src/client/common/process/internal/python.ts @@ -28,7 +28,7 @@ export function execCode(code: string, isolated = true): string[] { export function execModule(name: string, moduleArgs: string[], isolated = true): string[] { const args = ['-m', name, ...moduleArgs]; if (isolated) { - args[0] = ISOLATED.fileToCommandArgument(); + args[0] = ISOLATED; // replace } // "code" isn't specific enough to know how to parse it, // so we only return the args. diff --git a/src/client/common/process/internal/scripts/index.ts b/src/client/common/process/internal/scripts/index.ts index 74408e81e1e6..ec59c6f6a483 100644 --- a/src/client/common/process/internal/scripts/index.ts +++ b/src/client/common/process/internal/scripts/index.ts @@ -306,7 +306,7 @@ export function shell_exec(command: string, lockfile: string, shellArgs: string[ // We don't bother with a "parse" function since the output // could be anything. return [ - ISOLATED.fileToCommandArgument(), + ISOLATED, script, command.fileToCommandArgument(), // The shell args must come after the command diff --git a/src/test/common/installer/moduleInstaller.unit.test.ts b/src/test/common/installer/moduleInstaller.unit.test.ts index 93594c050e46..73ad8ded6dfa 100644 --- a/src/test/common/installer/moduleInstaller.unit.test.ts +++ b/src/test/common/installer/moduleInstaller.unit.test.ts @@ -47,7 +47,7 @@ import { IServiceContainer } from '../../../client/ioc/types'; import { EnvironmentType, PythonEnvironment } from '../../../client/pythonEnvironments/info'; import { EXTENSION_ROOT_DIR_FOR_TESTS } from '../../constants'; -const isolated = path.join(EXTENSION_ROOT_DIR_FOR_TESTS, 'pythonFiles', 'pyvsc-run-isolated.py').replace(/\\/g, '/'); +const isolated = path.join(EXTENSION_ROOT_DIR_FOR_TESTS, 'pythonFiles', 'pyvsc-run-isolated.py'); /* Complex test to ensure we cover all combinations: We could have written separate tests for each installer, but we'd be replicate code. diff --git a/src/test/common/moduleInstaller.test.ts b/src/test/common/moduleInstaller.test.ts index a5bccb537869..abae5cd316a5 100644 --- a/src/test/common/moduleInstaller.test.ts +++ b/src/test/common/moduleInstaller.test.ts @@ -140,7 +140,7 @@ import { closeActiveWindows, initializeTest } from './../initialize'; chai_use(chaiAsPromised); -const isolated = path.join(EXTENSION_ROOT_DIR_FOR_TESTS, 'pythonFiles', 'pyvsc-run-isolated.py').replace(/\\/g, '/'); +const isolated = path.join(EXTENSION_ROOT_DIR_FOR_TESTS, 'pythonFiles', 'pyvsc-run-isolated.py'); const info: PythonEnvironment = { architecture: Architecture.Unknown, diff --git a/src/test/common/process/pythonProcess.unit.test.ts b/src/test/common/process/pythonProcess.unit.test.ts index cc0a847e7e7e..646ad8473c05 100644 --- a/src/test/common/process/pythonProcess.unit.test.ts +++ b/src/test/common/process/pythonProcess.unit.test.ts @@ -12,7 +12,7 @@ import { IProcessService, StdErrError } from '../../../client/common/process/typ import { EXTENSION_ROOT_DIR_FOR_TESTS } from '../../constants'; import { noop } from '../../core'; -const isolated = path.join(EXTENSION_ROOT_DIR_FOR_TESTS, 'pythonFiles', 'pyvsc-run-isolated.py').replace(/\\/g, '/'); +const isolated = path.join(EXTENSION_ROOT_DIR_FOR_TESTS, 'pythonFiles', 'pyvsc-run-isolated.py'); use(chaiAsPromised); diff --git a/src/test/common/terminals/synchronousTerminalService.unit.test.ts b/src/test/common/terminals/synchronousTerminalService.unit.test.ts index 75b0e27bb63a..082eeb1953ca 100644 --- a/src/test/common/terminals/synchronousTerminalService.unit.test.ts +++ b/src/test/common/terminals/synchronousTerminalService.unit.test.ts @@ -67,7 +67,7 @@ suite('Terminal Service (synchronous)', () => { }); }); suite('sendCommand', () => { - const isolated = path.join(EXTENSION_ROOT_DIR, 'pythonFiles', 'pyvsc-run-isolated.py').replace(/\\/g, '/'); + const isolated = path.join(EXTENSION_ROOT_DIR, 'pythonFiles', 'pyvsc-run-isolated.py'); const shellExecFile = path.join(EXTENSION_ROOT_DIR, 'pythonFiles', 'shell_exec.py'); test('run sendCommand in terminalService if there is no cancellation token', async () => { From 5da34fcb80a71a7b3af9869906e0a432262c7130 Mon Sep 17 00:00:00 2001 From: Rich Chiodo Date: Mon, 28 Sep 2020 10:51:17 -0700 Subject: [PATCH 10/24] Port escape fix to release branch (#14133) * Fix HTML escaping to match what Jupyter does (#14038) * Basic idea * add some functional tests * Add news entry * Fix functional tests * Update changelog --- CHANGELOG.md | 61 +++++++++++++++++++ .../datascience/jupyter/jupyterNotebook.ts | 27 +++++--- .../datascience/jupyter/kernelVariables.ts | 6 +- .../jupyter/kernels/cellExecution.ts | 18 +++++- .../jupyter/oldJupyterVariables.ts | 6 +- .../interactive-common/cellOutput.tsx | 24 +++++--- src/test/datascience/Untitled-1.ipynb | 47 ++++++++++++++ .../interactiveWindow.functional.test.tsx | 34 +++++------ .../datascience/liveshare.functional.test.tsx | 28 ++++----- .../nativeEditor.functional.test.tsx | 18 +++--- .../datascience/notebook.functional.test.ts | 15 ++++- .../trustedNotebooks.functional.test.tsx | 12 ++-- .../uiTests/ipywidget.ui.functional.test.ts | 2 +- 13 files changed, 225 insertions(+), 73 deletions(-) create mode 100644 src/test/datascience/Untitled-1.ipynb diff --git a/CHANGELOG.md b/CHANGELOG.md index dc0ec9e5af07..4d212c9f9067 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,66 @@ # Changelog +## 2020.9.1 (29 September 2020) + +### Fixes + +1. Fix escaping of output to encode HTML chars correctly. + ([#5678](https://github.com/Microsoft/vscode-python/issues/5678)) + +### Thanks + +Thanks to the following projects which we fully rely on to provide some of +our features: + +- [debugpy](https://pypi.org/project/debugpy/) +- [isort](https://pypi.org/project/isort/) +- [jedi](https://pypi.org/project/jedi/) + and [parso](https://pypi.org/project/parso/) +- [Microsoft Python Language Server](https://github.com/microsoft/python-language-server) +- [Pylance](https://github.com/microsoft/pylance-release) +- [exuberant ctags](http://ctags.sourceforge.net/) (user-installed) +- [rope](https://pypi.org/project/rope/) (user-installed) + +Also thanks to the various projects we provide integrations with which help +make this extension useful: + +- Debugging support: + [Django](https://pypi.org/project/Django/), + [Flask](https://pypi.org/project/Flask/), + [gevent](https://pypi.org/project/gevent/), + [Jinja](https://pypi.org/project/Jinja/), + [Pyramid](https://pypi.org/project/pyramid/), + [PySpark](https://pypi.org/project/pyspark/), + [Scrapy](https://pypi.org/project/Scrapy/), + [Watson](https://pypi.org/project/Watson/) +- Formatting: + [autopep8](https://pypi.org/project/autopep8/), + [black](https://pypi.org/project/black/), + [yapf](https://pypi.org/project/yapf/) +- Interpreter support: + [conda](https://conda.io/), + [direnv](https://direnv.net/), + [pipenv](https://pypi.org/project/pipenv/), + [pyenv](https://github.com/pyenv/pyenv), + [venv](https://docs.python.org/3/library/venv.html#module-venv), + [virtualenv](https://pypi.org/project/virtualenv/) +- Linting: + [bandit](https://pypi.org/project/bandit/), + [flake8](https://pypi.org/project/flake8/), + [mypy](https://pypi.org/project/mypy/), + [prospector](https://pypi.org/project/prospector/), + [pylint](https://pypi.org/project/pylint/), + [pydocstyle](https://pypi.org/project/pydocstyle/), + [pylama](https://pypi.org/project/pylama/) +- Testing: + [nose](https://pypi.org/project/nose/), + [pytest](https://pypi.org/project/pytest/), + [unittest](https://docs.python.org/3/library/unittest.html#module-unittest) + +And finally thanks to the [Python](https://www.python.org/) development team and +community for creating a fantastic programming language and community to be a +part of! + ## 2020.9.0 (23 September 2020) ### Enhancements diff --git a/src/client/datascience/jupyter/jupyterNotebook.ts b/src/client/datascience/jupyter/jupyterNotebook.ts index de8886658b00..ab1a015065e3 100644 --- a/src/client/datascience/jupyter/jupyterNotebook.ts +++ b/src/client/datascience/jupyter/jupyterNotebook.ts @@ -40,6 +40,10 @@ import { KernelConnectionMetadata } from './kernels/types'; // tslint:disable-next-line: no-require-imports import cloneDeep = require('lodash/cloneDeep'); +// tslint:disable-next-line: no-require-imports +import escape = require('lodash/escape'); +// tslint:disable-next-line: no-require-imports +import unescape = require('lodash/unescape'); import { concatMultilineString, formatStreamText } from '../../../datascience-ui/common'; import { RefBool } from '../../common/refBool'; import { PythonEnvironment } from '../../pythonEnvironments/info'; @@ -783,12 +787,12 @@ export class JupyterNotebookBase implements INotebook { outputs.forEach((o) => { if (o.output_type === 'stream') { const stream = o as nbformat.IStream; - result = result.concat(formatStreamText(concatMultilineString(stream.text, true))); + result = result.concat(formatStreamText(unescape(concatMultilineString(stream.text, true)))); } else { const data = o.data; if (data && data.hasOwnProperty('text/plain')) { // tslint:disable-next-line:no-any - result = result.concat((data as any)['text/plain']); + result = result.concat(unescape((data as any)['text/plain'])); } } }); @@ -1233,7 +1237,7 @@ export class JupyterNotebookBase implements INotebook { ) { // Check our length on text output if (msg.content.data && msg.content.data.hasOwnProperty('text/plain')) { - msg.content.data['text/plain'] = trimFunc(msg.content.data['text/plain'] as string); + msg.content.data['text/plain'] = escape(trimFunc(msg.content.data['text/plain'] as string)); } this.addToCellData( @@ -1262,7 +1266,7 @@ export class JupyterNotebookBase implements INotebook { if (o.data && o.data.hasOwnProperty('text/plain')) { // tslint:disable-next-line: no-any const str = (o.data as any)['text/plain'].toString(); - const data = trimFunc(str) as string; + const data = escape(trimFunc(str)) as string; this.addToCellData( cell, { @@ -1310,13 +1314,13 @@ export class JupyterNotebookBase implements INotebook { : undefined; if (existing) { // tslint:disable-next-line:restrict-plus-operands - existing.text = existing.text + msg.content.text; + existing.text = existing.text + escape(msg.content.text); const originalText = formatStreamText(concatMultilineString(existing.text)); originalTextLength = originalText.length; existing.text = trimFunc(originalText); trimmedTextLength = existing.text.length; } else { - const originalText = formatStreamText(concatMultilineString(msg.content.text)); + const originalText = formatStreamText(concatMultilineString(escape(msg.content.text))); originalTextLength = originalText.length; // Create a new stream entry const output: nbformat.IStream = { @@ -1346,6 +1350,11 @@ export class JupyterNotebookBase implements INotebook { } private handleDisplayData(msg: KernelMessage.IDisplayDataMsg, clearState: RefBool, cell: ICell) { + // Escape text output + if (msg.content.data && msg.content.data.hasOwnProperty('text/plain')) { + msg.content.data['text/plain'] = escape(msg.content.data['text/plain'] as string); + } + const output: nbformat.IDisplayData = { output_type: 'display_data', data: msg.content.data, @@ -1393,9 +1402,9 @@ export class JupyterNotebookBase implements INotebook { private handleError(msg: KernelMessage.IErrorMsg, clearState: RefBool, cell: ICell) { const output: nbformat.IError = { output_type: 'error', - ename: msg.content.ename, - evalue: msg.content.evalue, - traceback: msg.content.traceback + ename: escape(msg.content.ename), + evalue: escape(msg.content.evalue), + traceback: msg.content.traceback.map(escape) }; this.addToCellData(cell, output, clearState); cell.state = CellState.error; diff --git a/src/client/datascience/jupyter/kernelVariables.ts b/src/client/datascience/jupyter/kernelVariables.ts index 7d3d1b004bd3..501b3f25ef17 100644 --- a/src/client/datascience/jupyter/kernelVariables.ts +++ b/src/client/datascience/jupyter/kernelVariables.ts @@ -6,6 +6,8 @@ import { inject, injectable } from 'inversify'; import stripAnsi from 'strip-ansi'; import * as uuid from 'uuid/v4'; +// tslint:disable-next-line: no-require-imports +import unescape = require('lodash/unescape'); import { CancellationToken, Event, EventEmitter, Uri } from 'vscode'; import { PYTHON_LANGUAGE } from '../../common/constants'; import { traceError } from '../../common/logger'; @@ -246,7 +248,7 @@ export class KernelVariables implements IJupyterVariables { // Pull our text result out of the Jupyter cell private deserializeJupyterResult(cells: ICell[]): T { - const text = this.extractJupyterResultText(cells); + const text = unescape(this.extractJupyterResultText(cells)); return JSON.parse(text) as T; } @@ -371,7 +373,7 @@ export class KernelVariables implements IJupyterVariables { // Now execute the query if (notebook && query) { const cells = await notebook.execute(query.query, Identifiers.EmptyFileName, 0, uuid(), token, true); - const text = this.extractJupyterResultText(cells); + const text = unescape(this.extractJupyterResultText(cells)); // Apply the expression to it const matches = this.getAllMatches(query.parser, text); diff --git a/src/client/datascience/jupyter/kernels/cellExecution.ts b/src/client/datascience/jupyter/kernels/cellExecution.ts index f951d3979927..a0be2d08c54e 100644 --- a/src/client/datascience/jupyter/kernels/cellExecution.ts +++ b/src/client/datascience/jupyter/kernels/cellExecution.ts @@ -35,6 +35,8 @@ import { import { IKernel } from './types'; // tslint:disable-next-line: no-var-requires no-require-imports const vscodeNotebookEnums = require('vscode') as typeof import('vscode-proposed'); +// tslint:disable-next-line: no-require-imports +import escape = require('lodash/escape'); export class CellExecutionFactory { constructor( @@ -406,6 +408,11 @@ export class CellExecution { // See this for docs on the messages: // https://jupyter-client.readthedocs.io/en/latest/messaging.html#messaging-in-jupyter private handleExecuteResult(msg: KernelMessage.IExecuteResultMsg, clearState: RefBool) { + // Escape text output + if (msg.content.data && msg.content.data.hasOwnProperty('text/plain')) { + msg.content.data['text/plain'] = escape(msg.content.data['text/plain'] as string); + } + this.addToCellData( { output_type: 'execute_result', @@ -429,7 +436,7 @@ export class CellExecution { // Mark as stream output so the text is formatted because it likely has ansi codes in it. output_type: 'stream', // tslint:disable-next-line: no-any - text: (o.data as any)['text/plain'].toString(), + text: escape((o.data as any)['text/plain'].toString()), metadata: {}, execution_count: reply.execution_count }, @@ -463,10 +470,10 @@ export class CellExecution { lastOutput && lastOutput.outputKind === CellOutputKind.Text ? lastOutput : undefined; if (existing) { // tslint:disable-next-line:restrict-plus-operands - existing.text = formatStreamText(concatMultilineString(existing.text + msg.content.text)); + existing.text = formatStreamText(concatMultilineString(existing.text + escape(msg.content.text))); this.cell.outputs = [...this.cell.outputs]; // This is necessary to get VS code to update (for now) } else { - const originalText = formatStreamText(concatMultilineString(msg.content.text)); + const originalText = formatStreamText(concatMultilineString(escape(msg.content.text))); // Create a new stream entry const output: nbformat.IStream = { output_type: 'stream', @@ -478,6 +485,11 @@ export class CellExecution { } private handleDisplayData(msg: KernelMessage.IDisplayDataMsg, clearState: RefBool) { + // Escape text output + if (msg.content.data && msg.content.data.hasOwnProperty('text/plain')) { + msg.content.data['text/plain'] = escape(msg.content.data['text/plain'] as string); + } + const output: nbformat.IDisplayData = { output_type: 'display_data', data: msg.content.data, diff --git a/src/client/datascience/jupyter/oldJupyterVariables.ts b/src/client/datascience/jupyter/oldJupyterVariables.ts index 7c1d77de7135..6c25a3a3e547 100644 --- a/src/client/datascience/jupyter/oldJupyterVariables.ts +++ b/src/client/datascience/jupyter/oldJupyterVariables.ts @@ -11,6 +11,8 @@ import { Event, EventEmitter, Uri } from 'vscode'; import { PYTHON_LANGUAGE } from '../../common/constants'; import { traceError } from '../../common/logger'; +// tslint:disable-next-line: no-require-imports +import unescape = require('lodash/unescape'); import { IConfigurationService } from '../../common/types'; import * as localize from '../../common/utils/localize'; import { EXTENSION_ROOT_DIR } from '../../constants'; @@ -232,7 +234,7 @@ export class OldJupyterVariables implements IJupyterVariables { // Pull our text result out of the Jupyter cell private deserializeJupyterResult(cells: ICell[]): T { - const text = this.extractJupyterResultText(cells); + const text = unescape(this.extractJupyterResultText(cells)); return JSON.parse(text) as T; } @@ -357,7 +359,7 @@ export class OldJupyterVariables implements IJupyterVariables { // Now execute the query if (notebook && query) { const cells = await notebook.execute(query.query, Identifiers.EmptyFileName, 0, uuid(), undefined, true); - const text = this.extractJupyterResultText(cells); + const text = unescape(this.extractJupyterResultText(cells)); // Apply the expression to it const matches = this.getAllMatches(query.parser, text); diff --git a/src/datascience-ui/interactive-common/cellOutput.tsx b/src/datascience-ui/interactive-common/cellOutput.tsx index b48cc8d69cfd..b9a9d5bb2eee 100644 --- a/src/datascience-ui/interactive-common/cellOutput.tsx +++ b/src/datascience-ui/interactive-common/cellOutput.tsx @@ -314,26 +314,34 @@ export class CellOutput extends React.Component { input = JSON.stringify(output.data); renderWithScrollbars = true; isText = true; + } else if (output.output_type === 'execute_result' && input && input.hasOwnProperty('text/plain')) { + // Plain text should actually be shown as html so that escaped HTML shows up correctly + mimeType = 'text/html'; + isText = true; + isError = false; + renderWithScrollbars = true; + // tslint:disable-next-line: no-any + const text = (input as any)['text/plain']; + input = { + 'text/html': text // XML tags should have already been escaped. + }; } else if (output.output_type === 'stream') { - // Stream output needs to be wrapped in xmp so it - // show literally. Otherwise < chars start a new html element. mimeType = 'text/html'; isText = true; isError = false; renderWithScrollbars = true; // Sonar is wrong, TS won't compile without this AS const stream = output as nbformat.IStream; // NOSONAR - const formatted = concatMultilineString(stream.text); + const concatted = concatMultilineString(stream.text); input = { - 'text/html': formatted.includes('<') ? `${formatted}` : `
${formatted}
` + 'text/html': concatted // XML tags should have already been escaped. }; - // Output may have goofy ascii colorization chars in it. Try - // colorizing if we don't have html that needs around it (ex. <type ='string'>) + // Output may have ascii colorization chars in it. try { - if (ansiRegex().test(formatted)) { + if (ansiRegex().test(concatted)) { const converter = new CellOutput.ansiToHtmlClass(CellOutput.getAnsiToHtmlOptions()); - const html = converter.toHtml(formatted); + const html = converter.toHtml(concatted); input = { 'text/html': html }; diff --git a/src/test/datascience/Untitled-1.ipynb b/src/test/datascience/Untitled-1.ipynb new file mode 100644 index 000000000000..603be536258d --- /dev/null +++ b/src/test/datascience/Untitled-1.ipynb @@ -0,0 +1,47 @@ +{ + "metadata": { + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.6.6-final" + }, + "orig_nbformat": 2 + }, + "nbformat": 4, + "nbformat_minor": 2, + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [ + { + "output_type": "execute_result", + "data": { + "text/plain": "1" + }, + "metadata": {}, + "execution_count": 1 + } + ], + "source": [ + "a=1\n", + "a" + ] + } + ] +} \ No newline at end of file diff --git a/src/test/datascience/interactiveWindow.functional.test.tsx b/src/test/datascience/interactiveWindow.functional.test.tsx index 504e3499f401..d40cb82906ac 100644 --- a/src/test/datascience/interactiveWindow.functional.test.tsx +++ b/src/test/datascience/interactiveWindow.functional.test.tsx @@ -128,7 +128,7 @@ suite('DataScience Interactive Window output tests', () => { async () => { await addCode(ioc, 'a=1\na'); - verifyHtmlOnInteractiveCell('<span>1</span>', CellPosition.Last); + verifyHtmlOnInteractiveCell('1', CellPosition.Last); }, () => { return ioc; @@ -171,7 +171,7 @@ for i in range(10): addMockData(ioc, 'a=1', undefined, 'text/plain'); await addCode(ioc, 'a=1'); - verifyHtmlOnInteractiveCell('<span>1</span>', CellPosition.First); + verifyHtmlOnInteractiveCell('1', CellPosition.First); verifyHtmlOnInteractiveCell(undefined, CellPosition.Last); }, () => { @@ -236,7 +236,7 @@ for i in range(10): } } - verifyHtmlOnInteractiveCell('<span>1</span>', CellPosition.Last); + verifyHtmlOnInteractiveCell('1', CellPosition.Last); }, () => { return ioc; @@ -300,7 +300,7 @@ for i in range(10): } } - verifyHtmlOnInteractiveCell('<span>1</span>', CellPosition.Last); + verifyHtmlOnInteractiveCell('1', CellPosition.Last); }, () => { return ioc; @@ -698,7 +698,7 @@ for i in range(0, 100): const window = (await interactiveWindowProvider.getOrCreate(undefined)) as InteractiveWindow; await addCode(ioc, 'a=1\na'); const activeInterpreter = await interpreterService.getActiveInterpreter(window.owningResource); - verifyHtmlOnInteractiveCell('<span>1</span>', CellPosition.Last); + verifyHtmlOnInteractiveCell('1', CellPosition.Last); assert.equal( window.notebook!.getMatchingInterpreter()?.path, activeInterpreter?.path, @@ -724,7 +724,7 @@ for i in range(0, 100): activeInterpreter?.path, 'Active intrepreter used to launch second notebook when it should not have' ); - verifyHtmlOnCell(ioc.getWrapper('interactive'), 'InteractiveCell', '<span>1</span>', CellPosition.Last); + verifyHtmlOnCell(ioc.getWrapper('interactive'), 'InteractiveCell', '1', CellPosition.Last); } else { context.skip(); } @@ -867,7 +867,7 @@ for i in range(0, 100): // Then enter some code. await enterInput(mount, 'a=1\na', 'InteractiveCell'); - verifyHtmlOnInteractiveCell('<span>1</span>', CellPosition.Last); + verifyHtmlOnInteractiveCell('1', CellPosition.Last); }, () => { return ioc; @@ -888,7 +888,7 @@ for i in range(0, 100): // Then enter some code. await enterInput(mount, 'a=1\na', 'InteractiveCell'); - verifyHtmlOnInteractiveCell('<span>1</span>', CellPosition.Last); + verifyHtmlOnInteractiveCell('1', CellPosition.Last); const ImageButtons = getLastOutputCell(mount.wrapper, 'InteractiveCell').find(ImageButton); assert.equal(ImageButtons.length, 4, 'Cell buttons not found'); const copyToSource = ImageButtons.at(2); @@ -911,7 +911,7 @@ for i in range(0, 100): // Then enter some code. await enterInput(mount, 'a=1\na', 'InteractiveCell'); - verifyHtmlOnInteractiveCell('<span>1</span>', CellPosition.Last); + verifyHtmlOnInteractiveCell('1', CellPosition.Last); // Then delete the node const lastCell = getLastOutputCell(mount.wrapper, 'InteractiveCell'); @@ -928,7 +928,7 @@ for i in range(0, 100): // Should be able to enter again await enterInput(mount, 'a=1\na', 'InteractiveCell'); - verifyHtmlOnInteractiveCell('<span>1</span>', CellPosition.Last); + verifyHtmlOnInteractiveCell('1', CellPosition.Last); // Try a 3rd time with some new input addMockData(ioc, 'print("hello")', 'hello'); @@ -952,7 +952,7 @@ for i in range(0, 100): async () => { // Prime the pump await addCode(ioc, 'a=1\na'); - verifyHtmlOnInteractiveCell('<span>1</span>', CellPosition.Last); + verifyHtmlOnInteractiveCell('1', CellPosition.Last); // Then something that could possibly timeout addContinuousMockData(ioc, 'import time\r\ntime.sleep(1000)', (_c) => { @@ -979,7 +979,7 @@ for i in range(0, 100): // Now see if our wrapper still works. Interactive window should have forced a restart await window.addCode('a=1\na', Uri.file('foo'), 0); - verifyHtmlOnInteractiveCell('<span>1</span>', CellPosition.Last); + verifyHtmlOnInteractiveCell('1', CellPosition.Last); }, () => { return ioc; @@ -1071,7 +1071,7 @@ for i in range(0, 100): // Then enter some code. await enterInput(mount, 'a=1\na', 'InteractiveCell'); - verifyHtmlOnInteractiveCell('<span>1</span>', CellPosition.Last); + verifyHtmlOnInteractiveCell('1', CellPosition.Last); const ImageButtons = getLastOutputCell(mount.wrapper, 'InteractiveCell').find(ImageButton); assert.equal(ImageButtons.length, 4, 'Cell buttons not found'); const gatherCode = ImageButtons.at(0); @@ -1198,21 +1198,21 @@ for i in range(0, 100): await addCell(ne.mount, 'a=1\na', true); // Make sure both are correct - verifyHtmlOnCell(iw.mount.wrapper, 'InteractiveCell', '<span>1</span>', CellPosition.Last); - verifyHtmlOnCell(ne.mount.wrapper, 'NativeCell', '<span>1</span>', CellPosition.Last); + verifyHtmlOnCell(iw.mount.wrapper, 'InteractiveCell', '1', CellPosition.Last); + verifyHtmlOnCell(ne.mount.wrapper, 'NativeCell', '1', CellPosition.Last); // Close the interactive editor. await closeInteractiveWindow(ioc, iw.window); // Run another cell and make sure it works in the notebook await addCell(ne.mount, 'b=2\nb', true); - verifyHtmlOnCell(ne.mount.wrapper, 'NativeCell', '<span>2</span>', CellPosition.Last); + verifyHtmlOnCell(ne.mount.wrapper, 'NativeCell', '2', CellPosition.Last); // Rerun the interactive window iw = await getOrCreateInteractiveWindow(ioc); await addCode(ioc, 'a=1\na'); - verifyHtmlOnCell(iw.mount.wrapper, 'InteractiveCell', '<span>1</span>', CellPosition.Last); + verifyHtmlOnCell(iw.mount.wrapper, 'InteractiveCell', '1', CellPosition.Last); }); test('Multiple interactive windows', async () => { ioc.forceDataScienceSettingsChanged({ interactiveWindowMode: 'multiple' }); diff --git a/src/test/datascience/liveshare.functional.test.tsx b/src/test/datascience/liveshare.functional.test.tsx index 0a8b6a0fa3dc..55d45c04742c 100644 --- a/src/test/datascience/liveshare.functional.test.tsx +++ b/src/test/datascience/liveshare.functional.test.tsx @@ -217,7 +217,7 @@ suite('DataScience LiveShare tests', () => { // Just run some code in the host const wrapper = await addCodeToRole(vsls.Role.Host, 'a=1\na'); - verifyHtmlOnCell(wrapper, 'InteractiveCell', '<span>1</span>', CellPosition.Last); + verifyHtmlOnCell(wrapper, 'InteractiveCell', '1', CellPosition.Last); }); test('Host & Guest Simple', async function () { @@ -234,14 +234,14 @@ suite('DataScience LiveShare tests', () => { // Send code through the host const wrapper = await addCodeToRole(vsls.Role.Host, 'a=1\na'); - verifyHtmlOnCell(wrapper, 'InteractiveCell', '<span>1</span>', CellPosition.Last); + verifyHtmlOnCell(wrapper, 'InteractiveCell', '1', CellPosition.Last); // Verify it ended up on the guest too assert.ok(guestContainer.getInteractiveWebPanel(undefined), 'Guest wrapper not created'); verifyHtmlOnCell( guestContainer.getInteractiveWebPanel(undefined).wrapper, 'InteractiveCell', - '<span>1</span>', + '1', CellPosition.Last ); }); @@ -253,7 +253,7 @@ suite('DataScience LiveShare tests', () => { addMockData(hostContainer!, 'b=2\nb', 2); await getOrCreateInteractiveWindow(vsls.Role.Host); let wrapper = await addCodeToRole(vsls.Role.Host, 'a=1\na'); - verifyHtmlOnCell(wrapper, 'InteractiveCell', '<span>1</span>', CellPosition.Last); + verifyHtmlOnCell(wrapper, 'InteractiveCell', '1', CellPosition.Last); await startSession(vsls.Role.Host); await getOrCreateInteractiveWindow(vsls.Role.Guest); @@ -265,7 +265,7 @@ suite('DataScience LiveShare tests', () => { verifyHtmlOnCell( guestContainer.getInteractiveWebPanel(undefined).wrapper, 'InteractiveCell', - '<span>2</span>', + '2', CellPosition.Last ); }); @@ -280,14 +280,14 @@ suite('DataScience LiveShare tests', () => { // Send code through the host let wrapper = await addCodeToRole(vsls.Role.Host, 'a=1\na'); - verifyHtmlOnCell(wrapper, 'InteractiveCell', '<span>1</span>', CellPosition.Last); + verifyHtmlOnCell(wrapper, 'InteractiveCell', '1', CellPosition.Last); // Stop the session await stopSession(vsls.Role.Host); // Send code again. It should still work. wrapper = await addCodeToRole(vsls.Role.Host, 'a=1\na'); - verifyHtmlOnCell(wrapper, 'InteractiveCell', '<span>1</span>', CellPosition.Last); + verifyHtmlOnCell(wrapper, 'InteractiveCell', '1', CellPosition.Last); }); test('Host startup and guest restart', async function () { @@ -302,7 +302,7 @@ suite('DataScience LiveShare tests', () => { // Send code through the host let wrapper = await addCodeToRole(vsls.Role.Host, 'a=1\na'); - verifyHtmlOnCell(wrapper, 'InteractiveCell', '<span>1</span>', CellPosition.Last); + verifyHtmlOnCell(wrapper, 'InteractiveCell', '1', CellPosition.Last); // Shutdown the host host.window.dispose(); @@ -310,13 +310,13 @@ suite('DataScience LiveShare tests', () => { // Startup a guest and run some code. await startSession(vsls.Role.Guest); wrapper = await addCodeToRole(vsls.Role.Guest, 'a=1\na'); - verifyHtmlOnCell(wrapper, 'InteractiveCell', '<span>1</span>', CellPosition.Last); + verifyHtmlOnCell(wrapper, 'InteractiveCell', '1', CellPosition.Last); assert.ok(hostContainer.getInteractiveWebPanel(undefined), 'Host wrapper not created'); verifyHtmlOnCell( hostContainer.getInteractiveWebPanel(undefined).wrapper, 'InteractiveCell', - '<span>1</span>', + '1', CellPosition.Last ); }); @@ -349,12 +349,12 @@ suite('DataScience LiveShare tests', () => { assert.ok(both, 'Expected both guest and host to be used'); await codeWatcher.runAllCells(); }); - verifyHtmlOnCell(wrapper, 'InteractiveCell', '<span>1</span>', CellPosition.Last); + verifyHtmlOnCell(wrapper, 'InteractiveCell', '1', CellPosition.Last); assert.ok(hostContainer.getInteractiveWebPanel(undefined), 'Host wrapper not created for some reason'); verifyHtmlOnCell( hostContainer.getInteractiveWebPanel(undefined).wrapper, 'InteractiveCell', - '<span>1</span>', + '1', CellPosition.Last ); }); @@ -425,7 +425,7 @@ suite('DataScience LiveShare tests', () => { // Start just the host and verify it works await startSession(vsls.Role.Host); let wrapper = await addCodeToRole(vsls.Role.Host, '#%%\na=1\na'); - verifyHtmlOnCell(wrapper, 'InteractiveCell', '<span>1</span>', CellPosition.Last); + verifyHtmlOnCell(wrapper, 'InteractiveCell', '1', CellPosition.Last); // Disable guest checking on the guest (same as if the guest doesn't have the python extension) await startSession(vsls.Role.Guest); @@ -434,7 +434,7 @@ suite('DataScience LiveShare tests', () => { // Host should now be in a state that if any code runs, the session should end. However // the code should still run wrapper = await addCodeToRole(vsls.Role.Host, '#%%\na=1\na'); - verifyHtmlOnCell(wrapper, 'InteractiveCell', '<span>1</span>', CellPosition.Last); + verifyHtmlOnCell(wrapper, 'InteractiveCell', '1', CellPosition.Last); assert.equal(isSessionStarted(vsls.Role.Host), false, 'Host should have exited session'); assert.equal(isSessionStarted(vsls.Role.Guest), false, 'Guest should have exited session'); assert.ok(lastErrorMessage, 'Error was not set during session shutdown'); diff --git a/src/test/datascience/nativeEditor.functional.test.tsx b/src/test/datascience/nativeEditor.functional.test.tsx index f2ec387bd364..82e4f21ad91a 100644 --- a/src/test/datascience/nativeEditor.functional.test.tsx +++ b/src/test/datascience/nativeEditor.functional.test.tsx @@ -223,7 +223,7 @@ suite('DataScience Native Editor', () => { // Add a cell into the UI and wait for it to render await addCell(mount, 'a=1\na'); - verifyHtmlOnCell(mount.wrapper, 'NativeCell', '<span>1</span>', 1); + verifyHtmlOnCell(mount.wrapper, 'NativeCell', '1', 1); }); runMountedTest('Invalid session still runs', async (context) => { @@ -238,7 +238,7 @@ suite('DataScience Native Editor', () => { // Run the first cell. Should fail but then ask for another await addCell(mount, 'a=1\na'); - verifyHtmlOnCell(mount.wrapper, 'NativeCell', '<span>1</span>', 1); + verifyHtmlOnCell(mount.wrapper, 'NativeCell', '1', 1); } else { context.skip(); } @@ -414,7 +414,7 @@ suite('DataScience Native Editor', () => { // Run the first cell. Should fail but then ask for another await addCell(ne.mount, 'a=1\na'); - verifyHtmlOnCell(ne.mount.wrapper, 'NativeCell', '<span>1</span>', 1); + verifyHtmlOnCell(ne.mount.wrapper, 'NativeCell', '1', 1); } else { context.skip(); } @@ -448,7 +448,7 @@ suite('DataScience Native Editor', () => { // Verify we picked the valid kernel. await addCell(ne.mount, 'a=1\na'); - verifyHtmlOnCell(ne.mount.wrapper, 'NativeCell', '<span>1</span>', 2); + verifyHtmlOnCell(ne.mount.wrapper, 'NativeCell', '1', 2); } else { context.skip(); } @@ -1694,7 +1694,7 @@ df.head()`; wrapper.update(); // Ensure cell was executed. - verifyHtmlOnCell(wrapper, 'NativeCell', '<span>2</span>', 1); + verifyHtmlOnCell(wrapper, 'NativeCell', '2', 1); // The third cell should be selected. assert.ok(isCellSelected(wrapper, 'NativeCell', 2)); @@ -1732,7 +1732,7 @@ df.head()`; await update; // Ensure cell was executed. - verifyHtmlOnCell(wrapper, 'NativeCell', '<span>2</span>', 1); + verifyHtmlOnCell(wrapper, 'NativeCell', '2', 1); // The first cell should be selected. assert.ok(isCellSelected(wrapper, 'NativeCell', 1)); @@ -1962,7 +1962,7 @@ df.head()`; await update; // Ensure cell was executed. - verifyHtmlOnCell(wrapper, 'NativeCell', '<span>3</span>', 2); + verifyHtmlOnCell(wrapper, 'NativeCell', '3', 2); // Hide the output update = waitForMessage(ioc, InteractiveWindowMessages.OutputToggled); @@ -1970,7 +1970,7 @@ df.head()`; await update; // Ensure cell output is hidden (looking for cell results will throw an exception). - assert.throws(() => verifyHtmlOnCell(wrapper, 'NativeCell', '<span>3</span>', 2)); + assert.throws(() => verifyHtmlOnCell(wrapper, 'NativeCell', '3', 2)); // Display the output update = waitForMessage(ioc, InteractiveWindowMessages.OutputToggled); @@ -1978,7 +1978,7 @@ df.head()`; await update; // Ensure cell output is visible again. - verifyHtmlOnCell(wrapper, 'NativeCell', '<span>3</span>', 2); + verifyHtmlOnCell(wrapper, 'NativeCell', '3', 2); }); test("Toggle line numbers using the 'l' key", async () => { diff --git a/src/test/datascience/notebook.functional.test.ts b/src/test/datascience/notebook.functional.test.ts index 5e89e9f675bd..aeec51594400 100644 --- a/src/test/datascience/notebook.functional.test.ts +++ b/src/test/datascience/notebook.functional.test.ts @@ -6,6 +6,8 @@ import { assert } from 'chai'; import { ChildProcess } from 'child_process'; import * as fs from 'fs-extra'; import { injectable } from 'inversify'; +// tslint:disable-next-line: no-require-imports +import escape = require('lodash/escape'); import * as os from 'os'; import * as path from 'path'; import { SemVer } from 'semver'; @@ -159,7 +161,7 @@ suite('DataScience notebook tests', () => { const data = extractDataOutput(cells[0]); if (pathVerify) { // For a path comparison normalize output - const normalizedOutput = path.normalize(data).toUpperCase().replace(/'/g, ''); + const normalizedOutput = path.normalize(data).toUpperCase().replace(/&#39;/g, ''); const normalizedTarget = path.normalize(expectedValue).toUpperCase().replace(/'/g, ''); assert.equal(normalizedOutput, normalizedTarget, 'Cell path values does not match'); } else { @@ -1022,6 +1024,15 @@ a`, result: 1, verifyValue: (d) => assert.equal(d, 1, 'Plain text invalid') }, + { + markdownRegEx: undefined, + code: `a="<a href=f>" +a`, + mimeType: 'text/plain', + cellType: 'code', + result: `<a href=f>`, + verifyValue: (d) => assert.equal(d, escape(`<a href=f>`), 'XML not escaped') + }, { markdownRegEx: undefined, code: `import pandas as pd @@ -1032,7 +1043,7 @@ df.head()`, cellType: 'error', // tslint:disable-next-line:quotemark verifyValue: (d) => - assert.ok((d as string).includes("has no attribute 'read'"), 'Unexpected error result') + assert.ok((d as string).includes(escape("has no attribute 'read'")), 'Unexpected error result') }, { markdownRegEx: undefined, diff --git a/src/test/datascience/trustedNotebooks.functional.test.tsx b/src/test/datascience/trustedNotebooks.functional.test.tsx index 57d5daef5269..302ad724e214 100644 --- a/src/test/datascience/trustedNotebooks.functional.test.tsx +++ b/src/test/datascience/trustedNotebooks.functional.test.tsx @@ -273,9 +273,9 @@ suite('Notebook trust', () => { suite('Open an untrusted notebook', async () => { test('Outputs are not rendered', () => { // No outputs should have rendered - assert.throws(() => verifyHtmlOnCell(wrapper, 'NativeCell', '<span>1</span>', 0)); - assert.throws(() => verifyHtmlOnCell(wrapper, 'NativeCell', '<span>2</span>', 1)); - assert.throws(() => verifyHtmlOnCell(wrapper, 'NativeCell', '<span>3</span>', 2)); + assert.throws(() => verifyHtmlOnCell(wrapper, 'NativeCell', '1', 0)); + assert.throws(() => verifyHtmlOnCell(wrapper, 'NativeCell', '2', 1)); + assert.throws(() => verifyHtmlOnCell(wrapper, 'NativeCell', '3', 2)); }); test('Cannot edit cell contents', async () => { await focusCell(0); @@ -332,7 +332,7 @@ suite('Notebook trust', () => { // Waiting for an execution rendered message should timeout await expect(promise).to.eventually.be.rejected; // No output should have been rendered - assert.throws(() => verifyHtmlOnCell(wrapper, 'NativeCell', '<span>2</span>', cellIndex)); + assert.throws(() => verifyHtmlOnCell(wrapper, 'NativeCell', '2', cellIndex)); }); test('Shift+enter does not execute cell or advance to next cell', async () => { const cellIndex = 1; @@ -344,7 +344,7 @@ suite('Notebook trust', () => { // Waiting for an execution rendered message should timeout await expect(promise).to.eventually.be.rejected; // No output should have been rendered - assert.throws(() => verifyHtmlOnCell(wrapper, 'NativeCell', '<span>2</span>', cellIndex)); + assert.throws(() => verifyHtmlOnCell(wrapper, 'NativeCell', '2', cellIndex)); // 3rd cell should be neither selected nor focused assert.isFalse(isCellSelected(wrapper, 'NativeCell', cellIndex + 1)); assert.isFalse(isCellFocused(wrapper, 'NativeCell', cellIndex + 1)); @@ -360,7 +360,7 @@ suite('Notebook trust', () => { // Waiting for an execution rendered message should timeout await expect(promise).to.eventually.be.rejected; // No output should have been rendered - assert.throws(() => verifyHtmlOnCell(wrapper, 'NativeCell', '<span>2</span>', cellIndex)); + assert.throws(() => verifyHtmlOnCell(wrapper, 'NativeCell', '2', cellIndex)); // No cell should have been added assert.equal(wrapper.find('NativeCell').length, 3, 'Cell added'); }); diff --git a/src/test/datascience/uiTests/ipywidget.ui.functional.test.ts b/src/test/datascience/uiTests/ipywidget.ui.functional.test.ts index f6916d491134..8c5850e37309 100644 --- a/src/test/datascience/uiTests/ipywidget.ui.functional.test.ts +++ b/src/test/datascience/uiTests/ipywidget.ui.functional.test.ts @@ -155,7 +155,7 @@ use(chaiAsPromised); await retryIfFail(async () => { await assert.eventually.isTrue(notebookUI.cellHasOutput(0)); const outputHtml = await notebookUI.getCellOutputHTML(0); - assert.include(outputHtml, '<span>1</span>'); + assert.include(outputHtml, '1'); }); }); From 5065d31530081d34b201d9deb089715986d499fe Mon Sep 17 00:00:00 2001 From: David Kutugata <dakutuga@microsoft.com> Date: Mon, 28 Sep 2020 13:34:00 -0700 Subject: [PATCH 11/24] update version and changelog (#14139) --- CHANGELOG.md | 4 +++- package-lock.json | 2 +- package.json | 2 +- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4d212c9f9067..b1fb41aa0ef2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,9 +4,11 @@ ### Fixes +1. Fix IPyKernel install issue with windows paths. + ([#13493](https://github.com/microsoft/vscode-python/issues/13493)) 1. Fix escaping of output to encode HTML chars correctly. ([#5678](https://github.com/Microsoft/vscode-python/issues/5678)) - + ### Thanks Thanks to the following projects which we fully rely on to provide some of diff --git a/package-lock.json b/package-lock.json index 02c8932d6c64..e57358bdbf7e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "python", - "version": "2020.9.0", + "version": "2020.9.1", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index b34f0ee31e6b..5cec5744359e 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "python", "displayName": "Python", "description": "Linting, Debugging (multi-threaded, remote), Intellisense, Jupyter Notebooks, code formatting, refactoring, unit tests, snippets, and more.", - "version": "2020.9.0", + "version": "2020.9.1", "featureFlags": { "usingNewInterpreterStorage": true }, From f276e07b47b454b6aa91d14d74ac86a14272bef2 Mon Sep 17 00:00:00 2001 From: Rich Chiodo <rchiodo@users.noreply.github.com> Date: Tue, 29 Sep 2020 10:30:38 -0700 Subject: [PATCH 12/24] Escaping fix broke a number of things (#14145) (#14154) * Fixes for escaping * Push a comment ot start PR again * Cache task is failing * Remove cache task * Not fixing so just put back cache task --- src/client/datascience/jupyter/jupyterDebugger.ts | 6 +++++- src/datascience-ui/interactive-common/cellOutput.tsx | 7 ++++++- src/test/datascience/notebook.functional.test.ts | 6 +++--- 3 files changed, 14 insertions(+), 5 deletions(-) diff --git a/src/client/datascience/jupyter/jupyterDebugger.ts b/src/client/datascience/jupyter/jupyterDebugger.ts index afa7df942137..796dd18ab771 100644 --- a/src/client/datascience/jupyter/jupyterDebugger.ts +++ b/src/client/datascience/jupyter/jupyterDebugger.ts @@ -3,6 +3,8 @@ 'use strict'; import type { nbformat } from '@jupyterlab/coreutils'; import { inject, injectable, named } from 'inversify'; +// tslint:disable-next-line: no-require-imports +import unescape = require('lodash/unescape'); import * as path from 'path'; import * as uuid from 'uuid/v4'; import { DebugConfiguration, Disposable } from 'vscode'; @@ -473,8 +475,10 @@ export class JupyterDebugger implements IJupyterDebugger, ICellHashListener { if (outputs.length > 0) { const data = outputs[0].data; if (data && data.hasOwnProperty('text/plain')) { + // Plain text should be escaped by our execution engine. Unescape it so + // we can parse it. // tslint:disable-next-line:no-any - return (data as any)['text/plain']; + return unescape((data as any)['text/plain']); } if (outputs[0].output_type === 'stream') { const stream = outputs[0] as nbformat.IStream; diff --git a/src/datascience-ui/interactive-common/cellOutput.tsx b/src/datascience-ui/interactive-common/cellOutput.tsx index b9a9d5bb2eee..8b545eac7086 100644 --- a/src/datascience-ui/interactive-common/cellOutput.tsx +++ b/src/datascience-ui/interactive-common/cellOutput.tsx @@ -314,7 +314,12 @@ export class CellOutput extends React.Component<ICellOutputProps> { input = JSON.stringify(output.data); renderWithScrollbars = true; isText = true; - } else if (output.output_type === 'execute_result' && input && input.hasOwnProperty('text/plain')) { + } else if ( + output.output_type === 'execute_result' && + input && + input.hasOwnProperty('text/plain') && + !input.hasOwnProperty('text/html') + ) { // Plain text should actually be shown as html so that escaped HTML shows up correctly mimeType = 'text/html'; isText = true; diff --git a/src/test/datascience/notebook.functional.test.ts b/src/test/datascience/notebook.functional.test.ts index aeec51594400..b8ff1d720cbd 100644 --- a/src/test/datascience/notebook.functional.test.ts +++ b/src/test/datascience/notebook.functional.test.ts @@ -182,7 +182,7 @@ suite('DataScience notebook tests', () => { const error = cell.outputs[0].evalue; if (error) { assert.ok(error, 'Error not found when expected'); - assert.equal(error, errorString, 'Unexpected error found'); + assert.ok(error.toString().includes(errorString), 'Unexpected error found'); } } @@ -757,7 +757,7 @@ suite('DataScience notebook tests', () => { await server!.waitForIdle(10000); console.log('Verifying restart'); - await verifyError(server, 'a', `name 'a' is not defined`); + await verifyError(server, 'a', `is not defined`); } catch (exc) { assert.ok( exc instanceof JupyterKernelPromiseFailedError, @@ -1031,7 +1031,7 @@ a`, mimeType: 'text/plain', cellType: 'code', result: `<a href=f>`, - verifyValue: (d) => assert.equal(d, escape(`<a href=f>`), 'XML not escaped') + verifyValue: (d) => assert.ok(d.includes(escape(`<a href=f>`)), 'XML not escaped') }, { markdownRegEx: undefined, From 2e129b4b6e238e54269ebea67a0388a501f9cba4 Mon Sep 17 00:00:00 2001 From: Ian Huff <ianhu@microsoft.com> Date: Thu, 1 Oct 2020 12:10:59 -0700 Subject: [PATCH 13/24] Port NB Convert Fix to point release branch (#14200) --- CHANGELOG.md | 62 +++++++++++++++++++ package.nls.json | 2 +- src/client/common/utils/localize.ts | 2 +- src/client/datascience/common.ts | 12 ++++ src/client/datascience/constants.ts | 2 +- .../dataViewerDependencyService.ts | 12 ++-- .../export/exportDependencyChecker.ts | 4 +- .../jupyterInterpreterDependencyService.ts | 19 ++++++ ...erInterpreterSubCommandExecutionService.ts | 23 +++++-- .../datascience/jupyter/jupyterExecution.ts | 5 +- .../jupyter/jupyterExecutionFactory.ts | 5 +- .../datascience/jupyter/jupyterImporter.ts | 47 +++++++++----- .../liveshare/guestJupyterExecution.ts | 36 ++++++----- .../jupyter/liveshare/hostJupyterExecution.ts | 6 +- src/client/datascience/types.ts | 12 ++-- src/test/datascience/execution.unit.test.ts | 14 ++++- ...terSubCommandExecutionService.unit.test.ts | 4 +- .../datascience/notebook.functional.test.ts | 2 +- 18 files changed, 204 insertions(+), 65 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b1fb41aa0ef2..f2923a846854 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,67 @@ # Changelog +## 2020.9.2 (1 October 2020) + +### Fixes + +1. Support nbconvert version 6+ for exporting notebooks to python code. + ([#14169](https://github.com/Microsoft/vscode-python/issues/14169)) + +### Thanks + +Thanks to the following projects which we fully rely on to provide some of +our features: + +- [debugpy](https://pypi.org/project/debugpy/) +- [isort](https://pypi.org/project/isort/) +- [jedi](https://pypi.org/project/jedi/) + and [parso](https://pypi.org/project/parso/) +- [Microsoft Python Language Server](https://github.com/microsoft/python-language-server) +- [Pylance](https://github.com/microsoft/pylance-release) +- [exuberant ctags](http://ctags.sourceforge.net/) (user-installed) +- [rope](https://pypi.org/project/rope/) (user-installed) + +Also thanks to the various projects we provide integrations with which help +make this extension useful: + +- Debugging support: + [Django](https://pypi.org/project/Django/), + [Flask](https://pypi.org/project/Flask/), + [gevent](https://pypi.org/project/gevent/), + [Jinja](https://pypi.org/project/Jinja/), + [Pyramid](https://pypi.org/project/pyramid/), + [PySpark](https://pypi.org/project/pyspark/), + [Scrapy](https://pypi.org/project/Scrapy/), + [Watson](https://pypi.org/project/Watson/) +- Formatting: + [autopep8](https://pypi.org/project/autopep8/), + [black](https://pypi.org/project/black/), + [yapf](https://pypi.org/project/yapf/) +- Interpreter support: + [conda](https://conda.io/), + [direnv](https://direnv.net/), + [pipenv](https://pypi.org/project/pipenv/), + [pyenv](https://github.com/pyenv/pyenv), + [venv](https://docs.python.org/3/library/venv.html#module-venv), + [virtualenv](https://pypi.org/project/virtualenv/) +- Linting: + [bandit](https://pypi.org/project/bandit/), + [flake8](https://pypi.org/project/flake8/), + [mypy](https://pypi.org/project/mypy/), + [prospector](https://pypi.org/project/prospector/), + [pylint](https://pypi.org/project/pylint/), + [pydocstyle](https://pypi.org/project/pydocstyle/), + [pylama](https://pypi.org/project/pylama/) +- Testing: + [nose](https://pypi.org/project/nose/), + [pytest](https://pypi.org/project/pytest/), + [unittest](https://docs.python.org/3/library/unittest.html#module-unittest) + +And finally thanks to the [Python](https://www.python.org/) development team and +community for creating a fantastic programming language and community to be a +part of! + + ## 2020.9.1 (29 September 2020) ### Fixes diff --git a/package.nls.json b/package.nls.json index b1b7007f43d8..d916bd2600e3 100644 --- a/package.nls.json +++ b/package.nls.json @@ -31,7 +31,7 @@ "DataScience.openExportFileYes": "Yes", "DataScience.openExportFileNo": "No", "DataScience.failedExportMessage": "Export failed.", - "DataScience.exportFailedGeneralMessage": "Export failed. Please check the 'Python' [output](command:python.viewOutput) panel for further details.", + "DataScience.exportFailedGeneralMessage": "Please check the 'Python' [output](command:python.viewOutput) panel for further details.", "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.", "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)", "DataScience.launchNotebookTrustPrompt.yes": "Trust", diff --git a/src/client/common/utils/localize.ts b/src/client/common/utils/localize.ts index 4a4981f8ae63..089cb95d873e 100644 --- a/src/client/common/utils/localize.ts +++ b/src/client/common/utils/localize.ts @@ -892,7 +892,7 @@ export namespace DataScience { export const openExportFileNo = localize('DataScience.openExportFileNo', 'No'); export const exportFailedGeneralMessage = localize( 'DataScience.exportFailedGeneralMessage', - `Export failed. Please check the 'Python' [output](command:python.viewOutput) panel for further details.` + `Please check the 'Python' [output](command:python.viewOutput) panel for further details.` ); export const exportToPDFDependencyMessage = localize( 'DataScience.exportToPDFDependencyMessage', diff --git a/src/client/datascience/common.ts b/src/client/datascience/common.ts index efa0a104d7e7..06b1da13b837 100644 --- a/src/client/datascience/common.ts +++ b/src/client/datascience/common.ts @@ -3,6 +3,7 @@ 'use strict'; import type { nbformat } from '@jupyterlab/coreutils'; import * as os from 'os'; +import { parse, SemVer } from 'semver'; import { Memento, Uri } from 'vscode'; import { splitMultilineString } from '../../datascience-ui/common'; import { traceError, traceInfo } from '../common/logger'; @@ -188,3 +189,14 @@ export async function getRealPath( } } } + +// For the given string parse it out to a SemVer or return undefined +export function parseSemVer(versionString: string): SemVer | undefined { + const versionMatch = /^\s*(\d+)\.(\d+)\.(.+)\s*$/.exec(versionString); + if (versionMatch && versionMatch.length > 2) { + const major = parseInt(versionMatch[1], 10); + const minor = parseInt(versionMatch[2], 10); + const build = parseInt(versionMatch[3], 10); + return parse(`${major}.${minor}.${build}`, true) ?? undefined; + } +} diff --git a/src/client/datascience/constants.ts b/src/client/datascience/constants.ts index f168c60438e5..ee17840a2c39 100644 --- a/src/client/datascience/constants.ts +++ b/src/client/datascience/constants.ts @@ -569,7 +569,7 @@ export namespace LiveShare { export namespace LiveShareCommands { export const isNotebookSupported = 'isNotebookSupported'; - export const isImportSupported = 'isImportSupported'; + export const getImportPackageVersion = 'getImportPackageVersion'; export const connectToNotebookServer = 'connectToNotebookServer'; export const getUsableJupyterPython = 'getUsableJupyterPython'; export const executeObservable = 'executeObservable'; diff --git a/src/client/datascience/data-viewing/dataViewerDependencyService.ts b/src/client/datascience/data-viewing/dataViewerDependencyService.ts index 90bf35756a81..67e2dc1d1224 100644 --- a/src/client/datascience/data-viewing/dataViewerDependencyService.ts +++ b/src/client/datascience/data-viewing/dataViewerDependencyService.ts @@ -4,7 +4,7 @@ 'use strict'; import { inject, injectable } from 'inversify'; -import { parse, SemVer } from 'semver'; +import { SemVer } from 'semver'; import { CancellationToken } from 'vscode'; import { IApplicationShell } from '../../common/application/types'; import { Cancellation, createPromiseFromCancellation, wrapCancellationTokens } from '../../common/cancellation'; @@ -14,6 +14,7 @@ import { IInstaller, InstallerResponse, Product } from '../../common/types'; import { Common, DataScience } from '../../common/utils/localize'; import { PythonEnvironment } from '../../pythonEnvironments/info'; import { sendTelemetryEvent } from '../../telemetry'; +import { parseSemVer } from '../common'; import { Telemetry } from '../constants'; const minimumSupportedPandaVersion = '0.20.0'; @@ -104,13 +105,8 @@ export class DataViewerDependencyService { throwOnStdErr: true, token }); - const versionMatch = /^\s*(\d+)\.(\d+)\.(.+)\s*$/.exec(result.stdout); - if (versionMatch && versionMatch.length > 2) { - const major = parseInt(versionMatch[1], 10); - const minor = parseInt(versionMatch[2], 10); - const build = parseInt(versionMatch[3], 10); - return parse(`${major}.${minor}.${build}`, true) ?? undefined; - } + + return parseSemVer(result.stdout); } catch (ex) { traceWarning('Failed to get version of Pandas to use Data Viewer', ex); return; diff --git a/src/client/datascience/export/exportDependencyChecker.ts b/src/client/datascience/export/exportDependencyChecker.ts index 0ec028999e51..e357d63ed020 100644 --- a/src/client/datascience/export/exportDependencyChecker.ts +++ b/src/client/datascience/export/exportDependencyChecker.ts @@ -17,9 +17,9 @@ export class ExportDependencyChecker { // Before we try the import, see if we don't support it, if we don't give a chance to install dependencies const reporter = this.progressReporter.createProgressIndicator(`Exporting to ${format}`); try { - if (!(await this.jupyterExecution.isImportSupported())) { + if (!(await this.jupyterExecution.getImportPackageVersion())) { await this.dependencyManager.installMissingDependencies(); - if (!(await this.jupyterExecution.isImportSupported())) { + if (!(await this.jupyterExecution.getImportPackageVersion())) { throw new Error(localize.DataScience.jupyterNbConvertNotSupported()); } } diff --git a/src/client/datascience/jupyter/interpreter/jupyterInterpreterDependencyService.ts b/src/client/datascience/jupyter/interpreter/jupyterInterpreterDependencyService.ts index d951338264bf..c93626f704d1 100644 --- a/src/client/datascience/jupyter/interpreter/jupyterInterpreterDependencyService.ts +++ b/src/client/datascience/jupyter/interpreter/jupyterInterpreterDependencyService.ts @@ -4,6 +4,7 @@ 'use strict'; import { inject, injectable } from 'inversify'; +import { SemVer } from 'semver'; import { CancellationToken } from 'vscode'; import { IApplicationShell } from '../../../common/application/types'; import { Cancellation, createPromiseFromCancellation, wrapCancellationTokens } from '../../../common/cancellation'; @@ -14,6 +15,7 @@ import { Common, DataScience } from '../../../common/utils/localize'; import { noop } from '../../../common/utils/misc'; import { PythonEnvironment } from '../../../pythonEnvironments/info'; import { sendTelemetryEvent } from '../../../telemetry'; +import { parseSemVer } from '../../common'; import { HelpLinks, JupyterCommands, Telemetry } from '../../constants'; import { reportAction } from '../../progress/decorator'; import { ReportableAction } from '../../progress/types'; @@ -241,6 +243,23 @@ export class JupyterInterpreterDependencyService { return installed; } + public async getNbConvertVersion( + interpreter: PythonEnvironment, + _token?: CancellationToken + ): Promise<SemVer | undefined> { + const command = this.commandFactory.createInterpreterCommand( + JupyterCommands.ConvertCommand, + 'jupyter', + ['-m', 'jupyter', 'nbconvert'], + interpreter, + false + ); + + const result = await command.exec(['--version'], { throwOnStdErr: true }); + + return parseSemVer(result.stdout); + } + /** * Gets a list of the dependencies not installed, dependencies that are required to launch the jupyter notebook server. * diff --git a/src/client/datascience/jupyter/interpreter/jupyterInterpreterSubCommandExecutionService.ts b/src/client/datascience/jupyter/interpreter/jupyterInterpreterSubCommandExecutionService.ts index e65c08a4c544..905cd6eb42bc 100644 --- a/src/client/datascience/jupyter/interpreter/jupyterInterpreterSubCommandExecutionService.ts +++ b/src/client/datascience/jupyter/interpreter/jupyterInterpreterSubCommandExecutionService.ts @@ -5,6 +5,7 @@ import { inject, injectable, named } from 'inversify'; import * as path from 'path'; +import { SemVer } from 'semver'; import { CancellationToken, Uri } from 'vscode'; import { Cancellation } from '../../../common/cancellation'; import { traceError, traceInfo, traceWarning } from '../../../common/logger'; @@ -76,12 +77,16 @@ export class JupyterInterpreterSubCommandExecutionService } return this.jupyterDependencyService.areDependenciesInstalled(interpreter, token); } - public async isExportSupported(token?: CancellationToken): Promise<boolean> { + public async getExportPackageVersion(token?: CancellationToken): Promise<SemVer | undefined> { const interpreter = await this.jupyterInterpreter.getSelectedInterpreter(token); if (!interpreter) { - return false; + return; + } + + // If nbconvert is there check and return the version + if (await this.jupyterDependencyService.isExportSupported(interpreter, token)) { + return this.jupyterDependencyService.getNbConvertVersion(interpreter, token); } - return this.jupyterDependencyService.isExportSupported(interpreter, token); } public async getReasonForJupyterNotebookNotBeingSupported(token?: CancellationToken): Promise<string> { let interpreter = await this.jupyterInterpreter.getSelectedInterpreter(token); @@ -176,11 +181,21 @@ export class JupyterInterpreterSubCommandExecutionService const args = template ? [file.fsPath, '--to', 'python', '--stdout', '--template', template] : [file.fsPath, '--to', 'python', '--stdout']; + // Ignore stderr, as nbconvert writes conversion result to stderr. // stdout contains the generated python code. return daemon .execModule('jupyter', ['nbconvert'].concat(args), { throwOnStdErr: false, encoding: 'utf8', token }) - .then((output) => output.stdout); + .then((output) => { + // We can't check stderr (as nbconvert puts diag output there) but we need to verify here that we actually + // converted something. If it's zero size then just raise an error + if (output.stdout === '') { + traceError('nbconvert zero size output'); + throw new Error(output.stderr); + } else { + return output.stdout; + } + }); } public async openNotebook(notebookFile: string): Promise<void> { const interpreter = await this.getSelectedInterpreterAndThrowIfNotAvailable(); diff --git a/src/client/datascience/jupyter/jupyterExecution.ts b/src/client/datascience/jupyter/jupyterExecution.ts index 476685dff607..b0f3b495bf06 100644 --- a/src/client/datascience/jupyter/jupyterExecution.ts +++ b/src/client/datascience/jupyter/jupyterExecution.ts @@ -2,6 +2,7 @@ // Licensed under the MIT License. 'use strict'; import * as path from 'path'; +import { SemVer } from 'semver'; import * as uuid from 'uuid/v4'; import { CancellationToken, CancellationTokenSource, Event, EventEmitter, Uri } from 'vscode'; @@ -123,9 +124,9 @@ export class JupyterExecutionBase implements IJupyterExecution { } @reportAction(ReportableAction.CheckingIfImportIsSupported) - public async isImportSupported(cancelToken?: CancellationToken): Promise<boolean> { + public async getImportPackageVersion(cancelToken?: CancellationToken): Promise<SemVer | undefined> { // See if we can find the command nbconvert - return this.jupyterInterpreterService.isExportSupported(cancelToken); + return this.jupyterInterpreterService.getExportPackageVersion(cancelToken); } public isSpawnSupported(cancelToken?: CancellationToken): Promise<boolean> { diff --git a/src/client/datascience/jupyter/jupyterExecutionFactory.ts b/src/client/datascience/jupyter/jupyterExecutionFactory.ts index 9888b3147ab3..a2d2b636e92b 100644 --- a/src/client/datascience/jupyter/jupyterExecutionFactory.ts +++ b/src/client/datascience/jupyter/jupyterExecutionFactory.ts @@ -2,6 +2,7 @@ // Licensed under the MIT License. 'use strict'; import { inject, injectable, named } from 'inversify'; +import { SemVer } from 'semver'; import { CancellationToken, Event, EventEmitter, Uri } from 'vscode'; import { IApplicationShell, ILiveShareApi, IWorkspaceService } from '../../common/application/types'; @@ -117,9 +118,9 @@ export class JupyterExecutionFactory implements IJupyterExecution, IAsyncDisposa return execution.getNotebookError(); } - public async isImportSupported(cancelToken?: CancellationToken): Promise<boolean> { + public async getImportPackageVersion(cancelToken?: CancellationToken): Promise<SemVer | undefined> { const execution = await this.executionFactory.get(); - return execution.isImportSupported(cancelToken); + return execution.getImportPackageVersion(cancelToken); } public async isSpawnSupported(cancelToken?: CancellationToken): Promise<boolean> { const execution = await this.executionFactory.get(); diff --git a/src/client/datascience/jupyter/jupyterImporter.ts b/src/client/datascience/jupyter/jupyterImporter.ts index cc0016992b83..eaecc62e2d9e 100644 --- a/src/client/datascience/jupyter/jupyterImporter.ts +++ b/src/client/datascience/jupyter/jupyterImporter.ts @@ -26,11 +26,11 @@ import { export class JupyterImporter implements INotebookImporter { public isDisposed: boolean = false; // Template that changes markdown cells to have # %% [markdown] in the comments - private readonly nbconvertTemplateFormat = + private readonly nbconvertBaseTemplateFormat = // tslint:disable-next-line:no-multiline-string - `{%- extends 'null.tpl' -%} + `{%- extends '{0}' -%} {% block codecell %} -{0} +{1} {{ super() }} {% endblock codecell %} {% block in_prompt %}{% endblock in_prompt %} @@ -38,8 +38,10 @@ export class JupyterImporter implements INotebookImporter { {% block markdowncell scoped %}{0} [markdown] {{ cell.source | comment_lines }} {% endblock markdowncell %}`; - - private templatePromise: Promise<string | undefined>; + private readonly nbconvert5Null = 'null.tpl'; + private readonly nbconvert6Null = 'base/null.j2'; + private template5Promise?: Promise<string | undefined>; + private template6Promise?: Promise<string | undefined>; constructor( @inject(IDataScienceFileSystem) private fs: IDataScienceFileSystem, @@ -50,13 +52,9 @@ export class JupyterImporter implements INotebookImporter { @inject(IPlatformService) private readonly platform: IPlatformService, @inject(IJupyterInterpreterDependencyManager) private readonly dependencyManager: IJupyterInterpreterDependencyManager - ) { - this.templatePromise = this.createTemplateFile(); - } + ) {} public async importFromFile(sourceFile: Uri): Promise<string> { - const template = await this.templatePromise; - // If the user has requested it, add a cd command to the imported file so that relative paths still work const settings = this.configuration.getSettings(); let directoryChange: string | undefined; @@ -65,12 +63,30 @@ export class JupyterImporter implements INotebookImporter { } // Before we try the import, see if we don't support it, if we don't give a chance to install dependencies - if (!(await this.jupyterExecution.isImportSupported())) { + if (!(await this.jupyterExecution.getImportPackageVersion())) { await this.dependencyManager.installMissingDependencies(); } + const nbConvertVersion = await this.jupyterExecution.getImportPackageVersion(); // Use the jupyter nbconvert functionality to turn the notebook into a python file - if (await this.jupyterExecution.isImportSupported()) { + if (nbConvertVersion) { + // nbconvert 5 and 6 use a different base template file + // Create and select the correct one + let template: string | undefined; + if (nbConvertVersion.major >= 6) { + if (!this.template6Promise) { + this.template6Promise = this.createTemplateFile(true); + } + + template = await this.template6Promise; + } else { + if (!this.template5Promise) { + this.template5Promise = this.createTemplateFile(false); + } + + template = await this.template5Promise; + } + let fileOutput: string = await this.jupyterExecution.importNotebook(sourceFile, template); if (fileOutput.includes('get_ipython()')) { fileOutput = this.addIPythonImport(fileOutput); @@ -153,7 +169,7 @@ export class JupyterImporter implements INotebookImporter { } } - private async createTemplateFile(): Promise<string | undefined> { + private async createTemplateFile(nbconvert6: boolean): Promise<string | undefined> { // Create a temp file on disk const file = await this.fs.createTemporaryLocalFile('.tpl'); @@ -164,7 +180,10 @@ export class JupyterImporter implements INotebookImporter { this.disposableRegistry.push(file); await this.fs.appendLocalFile( file.filePath, - this.nbconvertTemplateFormat.format(this.defaultCellMarker) + this.nbconvertBaseTemplateFormat.format( + nbconvert6 ? this.nbconvert6Null : this.nbconvert5Null, + this.defaultCellMarker + ) ); // Now we should have a template that will convert diff --git a/src/client/datascience/jupyter/liveshare/guestJupyterExecution.ts b/src/client/datascience/jupyter/liveshare/guestJupyterExecution.ts index 6e585661ed32..3ebcf0606669 100644 --- a/src/client/datascience/jupyter/liveshare/guestJupyterExecution.ts +++ b/src/client/datascience/jupyter/liveshare/guestJupyterExecution.ts @@ -2,6 +2,7 @@ // Licensed under the MIT License. 'use strict'; import { injectable } from 'inversify'; +import { SemVer } from 'semver'; import * as uuid from 'uuid/v4'; import { CancellationToken } from 'vscode'; @@ -72,10 +73,27 @@ export class GuestJupyterExecution extends LiveShareParticipantGuest( } public async isNotebookSupported(cancelToken?: CancellationToken): Promise<boolean> { - return this.checkSupported(LiveShareCommands.isNotebookSupported, cancelToken); + const service = await this.waitForService(); + + // Make a remote call on the proxy + if (service) { + const result = await service.request(LiveShareCommands.isNotebookSupported, [], cancelToken); + return result as boolean; + } + + return false; } - public isImportSupported(cancelToken?: CancellationToken): Promise<boolean> { - return this.checkSupported(LiveShareCommands.isImportSupported, cancelToken); + public async getImportPackageVersion(cancelToken?: CancellationToken): Promise<SemVer | undefined> { + const service = await this.waitForService(); + + // Make a remote call on the proxy + if (service) { + const result = await service.request(LiveShareCommands.getImportPackageVersion, [], cancelToken); + + if (result) { + return result as SemVer; + } + } } public isSpawnSupported(_cancelToken?: CancellationToken): Promise<boolean> { return Promise.resolve(false); @@ -144,16 +162,4 @@ export class GuestJupyterExecution extends LiveShareParticipantGuest( public async getServer(options?: INotebookServerOptions): Promise<INotebookServer | undefined> { return this.serverCache.get(options); } - - private async checkSupported(command: string, cancelToken?: CancellationToken): Promise<boolean> { - const service = await this.waitForService(); - - // Make a remote call on the proxy - if (service) { - const result = await service.request(command, [], cancelToken); - return result as boolean; - } - - return false; - } } diff --git a/src/client/datascience/jupyter/liveshare/hostJupyterExecution.ts b/src/client/datascience/jupyter/liveshare/hostJupyterExecution.ts index 422963c45ba7..72adb1dd8ff0 100644 --- a/src/client/datascience/jupyter/liveshare/hostJupyterExecution.ts +++ b/src/client/datascience/jupyter/liveshare/hostJupyterExecution.ts @@ -122,7 +122,7 @@ export class HostJupyterExecution // Register handlers for all of the supported remote calls if (service) { service.onRequest(LiveShareCommands.isNotebookSupported, this.onRemoteIsNotebookSupported); - service.onRequest(LiveShareCommands.isImportSupported, this.onRemoteIsImportSupported); + service.onRequest(LiveShareCommands.getImportPackageVersion, this.onRemoteGetImportPackageVersion); service.onRequest(LiveShareCommands.connectToNotebookServer, this.onRemoteConnectToNotebookServer); service.onRequest(LiveShareCommands.getUsableJupyterPython, this.onRemoteGetUsableJupyterPython); } @@ -153,9 +153,9 @@ export class HostJupyterExecution return this.isNotebookSupported(cancellation); }; - private onRemoteIsImportSupported = (_args: any[], cancellation: CancellationToken): Promise<any> => { + private onRemoteGetImportPackageVersion = (_args: any[], cancellation: CancellationToken): Promise<any> => { // Just call local - return this.isImportSupported(cancellation); + return this.getImportPackageVersion(cancellation); }; private onRemoteConnectToNotebookServer = async ( diff --git a/src/client/datascience/types.ts b/src/client/datascience/types.ts index b651af2b7750..e97bcada4539 100644 --- a/src/client/datascience/types.ts +++ b/src/client/datascience/types.ts @@ -7,6 +7,7 @@ import type { Kernel, KernelMessage } from '@jupyterlab/services/lib/kernel'; import type { JSONObject } from '@phosphor/coreutils'; import { WriteStream } from 'fs-extra'; import { Observable } from 'rxjs/Observable'; +import { SemVer } from 'semver'; import { CancellationToken, CodeLens, @@ -282,7 +283,7 @@ export const IJupyterExecution = Symbol('IJupyterExecution'); export interface IJupyterExecution extends IAsyncDisposable { serverStarted: Event<INotebookServerOptions | undefined>; isNotebookSupported(cancelToken?: CancellationToken): Promise<boolean>; - isImportSupported(cancelToken?: CancellationToken): Promise<boolean>; + getImportPackageVersion(cancelToken?: CancellationToken): Promise<SemVer | undefined>; isSpawnSupported(cancelToken?: CancellationToken): Promise<boolean>; connectToNotebookServer( options?: INotebookServerOptions, @@ -993,13 +994,10 @@ export interface IJupyterSubCommandExecutionService { */ isNotebookSupported(cancelToken?: CancellationToken): Promise<boolean>; /** - * Checks whether exporting of ipynb is supported. - * - * @param {CancellationToken} [cancelToken] - * @returns {Promise<boolean>} - * @memberof IJupyterSubCommandExecutionService + * If exporting is supported return the version of nbconvert available + * otherwise undefined. */ - isExportSupported(cancelToken?: CancellationToken): Promise<boolean>; + getExportPackageVersion(cancelToken?: CancellationToken): Promise<SemVer | undefined>; /** * Error message indicating why jupyter notebook isn't supported. * diff --git a/src/test/datascience/execution.unit.test.ts b/src/test/datascience/execution.unit.test.ts index 78203130f51f..1d1c66e70442 100644 --- a/src/test/datascience/execution.unit.test.ts +++ b/src/test/datascience/execution.unit.test.ts @@ -1000,6 +1000,14 @@ suite('Jupyter Execution', async () => { return []; } ); + when(dependencyService.getNbConvertVersion(anything(), anything())).thenCall( + async (interpreter: PythonEnvironment) => { + if (interpreter === missingNotebookPython) { + return undefined; + } + return new SemVer('1.1.1'); + } + ); const oldStore = mock(JupyterInterpreterOldCacheStateStore); when(oldStore.getCachedInterpreterPath()).thenReturn(); const jupyterInterpreterService = mock(JupyterInterpreterService); @@ -1047,7 +1055,8 @@ suite('Jupyter Execution', async () => { const jupyterExecutionFactory = createExecution(workingPython); await assert.eventually.equal(jupyterExecutionFactory.isNotebookSupported(), true, 'Notebook not supported'); - await assert.eventually.equal(jupyterExecutionFactory.isImportSupported(), true, 'Import not supported'); + const nbConvertVer = await jupyterExecutionFactory.getImportPackageVersion(); + assert.isTrue(nbConvertVer?.compare('1.1.1') === 0); const usableInterpreter = await jupyterExecutionFactory.getUsableJupyterPython(); assert.isOk(usableInterpreter, 'Usable interpreter not found'); await assert.isFulfilled(jupyterExecutionFactory.connectToNotebookServer(), 'Should be able to start a server'); @@ -1062,7 +1071,8 @@ suite('Jupyter Execution', async () => { ); await assert.eventually.equal(jupyterExecutionFactory.isNotebookSupported(), true, 'Notebook not supported'); - await assert.eventually.equal(jupyterExecutionFactory.isImportSupported(), true, 'Import not supported'); + const nbConvertVer = await jupyterExecutionFactory.getImportPackageVersion(); + assert.isTrue(nbConvertVer?.compare('1.1.1') === 0); const usableInterpreter = await jupyterExecutionFactory.getUsableJupyterPython(); assert.isOk(usableInterpreter, 'Usable interpreter not found'); await assert.isFulfilled(jupyterExecutionFactory.connectToNotebookServer(), 'Should be able to start a server'); diff --git a/src/test/datascience/jupyter/interpreter/jupyterInterpreterSubCommandExecutionService.unit.test.ts b/src/test/datascience/jupyter/interpreter/jupyterInterpreterSubCommandExecutionService.unit.test.ts index a95b8b8f79a4..e6704bd35fb4 100644 --- a/src/test/datascience/jupyter/interpreter/jupyterInterpreterSubCommandExecutionService.unit.test.ts +++ b/src/test/datascience/jupyter/interpreter/jupyterInterpreterSubCommandExecutionService.unit.test.ts @@ -101,8 +101,8 @@ suite('DataScience - Jupyter InterpreterSubCommandExecutionService', () => { assert.isFalse(isSupported); }); test('Export is not supported', async () => { - const isSupported = await jupyterInterpreterExecutionService.isExportSupported(undefined); - assert.isFalse(isSupported); + const isSupported = await jupyterInterpreterExecutionService.getExportPackageVersion(undefined); + assert.isUndefined(isSupported); }); test('Jupyter cannot be started because no interpreter has been selected', async () => { when(interperterService.getActiveInterpreter(undefined)).thenResolve(undefined); diff --git a/src/test/datascience/notebook.functional.test.ts b/src/test/datascience/notebook.functional.test.ts index b8ff1d720cbd..249aa1d310cc 100644 --- a/src/test/datascience/notebook.functional.test.ts +++ b/src/test/datascience/notebook.functional.test.ts @@ -875,7 +875,7 @@ suite('DataScience notebook tests', () => { ); assert.ok( await testCancelableMethod( - (t: CancellationToken) => jupyterExecution.isImportSupported(t), + (t: CancellationToken) => jupyterExecution.getImportPackageVersion(t), 'Cancel did not cancel isImport after {0}ms', true ) From ef278dae417cd1e4565acb42f98eccf2086c6cbc Mon Sep 17 00:00:00 2001 From: Rich Chiodo <rchiodo@users.noreply.github.com> Date: Thu, 1 Oct 2020 12:45:07 -0700 Subject: [PATCH 14/24] Port escape fix to release branch (#14202) * A different way of fixing escaping (#14186) * Move escaping to just output * Add some tests to verify round tripping * Fixup test for rountrip and make roundtripping actually work * Add news entry * Add to manual test file * Fix streaming problem and add more to the test * Fix traceback unit test * Fix problem caught by functional tests :) * Another functional test catch * Update changelog --- CHANGELOG.md | 5 +- .../editor-integration/cellhashprovider.ts | 14 +- .../datascience/jupyter/jupyterDebugger.ts | 6 +- .../datascience/jupyter/jupyterNotebook.ts | 72 ++++----- .../datascience/jupyter/kernelVariables.ts | 6 +- .../jupyter/oldJupyterVariables.ts | 6 +- .../interactive-common/cellOutput.tsx | 20 ++- src/datascience-ui/react-common/postOffice.ts | 2 +- .../interactiveWindow.functional.test.tsx | 8 + .../datascience/jupyterUtils.unit.test.ts | 12 +- .../manualTestFiles/manualTestFile.py | 3 + .../nativeEditor.functional.test.tsx | 146 ++++++++++++++++++ .../datascience/notebook.functional.test.ts | 19 +-- src/test/datascience/testHelpers.tsx | 3 +- 14 files changed, 241 insertions(+), 81 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f2923a846854..d033a7beb59f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,11 +1,13 @@ # Changelog -## 2020.9.2 (1 October 2020) +## 2020.9.2 (2 October 2020) ### Fixes 1. Support nbconvert version 6+ for exporting notebooks to python code. ([#14169](https://github.com/Microsoft/vscode-python/issues/14169)) +1. Do not escape output in the actual ipynb file. + ([#14182](https://github.com/Microsoft/vscode-python/issues/14182)) ### Thanks @@ -61,7 +63,6 @@ And finally thanks to the [Python](https://www.python.org/) development team and community for creating a fantastic programming language and community to be a part of! - ## 2020.9.1 (29 September 2020) ### Fixes diff --git a/src/client/datascience/editor-integration/cellhashprovider.ts b/src/client/datascience/editor-integration/cellhashprovider.ts index 7f9f14a9de8e..afc123713c84 100644 --- a/src/client/datascience/editor-integration/cellhashprovider.ts +++ b/src/client/datascience/editor-integration/cellhashprovider.ts @@ -29,7 +29,9 @@ import { // tslint:disable-next-line:no-require-imports no-var-requires const _escapeRegExp = require('lodash/escapeRegExp') as typeof import('lodash/escapeRegExp'); // NOSONAR -const LineNumberMatchRegex = /(;32m[ ->]*?)(\d+)/g; +// tslint:disable-next-line: no-require-imports no-var-requires +const _escape = require('lodash/escape') as typeof import('lodash/escape'); // NOSONAR +const LineNumberMatchRegex = /(;32m[ ->]*?)(\d+)(.*)/g; interface IRangedCellHash extends ICellHash { code: string; @@ -133,7 +135,7 @@ export class CellHashProvider implements ICellHashProvider, INotebookExecutionLo ...msg, content: { ...msg.content, - traceback: this.modifyTraceback(msg as KernelMessage.IErrorMsg) // NOSONAR + transient: this.modifyTraceback(msg as KernelMessage.IErrorMsg) // NOSONAR } }; } @@ -423,14 +425,16 @@ export class CellHashProvider implements ICellHashProvider, INotebookExecutionLo // Now attempt to find a cell that matches these source lines const offset = this.findCellOffset(this.hashes.get(match[0]), sourceLines); if (offset !== undefined) { - return traceFrame.replace(LineNumberMatchRegex, (_s, prefix, num) => { + return traceFrame.replace(LineNumberMatchRegex, (_s, prefix, num, suffix) => { const n = parseInt(num, 10); const newLine = offset + n - 1; - return `${prefix}<a href='file://${match[0]}?line=${newLine}'>${newLine + 1}</a>`; + return `${_escape(prefix)}<a href='file://${match[0]}?line=${newLine}'>${newLine + 1}</a>${_escape( + suffix + )}`; }); } } - return traceFrame; + return _escape(traceFrame); } } diff --git a/src/client/datascience/jupyter/jupyterDebugger.ts b/src/client/datascience/jupyter/jupyterDebugger.ts index 796dd18ab771..afa7df942137 100644 --- a/src/client/datascience/jupyter/jupyterDebugger.ts +++ b/src/client/datascience/jupyter/jupyterDebugger.ts @@ -3,8 +3,6 @@ 'use strict'; import type { nbformat } from '@jupyterlab/coreutils'; import { inject, injectable, named } from 'inversify'; -// tslint:disable-next-line: no-require-imports -import unescape = require('lodash/unescape'); import * as path from 'path'; import * as uuid from 'uuid/v4'; import { DebugConfiguration, Disposable } from 'vscode'; @@ -475,10 +473,8 @@ export class JupyterDebugger implements IJupyterDebugger, ICellHashListener { if (outputs.length > 0) { const data = outputs[0].data; if (data && data.hasOwnProperty('text/plain')) { - // Plain text should be escaped by our execution engine. Unescape it so - // we can parse it. // tslint:disable-next-line:no-any - return unescape((data as any)['text/plain']); + return (data as any)['text/plain']; } if (outputs[0].output_type === 'stream') { const stream = outputs[0] as nbformat.IStream; diff --git a/src/client/datascience/jupyter/jupyterNotebook.ts b/src/client/datascience/jupyter/jupyterNotebook.ts index ab1a015065e3..d931e3a7b015 100644 --- a/src/client/datascience/jupyter/jupyterNotebook.ts +++ b/src/client/datascience/jupyter/jupyterNotebook.ts @@ -40,11 +40,7 @@ import { KernelConnectionMetadata } from './kernels/types'; // tslint:disable-next-line: no-require-imports import cloneDeep = require('lodash/cloneDeep'); -// tslint:disable-next-line: no-require-imports -import escape = require('lodash/escape'); -// tslint:disable-next-line: no-require-imports -import unescape = require('lodash/unescape'); -import { concatMultilineString, formatStreamText } from '../../../datascience-ui/common'; +import { concatMultilineString, formatStreamText, splitMultilineString } from '../../../datascience-ui/common'; import { RefBool } from '../../common/refBool'; import { PythonEnvironment } from '../../pythonEnvironments/info'; import { getInterpreterFromKernelConnectionMetadata, isPythonKernelConnection } from './kernels/helpers'; @@ -787,12 +783,12 @@ export class JupyterNotebookBase implements INotebook { outputs.forEach((o) => { if (o.output_type === 'stream') { const stream = o as nbformat.IStream; - result = result.concat(formatStreamText(unescape(concatMultilineString(stream.text, true)))); + result = result.concat(formatStreamText(concatMultilineString(stream.text, true))); } else { const data = o.data; if (data && data.hasOwnProperty('text/plain')) { // tslint:disable-next-line:no-any - result = result.concat(unescape((data as any)['text/plain'])); + result = result.concat((data as any)['text/plain']); } } }); @@ -1206,12 +1202,7 @@ export class JupyterNotebookBase implements INotebook { private addToCellData = ( cell: ICell, - output: - | nbformat.IUnrecognizedOutput - | nbformat.IExecuteResult - | nbformat.IDisplayData - | nbformat.IStream - | nbformat.IError, + output: nbformat.IExecuteResult | nbformat.IDisplayData | nbformat.IStream | nbformat.IError, clearState: RefBool ) => { const data: nbformat.ICodeCell = cell.data as nbformat.ICodeCell; @@ -1237,7 +1228,10 @@ export class JupyterNotebookBase implements INotebook { ) { // Check our length on text output if (msg.content.data && msg.content.data.hasOwnProperty('text/plain')) { - msg.content.data['text/plain'] = escape(trimFunc(msg.content.data['text/plain'] as string)); + msg.content.data['text/plain'] = splitMultilineString( + // tslint:disable-next-line: no-any + trimFunc(concatMultilineString(msg.content.data['text/plain'] as any)) + ); } this.addToCellData( @@ -1265,14 +1259,15 @@ export class JupyterNotebookBase implements INotebook { reply.payload.forEach((o) => { if (o.data && o.data.hasOwnProperty('text/plain')) { // tslint:disable-next-line: no-any - const str = (o.data as any)['text/plain'].toString(); - const data = escape(trimFunc(str)) as string; + const str = concatMultilineString((o.data as any)['text/plain']); // NOSONAR + const data = trimFunc(str); this.addToCellData( cell, { // Mark as stream output so the text is formatted because it likely has ansi codes in it. output_type: 'stream', - text: data, + text: splitMultilineString(data), + name: 'stdout', metadata: {}, execution_count: reply.execution_count }, @@ -1313,23 +1308,25 @@ export class JupyterNotebookBase implements INotebook { ? data.outputs[data.outputs.length - 1] : undefined; if (existing) { - // tslint:disable-next-line:restrict-plus-operands - existing.text = existing.text + escape(msg.content.text); - const originalText = formatStreamText(concatMultilineString(existing.text)); + const originalText = formatStreamText( + // tslint:disable-next-line: no-any + `${concatMultilineString(existing.text as any)}${concatMultilineString(msg.content.text)}` + ); originalTextLength = originalText.length; - existing.text = trimFunc(originalText); - trimmedTextLength = existing.text.length; + const newText = trimFunc(originalText); + trimmedTextLength = newText.length; + existing.text = splitMultilineString(newText); } else { - const originalText = formatStreamText(concatMultilineString(escape(msg.content.text))); + const originalText = formatStreamText(concatMultilineString(msg.content.text)); originalTextLength = originalText.length; // Create a new stream entry const output: nbformat.IStream = { output_type: 'stream', name: msg.content.name, - text: trimFunc(originalText) + text: [trimFunc(originalText)] }; data.outputs = [...data.outputs, output]; - trimmedTextLength = output.text.length; + trimmedTextLength = output.text[0].length; cell.data = data; } @@ -1338,23 +1335,16 @@ export class JupyterNotebookBase implements INotebook { // the output is trimmed and what setting changes that. // * If data.metadata.tags is undefined, define it so the following // code is can rely on it being defined. - if (data.metadata.tags === undefined) { - data.metadata.tags = []; - } - - data.metadata.tags = data.metadata.tags.filter((t) => t !== 'outputPrepend'); - if (trimmedTextLength < originalTextLength) { + if (data.metadata.tags === undefined) { + data.metadata.tags = []; + } + data.metadata.tags = data.metadata.tags.filter((t) => t !== 'outputPrepend'); data.metadata.tags.push('outputPrepend'); } } private handleDisplayData(msg: KernelMessage.IDisplayDataMsg, clearState: RefBool, cell: ICell) { - // Escape text output - if (msg.content.data && msg.content.data.hasOwnProperty('text/plain')) { - msg.content.data['text/plain'] = escape(msg.content.data['text/plain'] as string); - } - const output: nbformat.IDisplayData = { output_type: 'display_data', data: msg.content.data, @@ -1402,10 +1392,14 @@ export class JupyterNotebookBase implements INotebook { private handleError(msg: KernelMessage.IErrorMsg, clearState: RefBool, cell: ICell) { const output: nbformat.IError = { output_type: 'error', - ename: escape(msg.content.ename), - evalue: escape(msg.content.evalue), - traceback: msg.content.traceback.map(escape) + ename: msg.content.ename, + evalue: msg.content.evalue, + traceback: msg.content.traceback }; + if (msg.content.hasOwnProperty('transient')) { + // tslint:disable-next-line: no-any + output.transient = (msg.content as any).transient; + } this.addToCellData(cell, output, clearState); cell.state = CellState.error; diff --git a/src/client/datascience/jupyter/kernelVariables.ts b/src/client/datascience/jupyter/kernelVariables.ts index 501b3f25ef17..7d3d1b004bd3 100644 --- a/src/client/datascience/jupyter/kernelVariables.ts +++ b/src/client/datascience/jupyter/kernelVariables.ts @@ -6,8 +6,6 @@ import { inject, injectable } from 'inversify'; import stripAnsi from 'strip-ansi'; import * as uuid from 'uuid/v4'; -// tslint:disable-next-line: no-require-imports -import unescape = require('lodash/unescape'); import { CancellationToken, Event, EventEmitter, Uri } from 'vscode'; import { PYTHON_LANGUAGE } from '../../common/constants'; import { traceError } from '../../common/logger'; @@ -248,7 +246,7 @@ export class KernelVariables implements IJupyterVariables { // Pull our text result out of the Jupyter cell private deserializeJupyterResult<T>(cells: ICell[]): T { - const text = unescape(this.extractJupyterResultText(cells)); + const text = this.extractJupyterResultText(cells); return JSON.parse(text) as T; } @@ -373,7 +371,7 @@ export class KernelVariables implements IJupyterVariables { // Now execute the query if (notebook && query) { const cells = await notebook.execute(query.query, Identifiers.EmptyFileName, 0, uuid(), token, true); - const text = unescape(this.extractJupyterResultText(cells)); + const text = this.extractJupyterResultText(cells); // Apply the expression to it const matches = this.getAllMatches(query.parser, text); diff --git a/src/client/datascience/jupyter/oldJupyterVariables.ts b/src/client/datascience/jupyter/oldJupyterVariables.ts index 6c25a3a3e547..7c1d77de7135 100644 --- a/src/client/datascience/jupyter/oldJupyterVariables.ts +++ b/src/client/datascience/jupyter/oldJupyterVariables.ts @@ -11,8 +11,6 @@ import { Event, EventEmitter, Uri } from 'vscode'; import { PYTHON_LANGUAGE } from '../../common/constants'; import { traceError } from '../../common/logger'; -// tslint:disable-next-line: no-require-imports -import unescape = require('lodash/unescape'); import { IConfigurationService } from '../../common/types'; import * as localize from '../../common/utils/localize'; import { EXTENSION_ROOT_DIR } from '../../constants'; @@ -234,7 +232,7 @@ export class OldJupyterVariables implements IJupyterVariables { // Pull our text result out of the Jupyter cell private deserializeJupyterResult<T>(cells: ICell[]): T { - const text = unescape(this.extractJupyterResultText(cells)); + const text = this.extractJupyterResultText(cells); return JSON.parse(text) as T; } @@ -359,7 +357,7 @@ export class OldJupyterVariables implements IJupyterVariables { // Now execute the query if (notebook && query) { const cells = await notebook.execute(query.query, Identifiers.EmptyFileName, 0, uuid(), undefined, true); - const text = unescape(this.extractJupyterResultText(cells)); + const text = this.extractJupyterResultText(cells); // Apply the expression to it const matches = this.getAllMatches(query.parser, text); diff --git a/src/datascience-ui/interactive-common/cellOutput.tsx b/src/datascience-ui/interactive-common/cellOutput.tsx index 8b545eac7086..33cea77eecbb 100644 --- a/src/datascience-ui/interactive-common/cellOutput.tsx +++ b/src/datascience-ui/interactive-common/cellOutput.tsx @@ -20,6 +20,8 @@ import { getRichestMimetype, getTransform, isIPyWidgetOutput, isMimeTypeSupporte // tslint:disable-next-line: no-var-requires no-require-imports const ansiToHtml = require('ansi-to-html'); +// tslint:disable-next-line: no-var-requires no-require-imports +const lodashEscape = require('lodash/escape'); // tslint:disable-next-line: no-require-imports no-var-requires const cloneDeep = require('lodash/cloneDeep'); @@ -328,7 +330,7 @@ export class CellOutput extends React.Component<ICellOutputProps> { // tslint:disable-next-line: no-any const text = (input as any)['text/plain']; input = { - 'text/html': text // XML tags should have already been escaped. + 'text/html': lodashEscape(concatMultilineString(text)) }; } else if (output.output_type === 'stream') { mimeType = 'text/html'; @@ -337,7 +339,7 @@ export class CellOutput extends React.Component<ICellOutputProps> { renderWithScrollbars = true; // Sonar is wrong, TS won't compile without this AS const stream = output as nbformat.IStream; // NOSONAR - const concatted = concatMultilineString(stream.text); + const concatted = lodashEscape(concatMultilineString(stream.text)); input = { 'text/html': concatted // XML tags should have already been escaped. }; @@ -363,14 +365,18 @@ export class CellOutput extends React.Component<ICellOutputProps> { const error = output as nbformat.IError; // NOSONAR try { const converter = new CellOutput.ansiToHtmlClass(CellOutput.getAnsiToHtmlOptions()); - const trace = error.traceback.length ? converter.toHtml(error.traceback.join('\n')) : error.evalue; + // Modified traceback may exist. If so use that instead. It's only at run time though + const traceback: string[] = error.transient + ? (error.transient as string[]) + : error.traceback.map(lodashEscape); + const trace = traceback ? converter.toHtml(traceback.join('\n')) : error.evalue; input = { 'text/html': trace }; } catch { // This can fail during unit tests, just use the raw data input = { - 'text/html': error.evalue + 'text/html': lodashEscape(error.evalue) }; } } else if (input) { @@ -395,6 +401,12 @@ export class CellOutput extends React.Component<ICellOutputProps> { data = fixMarkdown(concatMultilineString(data as nbformat.MultilineString, true), true); } + // Make sure text output is escaped (nteract texttransform won't) + if (mimeType === 'text/plain' && data) { + data = lodashEscape(data.toString()); + mimeType = 'text/html'; + } + return { isText, isError, diff --git a/src/datascience-ui/react-common/postOffice.ts b/src/datascience-ui/react-common/postOffice.ts index 8e671b2376e1..041432356cc1 100644 --- a/src/datascience-ui/react-common/postOffice.ts +++ b/src/datascience-ui/react-common/postOffice.ts @@ -88,7 +88,7 @@ export class PostOffice implements IDisposable { // See ./src/datascience-ui/native-editor/index.html // tslint:disable-next-line: no-any const api = (this.vscodeApi as any) as { handleMessage?: Function }; - if (api.handleMessage) { + if (api && api.handleMessage) { api.handleMessage(this.handleMessages.bind(this)); } } catch { diff --git a/src/test/datascience/interactiveWindow.functional.test.tsx b/src/test/datascience/interactiveWindow.functional.test.tsx index d40cb82906ac..243a970b1aba 100644 --- a/src/test/datascience/interactiveWindow.functional.test.tsx +++ b/src/test/datascience/interactiveWindow.functional.test.tsx @@ -63,6 +63,8 @@ import { verifyLastCellInputState } from './testHelpers'; import { ITestInteractiveWindowProvider } from './testInteractiveWindowProvider'; +// tslint:disable-next-line: no-require-imports no-var-requires +const _escape = require('lodash/escape') as typeof import('lodash/escape'); // NOSONAR // tslint:disable:max-func-body-length trailing-comma no-any no-multiline-string suite('DataScience Interactive Window output tests', () => { @@ -385,6 +387,9 @@ for _ in range(50): time.sleep(0.1) sys.stdout.write('\\r')`; + const exception = 'raise Exception("<html check>")'; + addMockData(ioc, exception, `"<html check>"`, 'text/html', 'error'); + addMockData(ioc, badPanda, `pandas has no attribute 'read'`, 'text/html', 'error'); addMockData(ioc, goodPanda, `<td>A table</td>`, 'text/html'); addMockData(ioc, matPlotLib, matPlotLibResults, 'text/html'); @@ -401,6 +406,9 @@ for _ in range(50): return Promise.resolve({ result: result, haveMore: loops > 0 }); }); + await addCode(ioc, exception, true); + verifyHtmlOnInteractiveCell(_escape(`<html check>`), CellPosition.Last); + await addCode(ioc, badPanda, true); verifyHtmlOnInteractiveCell(`has no attribute 'read'`, CellPosition.Last); diff --git a/src/test/datascience/jupyterUtils.unit.test.ts b/src/test/datascience/jupyterUtils.unit.test.ts index 487c0205a729..6675afcbedee 100644 --- a/src/test/datascience/jupyterUtils.unit.test.ts +++ b/src/test/datascience/jupyterUtils.unit.test.ts @@ -87,7 +87,7 @@ suite('DataScience JupyterUtils', () => { }; // tslint:disable-next-line: no-any - return (hashProvider.preHandleIOPub(dummyMessage).content as any).traceback; + return (hashProvider.preHandleIOPub(dummyMessage).content as any).transient; } function addCell(code: string, file: string, line: number) { @@ -134,7 +134,7 @@ suite('DataScience JupyterUtils', () => { const after2 = [ '\u001b[1;31m---------------------------------------------------------------------------\u001b[0m', '\u001b[1;31mException\u001b[0m Traceback (most recent call last)', - `\u001b[1;32md:\\Training\\SnakePython\\manualTestFile.pytastic\u001b[0m in \u001b[0;36m<module>\u001b[1;34m\u001b[0m\n\u001b[0;32m <a href='file://d:\\Training\\SnakePython\\manualTestFile.py?line=3'>4</a>\u001b[0m \u001b[1;32mfor\u001b[0m \u001b[0mi\u001b[0m \u001b[1;32min\u001b[0m \u001b[0mtrange\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;36m100\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m <a href='file://d:\\Training\\SnakePython\\manualTestFile.py?line=4'>5</a>\u001b[0m \u001b[0mtime\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0msleep\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;36m0.01\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m----> <a href='file://d:\\Training\\SnakePython\\manualTestFile.py?line=5'>6</a>\u001b[1;33m \u001b[1;32mraise\u001b[0m \u001b[0mException\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;34m'spam'\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m`, + `\u001b[1;32md:\\Training\\SnakePython\\manualTestFile.pytastic\u001b[0m in \u001b[0;36m<module>\u001b[1;34m\u001b[0m\n\u001b[0;32m <a href='file://d:\\Training\\SnakePython\\manualTestFile.py?line=3'>4</a>\u001b[0m \u001b[1;32mfor\u001b[0m \u001b[0mi\u001b[0m \u001b[1;32min\u001b[0m \u001b[0mtrange\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;36m100\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m <a href='file://d:\\Training\\SnakePython\\manualTestFile.py?line=4'>5</a>\u001b[0m \u001b[0mtime\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0msleep\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;36m0.01\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m----&gt; <a href='file://d:\\Training\\SnakePython\\manualTestFile.py?line=5'>6</a>\u001b[1;33m \u001b[1;32mraise\u001b[0m \u001b[0mException\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;34m&#39;spam&#39;\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m`, '\u001b[1;31mException\u001b[0m: spam' ]; assert.equal(after2.join('\n'), modifyTraceback(trace2).join('\n'), 'Exception failure'); @@ -158,8 +158,8 @@ import matplotlib.pyplot as plt`, const after3 = [ '\u001b[0;31m---------------------------------------------------------------------------\u001b[0m', '\u001b[0;31mModuleNotFoundError\u001b[0m Traceback (most recent call last)', - "\u001b[0;32m~/Test/manualTestFile.py\u001b[0m in \u001b[0;36m<module>\u001b[0;34m\u001b[0m\n\u001b[0;32m----> <a href='file:///home/rich/Test/manualTestFile.py?line=24'>25</a>\u001b[0;31m \u001b[0;32mimport\u001b[0m \u001b[0mnumpy\u001b[0m \u001b[0;32mas\u001b[0m \u001b[0mnp\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m <a href='file:///home/rich/Test/manualTestFile.py?line=25'>26</a>\u001b[0m \u001b[0;32mimport\u001b[0m \u001b[0mpandas\u001b[0m \u001b[0;32mas\u001b[0m \u001b[0mpd\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m <a href='file:///home/rich/Test/manualTestFile.py?line=26'>27</a>\u001b[0m \u001b[0;32mimport\u001b[0m \u001b[0mmatplotlib\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mpyplot\u001b[0m \u001b[0;32mas\u001b[0m \u001b[0mplt\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", - "\u001b[0;31mModuleNotFoundError\u001b[0m: No module named 'numpy'" + "\u001b[0;32m~/Test/manualTestFile.py\u001b[0m in \u001b[0;36m<module>\u001b[0;34m\u001b[0m\n\u001b[0;32m----&gt; <a href='file:///home/rich/Test/manualTestFile.py?line=24'>25</a>\u001b[0;31m \u001b[0;32mimport\u001b[0m \u001b[0mnumpy\u001b[0m \u001b[0;32mas\u001b[0m \u001b[0mnp\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m <a href='file:///home/rich/Test/manualTestFile.py?line=25'>26</a>\u001b[0m \u001b[0;32mimport\u001b[0m \u001b[0mpandas\u001b[0m \u001b[0;32mas\u001b[0m \u001b[0mpd\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m <a href='file:///home/rich/Test/manualTestFile.py?line=26'>27</a>\u001b[0m \u001b[0;32mimport\u001b[0m \u001b[0mmatplotlib\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mpyplot\u001b[0m \u001b[0;32mas\u001b[0m \u001b[0mplt\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", + '\u001b[0;31mModuleNotFoundError\u001b[0m: No module named &#39;numpy&#39;' ]; assert.equal(after3.join('\n'), modifyTraceback(trace3).join('\n'), 'Exception unix failure'); when(fileSystem.getDisplayName(anything())).thenReturn('d:\\Training\\SnakePython\\foo.py'); @@ -194,8 +194,8 @@ cause_error()`, const after4 = [ '\u001b[1;31m---------------------------------------------------------------------------\u001b[0m', '\u001b[1;31mZeroDivisionError\u001b[0m Traceback (most recent call last)', - "\u001b[1;32md:\\Training\\SnakePython\\foo.py\u001b[0m in \u001b[0;36m<module>\u001b[1;34m\u001b[0m\n\u001b[0;32m <a href='file://d:\\Training\\SnakePython\\foo.py?line=143'>144</a>\u001b[0m \u001b[0mprint\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;34m'some more'\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m <a href='file://d:\\Training\\SnakePython\\foo.py?line=144'>145</a>\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m----> <a href='file://d:\\Training\\SnakePython\\foo.py?line=145'>146</a>\u001b[1;33m \u001b[0mcause_error\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m", - "\u001b[1;32md:\\Training\\SnakePython\\foo.py\u001b[0m in \u001b[0;36mcause_error\u001b[1;34m()\u001b[0m\n\u001b[0;32m <a href='file://d:\\Training\\SnakePython\\foo.py?line=138'>139</a>\u001b[0m \u001b[0mprint\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;34m'now'\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m <a href='file://d:\\Training\\SnakePython\\foo.py?line=139'>140</a>\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m----> <a href='file://d:\\Training\\SnakePython\\foo.py?line=140'>141</a>\u001b[1;33m \u001b[0mprint\u001b[0m\u001b[1;33m(\u001b[0m \u001b[1;36m1\u001b[0m \u001b[1;33m/\u001b[0m \u001b[1;36m0\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m" + "\u001b[1;32md:\\Training\\SnakePython\\foo.py\u001b[0m in \u001b[0;36m<module>\u001b[1;34m\u001b[0m\n\u001b[0;32m <a href='file://d:\\Training\\SnakePython\\foo.py?line=143'>144</a>\u001b[0m \u001b[0mprint\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;34m&#39;some more&#39;\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m <a href='file://d:\\Training\\SnakePython\\foo.py?line=144'>145</a>\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m----&gt; <a href='file://d:\\Training\\SnakePython\\foo.py?line=145'>146</a>\u001b[1;33m \u001b[0mcause_error\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m", + "\u001b[1;32md:\\Training\\SnakePython\\foo.py\u001b[0m in \u001b[0;36mcause_error\u001b[1;34m()\u001b[0m\n\u001b[0;32m <a href='file://d:\\Training\\SnakePython\\foo.py?line=138'>139</a>\u001b[0m \u001b[0mprint\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;34m&#39;now&#39;\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m <a href='file://d:\\Training\\SnakePython\\foo.py?line=139'>140</a>\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m----&gt; <a href='file://d:\\Training\\SnakePython\\foo.py?line=140'>141</a>\u001b[1;33m \u001b[0mprint\u001b[0m\u001b[1;33m(\u001b[0m \u001b[1;36m1\u001b[0m \u001b[1;33m/\u001b[0m \u001b[1;36m0\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m" ]; assert.equal(after4.join('\n'), modifyTraceback(trace4).join('\n'), 'Multiple levels'); }); diff --git a/src/test/datascience/manualTestFiles/manualTestFile.py b/src/test/datascience/manualTestFiles/manualTestFile.py index b6009d6f04d0..6655a33ae856 100644 --- a/src/test/datascience/manualTestFiles/manualTestFile.py +++ b/src/test/datascience/manualTestFiles/manualTestFile.py @@ -10,6 +10,9 @@ plt.plot(x, np.sin(x)) plt.show() +#%% Test exception +raise Exception("<This is bracketed>") + # %% Bokeh Plot from bokeh.io import output_notebook, show from bokeh.plotting import figure diff --git a/src/test/datascience/nativeEditor.functional.test.tsx b/src/test/datascience/nativeEditor.functional.test.tsx index 82e4f21ad91a..095c5b7c8d5b 100644 --- a/src/test/datascience/nativeEditor.functional.test.tsx +++ b/src/test/datascience/nativeEditor.functional.test.tsx @@ -34,6 +34,7 @@ import { KeyPrefix } from '../../client/datascience/notebookStorage/nativeEditor import { ICell, IDataScienceErrorHandler, + IDataScienceFileSystem, IJupyterExecution, INotebookEditor, INotebookEditorProvider, @@ -880,6 +881,151 @@ df.head()`; verifyHtmlOnCell(ne.mount.wrapper, 'NativeCell', `3`, 2); }); + runMountedTest('Roundtrip with jupyter', async () => { + // Write out a temporary file + const baseFile = ` +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "metadata": { + "collapsed": true + }, + "outputs": [ + { + "data": { + "text/plain": [ + "'<1>'" + ] + }, + "execution_count": 1, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "a='<1>'\\n", + "a" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [ + { + "output_type": "stream", + "text": [ + "Hello World 9!\\n" + ], + "name": "stdout" + } + ], + "source": [ + "from IPython.display import clear_output\\n", + "for i in range(10):\\n", + " clear_output()\\n", + " print(\\"Hello World {0}!\\".format(i))\\n" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "3" + ] + }, + "execution_count": 3, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "c=3\\n", + "c" + ] + } + ], + "metadata": { + "file_extension": ".py", + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.7.4" + }, + "mimetype": "text/x-python", + "name": "python", + "npconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": 3 + }, + "nbformat": 4, + "nbformat_minor": 2 +}`; + addMockData(ioc, `a='<1>'\na`, `'<1>'`); + addContinuousMockData( + ioc, + 'from IPython.display import clear_output\nfor i in range(10):\n clear_output()\n print("Hello World {0}!".format(i))\n', + async () => { + return { result: 'Hello World 9!\n', haveMore: false }; + } + ); + addMockData(ioc, 'c=3\nc', 3); + const dsfs = ioc.get<IDataScienceFileSystem>(IDataScienceFileSystem); + const tf = await dsfs.createTemporaryLocalFile('.ipynb'); + try { + await dsfs.writeLocalFile(tf.filePath, baseFile); + + // File should exist. Open and run all cells + const n = await openEditor(ioc, '', tf.filePath); + const threeCellsUpdated = n.mount.waitForMessage(InteractiveWindowMessages.ExecutionRendered, { + numberOfTimes: 3 + }); + n.editor.runAllCells(); + await threeCellsUpdated; + + // Save the file + const saveButton = findButton(n.mount.wrapper, NativeEditor, 8); + const saved = waitForMessage(ioc, InteractiveWindowMessages.NotebookClean); + saveButton!.simulate('click'); + await saved; + + // Read in the file contents. Should match the original + const savedContents = await dsfs.readLocalFile(tf.filePath); + const savedJSON = JSON.parse(savedContents); + const baseJSON = JSON.parse(baseFile); + + // Don't compare kernelspec names + delete savedJSON.metadata.kernelspec.display_name; + delete baseJSON.metadata.kernelspec.display_name; + + // Don't compare python versions + delete savedJSON.metadata.language_info.version; + delete baseJSON.metadata.language_info.version; + + assert.deepEqual(savedJSON, baseJSON, 'File contents were changed by execution'); + } finally { + tf.dispose(); + } + }); + runMountedTest('Startup and shutdown', async () => { // Turn off raw kernel for this test as it's testing jupyterserver start / shutdown ioc.setExperimentState(LocalZMQKernel.experiment, false); diff --git a/src/test/datascience/notebook.functional.test.ts b/src/test/datascience/notebook.functional.test.ts index 249aa1d310cc..06d47927ad01 100644 --- a/src/test/datascience/notebook.functional.test.ts +++ b/src/test/datascience/notebook.functional.test.ts @@ -6,8 +6,6 @@ import { assert } from 'chai'; import { ChildProcess } from 'child_process'; import * as fs from 'fs-extra'; import { injectable } from 'inversify'; -// tslint:disable-next-line: no-require-imports -import escape = require('lodash/escape'); import * as os from 'os'; import * as path from 'path'; import { SemVer } from 'semver'; @@ -130,7 +128,7 @@ suite('DataScience notebook tests', () => { return path.join(EXTENSION_ROOT_DIR, 'src', 'test', 'datascience'); } - function extractDataOutput(cell: ICell): any { + function extractDataOutput(cell: ICell): string | undefined { assert.equal(cell.data.cell_type, 'code', `Wrong type of cell returned`); const codeCell = cell.data as nbformat.ICodeCell; if (codeCell.outputs.length > 0) { @@ -145,7 +143,7 @@ suite('DataScience notebook tests', () => { // For linter assert.ok(data.hasOwnProperty('text/plain'), `Cell mime type not correct`); assert.ok((data as any)['text/plain'], `Cell mime type not correct`); - return (data as any)['text/plain']; + return concatMultilineString((data as any)['text/plain']); } } } @@ -159,9 +157,9 @@ suite('DataScience notebook tests', () => { const cells = await notebook!.execute(code, path.join(srcDirectory(), 'foo.py'), 2, uuid()); assert.equal(cells.length, 1, `Wrong number of cells returned`); const data = extractDataOutput(cells[0]); - if (pathVerify) { + if (pathVerify && data) { // For a path comparison normalize output - const normalizedOutput = path.normalize(data).toUpperCase().replace(/&#39;/g, ''); + const normalizedOutput = path.normalize(data).toUpperCase().replace(/'/g, ''); const normalizedTarget = path.normalize(expectedValue).toUpperCase().replace(/'/g, ''); assert.equal(normalizedOutput, normalizedTarget, 'Cell path values does not match'); } else { @@ -1031,7 +1029,7 @@ a`, mimeType: 'text/plain', cellType: 'code', result: `<a href=f>`, - verifyValue: (d) => assert.ok(d.includes(escape(`<a href=f>`)), 'XML not escaped') + verifyValue: (d) => assert.ok(d.includes(`<a href=f>`), 'Should not escape at the notebook level') }, { markdownRegEx: undefined, @@ -1043,7 +1041,7 @@ df.head()`, cellType: 'error', // tslint:disable-next-line:quotemark verifyValue: (d) => - assert.ok((d as string).includes(escape("has no attribute 'read'")), 'Unexpected error result') + assert.ok((d as string).includes("has no attribute 'read'"), 'Unexpected error result') }, { markdownRegEx: undefined, @@ -1340,7 +1338,10 @@ plt.show()`, } public async postExecute(cell: ICell, silent: boolean): Promise<void> { if (!silent) { - outputs.push(extractDataOutput(cell)); + const data = extractDataOutput(cell); + if (data) { + outputs.push(data); + } } } } diff --git a/src/test/datascience/testHelpers.tsx b/src/test/datascience/testHelpers.tsx index 88798f4334f9..fbd453e00ad4 100644 --- a/src/test/datascience/testHelpers.tsx +++ b/src/test/datascience/testHelpers.tsx @@ -7,7 +7,6 @@ import { min } from 'lodash'; import * as path from 'path'; import * as React from 'react'; import { Provider } from 'react-redux'; -import { isString } from 'util'; import { CancellationToken } from 'vscode'; import { EXTENSION_ROOT_DIR } from '../../client/common/constants'; @@ -273,7 +272,7 @@ export function verifyHtmlOnCell( output = targetCell!.find('div.markdown-cell-output'); } const outputHtml = output.length > 0 ? output.html() : undefined; - if (html && isString(html)) { + if (html && typeof html === 'string') { // Extract only the first 100 chars from the input string const sliced = html.substr(0, min([html.length, 100])); assert.ok(output.length > 0, 'No output cell found'); From f4762cb69d8ff3d8331f8ecbd05e46e348a9022f Mon Sep 17 00:00:00 2001 From: Ian Huff <ianhu@microsoft.com> Date: Fri, 2 Oct 2020 14:42:57 -0700 Subject: [PATCH 15/24] Port interactive window export fix (#14232) --- CHANGELOG.md | 4 +++- .../datascience/interactive-window/interactiveWindow.ts | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d033a7beb59f..5e0fadce9e00 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # Changelog -## 2020.9.2 (2 October 2020) +## 2020.9.2 (5 October 2020) ### Fixes @@ -8,6 +8,8 @@ ([#14169](https://github.com/Microsoft/vscode-python/issues/14169)) 1. Do not escape output in the actual ipynb file. ([#14182](https://github.com/Microsoft/vscode-python/issues/14182)) +1. Fix exporting from the interactive window. + ([#14210](https://github.com/Microsoft/vscode-python/issues/14210)) ### Thanks diff --git a/src/client/datascience/interactive-window/interactiveWindow.ts b/src/client/datascience/interactive-window/interactiveWindow.ts index 3dd40ec874cd..fd1b2388a161 100644 --- a/src/client/datascience/interactive-window/interactiveWindow.ts +++ b/src/client/datascience/interactive-window/interactiveWindow.ts @@ -256,7 +256,7 @@ export class InteractiveWindow extends InteractiveBase implements IInteractiveWi break; case InteractiveWindowMessages.ExportNotebookAs: - this.handleMessage(message, payload, this.exportAs.bind); + this.handleMessage(message, payload, this.exportAs); break; case InteractiveWindowMessages.HasCellResponse: From 8022bfef90ce45884c733460c0cdfba25085bef0 Mon Sep 17 00:00:00 2001 From: Don Jayamanne <don.jayamanne@yahoo.com> Date: Fri, 2 Oct 2020 15:29:46 -0700 Subject: [PATCH 16/24] Select kernel based on metadata in notebook (#14217) (#14234) * Fix picking kernel based on metadata --- news/2 Fixes/14213.md | 1 + .../datascience/jupyter/kernels/helpers.ts | 8 ++-- .../jupyter/kernels/kernelSelector.ts | 2 +- .../jupyter/kernels/kernelSwitcher.ts | 5 ++- .../kernel-launcher/kernelFinder.ts | 44 +++++++++---------- .../kernel-launcher/kernelProcess.ts | 5 +-- .../datascience/notebookStorage/baseModel.ts | 37 +++++++++------- .../kernels/kernelSelector.unit.test.ts | 4 -- .../datascience/kernelFinder.unit.test.ts | 20 --------- 9 files changed, 52 insertions(+), 74 deletions(-) create mode 100644 news/2 Fixes/14213.md diff --git a/news/2 Fixes/14213.md b/news/2 Fixes/14213.md new file mode 100644 index 000000000000..2e3a90f6bdd0 --- /dev/null +++ b/news/2 Fixes/14213.md @@ -0,0 +1 @@ +Use the kernel defined in the metadata of Notebook instead of using the default workspace interpreter. diff --git a/src/client/datascience/jupyter/kernels/helpers.ts b/src/client/datascience/jupyter/kernels/helpers.ts index cebda0e1e334..bcbe703e4d33 100644 --- a/src/client/datascience/jupyter/kernels/helpers.ts +++ b/src/client/datascience/jupyter/kernels/helpers.ts @@ -25,8 +25,6 @@ import { // Helper functions for dealing with kernels and kernelspecs -export const defaultKernelSpecName = 'python_defaultSpec_'; - // https://jupyter-client.readthedocs.io/en/stable/kernels.html const connectionFilePlaceholder = '{connection_file}'; @@ -136,13 +134,13 @@ export function getKernelConnectionLanguage(kernelConnection?: KernelConnectionM return model?.language || kernelSpec?.language; } // Create a default kernelspec with the given display name -export function createDefaultKernelSpec(displayName?: string): IJupyterKernelSpec { +export function createDefaultKernelSpec(interpreter?: PythonEnvironment): IJupyterKernelSpec { // This creates a default kernel spec. When launched, 'python' argument will map to using the interpreter // associated with the current resource for launching. const defaultSpec: Kernel.ISpecModel = { - name: defaultKernelSpecName + Date.now().toString(), + name: interpreter?.displayName || 'Python 3', language: 'python', - display_name: displayName || 'Python 3', + display_name: interpreter?.displayName || 'Python 3', metadata: {}, argv: ['python', '-m', 'ipykernel_launcher', '-f', connectionFilePlaceholder], env: {}, diff --git a/src/client/datascience/jupyter/kernels/kernelSelector.ts b/src/client/datascience/jupyter/kernels/kernelSelector.ts index e398ded46d5f..4e9a172ce817 100644 --- a/src/client/datascience/jupyter/kernels/kernelSelector.ts +++ b/src/client/datascience/jupyter/kernels/kernelSelector.ts @@ -549,7 +549,7 @@ export class KernelSelector implements IKernelSelectionUsage { // When switching to an interpreter in raw kernel mode then just create a default kernelspec for that interpreter to use private async useInterpreterAndDefaultKernel(interpreter: PythonEnvironment): Promise<KernelConnectionMetadata> { - const kernelSpec = createDefaultKernelSpec(interpreter.displayName); + const kernelSpec = createDefaultKernelSpec(interpreter); return { kernelSpec, interpreter, kind: 'startUsingPythonInterpreter' }; } diff --git a/src/client/datascience/jupyter/kernels/kernelSwitcher.ts b/src/client/datascience/jupyter/kernels/kernelSwitcher.ts index 73b0613c4e92..bce7b401799e 100644 --- a/src/client/datascience/jupyter/kernels/kernelSwitcher.ts +++ b/src/client/datascience/jupyter/kernels/kernelSwitcher.ts @@ -90,7 +90,10 @@ export class KernelSwitcher { const kernelSpec = kernelConnectionMetadataHasKernelSpec(kernelConnection) ? kernelConnection : undefined; const kernelName = kernelSpec?.kernelSpec?.name || kernelModel?.kernelModel?.name; // One of them is bound to be non-empty. - const displayName = kernelModel?.kernelModel?.display_name || kernelName || ''; + const displayName = + kernelConnection.kind === 'startUsingPythonInterpreter' + ? kernelConnection.interpreter.displayName || kernelConnection.interpreter.path + : kernelModel?.kernelModel?.display_name || kernelName || ''; const options: ProgressOptions = { location: ProgressLocation.Notification, cancellable: false, diff --git a/src/client/datascience/kernel-launcher/kernelFinder.ts b/src/client/datascience/kernel-launcher/kernelFinder.ts index 5315c09caf05..69e34934a0a2 100644 --- a/src/client/datascience/kernel-launcher/kernelFinder.ts +++ b/src/client/datascience/kernel-launcher/kernelFinder.ts @@ -16,7 +16,6 @@ import { IInterpreterLocatorService, IInterpreterService, KNOWN_PATH_SERVICE } f import { captureTelemetry } from '../../telemetry'; import { getRealPath } from '../common'; import { Telemetry } from '../constants'; -import { defaultKernelSpecName } from '../jupyter/kernels/helpers'; import { JupyterKernelSpec } from '../jupyter/kernels/jupyterKernelSpec'; import { IDataScienceFileSystem, IJupyterKernelSpec } from '../types'; import { IKernelFinder } from './types'; @@ -70,35 +69,32 @@ export class KernelFinder implements IKernelFinder { const kernelName = kernelSpecMetadata?.name; if (kernelSpecMetadata && kernelName) { - // For a non default kernelspec search for it - if (!kernelName.includes(defaultKernelSpecName)) { - let kernelSpec = await this.searchCache(kernelName); + let kernelSpec = await this.searchCache(kernelName); - if (kernelSpec) { - return kernelSpec; - } + if (kernelSpec) { + return kernelSpec; + } - // Check in active interpreter first - kernelSpec = await this.getKernelSpecFromActiveInterpreter(kernelName, resource); + // Check in active interpreter first + kernelSpec = await this.getKernelSpecFromActiveInterpreter(kernelName, resource); - if (kernelSpec) { - this.writeCache().ignoreErrors(); - return kernelSpec; - } - - const diskSearch = this.findDiskPath(kernelName); - const interpreterSearch = this.getInterpreterPaths(resource).then((interpreterPaths) => { - return this.findInterpreterPath(interpreterPaths, kernelName); - }); + if (kernelSpec) { + this.writeCache().ignoreErrors(); + return kernelSpec; + } - let result = await Promise.race([diskSearch, interpreterSearch]); - if (!result) { - const both = await Promise.all([diskSearch, interpreterSearch]); - result = both[0] ? both[0] : both[1]; - } + const diskSearch = this.findDiskPath(kernelName); + const interpreterSearch = this.getInterpreterPaths(resource).then((interpreterPaths) => { + return this.findInterpreterPath(interpreterPaths, kernelName); + }); - foundKernel = result; + let result = await Promise.race([diskSearch, interpreterSearch]); + if (!result) { + const both = await Promise.all([diskSearch, interpreterSearch]); + result = both[0] ? both[0] : both[1]; } + + foundKernel = result; } this.writeCache().ignoreErrors(); diff --git a/src/client/datascience/kernel-launcher/kernelProcess.ts b/src/client/datascience/kernel-launcher/kernelProcess.ts index dfeda3d60198..46b79635dc90 100644 --- a/src/client/datascience/kernel-launcher/kernelProcess.ts +++ b/src/client/datascience/kernel-launcher/kernelProcess.ts @@ -151,10 +151,7 @@ export class KernelProcess implements IKernelProcess { let kernelSpec = this._kernelConnectionMetadata.kernelSpec; // If there is no kernelspec & when launching a Python process, generate a dummy `kernelSpec` if (!kernelSpec && this._kernelConnectionMetadata.kind === 'startUsingPythonInterpreter') { - kernelSpec = createDefaultKernelSpec( - this._kernelConnectionMetadata.interpreter.displayName || - this._kernelConnectionMetadata.interpreter.path - ); + kernelSpec = createDefaultKernelSpec(this._kernelConnectionMetadata.interpreter); } // We always expect a kernel spec. if (!kernelSpec) { diff --git a/src/client/datascience/notebookStorage/baseModel.ts b/src/client/datascience/notebookStorage/baseModel.ts index 2a28e231352f..db62829a4d0e 100644 --- a/src/client/datascience/notebookStorage/baseModel.ts +++ b/src/client/datascience/notebookStorage/baseModel.ts @@ -56,7 +56,22 @@ export function updateNotebookMetadata( kernelConnection && kernelConnectionMetadataHasKernelModel(kernelConnection) ? kernelConnection.kernelModel : kernelConnection?.kernelSpec; - if (kernelSpecOrModel && !metadata.kernelspec) { + if (kernelConnection?.kind === 'startUsingPythonInterpreter') { + // Store interpreter name, we expect the kernel finder will find the corresponding interpreter based on this name. + const name = kernelConnection.interpreter.displayName || ''; + if (metadata.kernelspec?.name !== name || metadata.kernelspec?.display_name !== name) { + changed = true; + metadata.kernelspec = { + name, + display_name: name, + metadata: { + interpreter: { + hash: sha256().update(kernelConnection.interpreter.path).digest('hex') + } + } + }; + } + } else if (kernelSpecOrModel && !metadata.kernelspec) { // Add a new spec in this case metadata.kernelspec = { name: kernelSpecOrModel.name || kernelSpecOrModel.display_name || '', @@ -78,20 +93,12 @@ export function updateNotebookMetadata( metadata.kernelspec.display_name = displayName; kernelId = kernelSpecOrModel.id; } - } else if (kernelConnection?.kind === 'startUsingPythonInterpreter') { - // Store interpreter name, we expect the kernel finder will find the corresponding interpreter based on this name. - const name = kernelConnection.interpreter.displayName || kernelConnection.interpreter.path; - if (metadata.kernelspec?.name !== name || metadata.kernelspec?.display_name !== name) { - changed = true; - metadata.kernelspec = { - name, - display_name: name, - metadata: { - interpreter: { - hash: sha256().update(kernelConnection.interpreter.path).digest('hex') - } - } - }; + try { + // This is set only for when we select an interpreter. + // tslint:disable-next-line: no-any + delete (metadata.kernelspec as any).metadata; + } catch { + // Noop. } } return { changed, kernelId }; diff --git a/src/test/datascience/jupyter/kernels/kernelSelector.unit.test.ts b/src/test/datascience/jupyter/kernels/kernelSelector.unit.test.ts index 2b15157b520b..de2c3fddf0a5 100644 --- a/src/test/datascience/jupyter/kernels/kernelSelector.unit.test.ts +++ b/src/test/datascience/jupyter/kernels/kernelSelector.unit.test.ts @@ -19,7 +19,6 @@ import { Architecture } from '../../../../client/common/utils/platform'; import { StopWatch } from '../../../../client/common/utils/stopWatch'; import { JupyterSessionManager } from '../../../../client/datascience/jupyter/jupyterSessionManager'; import { JupyterSessionManagerFactory } from '../../../../client/datascience/jupyter/jupyterSessionManagerFactory'; -import { defaultKernelSpecName } from '../../../../client/datascience/jupyter/kernels/helpers'; import { KernelDependencyService } from '../../../../client/datascience/jupyter/kernels/kernelDependencyService'; import { KernelSelectionProvider } from '../../../../client/datascience/jupyter/kernels/kernelSelections'; import { KernelSelector } from '../../../../client/datascience/jupyter/kernels/kernelSelector'; @@ -453,9 +452,6 @@ suite('DataScience - KernelSelector', () => { assert.deepEqual(kernel?.interpreter, interpreter); expect((kernel as any)?.kernelSpec, 'Should have kernelspec').to.not.be.undefined; - expect((kernel as any)?.kernelSpec!.name, 'Spec should have default name').to.include( - defaultKernelSpecName - ); }); test('For a raw connection, if a kernel spec is selected return it with the interpreter', async () => { when(dependencyService.areDependenciesInstalled(interpreter, anything())).thenResolve(true); diff --git a/src/test/datascience/kernelFinder.unit.test.ts b/src/test/datascience/kernelFinder.unit.test.ts index bf37540a375c..8fc98be82c66 100644 --- a/src/test/datascience/kernelFinder.unit.test.ts +++ b/src/test/datascience/kernelFinder.unit.test.ts @@ -14,7 +14,6 @@ import { PythonExecutionFactory } from '../../client/common/process/pythonExecut import { IExtensionContext, IPathUtils, Resource } from '../../client/common/types'; import { Architecture } from '../../client/common/utils/platform'; import { IEnvironmentVariablesProvider } from '../../client/common/variables/types'; -import { defaultKernelSpecName } from '../../client/datascience/jupyter/kernels/helpers'; import { JupyterKernelSpec } from '../../client/datascience/jupyter/kernels/jupyterKernelSpec'; import { KernelFinder } from '../../client/datascience/kernel-launcher/kernelFinder'; import { IKernelFinder } from '../../client/datascience/kernel-launcher/types'; @@ -526,25 +525,6 @@ suite('Kernel Finder', () => { fileSystem.reset(); }); - test('Kernel metadata already has a default spec, and kernel spec not found, then return undefined', async () => { - setupFileSystem(); - fileSystem - .setup((fs) => fs.readLocalFile(typemoq.It.isAnyString())) - .returns((pathParam: string) => { - if (pathParam.includes(cacheFile)) { - return Promise.resolve('[]'); - } - return Promise.resolve('{}'); - }); - // get default kernel - const spec = await kernelFinder.findKernelSpec(resource, { - name: defaultKernelSpecName, - display_name: 'TargetDisplayName' - }); - assert.isUndefined(spec); - fileSystem.reset(); - }); - test('Look for KernelA with no cache, find KernelA and KenelB, then search for KernelB and find it in cache', async () => { setupFileSystem(); fileSystem From f07631180420fba3c1e3fd34b7ba8e5b93d6dd72 Mon Sep 17 00:00:00 2001 From: Rich Chiodo <rchiodo@users.noreply.github.com> Date: Fri, 2 Oct 2020 16:59:48 -0700 Subject: [PATCH 17/24] Port more escape fixes to point release (#14242) * Fix two problems with escaping (#14228) * Fixup changelog --- .../datascience/jupyter/jupyterDebugger.ts | 2 +- .../interactive-common/cellOutput.tsx | 24 +------------------ 2 files changed, 2 insertions(+), 24 deletions(-) diff --git a/src/client/datascience/jupyter/jupyterDebugger.ts b/src/client/datascience/jupyter/jupyterDebugger.ts index afa7df942137..3d04d70c14af 100644 --- a/src/client/datascience/jupyter/jupyterDebugger.ts +++ b/src/client/datascience/jupyter/jupyterDebugger.ts @@ -474,7 +474,7 @@ export class JupyterDebugger implements IJupyterDebugger, ICellHashListener { const data = outputs[0].data; if (data && data.hasOwnProperty('text/plain')) { // tslint:disable-next-line:no-any - return (data as any)['text/plain']; + return concatMultilineString((data as any)['text/plain']); } if (outputs[0].output_type === 'stream') { const stream = outputs[0] as nbformat.IStream; diff --git a/src/datascience-ui/interactive-common/cellOutput.tsx b/src/datascience-ui/interactive-common/cellOutput.tsx index 33cea77eecbb..fe9467110940 100644 --- a/src/datascience-ui/interactive-common/cellOutput.tsx +++ b/src/datascience-ui/interactive-common/cellOutput.tsx @@ -316,22 +316,6 @@ export class CellOutput extends React.Component<ICellOutputProps> { input = JSON.stringify(output.data); renderWithScrollbars = true; isText = true; - } else if ( - output.output_type === 'execute_result' && - input && - input.hasOwnProperty('text/plain') && - !input.hasOwnProperty('text/html') - ) { - // Plain text should actually be shown as html so that escaped HTML shows up correctly - mimeType = 'text/html'; - isText = true; - isError = false; - renderWithScrollbars = true; - // tslint:disable-next-line: no-any - const text = (input as any)['text/plain']; - input = { - 'text/html': lodashEscape(concatMultilineString(text)) - }; } else if (output.output_type === 'stream') { mimeType = 'text/html'; isText = true; @@ -341,7 +325,7 @@ export class CellOutput extends React.Component<ICellOutputProps> { const stream = output as nbformat.IStream; // NOSONAR const concatted = lodashEscape(concatMultilineString(stream.text)); input = { - 'text/html': concatted // XML tags should have already been escaped. + 'text/html': concatted }; // Output may have ascii colorization chars in it. @@ -401,12 +385,6 @@ export class CellOutput extends React.Component<ICellOutputProps> { data = fixMarkdown(concatMultilineString(data as nbformat.MultilineString, true), true); } - // Make sure text output is escaped (nteract texttransform won't) - if (mimeType === 'text/plain' && data) { - data = lodashEscape(data.toString()); - mimeType = 'text/html'; - } - return { isText, isError, From aaca6119296bc719a4f33b4d1d566f86720def47 Mon Sep 17 00:00:00 2001 From: Rich Chiodo <rchiodo@users.noreply.github.com> Date: Mon, 5 Oct 2020 09:06:35 -0700 Subject: [PATCH 18/24] Port prune fix from main to release (#14243) * Remove unneeded cell keys when exporting (#14241) * Remove transient output when exporting from the interactive window * Add news entry * Update changelog --- news/2 Fixes/14213.md | 1 - src/client/datascience/common.ts | 4 +- .../datascience/jupyter/jupyterExporter.ts | 5 +- .../interactiveWindow.functional.test.tsx | 118 +++++++++++------- src/test/datascience/mockJupyterManager.ts | 4 +- src/test/datascience/testHelpers.tsx | 5 +- 6 files changed, 85 insertions(+), 52 deletions(-) delete mode 100644 news/2 Fixes/14213.md diff --git a/news/2 Fixes/14213.md b/news/2 Fixes/14213.md deleted file mode 100644 index 2e3a90f6bdd0..000000000000 --- a/news/2 Fixes/14213.md +++ /dev/null @@ -1 +0,0 @@ -Use the kernel defined in the metadata of Notebook instead of using the default workspace interpreter. diff --git a/src/client/datascience/common.ts b/src/client/datascience/common.ts index 06b1da13b837..a1ab3a6935eb 100644 --- a/src/client/datascience/common.ts +++ b/src/client/datascience/common.ts @@ -38,7 +38,7 @@ const dummyExecuteResultObj: nbformat.IExecuteResult = { data: {}, metadata: {} }; -const AllowedKeys = { +export const AllowedCellOutputKeys = { ['stream']: new Set(Object.keys(dummyStreamObj)), ['error']: new Set(Object.keys(dummyErrorObj)), ['display_data']: new Set(Object.keys(dummyDisplayObj)), @@ -73,7 +73,7 @@ function fixupOutput(output: nbformat.IOutput): nbformat.IOutput { case 'error': case 'execute_result': case 'display_data': - allowedKeys = AllowedKeys[output.output_type]; + allowedKeys = AllowedCellOutputKeys[output.output_type]; break; default: return output; diff --git a/src/client/datascience/jupyter/jupyterExporter.ts b/src/client/datascience/jupyter/jupyterExporter.ts index 23be4872b3d9..c867685a4856 100644 --- a/src/client/datascience/jupyter/jupyterExporter.ts +++ b/src/client/datascience/jupyter/jupyterExporter.ts @@ -17,6 +17,7 @@ import { IConfigurationService } from '../../common/types'; import * as localize from '../../common/utils/localize'; import { noop } from '../../common/utils/misc'; import { CellMatcher } from '../cellMatcher'; +import { pruneCell } from '../common'; import { CodeSnippets, Identifiers } from '../constants'; import { CellState, @@ -235,9 +236,11 @@ export class JupyterExporter implements INotebookExporter { }; private pruneCell = (cell: ICell, cellMatcher: CellMatcher): nbformat.IBaseCell => { + // Prune with the common pruning function first. + const copy = pruneCell({ ...cell.data }); + // Remove the #%% of the top of the source if there is any. We don't need // this to end up in the exported ipynb file. - const copy = { ...cell.data }; copy.source = this.pruneSource(cell.data.source, cellMatcher); return copy; }; diff --git a/src/test/datascience/interactiveWindow.functional.test.tsx b/src/test/datascience/interactiveWindow.functional.test.tsx index 243a970b1aba..3b1d3e19ef16 100644 --- a/src/test/datascience/interactiveWindow.functional.test.tsx +++ b/src/test/datascience/interactiveWindow.functional.test.tsx @@ -9,6 +9,7 @@ import * as path from 'path'; import * as TypeMoq from 'typemoq'; import { Disposable, Memento, Selection, TextDocument, TextEditor, Uri } from 'vscode'; +import { nbformat } from '@jupyterlab/coreutils'; import { ReactWrapper } from 'enzyme'; import { anything, when } from 'ts-mockito'; import { IApplicationShell, IDocumentManager } from '../../client/common/application/types'; @@ -17,11 +18,12 @@ import { createDeferred, sleep, waitForPromise } from '../../client/common/utils import { noop } from '../../client/common/utils/misc'; import { EXTENSION_ROOT_DIR } from '../../client/constants'; import { generateCellsFromDocument } from '../../client/datascience/cellFactory'; +import { AllowedCellOutputKeys } from '../../client/datascience/common'; import { EditorContexts } from '../../client/datascience/constants'; import { InteractiveWindowMessages } from '../../client/datascience/interactive-common/interactiveWindowTypes'; import { InteractiveWindow } from '../../client/datascience/interactive-window/interactiveWindow'; import { AskedForPerFileSettingKey } from '../../client/datascience/interactive-window/interactiveWindowProvider'; -import { IInteractiveWindowProvider } from '../../client/datascience/types'; +import { IDataScienceFileSystem, IInteractiveWindowProvider } from '../../client/datascience/types'; import { IInterpreterService } from '../../client/interpreter/contracts'; import { concatMultilineString } from '../../datascience-ui/common'; import { InteractivePanel } from '../../datascience-ui/history-react/interactivePanel'; @@ -635,53 +637,81 @@ for i in range(0, 100): return; } }; - let exportCalled = false; - const appShell = TypeMoq.Mock.ofType<IApplicationShell>(); - appShell - .setup((a) => a.showErrorMessage(TypeMoq.It.isAnyString())) - .returns((e) => { - throw e; - }); - appShell - .setup((a) => a.showInformationMessage(TypeMoq.It.isAny(), TypeMoq.It.isAny())) - .returns(() => Promise.resolve('')); - appShell - .setup((a) => a.showSaveDialog(TypeMoq.It.isAny())) - .returns(() => { - exportCalled = true; - return Promise.resolve(undefined); - }); - appShell.setup((a) => a.setStatusBarMessage(TypeMoq.It.isAny())).returns(() => dummyDisposable); - ioc.serviceManager.rebindInstance<IApplicationShell>(IApplicationShell, appShell.object); - - // Make sure to create the interactive window after the rebind or it gets the wrong application shell. - await addCode(ioc, 'a=1\na'); - const { window, mount } = await getOrCreateInteractiveWindow(ioc); - - // Export should cause exportCalled to change to true - const exportPromise = mount.waitForMessage(InteractiveWindowMessages.ReturnAllCells); - window.exportCells(); - await exportPromise; - await sleep(100); // Give time for appshell to come up - assert.equal(exportCalled, true, 'Export is not being called during export'); + const dsfs = ioc.get<IDataScienceFileSystem>(IDataScienceFileSystem); + const tf = await dsfs.createTemporaryLocalFile('.ipynb'); + try { + let exportCalled = false; + const appShell = TypeMoq.Mock.ofType<IApplicationShell>(); + appShell + .setup((a) => a.showErrorMessage(TypeMoq.It.isAnyString())) + .returns((e) => { + throw e; + }); + appShell + .setup((a) => a.showInformationMessage(TypeMoq.It.isAny(), TypeMoq.It.isAny())) + .returns(() => Promise.resolve('')); + appShell + .setup((a) => a.showSaveDialog(TypeMoq.It.isAny())) + .returns(() => { + exportCalled = true; + return Promise.resolve(Uri.file(tf.filePath)); + }); + appShell.setup((a) => a.setStatusBarMessage(TypeMoq.It.isAny())).returns(() => dummyDisposable); + ioc.serviceManager.rebindInstance<IApplicationShell>(IApplicationShell, appShell.object); + const exportCode = ` +for i in range(100): + time.sleep(0.1) + raise Exception('test') +`; - // Remove the cell - const exportButton = findButton(mount.wrapper, InteractivePanel, 6); - const undo = findButton(mount.wrapper, InteractivePanel, 2); + // Make sure to create the interactive window after the rebind or it gets the wrong application shell. + addMockData(ioc, exportCode, 'NameError', 'type/error', 'error', [ + '\u001b[1;31m---------------------------------------------------------------------------\u001b[0m', + '\u001b[1;31mNameError\u001b[0m Traceback (most recent call last)', + "\u001b[1;32md:\\Source\\Testing_3\\manualTestFile.py\u001b[0m in \u001b[0;36m<module>\u001b[1;34m\u001b[0m\n\u001b[0;32m 1\u001b[0m \u001b[1;32mfor\u001b[0m \u001b[0mi\u001b[0m \u001b[1;32min\u001b[0m \u001b[0mrange\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;36m100\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m----> 2\u001b[1;33m \u001b[0mtime\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0msleep\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;36m0.1\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m\u001b[0;32m 3\u001b[0m \u001b[1;32mraise\u001b[0m \u001b[0mException\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;34m'test'\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n", + "\u001b[1;31mNameError\u001b[0m: name 'time' is not defined" + ]); + await addCode(ioc, exportCode); + const { window, mount } = await getOrCreateInteractiveWindow(ioc); - // Now verify if we undo, we have no cells - const afterUndo = await getInteractiveCellResults(ioc, () => { - undo!.simulate('click'); - return Promise.resolve(); - }); + // Export should cause exportCalled to change to true + const exportPromise = mount.waitForMessage(InteractiveWindowMessages.ReturnAllCells); + window.exportCells(); + await exportPromise; + await sleep(100); // Give time for appshell to come up + assert.equal(exportCalled, true, 'Export is not being called during export'); + + // Read file contents into a jupyter structure. Make sure we have only the expected values + const contents = await dsfs.readLocalFile(tf.filePath); + const struct = JSON.parse(contents) as nbformat.INotebookContent; + assert.strictEqual(struct.cells.length, 1, 'Wrong number of cells'); + const outputs = struct.cells[0].outputs as nbformat.IOutput[]; + assert.strictEqual(outputs.length, 1, 'Not correct number of outputs'); + assert.strictEqual(outputs[0].output_type, 'error', 'Error not found'); + const allowedKeys = [...AllowedCellOutputKeys.error]; + const actualKeys = Object.keys(outputs[0]); + assert.deepStrictEqual(allowedKeys, actualKeys, 'Invalid keys in output'); + + // Remove the cell + const exportButton = findButton(mount.wrapper, InteractivePanel, 6); + const undo = findButton(mount.wrapper, InteractivePanel, 2); + + // Now verify if we undo, we have no cells + const afterUndo = await getInteractiveCellResults(ioc, () => { + undo!.simulate('click'); + return Promise.resolve(); + }); - assert.equal(afterUndo.length, 1, 'Undo should remove cells'); + assert.equal(afterUndo.length, 1, 'Undo should remove cells'); - // Then verify we cannot click the button (it should be disabled) - exportCalled = false; - exportButton!.simulate('click'); - await sleep(100); - assert.equal(exportCalled, false, 'Export should not be called when no cells visible'); + // Then verify we cannot click the button (it should be disabled) + exportCalled = false; + exportButton!.simulate('click'); + await sleep(100); + assert.equal(exportCalled, false, 'Export should not be called when no cells visible'); + } finally { + tf.dispose(); + } }, () => { return ioc; diff --git a/src/test/datascience/mockJupyterManager.ts b/src/test/datascience/mockJupyterManager.ts index e690c6ef6a22..966b490b6280 100644 --- a/src/test/datascience/mockJupyterManager.ts +++ b/src/test/datascience/mockJupyterManager.ts @@ -289,13 +289,13 @@ export class MockJupyterManager implements IJupyterSessionManager { this.makeActive(interpreter); } - public addError(code: string, message: string) { + public addError(code: string, message: string, traceback?: string[]) { // Turn the message into an nbformat.IError const result: nbformat.IError = { output_type: 'error', ename: message, evalue: message, - traceback: [message] + traceback: traceback ? traceback : [message] }; this.addCell(code, result); diff --git a/src/test/datascience/testHelpers.tsx b/src/test/datascience/testHelpers.tsx index fbd453e00ad4..27f75eaa31ef 100644 --- a/src/test/datascience/testHelpers.tsx +++ b/src/test/datascience/testHelpers.tsx @@ -89,11 +89,12 @@ export function addMockData( code: string, result: string | number | undefined | string[], mimeType?: string | string[], - cellType?: string + cellType?: string, + traceback?: string[] ) { if (ioc.mockJupyter) { if (cellType && cellType === 'error') { - ioc.mockJupyter.addError(code, result ? result.toString() : ''); + ioc.mockJupyter.addError(code, result ? result.toString() : '', traceback); } else { if (result) { ioc.mockJupyter.addCell(code, result, mimeType); From 3efbc39bc09f270d55a9dbe8b04887105de09e9c Mon Sep 17 00:00:00 2001 From: Don Jayamanne <don.jayamanne@yahoo.com> Date: Tue, 6 Oct 2020 09:04:30 -0700 Subject: [PATCH 19/24] Merge fixes related to remembering interpreter (#14270) --- .../jupyter/kernels/kernelSelector.ts | 26 +++++++++++++++++++ .../datascience/notebookStorage/baseModel.ts | 13 ++++++++++ 2 files changed, 39 insertions(+) diff --git a/src/client/datascience/jupyter/kernels/kernelSelector.ts b/src/client/datascience/jupyter/kernels/kernelSelector.ts index 4e9a172ce817..93ff0a676d8b 100644 --- a/src/client/datascience/jupyter/kernels/kernelSelector.ts +++ b/src/client/datascience/jupyter/kernels/kernelSelector.ts @@ -2,6 +2,7 @@ // Licensed under the MIT License. import type { nbformat } from '@jupyterlab/coreutils'; import type { Kernel } from '@jupyterlab/services'; +import { sha256 } from 'hash.js'; import { inject, injectable } from 'inversify'; // tslint:disable-next-line: no-require-imports import cloneDeep = require('lodash/cloneDeep'); @@ -18,6 +19,7 @@ import { PythonEnvironment } from '../../../pythonEnvironments/info'; import { IEventNamePropertyMapping, sendTelemetryEvent } from '../../../telemetry'; import { Commands, KnownNotebookLanguages, Settings, Telemetry } from '../../constants'; import { IKernelFinder } from '../../kernel-launcher/types'; +import { getInterpreterInfoStoredInMetadata } from '../../notebookStorage/baseModel'; import { reportAction } from '../../progress/decorator'; import { ReportableAction } from '../../progress/types'; import { @@ -486,6 +488,17 @@ export class KernelSelector implements IKernelSelectionUsage { } } } + private async findInterpreterStoredInNotebookMetadata( + resource: Resource, + notebookMetadata?: nbformat.INotebookMetadata + ): Promise<PythonEnvironment | undefined> { + const info = getInterpreterInfoStoredInMetadata(notebookMetadata); + if (!info) { + return; + } + const interpreters = await this.interpreterService.getInterpreters(resource); + return interpreters.find((item) => sha256().update(item.path).digest('hex') === info.hash); + } // Get our kernelspec and interpreter for a local raw connection private async getKernelForLocalRawConnection( @@ -494,6 +507,19 @@ export class KernelSelector implements IKernelSelectionUsage { cancelToken?: CancellationToken, ignoreDependencyCheck?: boolean ): Promise<KernelSpecConnectionMetadata | PythonKernelConnectionMetadata | undefined> { + // If user had selected an interpreter (raw kernel), then that interpreter would be stored in the kernelspec metadata. + // Find this matching interpreter & start that using raw kernel. + const interpreterStoredInKernelSpec = await this.findInterpreterStoredInNotebookMetadata( + resource, + notebookMetadata + ); + if (interpreterStoredInKernelSpec) { + return { + kind: 'startUsingPythonInterpreter', + interpreter: interpreterStoredInKernelSpec + }; + } + // First use our kernel finder to locate a kernelspec on disk const kernelSpec = await this.kernelFinder.findKernelSpec( resource, diff --git a/src/client/datascience/notebookStorage/baseModel.ts b/src/client/datascience/notebookStorage/baseModel.ts index db62829a4d0e..1a4de63db0b5 100644 --- a/src/client/datascience/notebookStorage/baseModel.ts +++ b/src/client/datascience/notebookStorage/baseModel.ts @@ -23,6 +23,19 @@ type KernelIdListEntry = { kernelId: string | undefined; }; +export function getInterpreterInfoStoredInMetadata( + metadata?: nbformat.INotebookMetadata +): { displayName: string; hash: string } | undefined { + if (!metadata || !metadata.kernelspec || !metadata.kernelspec.name) { + return; + } + // See `updateNotebookMetadata` to determine how & where exactly interpreter hash is stored. + // tslint:disable-next-line: no-any + const kernelSpecMetadata: undefined | any = metadata.kernelspec.metadata as any; + const interpreterHash = kernelSpecMetadata?.interpreter?.hash; + return interpreterHash ? { displayName: metadata.kernelspec.name, hash: interpreterHash } : undefined; +} + // tslint:disable-next-line: cyclomatic-complexity export function updateNotebookMetadata( metadata?: nbformat.INotebookMetadata, From b05589ef18a485380e29da70e963c11c7776af08 Mon Sep 17 00:00:00 2001 From: David Kutugata <dakutuga@microsoft.com> Date: Tue, 6 Oct 2020 11:37:19 -0700 Subject: [PATCH 20/24] update version, changelog and thrid party notices (#14280) --- CHANGELOG.md | 2 +- ThirdPartyNotices-Distribution.txt | 945 +++++++++++++++++++++++------ package-lock.json | 2 +- package.json | 2 +- 4 files changed, 776 insertions(+), 175 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5e0fadce9e00..5ce03027896b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # Changelog -## 2020.9.2 (5 October 2020) +## 2020.9.2 (6 October 2020) ### Fixes diff --git a/ThirdPartyNotices-Distribution.txt b/ThirdPartyNotices-Distribution.txt index ddb4327c18dd..582b910f7aab 100644 --- a/ThirdPartyNotices-Distribution.txt +++ b/ThirdPartyNotices-Distribution.txt @@ -3265,13 +3265,12 @@ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND --------------------------------------------------------- -estraverse 4.3.0 - BSD-2-Clause -https://github.com/estools/estraverse +estraverse 1.5.1 - BSD-2-Clause +https://github.com/Constellation/estraverse -Copyright (c) 2014 Yusuke Suzuki <utatane.tea@gmail.com> Copyright (c) 2012 Ariya Hidayat <ariya.hidayat@gmail.com> Copyright (c) 2012-2013 Yusuke Suzuki <utatane.tea@gmail.com> -Copyright (c) 2012-2016 Yusuke Suzuki (http://github.com/Constellation) +Copyright (c) 2012-2013 Yusuke Suzuki (http://github.com/Constellation) Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: @@ -3298,12 +3297,13 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. --------------------------------------------------------- -estraverse 1.5.1 - BSD-2-Clause -https://github.com/Constellation/estraverse +estraverse 4.3.0 - BSD-2-Clause +https://github.com/estools/estraverse +Copyright (c) 2014 Yusuke Suzuki <utatane.tea@gmail.com> Copyright (c) 2012 Ariya Hidayat <ariya.hidayat@gmail.com> Copyright (c) 2012-2013 Yusuke Suzuki <utatane.tea@gmail.com> -Copyright (c) 2012-2013 Yusuke Suzuki (http://github.com/Constellation) +Copyright (c) 2012-2016 Yusuke Suzuki (http://github.com/Constellation) Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: @@ -3540,6 +3540,48 @@ Redistribution and use in source and binary forms, with or without modification, THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +--------------------------------------------------------- + +--------------------------------------------------------- + +click 7.1.2 - BSD-2-Clause AND BSD-3-Clause + + +Copyright 2014 Pallets +copyright 2014 Pallets +Copyright 2001-2006 Gregory P. Ward. +Copyright 2002-2006 Python Software Foundation. + +Copyright 2014 Pallets + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A +PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED +TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + --------------------------------------------------------- --------------------------------------------------------- @@ -4436,7 +4478,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. --------------------------------------------------------- -hoist-non-react-statics 3.3.1 - BSD-3-Clause +hoist-non-react-statics 3.3.0 - BSD-3-Clause https://github.com/mridgway/hoist-non-react-statics#readme Copyright 2015, Yahoo! Inc. @@ -4477,7 +4519,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. --------------------------------------------------------- -hoist-non-react-statics 3.3.0 - BSD-3-Clause +hoist-non-react-statics 3.3.1 - BSD-3-Clause https://github.com/mridgway/hoist-non-react-statics#readme Copyright 2015, Yahoo! Inc. @@ -4597,7 +4639,7 @@ The complete list of contributors can be found at: https://github.com/hapijs/qs/ --------------------------------------------------------- -source-map 0.6.1 - BSD-3-Clause +source-map 0.5.7 - BSD-3-Clause https://github.com/mozilla/source-map Copyright 2011 The Closure Compiler @@ -4640,7 +4682,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. --------------------------------------------------------- -source-map 0.5.7 - BSD-3-Clause +source-map 0.6.1 - BSD-3-Clause https://github.com/mozilla/source-map Copyright 2011 The Closure Compiler @@ -5502,8 +5544,8 @@ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. --------------------------------------------------------- -@babel/runtime 7.5.4 - MIT - +@babel/runtime 7.8.3 - MIT +https://babeljs.io/docs/en/next/babel-runtime Copyright (c) 2014-present Sebastian McKenzie and other contributors @@ -5535,8 +5577,8 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. --------------------------------------------------------- -@babel/runtime 7.8.3 - MIT -https://babeljs.io/docs/en/next/babel-runtime +@babel/runtime 7.5.4 - MIT + Copyright (c) 2014-present Sebastian McKenzie and other contributors @@ -7420,7 +7462,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. --------------------------------------------------------- -date-format 2.1.0 - MIT +date-format 3.0.0 - MIT https://github.com/nomiddlename/date-format#readme Copyright (c) 2013 Gareth Jones @@ -7451,7 +7493,7 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. --------------------------------------------------------- -date-format 3.0.0 - MIT +date-format 2.1.0 - MIT https://github.com/nomiddlename/date-format#readme Copyright (c) 2013 Gareth Jones @@ -7482,7 +7524,7 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. --------------------------------------------------------- -debug 3.1.0 - MIT +debug 4.1.1 - MIT https://github.com/visionmedia/debug#readme Copyright (c) 2014 TJ Holowaychuk <tj@vision-media.ca> @@ -7575,7 +7617,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. --------------------------------------------------------- -debug 4.1.1 - MIT +debug 3.1.0 - MIT https://github.com/visionmedia/debug#readme Copyright (c) 2014 TJ Holowaychuk <tj@vision-media.ca> @@ -8764,6 +8806,95 @@ THE SOFTWARE. +--------------------------------------------------------- + +--------------------------------------------------------- + +future 0.18.2 - MIT + + +Copyright 2006 Google, Inc. +Copyright 2013 by the Jinja team +Copyright (c) 2013 - Damian Avila +Copyright 2008 by Armin Ronacher. +Copyright (c) 2000 Bastian Kleineidam +Copyright (c) 2010 by Armin Ronacher. +Copyright (c) 1999-2002 by Fredrik Lundh. +Copyright (c) 1999-2002 by Secret Labs AB. +Copyright 2013-2019 Python Charmers Pty Ltd +copyright u'2013-2019, Python Charmers Pty Ltd +Copyright (c) 2013-2019 Python Charmers Pty Ltd +Copyright (c) 2001-2006 Python Software Foundation +Copyright (c) 2001-2007 Python Software Foundation +Copyright (c) 2001-2010 Python Software Foundation +Copyright (c) 2002-2006 Python Software Foundation +Copyright (c) 2002-2007 Python Software Foundation +Copyright (c) 2004-2006 Python Software Foundation +Copyright 2000 by Timothy O'Malley <timo@alum.mit.edu> +Copyright 2011 by Armin Ronacher. :license Flask Design License +Copyright (c) 2000 Luke Kenneth Casson Leighton <lkcl@samba.org> +Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 Python Software Foundation. + +Copyright (c) 2013-2019 Python Charmers Pty Ltd, Australia + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + + +Copyright (c) 2010 by Armin Ronacher. + +Some rights reserved. + +Redistribution and use in source and binary forms of the theme, with or +without modification, are permitted provided that the following conditions +are met: + +* Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + +* The names of the contributors may not be used to endorse or + promote products derived from this software without specific + prior written permission. + +We kindly ask you to only use these themes in an unmodified manner just +for Flask and Flask-related products, not for unrelated projects. If you +like the visual style and want to use it for your own projects, please +consider making some larger changes to the themes (such as changing +font faces, sizes, colors or margins). + +THIS THEME IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS THEME, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + + --------------------------------------------------------- --------------------------------------------------------- @@ -8928,7 +9059,7 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI --------------------------------------------------------- -got 9.6.0 - MIT +got 8.3.2 - MIT https://github.com/sindresorhus/got#readme Copyright (c) Sindre Sorhus <sindresorhus@gmail.com> (sindresorhus.com) @@ -8948,7 +9079,7 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI --------------------------------------------------------- -got 8.3.2 - MIT +got 9.6.0 - MIT https://github.com/sindresorhus/got#readme Copyright (c) Sindre Sorhus <sindresorhus@gmail.com> (sindresorhus.com) @@ -9431,7 +9562,7 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. --------------------------------------------------------- -isarray 0.0.1 - MIT +isarray 1.0.0 - MIT https://github.com/juliangruber/isarray Copyright (c) 2013 Julian Gruber <julian@juliangruber.com> @@ -9450,7 +9581,7 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI --------------------------------------------------------- -isarray 1.0.0 - MIT +isarray 0.0.1 - MIT https://github.com/juliangruber/isarray Copyright (c) 2013 Julian Gruber <julian@juliangruber.com> @@ -9922,66 +10053,21 @@ THE SOFTWARE. --------------------------------------------------------- -jsbn 0.1.1 - MIT -https://github.com/andyperlitch/jsbn#readme - -Copyright (c) 2005 Tom Wu -Copyright (c) 2003-2005 Tom Wu -Copyright (c) 2005-2009 Tom Wu - -Licensing ---------- - -This software is covered under the following copyright: - -/* - * Copyright (c) 2003-2005 Tom Wu - * All Rights Reserved. - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS-IS" AND WITHOUT WARRANTY OF ANY KIND, - * EXPRESS, IMPLIED OR OTHERWISE, INCLUDING WITHOUT LIMITATION, ANY - * WARRANTY OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. - * - * IN NO EVENT SHALL TOM WU BE LIABLE FOR ANY SPECIAL, INCIDENTAL, - * INDIRECT OR CONSEQUENTIAL DAMAGES OF ANY KIND, OR ANY DAMAGES WHATSOEVER - * RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER OR NOT ADVISED OF - * THE POSSIBILITY OF DAMAGE, AND ON ANY THEORY OF LIABILITY, ARISING OUT - * OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - * - * In addition, the following condition applies: - * - * All redistributions must retain an intact copy of this copyright notice - * and disclaimer. - */ - -Address all questions regarding this license to: - - Tom Wu - tjw@cs.Stanford.EDU - ---------------------------------------------------------- +jedi 0.17.2 - MIT ---------------------------------------------------------- -json5 2.1.0 - MIT -http://json5.org/ +Copyright (c) <2013> +Copyright (c) Maxim Kurnikov. +copyright u'jedi contributors +copyright (c) 2014 by Armin Ronacher. +Copyright (c) 2015 Jukka Lehtosalo and contributors -Copyright (c) 2012-2018 Aseem Kishore, and others +All contributions towards Jedi are MIT licensed. -MIT License +------------------------------------------------------------------------------- +The MIT License (MIT) -Copyright (c) 2012-2018 Aseem Kishore, and [others]. +Copyright (c) <2013> <David Halter and others, see AUTHORS.txt> Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -9990,83 +10076,417 @@ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. -[others]: https://github.com/json5/json5/contributors +Copyright (c) Maxim Kurnikov. +All rights reserved. ---------------------------------------------------------- +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: ---------------------------------------------------------- +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. -json-buffer 3.0.0 - MIT -https://github.com/dominictarr/json-buffer +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -Copyright (c) 2013 Dominic Tarr -Copyright (c) 2013 Dominic Tarr +The "typeshed" project is licensed under the terms of the Apache license, as +reproduced below. -Permission is hereby granted, free of charge, -to any person obtaining a copy of this software and -associated documentation files (the "Software"), to -deal in the Software without restriction, including -without limitation the rights to use, copy, modify, -merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom -the Software is furnished to do so, -subject to the following conditions: += = = = = -The above copyright notice and this permission notice -shall be included in all copies or substantial portions of the Software. +Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR -ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + 1. Definitions. ---------------------------------------------------------- + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. ---------------------------------------------------------- + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. -jsonc-parser 2.1.0 - MIT -https://github.com/Microsoft/node-jsonc-parser#readme + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. -Copyright (c) Microsoft -Copyright 2018, Microsoft -Copyright (c) Microsoft Corporation. + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. -The MIT License (MIT) + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. -Copyright (c) Microsoft + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright {yyyy} {name of copyright owner} + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + += = = = = + +Parts of typeshed are licensed under different licenses (like the MIT +license), reproduced below. + += = = = = + +The MIT License + +Copyright (c) 2015 Jukka Lehtosalo and contributors + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the "Software"), +to deal in the Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. + += = = = = + + + +--------------------------------------------------------- + +--------------------------------------------------------- + +jsbn 0.1.1 - MIT +https://github.com/andyperlitch/jsbn#readme + +Copyright (c) 2005 Tom Wu +Copyright (c) 2003-2005 Tom Wu +Copyright (c) 2005-2009 Tom Wu + +Licensing +--------- + +This software is covered under the following copyright: + +/* + * Copyright (c) 2003-2005 Tom Wu + * All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS-IS" AND WITHOUT WARRANTY OF ANY KIND, + * EXPRESS, IMPLIED OR OTHERWISE, INCLUDING WITHOUT LIMITATION, ANY + * WARRANTY OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. + * + * IN NO EVENT SHALL TOM WU BE LIABLE FOR ANY SPECIAL, INCIDENTAL, + * INDIRECT OR CONSEQUENTIAL DAMAGES OF ANY KIND, OR ANY DAMAGES WHATSOEVER + * RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER OR NOT ADVISED OF + * THE POSSIBILITY OF DAMAGE, AND ON ANY THEORY OF LIABILITY, ARISING OUT + * OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * In addition, the following condition applies: + * + * All redistributions must retain an intact copy of this copyright notice + * and disclaimer. + */ + +Address all questions regarding this license to: + + Tom Wu + tjw@cs.Stanford.EDU + +--------------------------------------------------------- + +--------------------------------------------------------- + +json5 2.1.0 - MIT +http://json5.org/ + +Copyright (c) 2012-2018 Aseem Kishore, and others + +MIT License + +Copyright (c) 2012-2018 Aseem Kishore, and [others]. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +[others]: https://github.com/json5/json5/contributors + + +--------------------------------------------------------- + +--------------------------------------------------------- + +json-buffer 3.0.0 - MIT +https://github.com/dominictarr/json-buffer + +Copyright (c) 2013 Dominic Tarr + +Copyright (c) 2013 Dominic Tarr + +Permission is hereby granted, free of charge, +to any person obtaining a copy of this software and +associated documentation files (the "Software"), to +deal in the Software without restriction, including +without limitation the rights to use, copy, modify, +merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom +the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR +ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + +--------------------------------------------------------- + +--------------------------------------------------------- + +jsonc-parser 2.1.0 - MIT +https://github.com/Microsoft/node-jsonc-parser#readme + +Copyright (c) Microsoft +Copyright 2018, Microsoft +Copyright (c) Microsoft Corporation. + +The MIT License (MIT) + +Copyright (c) Microsoft + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. @@ -11051,7 +11471,7 @@ THE SOFTWARE. --------------------------------------------------------- -node-fetch 2.6.0 - MIT +node-fetch 2.6.1 - MIT https://github.com/bitinn/node-fetch Copyright (c) 2016 David Frank @@ -11173,9 +11593,10 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. --------------------------------------------------------- -normalize-url 4.5.0 - MIT +normalize-url 2.0.1 - MIT https://github.com/sindresorhus/normalize-url#readme +(c) Sindre Sorhus (https://sindresorhus.com) Copyright (c) Sindre Sorhus <sindresorhus@gmail.com> (sindresorhus.com) MIT License @@ -11193,10 +11614,9 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI --------------------------------------------------------- -normalize-url 2.0.1 - MIT +normalize-url 4.5.0 - MIT https://github.com/sindresorhus/normalize-url#readme -(c) Sindre Sorhus (https://sindresorhus.com) Copyright (c) Sindre Sorhus <sindresorhus@gmail.com> (sindresorhus.com) MIT License @@ -11488,6 +11908,154 @@ The above copyright notice and this permission notice shall be included in all c THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +--------------------------------------------------------- + +--------------------------------------------------------- + +parso 0.7.0 - MIT + + +Copyright (c) <2013-2017> +Copyright 2006 Google, Inc. +copyright u'parso contributors +Copyright (c) 2010 by Armin Ronacher. +Copyright David Halter and Contributors +Copyright 2004-2005 Elemental Security, Inc. +Copyright 2014 David Halter and Contributors +Copyright (c) 2014-2016 Ian Lee <IanLee1521@gmail.com> +Copyright (c) 2017-???? Dave Halter <davidhalter88@gmail.com> +Copyright (c) 2006-2009 Johann C. Rocholl <johann@rocholl.net> +Copyright 2010 by Armin Ronacher. :license Flask Design License +Copyright (c) 2009-2014 Florent Xicluna <florent.xicluna@gmail.com> +Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015 Python Software Foundation + +All contributions towards parso are MIT licensed. + +Some Python files have been taken from the standard library and are therefore +PSF licensed. Modifications on these files are dual licensed (both MIT and +PSF). These files are: + +- parso/pgen2/* +- parso/tokenize.py +- parso/token.py +- test/test_pgen2.py + +Also some test files under test/normalizer_issue_files have been copied from +https://github.com/PyCQA/pycodestyle (Expat License == MIT License). + +------------------------------------------------------------------------------- +The MIT License (MIT) + +Copyright (c) <2013-2017> <David Halter and others, see AUTHORS.txt> + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +------------------------------------------------------------------------------- + +PYTHON SOFTWARE FOUNDATION LICENSE VERSION 2 +-------------------------------------------- + +1. This LICENSE AGREEMENT is between the Python Software Foundation +("PSF"), and the Individual or Organization ("Licensee") accessing and +otherwise using this software ("Python") in source or binary form and +its associated documentation. + +2. Subject to the terms and conditions of this License Agreement, PSF hereby +grants Licensee a nonexclusive, royalty-free, world-wide license to reproduce, +analyze, test, perform and/or display publicly, prepare derivative works, +distribute, and otherwise use Python alone or in any derivative version, +provided, however, that PSF's License Agreement and PSF's notice of copyright, +i.e., "Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, +2011, 2012, 2013, 2014, 2015 Python Software Foundation; All Rights Reserved" +are retained in Python alone or in any derivative version prepared by Licensee. + +3. In the event Licensee prepares a derivative work that is based on +or incorporates Python or any part thereof, and wants to make +the derivative work available to others as provided herein, then +Licensee hereby agrees to include in any such work a brief summary of +the changes made to Python. + +4. PSF is making Python available to Licensee on an "AS IS" +basis. PSF MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR +IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, PSF MAKES NO AND +DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS +FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF PYTHON WILL NOT +INFRINGE ANY THIRD PARTY RIGHTS. + +5. PSF SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON +FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS +A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON, +OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF. + +6. This License Agreement will automatically terminate upon a material +breach of its terms and conditions. + +7. Nothing in this License Agreement shall be deemed to create any +relationship of agency, partnership, or joint venture between PSF and +Licensee. This License Agreement does not grant permission to use PSF +trademarks or trade name in a trademark sense to endorse or promote +products or services of Licensee, or any third party. + +8. By copying, installing or otherwise using Python, Licensee +agrees to be bound by the terms and conditions of this License +Agreement. + + +Copyright (c) 2010 by Armin Ronacher. + +Some rights reserved. + +Redistribution and use in source and binary forms of the theme, with or +without modification, are permitted provided that the following conditions +are met: + +* Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + +* The names of the contributors may not be used to endorse or + promote products derived from this software without specific + prior written permission. + +We kindly ask you to only use these themes in an unmodified manner just +for Flask and Flask-related products, not for unrelated projects. If you +like the visual style and want to use it for your own projects, please +consider making some larger changes to the themes (such as changing +font faces, sizes, colors or margins). + +THIS THEME IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS THEME, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + + --------------------------------------------------------- --------------------------------------------------------- @@ -11558,7 +12126,7 @@ SOFTWARE. --------------------------------------------------------- -p-cancelable 1.1.0 - MIT +p-cancelable 0.4.1 - MIT https://github.com/sindresorhus/p-cancelable#readme (c) Sindre Sorhus (https://sindresorhus.com) @@ -11579,7 +12147,7 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI --------------------------------------------------------- -p-cancelable 0.4.1 - MIT +p-cancelable 1.1.0 - MIT https://github.com/sindresorhus/p-cancelable#readme (c) Sindre Sorhus (https://sindresorhus.com) @@ -11931,7 +12499,7 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI --------------------------------------------------------- -process-nextick-args 1.0.7 - MIT +process-nextick-args 2.0.1 - MIT https://github.com/calvinmetcalf/process-nextick-args Copyright (c) 2015 Calvin Metcalf @@ -11961,7 +12529,7 @@ SOFTWARE.** --------------------------------------------------------- -process-nextick-args 2.0.1 - MIT +process-nextick-args 1.0.7 - MIT https://github.com/calvinmetcalf/process-nextick-args Copyright (c) 2015 Calvin Metcalf @@ -12128,7 +12696,7 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI --------------------------------------------------------- -pump 2.0.1 - MIT +pump 3.0.0 - MIT https://github.com/mafintosh/pump#readme Copyright (c) 2014 Mathias Buus @@ -12159,7 +12727,7 @@ THE SOFTWARE. --------------------------------------------------------- -pump 3.0.0 - MIT +pump 2.0.1 - MIT https://github.com/mafintosh/pump#readme Copyright (c) 2014 Mathias Buus @@ -12248,6 +12816,39 @@ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +--------------------------------------------------------- + +--------------------------------------------------------- + +python-jsonrpc-server 0.2.0 - MIT + + +Copyright 2017 Palantir Technologies, Inc. +Copyright 2018 Palantir Technologies, Inc. + +The MIT License (MIT) + +Copyright 2017 Palantir Technologies, Inc. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + --------------------------------------------------------- --------------------------------------------------------- @@ -12429,7 +13030,7 @@ IN THE SOFTWARE. --------------------------------------------------------- -readable-stream 2.3.6 - MIT +readable-stream 2.3.7 - MIT https://github.com/nodejs/readable-stream#readme Copyright Joyent, Inc. and other Node contributors. @@ -12487,7 +13088,7 @@ IN THE SOFTWARE. --------------------------------------------------------- -readable-stream 2.3.7 - MIT +readable-stream 2.3.6 - MIT https://github.com/nodejs/readable-stream#readme Copyright Joyent, Inc. and other Node contributors. @@ -12703,8 +13304,8 @@ SOFTWARE. --------------------------------------------------------- -resolve 1.11.1 - MIT -https://github.com/browserify/resolve#readme +resolve 1.1.7 - MIT +https://github.com/substack/node-resolve#readme This software is released under the MIT license: @@ -12731,8 +13332,8 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. --------------------------------------------------------- -resolve 1.1.7 - MIT -https://github.com/substack/node-resolve#readme +resolve 1.11.1 - MIT +https://github.com/browserify/resolve#readme This software is released under the MIT license: @@ -13134,7 +13735,7 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. --------------------------------------------------------- -static-module 3.0.3 - MIT +static-module 2.2.5 - MIT https://github.com/substack/static-module @@ -13162,7 +13763,7 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. --------------------------------------------------------- -static-module 2.2.5 - MIT +static-module 3.0.3 - MIT https://github.com/substack/static-module @@ -14067,7 +14668,7 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI --------------------------------------------------------- -unicode-trie 1.0.0 - MIT +unicode-trie 2.0.0 - MIT https://github.com/devongovett/unicode-trie Copyright 2018 @@ -14086,7 +14687,7 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI --------------------------------------------------------- -unicode-trie 2.0.0 - MIT +unicode-trie 1.0.0 - MIT https://github.com/devongovett/unicode-trie Copyright 2018 @@ -14320,27 +14921,6 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---------------------------------------------------------- - ---------------------------------------------------------- - -uuid 8.2.0 - MIT -https://github.com/uuidjs/uuid#readme - -Copyright 2011, Sebastian Tschan https://blueimp.net -Copyright (c) Paul Johnston 1999 - 2009 Other contributors Greg Holt, Andrew Kepert, Ydnar, Lostinet - -The MIT License (MIT) - -Copyright (c) 2010-2020 Robert Kieffer and other contributors - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - - --------------------------------------------------------- --------------------------------------------------------- @@ -14375,6 +14955,27 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +--------------------------------------------------------- + +--------------------------------------------------------- + +uuid 8.2.0 - MIT +https://github.com/uuidjs/uuid#readme + +Copyright 2011, Sebastian Tschan https://blueimp.net +Copyright (c) Paul Johnston 1999 - 2009 Other contributors Greg Holt, Andrew Kepert, Ydnar, Lostinet + +The MIT License (MIT) + +Copyright (c) 2010-2020 Robert Kieffer and other contributors + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + --------------------------------------------------------- --------------------------------------------------------- @@ -14825,7 +15426,7 @@ SOFTWARE. --------------------------------------------------------- -xml2js 0.2.8 - MIT +xml2js 0.4.19 - MIT https://github.com/Leonidas-from-XIV/node-xml2js Copyright 2010, 2011, 2012, 2013. @@ -14855,7 +15456,7 @@ IN THE SOFTWARE. --------------------------------------------------------- -xml2js 0.4.19 - MIT +xml2js 0.2.8 - MIT https://github.com/Leonidas-from-XIV/node-xml2js Copyright 2010, 2011, 2012, 2013. diff --git a/package-lock.json b/package-lock.json index e57358bdbf7e..95684038420f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "python", - "version": "2020.9.1", + "version": "2020.9.2", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 5cec5744359e..8d0095f9bfba 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "python", "displayName": "Python", "description": "Linting, Debugging (multi-threaded, remote), Intellisense, Jupyter Notebooks, code formatting, refactoring, unit tests, snippets, and more.", - "version": "2020.9.1", + "version": "2020.9.2", "featureFlags": { "usingNewInterpreterStorage": true }, From e822cc6f7beb6b17375dfdf1150380769ab8fc56 Mon Sep 17 00:00:00 2001 From: David Kutugata <dakutuga@microsoft.com> Date: Tue, 6 Oct 2020 16:23:39 -0700 Subject: [PATCH 21/24] remove VSC_PYTHON_CI_TEST_VSC_CHANNEL and test skips --- build/ci/templates/globals.yml | 1 - pythonFiles/tests/testing_tools/adapter/test_functional.py | 2 -- 2 files changed, 3 deletions(-) diff --git a/build/ci/templates/globals.yml b/build/ci/templates/globals.yml index 98bd51685dd0..03457023e99e 100644 --- a/build/ci/templates/globals.yml +++ b/build/ci/templates/globals.yml @@ -11,4 +11,3 @@ variables: npm_config_cache: $(Pipeline.Workspace)/.npm vmImageMacOS: 'macOS-10.15' TS_NODE_FILES: true # Temporarily enabled to allow using types from vscode.proposed.d.ts from ts-node (for tests). - VSC_PYTHON_CI_TEST_VSC_CHANNEL: '1.48.0' # Enforce this until https://github.com/microsoft/vscode-test/issues/73 is fixed diff --git a/pythonFiles/tests/testing_tools/adapter/test_functional.py b/pythonFiles/tests/testing_tools/adapter/test_functional.py index c41fa8f5d4c7..153ad5508d9b 100644 --- a/pythonFiles/tests/testing_tools/adapter/test_functional.py +++ b/pythonFiles/tests/testing_tools/adapter/test_functional.py @@ -148,7 +148,6 @@ def test_discover_simple(self): ], ) - @pytest.mark.skip(reason="https://github.com/microsoft/vscode-python/issues/14023") def test_discover_complex_default(self): projroot, testroot = resolve_testroot("complex") expected = self.complex(projroot) @@ -169,7 +168,6 @@ def test_discover_complex_default(self): self.maxDiff = None self.assertEqual(sorted_object(result), sorted_object(expected)) - @pytest.mark.skip(reason="https://github.com/microsoft/vscode-python/issues/14023") def test_discover_complex_doctest(self): projroot, _ = resolve_testroot("complex") expected = self.complex(projroot) From 57d1561a4b096ca308e98ce4cc48c5c51feb60bd Mon Sep 17 00:00:00 2001 From: David Kutugata <dakutuga@microsoft.com> Date: Tue, 6 Oct 2020 17:13:38 -0700 Subject: [PATCH 22/24] delete ipynb file --- src/test/datascience/Untitled-1.ipynb | 47 --------------------------- 1 file changed, 47 deletions(-) delete mode 100644 src/test/datascience/Untitled-1.ipynb diff --git a/src/test/datascience/Untitled-1.ipynb b/src/test/datascience/Untitled-1.ipynb deleted file mode 100644 index 603be536258d..000000000000 --- a/src/test/datascience/Untitled-1.ipynb +++ /dev/null @@ -1,47 +0,0 @@ -{ - "metadata": { - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.6.6-final" - }, - "orig_nbformat": 2 - }, - "nbformat": 4, - "nbformat_minor": 2, - "cells": [ - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] - }, - { - "cell_type": "code", - "execution_count": 1, - "metadata": {}, - "outputs": [ - { - "output_type": "execute_result", - "data": { - "text/plain": "1" - }, - "metadata": {}, - "execution_count": 1 - } - ], - "source": [ - "a=1\n", - "a" - ] - } - ] -} \ No newline at end of file From 862f5673606c700fc051b451b642836ae45c1a14 Mon Sep 17 00:00:00 2001 From: David Kutugata <dakutuga@microsoft.com> Date: Tue, 6 Oct 2020 17:22:15 -0700 Subject: [PATCH 23/24] delete solved news files --- news/2 Fixes/14169.md | 1 - news/2 Fixes/14182.md | 1 - news/2 Fixes/14210.md | 1 - 3 files changed, 3 deletions(-) delete mode 100644 news/2 Fixes/14169.md delete mode 100644 news/2 Fixes/14182.md delete mode 100644 news/2 Fixes/14210.md diff --git a/news/2 Fixes/14169.md b/news/2 Fixes/14169.md deleted file mode 100644 index 5588237a9faf..000000000000 --- a/news/2 Fixes/14169.md +++ /dev/null @@ -1 +0,0 @@ -Support nbconvert version 6+ for exporting notebooks to python code. \ No newline at end of file diff --git a/news/2 Fixes/14182.md b/news/2 Fixes/14182.md deleted file mode 100644 index 53103b73ee62..000000000000 --- a/news/2 Fixes/14182.md +++ /dev/null @@ -1 +0,0 @@ -Do not escape output in the actual ipynb file. \ No newline at end of file diff --git a/news/2 Fixes/14210.md b/news/2 Fixes/14210.md deleted file mode 100644 index af800ca0f607..000000000000 --- a/news/2 Fixes/14210.md +++ /dev/null @@ -1 +0,0 @@ -Fix exporting from the interactive window. \ No newline at end of file From 7f1a48beb2cc54a72f9d3e379c8afcb2e80a46a5 Mon Sep 17 00:00:00 2001 From: David Kutugata <dakutuga@microsoft.com> Date: Tue, 6 Oct 2020 17:24:35 -0700 Subject: [PATCH 24/24] delete more news --- news/1 Enhancements/14027.md | 1 - news/2 Fixes/11151.md | 1 - news/2 Fixes/13956.md | 1 - news/2 Fixes/13981.md | 1 - news/2 Fixes/5678.md | 1 - 5 files changed, 5 deletions(-) delete mode 100644 news/1 Enhancements/14027.md delete mode 100644 news/2 Fixes/11151.md delete mode 100644 news/2 Fixes/13956.md delete mode 100644 news/2 Fixes/13981.md delete mode 100644 news/2 Fixes/5678.md diff --git a/news/1 Enhancements/14027.md b/news/1 Enhancements/14027.md deleted file mode 100644 index 810963c7b177..000000000000 --- a/news/1 Enhancements/14027.md +++ /dev/null @@ -1 +0,0 @@ -Upgraded to isort `5.5.3`. diff --git a/news/2 Fixes/11151.md b/news/2 Fixes/11151.md deleted file mode 100644 index 1b2eb171a996..000000000000 --- a/news/2 Fixes/11151.md +++ /dev/null @@ -1 +0,0 @@ -Remove transient data when saving a notebook from the interactive window. \ No newline at end of file diff --git a/news/2 Fixes/13956.md b/news/2 Fixes/13956.md deleted file mode 100644 index a0621c5ee7d1..000000000000 --- a/news/2 Fixes/13956.md +++ /dev/null @@ -1 +0,0 @@ -Correctly install ipykernel when launching from an interpreter. \ No newline at end of file diff --git a/news/2 Fixes/13981.md b/news/2 Fixes/13981.md deleted file mode 100644 index 265cd21bdb40..000000000000 --- a/news/2 Fixes/13981.md +++ /dev/null @@ -1 +0,0 @@ -Backup on custom editors is being ignored. \ No newline at end of file diff --git a/news/2 Fixes/5678.md b/news/2 Fixes/5678.md deleted file mode 100644 index 1a7d1c5fd977..000000000000 --- a/news/2 Fixes/5678.md +++ /dev/null @@ -1 +0,0 @@ -Fix escaping of output to encode HTML chars correctly. \ No newline at end of file