Changed import logic to better match behavior of python loader. If there's an empty directory and a python file with the same name, the import resolution logic now selects the file rather than failing resolution.

This commit is contained in:
Eric Traut 2019-11-23 01:34:48 -08:00
parent f71e1321bb
commit d79e43b8e0
9 changed files with 92 additions and 25 deletions

View File

@ -601,6 +601,7 @@ export class ImportResolver {
implicitImports = this._findImplicitImports(dirPath, [pyFilePath, pyiFilePath]);
} else {
for (let i = 0; i < moduleDescriptor.nameParts.length; i++) {
const isLastPart = i === moduleDescriptor.nameParts.length - 1;
dirPath = combinePaths(dirPath, moduleDescriptor.nameParts[i]);
let foundDirectory = false;
@ -620,60 +621,72 @@ export class ImportResolver {
foundDirectory = fs.existsSync(dirPath) && isDirectory(dirPath);
}
if (!foundDirectory) {
importFailureInfo.push(`Could not find directory '${ dirPath }'`);
if (foundDirectory) {
if (!isLastPart) {
// We are not at the last part, and we found a directory,
// so continue to look for the next part.
resolvedPaths.push(dirPath);
continue;
}
// We weren't able to find the subdirectory. See if we can
// find a ".py" or ".pyi" file with this name.
const pyFilePath = stripTrailingDirectorySeparator(dirPath) + '.py';
// See if we can find an __init__.py[i] in this directory.
const pyFilePath = combinePaths(dirPath, '__init__.py');
const pyiFilePath = pyFilePath + 'i';
const pydFilePath = pyFilePath + 'd';
let foundInit = false;
if (fs.existsSync(pyiFilePath) && isFile(pyiFilePath)) {
importFailureInfo.push(`Resolved import with file '${ pyiFilePath }'`);
resolvedPaths.push(pyiFilePath);
if (i === moduleDescriptor.nameParts.length - 1) {
if (isLastPart) {
isStubFile = true;
}
foundInit = true;
} else if (fs.existsSync(pyFilePath) && isFile(pyFilePath)) {
importFailureInfo.push(`Resolved import with file '${ pyFilePath }'`);
resolvedPaths.push(pyFilePath);
} else if (allowPydFile && fs.existsSync(pydFilePath) && isFile(pydFilePath)) {
importFailureInfo.push(`Resolved import with file '${ pydFilePath }'`);
resolvedPaths.push(pydFilePath);
if (i === moduleDescriptor.nameParts.length - 1) {
isPydFile = true;
}
} else {
importFailureInfo.push(`Did not find file '${ pyiFilePath }',` +
` '${ pyFilePath }' or '${ pydFilePath }'`);
foundInit = true;
}
if (foundInit) {
implicitImports = this._findImplicitImports(dirPath, [pyFilePath, pyiFilePath]);
break;
}
break;
}
const pyFilePath = combinePaths(dirPath, '__init__.py');
// We weren't able to find a directory or we found a directory with
// no __init__.py[i] file. See if we can find a ".py" or ".pyi" file
// with this name.
const pyFilePath = stripTrailingDirectorySeparator(dirPath) + '.py';
const pyiFilePath = pyFilePath + 'i';
const pydFilePath = pyFilePath + 'd';
if (fs.existsSync(pyiFilePath) && isFile(pyiFilePath)) {
importFailureInfo.push(`Resolved import with file '${ pyiFilePath }'`);
resolvedPaths.push(pyiFilePath);
if (i === moduleDescriptor.nameParts.length - 1) {
if (isLastPart) {
isStubFile = true;
}
} else if (fs.existsSync(pyFilePath) && isFile(pyFilePath)) {
importFailureInfo.push(`Resolved import with file '${ pyFilePath }'`);
resolvedPaths.push(pyFilePath);
} else {
} else if (allowPydFile && fs.existsSync(pydFilePath) && isFile(pydFilePath)) {
importFailureInfo.push(`Resolved import with file '${ pydFilePath }'`);
resolvedPaths.push(pydFilePath);
if (isLastPart) {
isPydFile = true;
}
} else if (foundDirectory) {
importFailureInfo.push(`Partially resolved import with directory '${ dirPath }'`);
resolvedPaths.push('');
if (i === moduleDescriptor.nameParts.length - 1) {
if (isLastPart) {
implicitImports = this._findImplicitImports(dirPath, [pyFilePath, pyiFilePath]);
isNamespacePackage = true;
}
} else {
importFailureInfo.push(`Did not find file '${ pyiFilePath }',` +
` '${ pyFilePath }' or '${ pydFilePath }'`);
}
if (i === moduleDescriptor.nameParts.length - 1) {
implicitImports = this._findImplicitImports(dirPath, [pyFilePath, pyiFilePath]);
}
break;
}
}

View File

@ -828,6 +828,11 @@ test('Import1', () => {
validateResults(analysisResults, 0);
});
test('Import2', () => {
const analysisResults = TestUtils.typeAnalyzeSampleFiles(['import2.py']);
validateResults(analysisResults, 2);
});
test('Overload1', () => {
const analysisResults = TestUtils.typeAnalyzeSampleFiles(['overload1.py']);
validateResults(analysisResults, 2);

View File

@ -0,0 +1,27 @@
# This sample tests import resolution for relative imports.
from datetime import datetime
import .package1 as p1
a = p1.foo()
from .package1 import foo
b = foo()
# This should generate an error because there is no
# directory or file named package2.
import .package2 as p2
# This should generate an error too.
from .package2 import foo
from .package1.sub import subfoo
# subfoo should resolve to the package1/sub/__init__.py,
# which returns a datetime. Verify that it does.
c: datetime = subfoo()
from .package1.psyche import psyche1
# This should resolve to package1/psyche.py even though
# there is a package1/psyche directory present.
d: int = psyche1()

View File

@ -0,0 +1,3 @@
def foo():
pass

View File

@ -0,0 +1,4 @@
def psyche1() -> int:
return 3

View File

@ -0,0 +1,4 @@
def psyche1() -> str:
return "3"

View File

@ -0,0 +1,4 @@
def subfoo() -> str:
return 'hello'

View File

@ -0,0 +1,2 @@
def subfoo() -> int: ...

View File

@ -0,0 +1,5 @@
from datetime import datetime
def subfoo() -> datetime:
return datetime.now()