Skip to content
Merged
50 changes: 29 additions & 21 deletions mypy/modulefinder.py
Original file line number Diff line number Diff line change
Expand Up @@ -170,27 +170,35 @@ def _find_module(self, id: str) -> Optional[str]:
# Now just look for 'baz.pyi', 'baz/__init__.py', etc., inside those directories.
seplast = os.sep + components[-1] # so e.g. '/baz'
sepinit = os.sep + '__init__'
for base_dir, verify in candidate_base_dirs:
base_path = base_dir + seplast # so e.g. '/usr/lib/python3.4/foo/bar/baz'
# Prefer package over module, i.e. baz/__init__.py* over baz.py*.
for extension in PYTHON_EXTENSIONS:
path = base_path + sepinit + extension
path_stubs = base_path + '-stubs' + sepinit + extension
if fscache.isfile_case(path):
if verify and not verify_module(fscache, id, path):
continue
return path
elif fscache.isfile_case(path_stubs):
if verify and not verify_module(fscache, id, path_stubs):
continue
return path_stubs
# No package, look for module.
for extension in PYTHON_EXTENSIONS:
path = base_path + extension
if fscache.isfile_case(path):
if verify and not verify_module(fscache, id, path):
continue
return path
verify_flags = [True]
if self.options is not None and self.options.namespace_packages:
verify_flags.append(False)
# If --namespace-packages, we do the whole thing twice:
# - once with classic rules (verify if requested)
# - once looking for namespace packages (never verify)
for verify_flag in verify_flags:
for base_dir, verify in candidate_base_dirs:
verify = verify and verify_flag
base_path = base_dir + seplast # so e.g. '/usr/lib/python3.4/foo/bar/baz'
# Prefer package over module, i.e. baz/__init__.py* over baz.py*.
for extension in PYTHON_EXTENSIONS:
path = base_path + sepinit + extension
path_stubs = base_path + '-stubs' + sepinit + extension
if fscache.isfile_case(path):
if verify and not verify_module(fscache, id, path):
continue
return path
elif fscache.isfile_case(path_stubs):
if verify and not verify_module(fscache, id, path_stubs):
continue
return path_stubs
# No package, look for module.
for extension in PYTHON_EXTENSIONS:
path = base_path + extension
if fscache.isfile_case(path):
if verify and not verify_module(fscache, id, path):
continue
return path
return None

def find_modules_recursive(self, module: str) -> List[BuildSource]:
Expand Down
49 changes: 49 additions & 0 deletions test-data/unit/check-modules.test
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
-- Type checker test cases dealing with modules and imports.
-- Towards the end there are tests for PEP 420 (namespace packages, i.e. __init__.py-less packages).

[case testAccessImportedDefinitions]
import m
Expand Down Expand Up @@ -2525,3 +2526,51 @@ def __radd__(self) -> int: ...

[case testFunctionWithInPlaceDunderName]
def __iadd__(self) -> int: ...

-- Tests for PEP 420 namespace packages.
[case testClassicPackage]
from foo.bar import x
[file foo/__init__.py]
# empty
[file foo/bar.py]
x = 0

[case testClassicNotPackage]
from foo.bar import x
[file foo/bar.py]
x = 0
[out]
main:1: error: Cannot find module named 'foo.bar'
main:1: note: (Perhaps setting MYPYPATH or using the "--ignore-missing-imports" flag would help)

[case testNamespacePackage]
# flags: --namespace-packages
from foo.bar import x
[file foo/bar.py]
x = 0

[case testNamespacePackageWithMypyPath]
# flags: --namespace-packages --config-file tmp/mypy.ini
from foo.bax import x
from foo.bay import y
from foo.baz import z
[file xx/foo/bax.py]
x = 0
[file yy/foo/bay.py]
y = 0
[file foo/baz.py]
z = 0
[file mypy.ini]
[[mypy]
mypy_path = tmp/xx, tmp/yy

[case testClassicPackageIgnoresEarlierNamespacePackage]
# flags: --namespace-packages --config-file tmp/mypy.ini
from foo.bar import y
[file xx/foo/bar.py]
[file yy/foo/bar.py]
y = 0
[file yy/foo/__init__.py]
[file mypy.ini]
[[mypy]
mypy_path = tmp/xx, tmp/yy