Skip to content

Commit 8d2b9ea

Browse files
authored
Use PYTHONPATH variable as additional search paths for python modules (microsoft#2595)
1 parent dff2699 commit 8d2b9ea

File tree

4 files changed

+88
-4
lines changed

4 files changed

+88
-4
lines changed

news/2 Fixes/2518.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Provide paths form `PYTHONPATH` environment variable to the language server, as additional search locations of Python modules.

src/client/activation/interpreterDataService.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ export class InterpreterDataService {
104104
}
105105

106106
private async getSearchPaths(execService: IPythonExecutionService): Promise<string> {
107-
const result = await execService.exec(['-c', 'import sys; print(sys.path);'], {});
107+
const result = await execService.exec(['-c', 'import sys; import os; print(sys.path + os.getenv("PYTHONPATH", "").split(os.pathsep));'], {});
108108
if (!result.stdout) {
109109
throw Error('Unable to determine Python interpreter search paths.');
110110
}

src/client/activation/languageServer.ts

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,9 @@ import { IFileSystem, IPlatformService } from '../common/platform/types';
2323
import {
2424
BANNER_NAME_LS_SURVEY, DeprecatedFeatureInfo, IConfigurationService,
2525
IExtensionContext, IFeatureDeprecationManager, ILogger, IOutputChannel,
26-
IPythonExtensionBanner, IPythonSettings
26+
IPathUtils, IPythonExtensionBanner, IPythonSettings
2727
} from '../common/types';
28+
import { IEnvironmentVariablesProvider } from '../common/variables/types';
2829
import { IServiceContainer } from '../ioc/types';
2930
import { LanguageServerSymbolProvider } from '../providers/symbolProvider';
3031
import {
@@ -214,7 +215,8 @@ export class LanguageServerExtensionActivator implements IExtensionActivator {
214215
return new LanguageClient(PYTHON, languageClientName, serverOptions, clientOptions);
215216
}
216217

217-
private async getAnalysisOptions(): Promise<LanguageClientOptions | undefined> {
218+
// tslint:disable-next-line:member-ordering
219+
public async getAnalysisOptions(): Promise<LanguageClientOptions | undefined> {
218220
// tslint:disable-next-line:no-any
219221
const properties = new Map<string, any>();
220222
let interpreterData: InterpreterData | undefined;
@@ -249,7 +251,13 @@ export class LanguageServerExtensionActivator implements IExtensionActivator {
249251
searchPaths.push(...extraPaths);
250252
}
251253
}
252-
254+
const envVarsProvider = this.services.get<IEnvironmentVariablesProvider>(IEnvironmentVariablesProvider);
255+
const vars = await envVarsProvider.getEnvironmentVariables();
256+
if (vars.PYTHONPATH && vars.PYTHONPATH.length > 0) {
257+
const pathUtils = this.services.get<IPathUtils>(IPathUtils);
258+
const paths = vars.PYTHONPATH.split(pathUtils.delimiter).filter(item => item.trim().length > 0);
259+
searchPaths.push(...paths);
260+
}
253261
// Make sure paths do not contain multiple slashes so file URIs
254262
// in VS Code (Node.js) and in the language server (.NET) match.
255263
// Note: for the language server paths separator is always ;
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License.
3+
4+
'use strict';
5+
6+
// tslint:disable:max-func-body-length
7+
8+
import { expect } from 'chai';
9+
import * as path from 'path';
10+
import * as TypeMoq from 'typemoq';
11+
import { LanguageServerExtensionActivator } from '../../client/activation/languageServer';
12+
import { IApplicationShell, ICommandManager, IWorkspaceService } from '../../client/common/application/types';
13+
import { IPlatformService } from '../../client/common/platform/types';
14+
import { IConfigurationService, IDisposableRegistry, IExtensionContext, IFeatureDeprecationManager, IOutputChannel, IPathUtils, IPythonSettings } from '../../client/common/types';
15+
import { IEnvironmentVariablesProvider } from '../../client/common/variables/types';
16+
import { IServiceContainer } from '../../client/ioc/types';
17+
18+
suite('Language Server', () => {
19+
let serviceContainer: TypeMoq.IMock<IServiceContainer>;
20+
let pythonSettings: TypeMoq.IMock<IPythonSettings>;
21+
let appShell: TypeMoq.IMock<IApplicationShell>;
22+
let cmdManager: TypeMoq.IMock<ICommandManager>;
23+
let workspaceService: TypeMoq.IMock<IWorkspaceService>;
24+
let platformService: TypeMoq.IMock<IPlatformService>;
25+
let languageServer: LanguageServerExtensionActivator;
26+
let extensionContext: TypeMoq.IMock<IExtensionContext>;
27+
setup(() => {
28+
serviceContainer = TypeMoq.Mock.ofType<IServiceContainer>();
29+
extensionContext = TypeMoq.Mock.ofType<IExtensionContext>();
30+
appShell = TypeMoq.Mock.ofType<IApplicationShell>();
31+
workspaceService = TypeMoq.Mock.ofType<IWorkspaceService>();
32+
cmdManager = TypeMoq.Mock.ofType<ICommandManager>();
33+
platformService = TypeMoq.Mock.ofType<IPlatformService>();
34+
const configService = TypeMoq.Mock.ofType<IConfigurationService>();
35+
pythonSettings = TypeMoq.Mock.ofType<IPythonSettings>();
36+
37+
workspaceService.setup(w => w.hasWorkspaceFolders).returns(() => false);
38+
workspaceService.setup(w => w.workspaceFolders).returns(() => []);
39+
configService.setup(c => c.getSettings(TypeMoq.It.isAny())).returns(() => pythonSettings.object);
40+
41+
const output = TypeMoq.Mock.ofType<IOutputChannel>();
42+
serviceContainer.setup(c => c.get(TypeMoq.It.isValue(IOutputChannel), TypeMoq.It.isAny())).returns(() => output.object);
43+
serviceContainer.setup(c => c.get(TypeMoq.It.isValue(IWorkspaceService))).returns(() => workspaceService.object);
44+
serviceContainer.setup(c => c.get(TypeMoq.It.isValue(IApplicationShell))).returns(() => appShell.object);
45+
serviceContainer.setup(c => c.get(TypeMoq.It.isValue(IDisposableRegistry))).returns(() => []);
46+
serviceContainer.setup(c => c.get(TypeMoq.It.isValue(IConfigurationService))).returns(() => configService.object);
47+
serviceContainer.setup(c => c.get(TypeMoq.It.isValue(ICommandManager))).returns(() => cmdManager.object);
48+
serviceContainer.setup(c => c.get(TypeMoq.It.isValue(IPlatformService))).returns(() => platformService.object);
49+
serviceContainer.setup(c => c.get(TypeMoq.It.isValue(IExtensionContext))).returns(() => extensionContext.object);
50+
serviceContainer.setup(c => c.get(TypeMoq.It.isValue(IFeatureDeprecationManager))).returns(() => TypeMoq.Mock.ofType<IFeatureDeprecationManager>().object);
51+
52+
languageServer = new LanguageServerExtensionActivator(serviceContainer.object);
53+
});
54+
55+
test('Must get PYTHONPATH from env vars provider', async () => {
56+
const pathDelimiter = 'x';
57+
const pythonPathVar = ['A', 'B', '1'];
58+
const envVarsProvider = TypeMoq.Mock.ofType<IEnvironmentVariablesProvider>();
59+
const pathUtils = TypeMoq.Mock.ofType<IPathUtils>();
60+
extensionContext.setup(e => e.extensionPath).returns(() => path.join('a', 'b', 'c'));
61+
pathUtils.setup(p => p.delimiter).returns(() => pathDelimiter);
62+
serviceContainer.setup(c => c.get(TypeMoq.It.isValue(IEnvironmentVariablesProvider))).returns(() => envVarsProvider.object);
63+
serviceContainer.setup(c => c.get(TypeMoq.It.isValue(IPathUtils))).returns(() => pathUtils.object);
64+
envVarsProvider
65+
.setup(p => p.getEnvironmentVariables())
66+
.returns(() => { return Promise.resolve({ PYTHONPATH: pythonPathVar.join(pathDelimiter) }); })
67+
.verifiable(TypeMoq.Times.once());
68+
69+
const options = await languageServer.getAnalysisOptions();
70+
71+
expect(options!).not.to.equal(undefined, 'options cannot be undefined');
72+
expect(options!.initializationOptions).not.to.equal(undefined, 'initializationOptions cannot be undefined');
73+
expect(options!.initializationOptions!.searchPaths).to.include.members(pythonPathVar);
74+
});
75+
});

0 commit comments

Comments
 (0)