Skip to content
69 changes: 68 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,72 @@
# Changelog

## 2021.7.0-rc (12 July 2021)
## 2021.7.1 (21 July 2021)

### Enhancements

1. Update `debugpy` to the latest version.

### 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/)
- [jedi-language-server](https://pypi.org/project/jedi-language-server/)
- [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!

## 2021.7.0 (20 July 2021)

### Enhancements

1. Support starting a TensorBoard session with a remote URL hosting log files.
([#16461](https://github.com/Microsoft/vscode-python/issues/16461))
1. Sort environments in the selection quickpick by assumed usefulness.
([#16520](https://github.com/Microsoft/vscode-python/issues/16520))

Expand All @@ -24,6 +87,10 @@
([#16607](https://github.com/Microsoft/vscode-python/issues/16607))
1. The Jupyter Notebook extension will install any missing dependencies using Poetry or Pipenv if those are the selected environments. (thanks [Anthony Shaw](https://github.com/tonybaloney))
([#16615](https://github.com/Microsoft/vscode-python/issues/16615))
1. Ensure we block on autoselection when no interpreter is explictly set by user.
([#16723](https://github.com/Microsoft/vscode-python/issues/16723))
1. Fix autoselection when opening a python file directly.
([#16733](https://github.com/Microsoft/vscode-python/issues/16733))

### Thanks

Expand Down
1 change: 0 additions & 1 deletion news/1 Enhancements/16461.md

This file was deleted.

1 change: 0 additions & 1 deletion src/client/activation/activationManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,6 @@ export class ExtensionActivationManager implements IExtensionActivationManager {
Promise.all(this.singleActivationServices.map((item) => item.activate())),
this.activateWorkspace(this.activeResourceService.getActiveResource()),
]);
await this.autoSelection.autoSelectInterpreter(undefined);
}

@traceDecorators.error('Failed to activate a workspace')
Expand Down
11 changes: 3 additions & 8 deletions src/client/common/configSettings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -679,18 +679,13 @@ export class PythonSettings implements IPythonSettings {
const autoSelectedPythonInterpreter = this.interpreterAutoSelectionService.getAutoSelectedInterpreter(
this.workspaceRoot,
);
if (inExperiment) {
if (autoSelectedPythonInterpreter && this.workspaceRoot) {
this.pythonPath = autoSelectedPythonInterpreter.path;
if (autoSelectedPythonInterpreter) {
this.pythonPath = autoSelectedPythonInterpreter.path;
if (this.workspaceRoot) {
this.interpreterAutoSelectionService
.setWorkspaceInterpreter(this.workspaceRoot, autoSelectedPythonInterpreter)
.ignoreErrors();
}
} else if (autoSelectedPythonInterpreter && this.workspaceRoot) {
this.pythonPath = autoSelectedPythonInterpreter.path;
this.interpreterAutoSelectionService
.setWorkspaceInterpreter(this.workspaceRoot, autoSelectedPythonInterpreter)
.ignoreErrors();
}
}
if (inExperiment && this.pythonPath === DEFAULT_INTERPRETER_SETTING) {
Expand Down
33 changes: 33 additions & 0 deletions src/client/common/interpreterPathProxyService.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

'use strict';

import { inject, injectable } from 'inversify';
import { IWorkspaceService } from './application/types';
import { DeprecatePythonPath } from './experiments/groups';
import { IExperimentService, IInterpreterPathProxyService, IInterpreterPathService, Resource } from './types';
import { SystemVariables } from './variables/systemVariables';

@injectable()
export class InterpreterPathProxyService implements IInterpreterPathProxyService {
constructor(
@inject(IInterpreterPathService) private readonly interpreterPathService: IInterpreterPathService,
@inject(IExperimentService) private readonly experiment: IExperimentService,
@inject(IWorkspaceService) private readonly workspace: IWorkspaceService,
) {}

public get(resource: Resource): string {
const systemVariables = new SystemVariables(
undefined,
this.workspace.getWorkspaceFolder(resource)?.uri.fsPath,
this.workspace,
);
const pythonSettings = this.workspace.getConfiguration('python', resource);
return systemVariables.resolveAny(
this.experiment.inExperimentSync(DeprecatePythonPath.experiment)
? this.interpreterPathService.get(resource)
: pythonSettings.get<string>('pythonPath'),
)!;
}
}
6 changes: 6 additions & 0 deletions src/client/common/serviceRegistry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import {
IToolExecutionPath,
IsWindows,
ToolExecutionPath,
IInterpreterPathProxyService,
} from './types';
import { IServiceManager } from '../ioc/types';
import { JupyterExtensionDependencyManager } from '../jupyter/jupyterExtensionDependencyManager';
Expand Down Expand Up @@ -118,11 +119,16 @@ import { IMultiStepInputFactory, MultiStepInputFactory } from './utils/multiStep
import { Random } from './utils/random';
import { JupyterNotInstalledNotificationHelper } from '../jupyter/jupyterNotInstalledNotificationHelper';
import { IJupyterNotInstalledNotificationHelper } from '../jupyter/types';
import { InterpreterPathProxyService } from './interpreterPathProxyService';

export function registerTypes(serviceManager: IServiceManager): void {
serviceManager.addSingletonInstance<boolean>(IsWindows, IS_WINDOWS);

serviceManager.addSingleton<IActiveResourceService>(IActiveResourceService, ActiveResourceService);
serviceManager.addSingleton<IInterpreterPathProxyService>(
IInterpreterPathProxyService,
InterpreterPathProxyService,
);
serviceManager.addSingleton<IInterpreterPathService>(IInterpreterPathService, InterpreterPathService);
serviceManager.addSingleton<IExtensions>(IExtensions, Extensions);
serviceManager.addSingleton<IRandom>(IRandom, Random);
Expand Down
8 changes: 8 additions & 0 deletions src/client/common/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -557,6 +557,14 @@ export interface IInterpreterPathService {
copyOldInterpreterStorageValuesToNew(resource: Uri | undefined): Promise<void>;
}

/**
* Interface used to access current Interpreter Path
*/
export const IInterpreterPathProxyService = Symbol('IInterpreterPathProxyService');
export interface IInterpreterPathProxyService {
get(resource: Resource): string;
}

export type DefaultLSType = LanguageServerType.Jedi | LanguageServerType.JediLSP | LanguageServerType.Node;

/**
Expand Down
2 changes: 1 addition & 1 deletion src/client/interpreter/autoSelection/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -260,7 +260,7 @@ export class InterpreterAutoSelectionService implements IInterpreterAutoSelectio
* As such, we can sort interpreters based on what it returns.
*/
private async autoselectInterpreterWithLocators(resource: Resource): Promise<void> {
const interpreters = await this.interpreterService.getInterpreters(resource);
const interpreters = await this.interpreterService.getInterpreters(resource, { ignoreCache: true });
const workspaceUri = this.interpreterHelper.getActiveWorkspaceUri(resource);

// When auto-selecting an intepreter for a workspace, we either want to return a local one
Expand Down
16 changes: 12 additions & 4 deletions src/client/interpreter/display/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,13 @@ import { Disposable, OutputChannel, StatusBarAlignment, StatusBarItem, Uri } fro
import { IApplicationShell, IWorkspaceService } from '../../common/application/types';
import { STANDARD_OUTPUT_CHANNEL } from '../../common/constants';
import '../../common/extensions';
import { IConfigurationService, IDisposableRegistry, IOutputChannel, IPathUtils, Resource } from '../../common/types';
import {
IDisposableRegistry,
IInterpreterPathProxyService,
IOutputChannel,
IPathUtils,
Resource,
} from '../../common/types';
import { Interpreters } from '../../common/utils/localize';
import { IServiceContainer } from '../../ioc/types';
import { PythonEnvironment } from '../../pythonEnvironments/info';
Expand All @@ -22,7 +28,7 @@ export class InterpreterDisplay implements IInterpreterDisplay {
private readonly workspaceService: IWorkspaceService;
private readonly pathUtils: IPathUtils;
private readonly interpreterService: IInterpreterService;
private readonly configService: IConfigurationService;
private readonly interpreterPathExpHelper: IInterpreterPathProxyService;
private currentlySelectedInterpreterPath?: string;
private currentlySelectedWorkspaceFolder: Resource;
private readonly autoSelection: IInterpreterAutoSelectionService;
Expand All @@ -39,7 +45,9 @@ export class InterpreterDisplay implements IInterpreterDisplay {

const application = serviceContainer.get<IApplicationShell>(IApplicationShell);
const disposableRegistry = serviceContainer.get<Disposable[]>(IDisposableRegistry);
this.configService = serviceContainer.get<IConfigurationService>(IConfigurationService);
this.interpreterPathExpHelper = serviceContainer.get<IInterpreterPathProxyService>(
IInterpreterPathProxyService,
);

this.statusBar = application.createStatusBarItem(StatusBarAlignment.Left, 100);
this.statusBar.command = 'python.setInterpreter';
Expand Down Expand Up @@ -75,7 +83,7 @@ export class InterpreterDisplay implements IInterpreterDisplay {
}
}
private async updateDisplay(workspaceFolder?: Uri) {
const interpreterPath = this.configService.getSettings(workspaceFolder)?.pythonPath;
const interpreterPath = this.interpreterPathExpHelper.get(workspaceFolder);
if (!interpreterPath || interpreterPath === 'python') {
await this.autoSelection.autoSelectInterpreter(workspaceFolder); // Block on this only if no interpreter selected.
}
Expand Down
9 changes: 0 additions & 9 deletions src/test/activation/activationManager.unit.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -456,25 +456,16 @@ suite('Activation Manager', () => {
.setup((s) => s.activate())
.returns(() => Promise.resolve())
.verifiable(typemoq.Times.once());
autoSelection
.setup((a) => a.autoSelectInterpreter(undefined))
.returns(() => Promise.resolve())
.verifiable(typemoq.Times.once());
when(activeResourceService.getActiveResource()).thenReturn(resource);
await managerTest.activate();
assert.ok(initialize.calledOnce);
assert.ok(activateWorkspace.calledOnce);
singleActivationService.verifyAll();
autoSelection.verifyAll();
});

test('Throws error if execution fails', async () => {
singleActivationService
.setup((s) => s.activate())
.returns(() => Promise.resolve())
.verifiable(typemoq.Times.once());
autoSelection
.setup((a) => a.autoSelectInterpreter(undefined))
.returns(() => Promise.reject(new Error('Kaboom')))
.verifiable(typemoq.Times.once());
when(activeResourceService.getActiveResource()).thenReturn(resource);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
import { expect } from 'chai';
import * as path from 'path';
import * as sinon from 'sinon';
import { anything, instance, mock, when } from 'ts-mockito';
import { anything, instance, mock, verify, when } from 'ts-mockito';
import * as typemoq from 'typemoq';
import { Uri, WorkspaceConfiguration } from 'vscode';
import { IWorkspaceService } from '../../../client/common/application/types';
Expand Down Expand Up @@ -118,7 +118,24 @@ suite('Python Settings - pythonPath', () => {

expect(configSettings.pythonPath).to.be.equal('python');
});
test("If we don't have a custom python path and we do have an auto selected interpreter, then use it", () => {
test("If a workspace is opened and if we don't have a custom python path but we do have an auto selected interpreter, then use it", () => {
const pythonPath = path.join(__dirname, 'this is a python path that was auto selected');
const interpreter = { path: pythonPath } as PythonEnvironment;
const workspaceFolderUri = Uri.file(__dirname);
const selectionService = mock(MockAutoSelectionService);
when(selectionService.getAutoSelectedInterpreter(workspaceFolderUri)).thenReturn(interpreter);
when(selectionService.setWorkspaceInterpreter(workspaceFolderUri, anything())).thenResolve();
configSettings = new CustomPythonSettings(workspaceFolderUri, instance(selectionService));
pythonSettings
.setup((p) => p.get(typemoq.It.isValue('pythonPath')))
.returns(() => 'python')
.verifiable(typemoq.Times.atLeast(1));
configSettings.update(pythonSettings.object);

expect(configSettings.pythonPath).to.be.equal(pythonPath);
verify(selectionService.setWorkspaceInterpreter(workspaceFolderUri, interpreter)).once(); // Verify we set the autoselected interpreter
});
test("If no workspace is opened and we don't have a custom python path but we do have an auto selected interpreter, then use it", () => {
const pythonPath = path.join(__dirname, 'this is a python path that was auto selected');
const interpreter = { path: pythonPath } as PythonEnvironment;
const workspaceFolderUri = Uri.file(__dirname);
Expand Down
54 changes: 54 additions & 0 deletions src/test/common/interpreterPathProxyService.unit.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

'use strict';

import { Uri, WorkspaceConfiguration } from 'vscode';
import * as TypeMoq from 'typemoq';
import { expect } from 'chai';
import { InterpreterPathProxyService } from '../../client/common/interpreterPathProxyService';
import { IExperimentService, IInterpreterPathProxyService, IInterpreterPathService } from '../../client/common/types';
import { IWorkspaceService } from '../../client/common/application/types';
import { DeprecatePythonPath } from '../../client/common/experiments/groups';

suite('Interpreter Path Proxy Service', async () => {
let interpreterPathProxyService: IInterpreterPathProxyService;
let workspaceService: TypeMoq.IMock<IWorkspaceService>;
let experiments: TypeMoq.IMock<IExperimentService>;
let interpreterPathService: TypeMoq.IMock<IInterpreterPathService>;
const resource = Uri.parse('a');
const interpreterPath = 'path/to/interpreter';
setup(() => {
workspaceService = TypeMoq.Mock.ofType<IWorkspaceService>();
experiments = TypeMoq.Mock.ofType<IExperimentService>();
interpreterPathService = TypeMoq.Mock.ofType<IInterpreterPathService>();
workspaceService
.setup((w) => w.getWorkspaceFolder(resource))
.returns(() => ({
uri: resource,
name: 'Workspacefolder',
index: 0,
}));
interpreterPathProxyService = new InterpreterPathProxyService(
interpreterPathService.object,
experiments.object,
workspaceService.object,
);
});

test('When in experiment, use interpreter path service to get setting value', () => {
experiments.setup((e) => e.inExperimentSync(DeprecatePythonPath.experiment)).returns(() => true);
interpreterPathService.setup((i) => i.get(resource)).returns(() => interpreterPath);
const value = interpreterPathProxyService.get(resource);
expect(value).to.equal(interpreterPath);
});

test('When not in experiment, use workspace service to get setting value', () => {
experiments.setup((e) => e.inExperimentSync(DeprecatePythonPath.experiment)).returns(() => false);
const workspaceConfig = TypeMoq.Mock.ofType<WorkspaceConfiguration>();
workspaceService.setup((i) => i.getConfiguration('python', resource)).returns(() => workspaceConfig.object);
workspaceConfig.setup((w) => w.get('pythonPath')).returns(() => interpreterPath);
const value = interpreterPathProxyService.get(resource);
expect(value).to.equal(interpreterPath);
});
});
Loading