Skip to content

Commit b724cca

Browse files
awelzelGuido van Rossum
authored and
Guido van Rossum
committed
modulefinder: use only dirs containing no __init__.py as namespace package (#7108)
When used with --namespace-packages, a directory containing no __init__.pyi was previously added as a candidate to near_misses even if it contained a __init__.py. This logic doesn't seem sensible, particularly as this directory was then chosen as the result of find_module() instead of the __init__.py inside the directory. Closes #5854.
1 parent 00ccb78 commit b724cca

File tree

14 files changed

+147
-1
lines changed

14 files changed

+147
-1
lines changed

mypy/modulefinder.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -212,6 +212,7 @@ def _find_module(self, id: str) -> Optional[str]:
212212
near_misses = [] # Collect near misses for namespace mode (see below).
213213
for base_dir, verify in candidate_base_dirs:
214214
base_path = base_dir + seplast # so e.g. '/usr/lib/python3.4/foo/bar/baz'
215+
has_init = False
215216
dir_prefix = base_dir
216217
for _ in range(len(components) - 1):
217218
dir_prefix = os.path.dirname(dir_prefix)
@@ -220,6 +221,7 @@ def _find_module(self, id: str) -> Optional[str]:
220221
path = base_path + sepinit + extension
221222
path_stubs = base_path + '-stubs' + sepinit + extension
222223
if fscache.isfile_case(path, dir_prefix):
224+
has_init = True
223225
if verify and not verify_module(fscache, id, path, dir_prefix):
224226
near_misses.append((path, dir_prefix))
225227
continue
@@ -229,8 +231,12 @@ def _find_module(self, id: str) -> Optional[str]:
229231
near_misses.append((path_stubs, dir_prefix))
230232
continue
231233
return path_stubs
232-
elif self.options and self.options.namespace_packages and fscache.isdir(base_path):
234+
235+
# In namespace mode, register a potential namespace package
236+
if self.options and self.options.namespace_packages:
237+
if fscache.isdir(base_path) and not has_init:
233238
near_misses.append((base_path, dir_prefix))
239+
234240
# No package, look for module.
235241
for extension in PYTHON_EXTENSIONS:
236242
path = base_path + extension

mypy/test/testmodulefinder.py

Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
import os
2+
3+
from mypy.options import Options
4+
from mypy.modulefinder import FindModuleCache, SearchPaths
5+
6+
from mypy.test.helpers import Suite, assert_equal
7+
from mypy.test.config import package_path
8+
data_path = os.path.relpath(os.path.join(package_path, "modulefinder"))
9+
10+
11+
class ModuleFinderSuite(Suite):
12+
13+
def setUp(self) -> None:
14+
self.search_paths = SearchPaths(
15+
python_path=(),
16+
mypy_path=(
17+
os.path.join(data_path, "nsx-pkg1"),
18+
os.path.join(data_path, "nsx-pkg2"),
19+
os.path.join(data_path, "nsx-pkg3"),
20+
os.path.join(data_path, "nsy-pkg1"),
21+
os.path.join(data_path, "nsy-pkg2"),
22+
os.path.join(data_path, "pkg1"),
23+
os.path.join(data_path, "pkg2"),
24+
),
25+
package_path=(),
26+
typeshed_path=(),
27+
)
28+
options = Options()
29+
options.namespace_packages = True
30+
self.fmc_ns = FindModuleCache(self.search_paths, options=options)
31+
32+
options = Options()
33+
options.namespace_packages = False
34+
self.fmc_nons = FindModuleCache(self.search_paths, options=options)
35+
36+
def test__no_namespace_packages__nsx(self) -> None:
37+
"""
38+
If namespace_packages is False, we shouldn't find nsx
39+
"""
40+
found_module = self.fmc_nons.find_module("nsx")
41+
self.assertIsNone(found_module)
42+
43+
def test__no_namespace_packages__nsx_a(self) -> None:
44+
"""
45+
If namespace_packages is False, we shouldn't find nsx.a.
46+
"""
47+
found_module = self.fmc_nons.find_module("nsx.a")
48+
self.assertIsNone(found_module)
49+
50+
def test__no_namespace_packages__find_a_in_pkg1(self) -> None:
51+
"""
52+
Find find pkg1/a.py for "a" with namespace_packages False.
53+
"""
54+
found_module = self.fmc_nons.find_module("a")
55+
expected = os.path.join(data_path, "pkg1", "a.py")
56+
assert_equal(expected, found_module)
57+
58+
def test__no_namespace_packages__find_b_in_pkg2(self) -> None:
59+
found_module = self.fmc_ns.find_module("b")
60+
expected = os.path.join(data_path, "pkg2", "b", "__init__.py")
61+
assert_equal(expected, found_module)
62+
63+
def test__find_nsx_as_namespace_pkg_in_pkg1(self) -> None:
64+
"""
65+
There's no __init__.py in any of the nsx dirs, return
66+
the path to the first one found in mypypath.
67+
"""
68+
found_module = self.fmc_ns.find_module("nsx")
69+
expected = os.path.join(data_path, "nsx-pkg1", "nsx")
70+
assert_equal(expected, found_module)
71+
72+
def test__find_nsx_a_init_in_pkg1(self) -> None:
73+
"""
74+
Find nsx-pkg1/nsx/a/__init__.py for "nsx.a" in namespace mode.
75+
"""
76+
found_module = self.fmc_ns.find_module("nsx.a")
77+
expected = os.path.join(data_path, "nsx-pkg1", "nsx", "a", "__init__.py")
78+
assert_equal(expected, found_module)
79+
80+
def test__find_nsx_b_init_in_pkg2(self) -> None:
81+
"""
82+
Find nsx-pkg2/nsx/b/__init__.py for "nsx.b" in namespace mode.
83+
"""
84+
found_module = self.fmc_ns.find_module("nsx.b")
85+
expected = os.path.join(data_path, "nsx-pkg2", "nsx", "b", "__init__.py")
86+
assert_equal(expected, found_module)
87+
88+
def test__find_nsx_c_c_in_pkg3(self) -> None:
89+
"""
90+
Find nsx-pkg3/nsx/c/c.py for "nsx.c.c" in namespace mode.
91+
"""
92+
found_module = self.fmc_ns.find_module("nsx.c.c")
93+
expected = os.path.join(data_path, "nsx-pkg3", "nsx", "c", "c.py")
94+
assert_equal(expected, found_module)
95+
96+
def test__find_nsy_a__init_pyi(self) -> None:
97+
"""
98+
Prefer nsy-pkg1/a/__init__.pyi file over __init__.py.
99+
"""
100+
found_module = self.fmc_ns.find_module("nsy.a")
101+
expected = os.path.join(data_path, "nsy-pkg1", "nsy", "a", "__init__.pyi")
102+
assert_equal(expected, found_module)
103+
104+
def test__find_nsy_b__init_py(self) -> None:
105+
"""
106+
There is a nsy-pkg2/nsy/b.pyi, but also a nsy-pkg2/nsy/b/__init__.py.
107+
We expect to find the latter when looking up "nsy.b" as
108+
a package is preferred over a module.
109+
"""
110+
found_module = self.fmc_ns.find_module("nsy.b")
111+
expected = os.path.join(data_path, "nsy-pkg2", "nsy", "b", "__init__.py")
112+
assert_equal(expected, found_module)
113+
114+
def test__find_nsy_c_pyi(self) -> None:
115+
"""
116+
There is a nsy-pkg2/nsy/c.pyi and nsy-pkg2/nsy/c.py
117+
We expect to find the former when looking up "nsy.b" as
118+
.pyi is preferred over .py.
119+
"""
120+
found_module = self.fmc_ns.find_module("nsy.c")
121+
expected = os.path.join(data_path, "nsy-pkg2", "nsy", "c.pyi")
122+
assert_equal(expected, found_module)
123+
124+
def test__find_a_in_pkg1(self) -> None:
125+
found_module = self.fmc_ns.find_module("a")
126+
expected = os.path.join(data_path, "pkg1", "a.py")
127+
assert_equal(expected, found_module)
128+
129+
def test__find_b_init_in_pkg2(self) -> None:
130+
found_module = self.fmc_ns.find_module("b")
131+
expected = os.path.join(data_path, "pkg2", "b", "__init__.py")
132+
assert_equal(expected, found_module)
133+
134+
def test__find_d_nowhere(self) -> None:
135+
found_module = self.fmc_ns.find_module("d")
136+
self.assertIsNone(found_module)

test-data/packages/modulefinder/nsx-pkg1/nsx/a/__init__.py

Whitespace-only changes.

test-data/packages/modulefinder/nsx-pkg2/nsx/b/__init__.py

Whitespace-only changes.

test-data/packages/modulefinder/nsx-pkg3/nsx/c/c.py

Whitespace-only changes.

test-data/packages/modulefinder/nsy-pkg1/nsy/a/__init__.py

Whitespace-only changes.

test-data/packages/modulefinder/nsy-pkg1/nsy/a/__init__.pyi

Whitespace-only changes.

test-data/packages/modulefinder/nsy-pkg2/nsy/b.pyi

Whitespace-only changes.

test-data/packages/modulefinder/nsy-pkg2/nsy/b/__init__.py

Whitespace-only changes.

test-data/packages/modulefinder/nsy-pkg2/nsy/c.py

Whitespace-only changes.

test-data/packages/modulefinder/nsy-pkg2/nsy/c.pyi

Whitespace-only changes.

test-data/packages/modulefinder/pkg1/a.py

Whitespace-only changes.

test-data/packages/modulefinder/pkg2/b/__init__.py

Whitespace-only changes.
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
Samples for testing modulefinder.FindModuleCache.
2+
3+
Contains three packages for the `nsx` namespace, and two packages
4+
providing `a` and `b`.

0 commit comments

Comments
 (0)