Skip to content

Commit cf1d310

Browse files
author
Mikhail Arkhipov
authored
Support pipenv (#888)
* Fix pylint search * Handle quote escapes in strings * Escapes in strings * CR feedback * Missing pip * Test * Tests * Tests * Mac python path * Tests * Tests * Test * "Go To Python object" doesn't work * Proper detection and type population in virtual env * Test fixes * Simplify venv check * Remove duplicates * Test * Discover pylintrc better + tests * Undo change * CR feedback * Set interprereter before checking install * Fix typo and path compare on Windows * Rename method * DonJayamanne#815 - 'F' in flake8 means warning * 730 - same folder temp * Properly resolve ~ * Test * Test * Fix dot spacing * Remove banner * Delete banner code * Add pyenv and direnv folders * Basic venv path search tests * PYENV_ROOT resolution * PYENV_ROOT test * Use ICurrentProcess * Initial * Second * Display name * Simplify and clean * Test * Test * Tests * Performance * Test fixes * venv folder settings * Fix typo
1 parent 136c78b commit cf1d310

33 files changed

Lines changed: 557 additions & 86 deletions

package.json

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1086,6 +1086,19 @@
10861086
"description": "Path to folder with a list of Virtual Environments (e.g. ~/.pyenv, ~/Envs, ~/.virtualenvs).",
10871087
"scope": "resource"
10881088
},
1089+
"python.venvFolders": {
1090+
"type": "array",
1091+
"default": [
1092+
"envs",
1093+
".pyenv",
1094+
".direnv"
1095+
],
1096+
"description": "Folders to look into for virtual environments.",
1097+
"scope": "resource",
1098+
"items": {
1099+
"type": "string"
1100+
}
1101+
},
10891102
"python.envFile": {
10901103
"type": "string",
10911104
"description": "Absolute path to a file containing environment variable definitions.",
@@ -1860,4 +1873,4 @@
18601873
"publisherDisplayName": "Microsoft",
18611874
"publisherId": "998b010b-e2af-44a5-a6cd-0b5fd3b9b6f8"
18621875
}
1863-
}
1876+
}

