Removed support for pythonPath config file entry and VS Code setting. Added support for PYTHONPATH environment variable. The pythonPath setting were too confusing to users because VS Code’s pythonPath doesn't refer to the PYTHONPATH search paths; instead, it points to a python interpreter.

This commit is contained in:
Eric Traut 2019-03-31 12:26:58 -07:00
parent bb3039180f
commit bb4b846842
11 changed files with 192 additions and 141 deletions

View File

@ -99,13 +99,6 @@
],
"pattern": "^(Linux|Windows|Darwin)$"
},
"pythonPath": {
"$id": "#/properties/pythonPath",
"type": "string",
"title": "Path to directory containing a python environment",
"default": "",
"pattern": "^(.*)$"
},
"venvPath": {
"$id": "#/properties/venvPath",
"type": "string",

View File

@ -14,9 +14,7 @@ Pyright offers flexible configuration options specified in a JSON-formatted text
**typingsPath** [path, optional]: Path to a directory that contains custom type stubs. Each package's type stub file(s) are expected to be in its own subdirectory.
**pythonPath** [path, optional]: Path to the Python execution environment. This is used to resolve third-party modules when there is no `venvPath` specified in the config file.
**venvPath** [path, optional]: Path to a directory containing one or more subdirectories, each of which contains a virtual environment. Each execution environment (see below for details) can refer to a different virtual environment. This optional overrides the `pythonPath` option described above.
**venvPath** [path, optional]: Path to a directory containing one or more subdirectories, each of which contains a virtual environment. Each execution environment (see below for details) can refer to a different virtual environment. When used in conjunction with a **venv** setting (see below), pyright will search for imports in the virtual environments site-packages directory rather than the paths specified in PYTHONPATH.
**venv** [string, optional]: Used in conjunction with the venvPath, specifies the virtual environment to use. Individual execution environments may override this setting.
@ -38,7 +36,7 @@ The following settings control pyright's diagnostic output (warnings or errors).
## Execution Environment Options
Pyright allows multiple “execution environments” to be defined for different portions of your source tree. For example, a subtree may be designed to run with a different PYTHONPATH or a different version of the python interpreter than the rest of the source base.
Pyright allows multiple “execution environments” to be defined for different portions of your source tree. For example, a subtree may be designed to run with different import search paths or a different version of the python interpreter than the rest of the source base.
The following settings can be specified for each execution environment.
@ -56,7 +54,6 @@ The following settings can be specified for each execution environment.
# VS Code Extension Settings
Pyright will import the following settings set through VS Code. These override the values provided in the configuration file.
**python.pythonPath**: Same as the **pythonPath** setting described above.
**python.venvPath**: Same as the **venvPath** setting described above.
**python.analysis.typeshedPaths**: An array of typeshed paths to search. Pyright supports only one such path. If provided in the VS Code setting, the first entry overrides the **typeshedPath** configuration file entry described above.

View File

@ -14,6 +14,7 @@ import { convertPositionToOffset } from '../common/positionUtils';
import { ModuleNameNode, NameNode, ParseNode } from '../parser/parseNodes';
import { ParseResults } from '../parser/parser';
import { AnalyzerNodeInfo } from './analyzerNodeInfo';
import { ImportType } from './importResult';
import { ParseTreeUtils } from './parseTreeUtils';
import { SymbolCategory } from './symbol';
@ -52,7 +53,12 @@ export class HoverProvider {
}
if (importInfo.resolvedPaths[pathOffset]) {
return '```\n(module) "' + importInfo.resolvedPaths[pathOffset] + '"\n```';
let typeStubWarning = '';
if (importInfo.importType === ImportType.ThirdParty && !importInfo.isStubFile) {
typeStubWarning = '\nNo type stub found for this module. Imported symbol types are unknown.';
}
return '```\n(module) "' + importInfo.resolvedPaths[pathOffset] + '"```' + typeStubWarning;
}
return undefined;

View File

@ -11,11 +11,11 @@
import * as fs from 'fs';
import { ConfigOptions, ExecutionEnvironment } from '../common/configOptions';
import { combinePaths, ensureTrailingDirectorySeparator, getDirectoryPath,
getFileSystemEntries, isDirectory, isFile, stripFileExtension,
stripTrailingDirectorySeparator } from '../common/pathUtils';
import { combinePaths, getDirectoryPath, getFileSystemEntries, isDirectory, isFile,
stripFileExtension, stripTrailingDirectorySeparator } from '../common/pathUtils';
import { is3x, versionToString } from '../common/pythonVersion';
import { ImplicitImport, ImportResult, ImportType } from './importResult';
import { PythonPathUtils } from './pythonPathUtils';
export interface ImportedModuleName {
leadingDots: number;
@ -26,7 +26,7 @@ export class ImportResolver {
private _sourceFilePath: string;
private _configOptions: ConfigOptions;
private _executionEnvironment: ExecutionEnvironment;
private _cachedSitePackagePath: string | undefined;
private _cachedPythonSearchPaths: string[] | undefined;
constructor(sourceFilePath: string, configOptions: ConfigOptions, execEnv: ExecutionEnvironment) {
this._sourceFilePath = sourceFilePath;
@ -40,8 +40,9 @@ export class ImportResolver {
let importName = this._formatImportName(moduleName);
// Find the site packages for the configured virtual environment.
if (this._cachedSitePackagePath === undefined) {
this._cachedSitePackagePath = this._findSitePackagePath();
if (this._cachedPythonSearchPaths === undefined) {
this._cachedPythonSearchPaths = PythonPathUtils.findPythonSearchPaths(
this._configOptions, this._executionEnvironment);
}
// Is it a built-in path?
@ -101,14 +102,16 @@ export class ImportResolver {
}
// Look for the import in the list of third-party packages.
if (this._cachedSitePackagePath) {
// Allow partial resolution because some third-party packages
// use tricks to populate their package namespaces.
let thirdPartyImport = this._resolveAbsoluteImport(
this._cachedSitePackagePath, moduleName, importName, true);
if (thirdPartyImport) {
thirdPartyImport.importType = ImportType.ThirdParty;
return thirdPartyImport;
if (this._cachedPythonSearchPaths) {
for (let searchPath of this._cachedPythonSearchPaths) {
// Allow partial resolution because some third-party packages
// use tricks to populate their package namespaces.
let thirdPartyImport = this._resolveAbsoluteImport(
searchPath, moduleName, importName, true);
if (thirdPartyImport) {
thirdPartyImport.importType = ImportType.ThirdParty;
return thirdPartyImport;
}
}
}
@ -130,45 +133,34 @@ export class ImportResolver {
};
}
private _getTypeShedFallbackPath() {
// Assume that the 'typeshed-fallback' directory is up one level
// from this javascript file.
const moduleDirectory = (global as any).__rootDirectory;
if (moduleDirectory) {
return combinePaths(getDirectoryPath(
ensureTrailingDirectorySeparator(moduleDirectory)),
'typeshed-fallback');
}
return undefined;
}
private _findTypeshedPath(moduleName: ImportedModuleName, importName: string,
isStdLib: boolean): ImportResult | undefined {
let typeshedPath = '';
// Did the user specify a typeshed path? If not, we'll look in the
// default virtual environment, then in the typeshed-fallback directory.
// python search paths, then in the typeshed-fallback directory.
if (this._configOptions.typeshedPath) {
typeshedPath = this._configOptions.typeshedPath;
if (!fs.existsSync(typeshedPath) || !isDirectory(typeshedPath)) {
typeshedPath = '';
const possibleTypeshedPath = this._configOptions.typeshedPath;
if (fs.existsSync(possibleTypeshedPath) && isDirectory(possibleTypeshedPath)) {
typeshedPath = possibleTypeshedPath;
}
} else if (this._cachedSitePackagePath) {
typeshedPath = combinePaths(this._cachedSitePackagePath, 'typeshed');
if (!fs.existsSync(typeshedPath) || !isDirectory(typeshedPath)) {
typeshedPath = '';
} else if (this._cachedPythonSearchPaths) {
for (let searchPath of this._cachedPythonSearchPaths) {
const possibleTypeshedPath = combinePaths(searchPath, 'typeshed');
if (fs.existsSync(possibleTypeshedPath) && isDirectory(possibleTypeshedPath)) {
typeshedPath = possibleTypeshedPath;
break;
}
}
}
// Should we apply the fallback?
// If typeshed directory wasn't found in other locations, use the fallback.
if (!typeshedPath) {
typeshedPath = this._getTypeShedFallbackPath() || '';
typeshedPath = PythonPathUtils.getTypeShedFallbackPath() || '';
}
typeshedPath = combinePaths(typeshedPath, isStdLib ? 'stdlib' : 'third_party');
typeshedPath = PythonPathUtils.getTypeshedSubdirectory(typeshedPath, isStdLib);
if (!fs.existsSync(typeshedPath) || !isDirectory(typeshedPath)) {
return undefined;
@ -347,46 +339,6 @@ export class ImportResolver {
return Object.keys(implicitImportMap).map(key => implicitImportMap[key]);
}
private _findSitePackagePath(): string | undefined {
let pythonPath: string | undefined;
if (this._executionEnvironment.venv) {
if (this._configOptions.venvPath) {
pythonPath = combinePaths(this._configOptions.venvPath, this._executionEnvironment.venv);
}
} else if (this._configOptions.defaultVenv) {
if (this._configOptions.venvPath) {
pythonPath = combinePaths(this._configOptions.venvPath, this._configOptions.defaultVenv);
}
} else {
pythonPath = this._configOptions.pythonPath;
}
if (!pythonPath) {
return undefined;
}
let libPath = combinePaths(pythonPath, 'lib');
let sitePackagesPath = combinePaths(libPath, 'site-packages');
if (fs.existsSync(sitePackagesPath)) {
return sitePackagesPath;
}
// We didn't find a site-packages directory directly in the lib
// directory. Scan for a "python*" directory instead.
let entries = getFileSystemEntries(libPath);
for (let i = 0; i < entries.directories.length; i++) {
let dirName = entries.directories[i];
if (dirName.startsWith('python')) {
let dirPath = combinePaths(libPath, dirName, 'site-packages');
if (fs.existsSync(dirPath)) {
return dirPath;
}
}
}
return undefined;
}
private _formatImportName(moduleName: ImportedModuleName) {
let name = '';
for (let i = 0; i < moduleName.leadingDots; i++) {

View File

@ -0,0 +1,98 @@
/*
* pythonPathUtils.ts
* Copyright (c) Microsoft Corporation.
* Licensed under the MIT license.
* Author: Eric Traut
*
* Utility routines used to resolve various paths in python.
*/
import * as fs from 'fs';
import { ConfigOptions, ExecutionEnvironment } from '../common/configOptions';
import { combinePaths, ensureTrailingDirectorySeparator, getDirectoryPath,
getFileSystemEntries,
isDirectory,
normalizePath } from '../common/pathUtils';
export class PythonPathUtils {
static getTypeShedFallbackPath() {
// Assume that the 'typeshed-fallback' directory is up one level
// from this javascript file.
const moduleDirectory = (global as any).__rootDirectory;
if (moduleDirectory) {
return combinePaths(getDirectoryPath(
ensureTrailingDirectorySeparator(moduleDirectory)),
'typeshed-fallback');
}
return undefined;
}
static getTypeshedSubdirectory(typeshedPath: string, isStdLib: boolean) {
return combinePaths(typeshedPath, isStdLib ? 'stdlib' : 'third_party');
}
static findPythonSearchPaths(configOptions: ConfigOptions, execEnv?: ExecutionEnvironment):
string[] | undefined {
let venvPath: string | undefined;
if (execEnv && execEnv.venv) {
if (configOptions.venvPath) {
venvPath = combinePaths(configOptions.venvPath, execEnv.venv);
}
} else if (configOptions.defaultVenv) {
if (configOptions.venvPath) {
venvPath = combinePaths(configOptions.venvPath, configOptions.defaultVenv);
}
}
if (venvPath) {
let libPath = combinePaths(venvPath, 'lib');
let sitePackagesPath = combinePaths(libPath, 'site-packages');
if (fs.existsSync(sitePackagesPath)) {
return [sitePackagesPath];
}
// We didn't find a site-packages directory directly in the lib
// directory. Scan for a "python*" directory instead.
let entries = getFileSystemEntries(libPath);
for (let i = 0; i < entries.directories.length; i++) {
let dirName = entries.directories[i];
if (dirName.startsWith('python')) {
let dirPath = combinePaths(libPath, dirName, 'site-packages');
if (fs.existsSync(dirPath)) {
return [dirPath];
}
}
}
}
// Fall back on PYTHONPATH.
return this.getPythonPathEnvironmentVariable();
}
static getPythonPathEnvironmentVariable(): string[] {
let pythonPaths: string[] = [];
const rawPythonPath = process.env.PYTHONPATH;
if (rawPythonPath) {
// Some OSes use semicolon separators, others use colons. We'll support
// both here.
let separator = rawPythonPath.indexOf(':') >= 0 ? ':' : ';';
let pathSplit = rawPythonPath.split(separator);
for (let path of pathSplit) {
const normalizedPath = normalizePath(path);
// Make sure the path exists and is a directory. We don't currenlty
// support zip files and other formats.
if (fs.existsSync(normalizedPath) && isDirectory(normalizedPath)) {
pythonPaths.push(normalizedPath);
}
}
}
return pythonPaths;
}
}

View File

@ -15,10 +15,11 @@ import { ConfigOptions } from '../common/configOptions';
import { ConsoleInterface, StandardConsole } from '../common/console';
import { DiagnosticTextPosition, DocumentTextRange } from '../common/diagnostic';
import { FileDiagnostics } from '../common/diagnosticSink';
import { combinePaths, ensureTrailingDirectorySeparator, forEachAncestorDirectory,
getDirectoryPath, getFileSystemEntries, isDirectory, isFile, normalizePath } from '../common/pathUtils';
import { combinePaths, forEachAncestorDirectory, getDirectoryPath, getFileSystemEntries,
isDirectory, isFile, normalizePath } from '../common/pathUtils';
import { Duration, timingStats } from '../common/timing';
import { MaxAnalysisTime, Program } from './program';
import { PythonPathUtils } from './pythonPathUtils';
const _defaultConfigFileName = 'pyrightconfig.json';
@ -167,27 +168,37 @@ export class AnalyzerService {
this._updateConfigFileWatcher(configFilePath);
}
// Apply the command-line options. These override the config file.
if (commandLineOptions.venvPath) {
configOptions.venvPath = commandLineOptions.venvPath;
}
const reportDuplicateSetting = (settingName: string) => {
const settingSource = commandLineOptions.fromVsCodeSettings ?
'the VS Code settings' : 'a command-line option';
this._console.log(
`The ${ settingName } has been specified in both the config file and ` +
`${ settingSource }. The value in the config file (${ configOptions.venvPath }) ` +
`will take precedence`);
};
if (commandLineOptions.pythonPath) {
configOptions.pythonPath = commandLineOptions.pythonPath;
// Apply the command-line options if the corresponding
// item wasn't already set in the config file. Report any
// duplicates.
if (commandLineOptions.venvPath) {
if (!configOptions.venvPath) {
configOptions.venvPath = commandLineOptions.venvPath;
} else {
reportDuplicateSetting('venvPath');
}
}
if (commandLineOptions.typeshedPath) {
configOptions.typeshedPath = commandLineOptions.typeshedPath;
if (!configOptions.typeshedPath) {
configOptions.typeshedPath = commandLineOptions.typeshedPath;
} else {
reportDuplicateSetting('typeshedPath');
}
}
// Do some sanity checks on the specified settings and report missing
// or inconsistent information.
if (configOptions.pythonPath) {
if (!fs.existsSync(configOptions.pythonPath) || !isDirectory(configOptions.pythonPath)) {
this._console.log(
`pythonPath ${ configOptions.pythonPath } is not a valid directory.`);
}
} else if (configOptions.venvPath) {
if (configOptions.venvPath) {
if (!fs.existsSync(configOptions.venvPath) || !isDirectory(configOptions.venvPath)) {
this._console.log(
`venvPath ${ configOptions.venvPath } is not a valid directory.`);
@ -202,11 +213,24 @@ export class AnalyzerService {
this._console.log(
`venv ${ configOptions.defaultVenv } subdirectory not found ` +
`in venv path ${ configOptions.venvPath }.`);
} else if (PythonPathUtils.findPythonSearchPaths(configOptions) === undefined) {
this._console.log(
`site-packages directory cannot be located for venvPath ` +
`${ configOptions.venvPath } and venv ${ configOptions.defaultVenv }.`);
}
}
} else {
this._console.log(
`No pythonPath or venvPath specified.`);
`No venvPath specified. Falling back on PYTHONPATH:`);
const pythonPaths = PythonPathUtils.getPythonPathEnvironmentVariable();
if (pythonPaths.length === 0) {
this._console.log(
` No valid paths found in PYTHONPATH environment variable.`);
} else {
pythonPaths.forEach(path => {
this._console.log(` ${ path }`);
});
}
}
// Is there a reference to a venv? If so, there needs to be a valid venvPath.

View File

@ -11,8 +11,9 @@
// Some options can be specified by command line.
export class CommandLineOptions {
constructor(executionRoot: string) {
constructor(executionRoot: string, fromVsCodeSettings: boolean) {
this.executionRoot = executionRoot;
this.fromVsCodeSettings = fromVsCodeSettings;
}
// A list of file specs to include in the analysis. Can contain
@ -30,12 +31,14 @@ export class CommandLineOptions {
// Virtual environments directory.
venvPath?: string;
// Path of python environment.
pythonPath?: string;
// Path of typeshed stubs.
typeshedPath?: string;
// Absolute execution root (current working directory).
executionRoot: string;
// Indicates that the settings came from VS Code rather than
// from the command-line. Useful for providing clearer error
// messages.
fromVsCodeSettings: boolean;
}

View File

@ -83,10 +83,6 @@ export class ConfigOptions {
// the files being analyzed.
executionEnvironments: ExecutionEnvironment[] = [];
// Path to python interpreter environment -- used to resolve third-party
// modules when there is no "venv" option in the config file.
pythonPath?: string;
// Path to a directory containing one or more virtual environment
// directories. This is used in conjunction with the "venv" name in
// the config file to identify the python environment used for resolving
@ -186,16 +182,6 @@ export class ConfigOptions {
this.reportMissingTypeStubs = this._convertDiagnosticLevel(
configObj.reportMissingTypeStubs, 'reportMissingTypeStubs', 'none');
// Read the "pythonPath".
this.pythonPath = undefined;
if (configObj.pythonPath !== undefined) {
if (typeof configObj.pythonPath !== 'string') {
console.log(`Config "pythonPath" field must contain a string.`);
} else {
this.pythonPath = normalizePath(combinePaths(this.projectRoot, configObj.pythonPath));
}
}
// Read the "venvPath".
this.venvPath = undefined;
if (configObj.venvPath !== undefined) {

View File

@ -60,7 +60,7 @@ function processArgs() {
return;
}
let options = new PyrightCommandLineOptions(process.cwd());
let options = new PyrightCommandLineOptions(process.cwd(), false);
options.fileSpecs = args.files;
if (args.project) {
@ -70,9 +70,6 @@ function processArgs() {
if (args['venv-path']) {
options.venvPath = combinePaths(process.cwd(), normalizePath(args['venv-path']));
}
if (args['python-path']) {
options.pythonPath = combinePaths(process.cwd(), normalizePath(args['python-path']));
}
if (args['typeshed-path']) {
options.typeshedPath = combinePaths(process.cwd(), normalizePath(args['typeshed-path']));
}

View File

@ -18,7 +18,6 @@ import { combinePaths, getDirectoryPath, normalizePath } from './common/pathUtil
interface PythonSettings {
venvPath?: string;
pythonPath?: string;
analysis?: {
typeshedPaths: string[];
};
@ -153,17 +152,13 @@ _connection.onHover(params => {
});
function updateOptionsAndRestartService(settings?: Settings) {
let commandLineOptions = new CommandLineOptions(_rootPath);
let commandLineOptions = new CommandLineOptions(_rootPath, true);
commandLineOptions.watch = true;
if (settings && settings.python) {
if (settings.python.venvPath) {
commandLineOptions.venvPath = combinePaths(_rootPath,
normalizePath(settings.python.venvPath));
}
if (settings.python.pythonPath) {
commandLineOptions.pythonPath = combinePaths(_rootPath,
normalizePath(settings.python.pythonPath));
}
if (settings.python.analysis &&
settings.python.analysis.typeshedPaths &&
settings.python.analysis.typeshedPaths.length > 0) {

View File

@ -17,7 +17,7 @@ import { combinePaths } from '../common/pathUtils';
test('FindFilesWithConfigFile', () => {
let service = new AnalyzerService(new NullConsole());
let commandLineOptions = new CommandLineOptions(process.cwd());
let commandLineOptions = new CommandLineOptions(process.cwd(), true);
commandLineOptions.configFilePath = 'src/tests/samples/project1';
let configOptions = service.test_getConfigOptions(commandLineOptions);
@ -39,7 +39,7 @@ test('FindFilesWithConfigFile', () => {
test('FileSpecNotAnArray', () => {
let nullConsole = new NullConsole();
let service = new AnalyzerService(nullConsole);
let commandLineOptions = new CommandLineOptions(process.cwd());
let commandLineOptions = new CommandLineOptions(process.cwd(), false);
commandLineOptions.configFilePath = 'src/tests/samples/project2';
service.setOptions(commandLineOptions);
@ -52,7 +52,7 @@ test('FileSpecNotAnArray', () => {
test('FileSpecNotAString', () => {
let nullConsole = new NullConsole();
let service = new AnalyzerService(nullConsole);
let commandLineOptions = new CommandLineOptions(process.cwd());
let commandLineOptions = new CommandLineOptions(process.cwd(), false);
commandLineOptions.configFilePath = 'src/tests/samples/project3';
service.setOptions(commandLineOptions);
@ -65,7 +65,7 @@ test('FileSpecNotAString', () => {
test('SomeFileSpecsAreInvalid', () => {
let nullConsole = new NullConsole();
let service = new AnalyzerService(nullConsole);
let commandLineOptions = new CommandLineOptions(process.cwd());
let commandLineOptions = new CommandLineOptions(process.cwd(), false);
commandLineOptions.configFilePath = 'src/tests/samples/project4';
service.setOptions(commandLineOptions);
@ -87,7 +87,7 @@ test('SomeFileSpecsAreInvalid', () => {
test('ConfigBadJson', () => {
let nullConsole = new NullConsole();
let service = new AnalyzerService(nullConsole);
let commandLineOptions = new CommandLineOptions(process.cwd());
let commandLineOptions = new CommandLineOptions(process.cwd(), false);
commandLineOptions.configFilePath = 'src/tests/samples/project5';
service.setOptions(commandLineOptions);