Skip to content

Commit c90026b

Browse files
authored
Fix caching behavior of PEP561-installed namespace packages (#10937)
Since PEP561-installed namespace packages only show up in FindModuleCache as a side-effect, the submodules need to be listed first in the dependencies for things to work. This was handled already in the Import case but not the ImportFrom case. Also fix the cache handling of namespace packages so it doesn't always get reported stale. Fixes #9852.
1 parent 58c0a05 commit c90026b

File tree

3 files changed

+40
-10
lines changed

3 files changed

+40
-10
lines changed

mypy/build.py

+16-7
Original file line numberDiff line numberDiff line change
@@ -751,7 +751,6 @@ def correct_rel_imp(imp: Union[ImportFrom, ImportAll]) -> str:
751751
res.append((ancestor_pri, ".".join(ancestors), imp.line))
752752
elif isinstance(imp, ImportFrom):
753753
cur_id = correct_rel_imp(imp)
754-
pos = len(res)
755754
all_are_submodules = True
756755
# Also add any imported names that are submodules.
757756
pri = import_priority(imp, PRI_MED)
@@ -768,7 +767,10 @@ def correct_rel_imp(imp: Union[ImportFrom, ImportAll]) -> str:
768767
# if all of the imports are submodules, do the import at a lower
769768
# priority.
770769
pri = import_priority(imp, PRI_HIGH if not all_are_submodules else PRI_LOW)
771-
res.insert(pos, ((pri, cur_id, imp.line)))
770+
# The imported module goes in after the
771+
# submodules, for the same namespace related
772+
# reasons discussed in the Import case.
773+
res.append((pri, cur_id, imp.line))
772774
elif isinstance(imp, ImportAll):
773775
pri = import_priority(imp, PRI_HIGH)
774776
res.append((pri, correct_rel_imp(imp), imp.line))
@@ -1317,7 +1319,7 @@ def validate_meta(meta: Optional[CacheMeta], id: str, path: Optional[str],
13171319
st = manager.get_stat(path)
13181320
except OSError:
13191321
return None
1320-
if not stat.S_ISREG(st.st_mode):
1322+
if not (stat.S_ISREG(st.st_mode) or stat.S_ISDIR(st.st_mode)):
13211323
manager.log('Metadata abandoned for {}: file {} does not exist'.format(id, path))
13221324
return None
13231325

@@ -1360,7 +1362,11 @@ def validate_meta(meta: Optional[CacheMeta], id: str, path: Optional[str],
13601362

13611363
t0 = time.time()
13621364
try:
1363-
source_hash = manager.fscache.hash_digest(path)
1365+
# dir means it is a namespace package
1366+
if stat.S_ISDIR(st.st_mode):
1367+
source_hash = ''
1368+
else:
1369+
source_hash = manager.fscache.hash_digest(path)
13641370
except (OSError, UnicodeDecodeError, DecodeError):
13651371
return None
13661372
manager.add_stats(validate_hash_time=time.time() - t0)
@@ -1835,15 +1841,15 @@ def __init__(self,
18351841
if path:
18361842
self.abspath = os.path.abspath(path)
18371843
self.xpath = path or '<string>'
1838-
if path and source is None and self.manager.fscache.isdir(path):
1839-
source = ''
1840-
self.source = source
18411844
if path and source is None and self.manager.cache_enabled:
18421845
self.meta = find_cache_meta(self.id, path, manager)
18431846
# TODO: Get mtime if not cached.
18441847
if self.meta is not None:
18451848
self.interface_hash = self.meta.interface_hash
18461849
self.meta_source_hash = self.meta.hash
1850+
if path and source is None and self.manager.fscache.isdir(path):
1851+
source = ''
1852+
self.source = source
18471853
self.add_ancestors()
18481854
t0 = time.time()
18491855
self.meta = validate_meta(self.meta, self.id, self.path, self.ignore_all, manager)
@@ -2038,6 +2044,9 @@ def parse_file(self) -> None:
20382044
else:
20392045
err = "mypy: can't decode file '{}': {}".format(self.path, str(decodeerr))
20402046
raise CompileError([err], module_with_blocker=self.id) from decodeerr
2047+
elif self.path and self.manager.fscache.isdir(self.path):
2048+
source = ''
2049+
self.source_hash = ''
20412050
else:
20422051
assert source is not None
20432052
self.source_hash = compute_hash(source)

test-data/unit/check-incremental.test

+22
Original file line numberDiff line numberDiff line change
@@ -5563,3 +5563,25 @@ class D:
55635563
[out2]
55645564
tmp/a.py:2: note: Revealed type is "builtins.list[builtins.int]"
55655565
tmp/a.py:3: note: Revealed type is "builtins.list[builtins.str]"
5566+
5567+
[case testIncrementalNamespacePackage1]
5568+
# flags: --namespace-packages
5569+
import m
5570+
[file m.py]
5571+
from foo.bar import x
5572+
x + 0
5573+
[file foo/bar.py]
5574+
x = 0
5575+
[rechecked]
5576+
[stale]
5577+
5578+
[case testIncrementalNamespacePackage2]
5579+
# flags: --namespace-packages
5580+
import m
5581+
[file m.py]
5582+
from foo import bar
5583+
bar.x + 0
5584+
[file foo/bar.py]
5585+
x = 0
5586+
[rechecked]
5587+
[stale]

test-data/unit/pep561.test

+2-3
Original file line numberDiff line numberDiff line change
@@ -187,9 +187,8 @@ import a
187187
[out2]
188188
a.py:1: error: Unsupported operand types for + ("int" and "str")
189189

190-
-- Test for issue #9852, among others
191-
[case testTypedPkgNamespaceRegFromImportTwice-xfail]
192-
# pkgs: typedpkg, typedpkg_ns
190+
[case testTypedPkgNamespaceRegFromImportTwice]
191+
# pkgs: typedpkg_ns
193192
from typedpkg_ns import ns
194193
-- dummy should trigger a second iteration
195194
[file dummy.py.2]

0 commit comments

Comments
 (0)