src/client/common/application/types.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -413,8 +413,6 @@ export interface IWorkspaceService {
413413
* ~~The folder that is open in the editor. `undefined` when no folder
414414
* has been opened.~~
415415
*
416-
* @deprecated Use [`workspaceFolders`](#workspace.workspaceFolders) instead.
417-
*
418416
* @readonly
419417
*/
420418
readonly rootPath: string | undefined;

src/client/common/application/workspace.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ export class WorkspaceService implements IWorkspaceService {
1212
return vscode.workspace.onDidChangeConfiguration;
1313
}
1414
public get rootPath(): string | undefined {
15-
return vscode.workspace.rootPath;
15+
return Array.isArray(vscode.workspace.workspaceFolders) ? vscode.workspace.workspaceFolders[0].uri.fsPath : undefined;
1616
}
1717
public get workspaceFolders(): vscode.WorkspaceFolder[] | undefined {
1818
return vscode.workspace.workspaceFolders;

src/client/common/configSettings.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ export class PythonSettings extends EventEmitter implements IPythonSettings {
3232
public envFile: string;
3333
public disablePromptForFeatures: string[];
3434
public venvPath: string;
35+
public venvFolders: string[];
3536
public devOptions: string[];
3637
public linting: ILintingSettings;
3738
public formatting: IFormattingSettings;
@@ -106,6 +107,7 @@ export class PythonSettings extends EventEmitter implements IPythonSettings {
106107
this.pythonPath = getAbsolutePath(this.pythonPath, workspaceRoot);
107108
// tslint:disable-next-line:no-backbone-get-set-outside-model no-non-null-assertion
108109
this.venvPath = systemVariables.resolveAny(pythonSettings.get<string>('venvPath'))!;
110+
this.venvFolders = systemVariables.resolveAny(pythonSettings.get<string[]>('venvFolders'))!;
109111
// tslint:disable-next-line:no-backbone-get-set-outside-model no-non-null-assertion
110112
this.jediPath = systemVariables.resolveAny(pythonSettings.get<string>('jediPath'))!;
111113
if (typeof this.jediPath === 'string' && this.jediPath.length > 0) {

src/client/common/installer/channelManager.ts

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,9 +41,22 @@ export class InstallationChannelManager implements IInstallationChannelManager {
4141
}
4242

4343
public async getInstallationChannels(resource?: Uri): Promise<IModuleInstaller[]> {
44-
const installers = this.serviceContainer.getAll<IModuleInstaller>(IModuleInstaller);
44+
let installers = this.serviceContainer.getAll<IModuleInstaller>(IModuleInstaller);
4545
const supportedInstallers: IModuleInstaller[] = [];
46+
if (installers.length === 0) {
47+
return [];
48+
}
49+
// group by priority and pick supported from the highest priority
50+
installers = installers.sort((a, b) => b.priority - a.priority);
51+
let currentPri = installers[0].priority;
4652
for (const mi of installers) {
53+
if (mi.priority !== currentPri) {
54+
if (supportedInstallers.length > 0) {
55+
break; // return highest priority supported installers
56+
}
57+
// If none supported, try next priority group
58+
currentPri = mi.priority;
59+
}
4760
if (await mi.isSupported(resource)) {
4861
supportedInstallers.push(mi);
4962
}

src/client/common/installer/condaInstaller.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,9 @@ export class CondaInstaller extends ModuleInstaller implements IModuleInstaller
1515
public get displayName() {
1616
return 'Conda';
1717
}
18+
public get priority(): number {
19+
return 0;
20+
}
1821
constructor(@inject(IServiceContainer) serviceContainer: IServiceContainer) {
1922
super(serviceContainer);
2023
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License.
3+
4+
import { inject, injectable } from 'inversify';
5+
import { Uri } from 'vscode';
6+
import { IInterpreterLocatorService, PIPENV_SERVICE } from '../../interpreter/contracts';
7+
import { IServiceContainer } from '../../ioc/types';
8+
import { ITerminalServiceFactory } from '../terminal/types';
9+
import { IModuleInstaller } from './types';
10+
11+
const pipenvName = 'pipenv';
12+
13+
@injectable()
14+
export class PipEnvInstaller implements IModuleInstaller {
15+
private readonly pipenv: IInterpreterLocatorService;
16+
17+
public get displayName() {
18+
return pipenvName;
19+
}
20+
public get priority(): number {
21+
return 10;
22+
}
23+
24+
constructor(@inject(IServiceContainer) private serviceContainer: IServiceContainer) {
25+
this.pipenv = this.serviceContainer.get<IInterpreterLocatorService>(IInterpreterLocatorService, PIPENV_SERVICE);
26+
}
27+
28+
public installModule(name: string): Promise<void> {
29+
const terminalService = this.serviceContainer.get<ITerminalServiceFactory>(ITerminalServiceFactory).getTerminalService();
30+
return terminalService.sendCommand(pipenvName, ['install', name]);
31+
}
32+
33+
public async isSupported(resource?: Uri): Promise<boolean> {
34+
const interpreters = await this.pipenv.getInterpreters(resource);
35+
return interpreters && interpreters.length > 0;
36+
}
37+
}

src/client/common/installer/pipInstaller.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,14 +14,17 @@ export class PipInstaller extends ModuleInstaller implements IModuleInstaller {
1414
public get displayName() {
1515
return 'Pip';
1616
}
17-
constructor( @inject(IServiceContainer) serviceContainer: IServiceContainer) {
17+
public get priority(): number {
18+
return 0;
19+
}
20+
constructor(@inject(IServiceContainer) serviceContainer: IServiceContainer) {
1821
super(serviceContainer);
1922
}
2023
public isSupported(resource?: Uri): Promise<boolean> {
2124
return this.isPipAvailable(resource);
2225
}
2326
protected async getExecutionInfo(moduleName: string, resource?: Uri): Promise<ExecutionInfo> {
24-
const proxyArgs = [];
27+
const proxyArgs: string[] = [];
2528
const proxy = workspace.getConfiguration('http').get('proxy', '');
2629
if (proxy.length > 0) {
2730
proxyArgs.push('--proxy');

src/client/common/installer/serviceRegistry.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,13 @@
55
import { IServiceManager } from '../../ioc/types';
66
import { InstallationChannelManager } from './channelManager';
77
import { CondaInstaller } from './condaInstaller';
8+
import { PipEnvInstaller } from './pipEnvInstaller';
89
import { PipInstaller } from './pipInstaller';
910
import { IInstallationChannelManager, IModuleInstaller } from './types';
1011

1112
export function registerTypes(serviceManager: IServiceManager) {
1213
serviceManager.addSingleton<IModuleInstaller>(IModuleInstaller, CondaInstaller);
1314
serviceManager.addSingleton<IModuleInstaller>(IModuleInstaller, PipInstaller);
15+
serviceManager.addSingleton<IModuleInstaller>(IModuleInstaller, PipEnvInstaller);
1416
serviceManager.addSingleton<IInstallationChannelManager>(IInstallationChannelManager, InstallationChannelManager);
1517
}

src/client/common/installer/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { Product } from '../types';
77
export const IModuleInstaller = Symbol('IModuleInstaller');
88
export interface IModuleInstaller {
99
readonly displayName: string;
10+
readonly priority: number;
1011
installModule(name: string, resource?: Uri): Promise<void>;
1112
isSupported(resource?: Uri): Promise<boolean>;
1213
}

0 commit comments

Comments
 (0)