Skip to content

Commit

Permalink
gh-978: Add python.pipenvPath config setting. (#3957)
Browse files Browse the repository at this point in the history
  • Loading branch information
ericsnowcurrently committed Jan 11, 2019
1 parent e1964fd commit 26e75fd
Show file tree
Hide file tree
Showing 10 changed files with 65 additions and 11 deletions.
1 change: 1 addition & 0 deletions news/1 Enhancements/978.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Add the python.pipenvPath config setting.
6 changes: 6 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -1521,6 +1521,12 @@
"description": "Path to the conda executable to use for activation (version 4.4+).",
"scope": "resource"
},
"python.pipenvPath": {
"type": "string",
"default": "pipenv",
"description": "Path to the pipenv executable to use for activation.",
"scope": "window"
},
"python.sortImports.args": {
"type": "array",
"description": "Arguments passed in. Each argument is a separate item in the array.",
Expand Down
3 changes: 3 additions & 0 deletions src/client/common/configSettings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ export class PythonSettings extends EventEmitter implements IPythonSettings {
public venvPath = '';
public venvFolders: string[] = [];
public condaPath = '';
public pipenvPath = '';
public devOptions: string[] = [];
public linting!: ILintingSettings;
public formatting!: IFormattingSettings;
Expand Down Expand Up @@ -137,6 +138,8 @@ export class PythonSettings extends EventEmitter implements IPythonSettings {
this.venvFolders = systemVariables.resolveAny(pythonSettings.get<string[]>('venvFolders'))!;
const condaPath = systemVariables.resolveAny(pythonSettings.get<string>('condaPath'))!;
this.condaPath = condaPath && condaPath.length > 0 ? getAbsolutePath(condaPath, workspaceRoot) : condaPath;
const pipenvPath = systemVariables.resolveAny(pythonSettings.get<string>('pipenvPath'))!;
this.pipenvPath = pipenvPath && pipenvPath.length > 0 ? getAbsolutePath(pipenvPath, workspaceRoot) : pipenvPath;

this.downloadLanguageServer = systemVariables.resolveAny(pythonSettings.get<boolean>('downloadLanguageServer', true))!;
this.jediEnabled = systemVariables.resolveAny(pythonSettings.get<boolean>('jediEnabled', true))!;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,15 @@

import { inject, injectable } from 'inversify';
import { Uri } from 'vscode';
import { IInterpreterService, InterpreterType } from '../../../interpreter/contracts';
import { IInterpreterService, InterpreterType, IPipEnvService } from '../../../interpreter/contracts';
import { ITerminalActivationCommandProvider, TerminalShellType } from '../types';

@injectable()
export class PipEnvActivationCommandProvider implements ITerminalActivationCommandProvider {
constructor(@inject(IInterpreterService) private readonly interpreterService: IInterpreterService) { }
constructor(
@inject(IInterpreterService) private readonly interpreterService: IInterpreterService,
@inject(IPipEnvService) private readonly pipenvService: IPipEnvService
) { }

public isShellSupported(_targetShell: TerminalShellType): boolean {
return true;
Expand All @@ -22,6 +25,7 @@ export class PipEnvActivationCommandProvider implements ITerminalActivationComma
return;
}

return ['pipenv shell'];
const execName = this.pipenvService.executable;
return [`${execName} shell`];
}
}
1 change: 1 addition & 0 deletions src/client/common/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,7 @@ export interface IPythonSettings {
readonly venvPath: string;
readonly venvFolders: string[];
readonly condaPath: string;
readonly pipenvPath: string;
readonly downloadLanguageServer: boolean;
readonly jediEnabled: boolean;
readonly jediPath: string;
Expand Down
1 change: 1 addition & 0 deletions src/client/interpreter/contracts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,7 @@ export interface IInterpreterHelper {

export const IPipEnvService = Symbol('IPipEnvService');
export interface IPipEnvService {
executable: string;
isRelatedPipEnvironment(dir: string, pythonPath: string): Promise<boolean>;
}

Expand Down
11 changes: 9 additions & 2 deletions src/client/interpreter/locators/services/pipEnvService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,11 @@ import { IApplicationShell, IWorkspaceService } from '../../../common/applicatio
import { traceError } from '../../../common/logger';
import { IFileSystem, IPlatformService } from '../../../common/platform/types';
import { IProcessServiceFactory } from '../../../common/process/types';
import { ICurrentProcess, ILogger } from '../../../common/types';
import { IConfigurationService, ICurrentProcess, ILogger } from '../../../common/types';
import { IServiceContainer } from '../../../ioc/types';
import { IInterpreterHelper, InterpreterType, IPipEnvService, PythonInterpreter } from '../../contracts';
import { CacheableLocatorService } from './cacheableLocatorService';

const execName = 'pipenv';
const pipEnvFileNameVariable = 'PIPENV_PIPFILE';

@injectable()
Expand All @@ -23,6 +22,7 @@ export class PipEnvService extends CacheableLocatorService implements IPipEnvSer
private readonly workspace: IWorkspaceService;
private readonly fs: IFileSystem;
private readonly logger: ILogger;
private readonly configService: IConfigurationService;

constructor(@inject(IServiceContainer) serviceContainer: IServiceContainer) {
super('PipEnvService', serviceContainer);
Expand All @@ -31,6 +31,7 @@ export class PipEnvService extends CacheableLocatorService implements IPipEnvSer
this.workspace = this.serviceContainer.get<IWorkspaceService>(IWorkspaceService);
this.fs = this.serviceContainer.get<IFileSystem>(IFileSystem);
this.logger = this.serviceContainer.get<ILogger>(ILogger);
this.configService = this.serviceContainer.get<IConfigurationService>(IConfigurationService);
}
// tslint:disable-next-line:no-empty
public dispose() { }
Expand All @@ -42,6 +43,11 @@ export class PipEnvService extends CacheableLocatorService implements IPipEnvSer
const envName = await this.getInterpreterPathFromPipenv(dir, true);
return !!envName;
}

public get executable(): string {
return this.configService.getSettings().pipenvPath;
}

protected getInterpretersImplementation(resource?: Uri): Promise<PythonInterpreter[]> {
const pipenvCwd = this.getPipenvWorkingDirectory(resource);
if (!pipenvCwd) {
Expand Down Expand Up @@ -115,6 +121,7 @@ export class PipEnvService extends CacheableLocatorService implements IPipEnvSer
private async invokePipenv(arg: string, rootPath: string): Promise<string | undefined> {
try {
const processService = await this.processServiceFactory.create(Uri.file(rootPath));
const execName = this.executable;
const result = await processService.exec(execName, [arg], { cwd: rootPath });
if (result) {
const stdout = result.stdout ? result.stdout.trim() : '';
Expand Down
2 changes: 1 addition & 1 deletion src/test/common/configSettings/configSettings.unit.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ suite('Python Settings', () => {

function initializeConfig(sourceSettings: PythonSettings) {
// string settings
for (const name of ['pythonPath', 'venvPath', 'condaPath', 'envFile']) {
for (const name of ['pythonPath', 'venvPath', 'condaPath', 'pipenvPath', 'envFile']) {
config.setup(c => c.get<string>(name))
.returns(() => sourceSettings[name]);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,12 @@

import * as assert from 'assert';
import { instance, mock, when } from 'ts-mockito';
import * as TypeMoq from 'typemoq';
import { Uri } from 'vscode';
import { PipEnvActivationCommandProvider } from '../../../../client/common/terminal/environmentActivationProviders/pipEnvActivationProvider';
import { ITerminalActivationCommandProvider, TerminalShellType } from '../../../../client/common/terminal/types';
import { getNamesAndValues } from '../../../../client/common/utils/enum';
import { IInterpreterService, InterpreterType } from '../../../../client/interpreter/contracts';
import { IInterpreterService, InterpreterType, IPipEnvService } from '../../../../client/interpreter/contracts';
import { InterpreterService } from '../../../../client/interpreter/interpreterService';

// tslint:disable:no-any
Expand All @@ -19,9 +20,16 @@ suite('Terminals Activation - Pipenv', () => {
suite(resource ? 'With a resource' : 'Without a resource', () => {
let activationProvider: ITerminalActivationCommandProvider;
let interpreterService: IInterpreterService;
let pipenvService: TypeMoq.IMock<IPipEnvService>;
setup(() => {
interpreterService = mock(InterpreterService);
activationProvider = new PipEnvActivationCommandProvider(instance(interpreterService));
pipenvService = TypeMoq.Mock.ofType<IPipEnvService>();
activationProvider = new PipEnvActivationCommandProvider(
instance(interpreterService),
pipenvService.object
);

pipenvService.setup(p => p.executable).returns(() => 'pipenv');
});

test('No commands for no interpreter', async () => {
Expand Down
29 changes: 26 additions & 3 deletions src/test/interpreters/pipEnvService.unit.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

// tslint:disable:max-func-body-length no-any

import * as assert from 'assert';
import { expect } from 'chai';
import * as path from 'path';
import { SemVer } from 'semver';
Expand All @@ -13,10 +14,17 @@ import { Uri, WorkspaceFolder } from 'vscode';
import { IApplicationShell, IWorkspaceService } from '../../client/common/application/types';
import { IFileSystem, IPlatformService } from '../../client/common/platform/types';
import { IProcessService, IProcessServiceFactory } from '../../client/common/process/types';
import { ICurrentProcess, ILogger, IPersistentState, IPersistentStateFactory } from '../../client/common/types';
import {
IConfigurationService,
ICurrentProcess,
ILogger,
IPersistentState,
IPersistentStateFactory,
IPythonSettings
} from '../../client/common/types';
import { getNamesAndValues } from '../../client/common/utils/enum';
import { IEnvironmentVariablesProvider } from '../../client/common/variables/types';
import { IInterpreterHelper, IInterpreterLocatorService } from '../../client/interpreter/contracts';
import { IInterpreterHelper } from '../../client/interpreter/contracts';
import { PipEnvService } from '../../client/interpreter/locators/services/pipEnvService';
import { IServiceContainer } from '../../client/ioc/types';

Expand All @@ -30,7 +38,7 @@ suite('Interpreters - PipEnv', () => {
[undefined, Uri.file(path.join(rootWorkspace, 'one.py'))].forEach(resource => {
const testSuffix = ` (${os.name}, ${resource ? 'with' : 'without'} a workspace)`;

let pipEnvService: IInterpreterLocatorService;
let pipEnvService: PipEnvService;
let serviceContainer: TypeMoq.IMock<IServiceContainer>;
let interpreterHelper: TypeMoq.IMock<IInterpreterHelper>;
let processService: TypeMoq.IMock<IProcessService>;
Expand All @@ -42,6 +50,9 @@ suite('Interpreters - PipEnv', () => {
let procServiceFactory: TypeMoq.IMock<IProcessServiceFactory>;
let logger: TypeMoq.IMock<ILogger>;
let platformService: TypeMoq.IMock<IPlatformService>;
let config: TypeMoq.IMock<IConfigurationService>;
let settings: TypeMoq.IMock<IPythonSettings>;
let pipenvPathSetting: string;
setup(() => {
serviceContainer = TypeMoq.Mock.ofType<IServiceContainer>();
const workspaceService = TypeMoq.Mock.ofType<IWorkspaceService>();
Expand Down Expand Up @@ -80,6 +91,13 @@ suite('Interpreters - PipEnv', () => {
serviceContainer.setup(c => c.get(TypeMoq.It.isValue(IEnvironmentVariablesProvider))).returns(() => envVarsProvider.object);
serviceContainer.setup(c => c.get(TypeMoq.It.isValue(ILogger))).returns(() => logger.object);
serviceContainer.setup(c => c.get(TypeMoq.It.isValue(IPlatformService))).returns(() => platformService.object);
serviceContainer.setup(c => c.get(TypeMoq.It.isValue(IConfigurationService), TypeMoq.It.isAny())).returns(() => config.object);

config = TypeMoq.Mock.ofType<IConfigurationService>();
settings = TypeMoq.Mock.ofType<IPythonSettings>();
config.setup(c => c.getSettings(TypeMoq.It.isValue(undefined))).returns(() => settings.object);
settings.setup(p => p.pipenvPath).returns(() => pipenvPathSetting);
pipenvPathSetting = 'pipenv';

pipEnvService = new PipEnvService(serviceContainer.object);
});
Expand Down Expand Up @@ -156,6 +174,11 @@ suite('Interpreters - PipEnv', () => {
expect(environments).to.be.lengthOf(1);
fileSystem.verifyAll();
});
test('Must use \'python.pipenvPath\' setting', async () => {
pipenvPathSetting = 'spam-spam-pipenv-spam-spam';
const pipenvExe = pipEnvService.executable;
assert.equal(pipenvExe, 'spam-spam-pipenv-spam-spam', 'Failed to identify pipenv.exe');
});
});
});
});

0 comments on commit 26e75fd

Please sign in to comment.