Skip to content

Commit 7f5a8dd

Browse files
authored
Prioritize .pyi from -stubs packages over bundled .pyi (#19001)
<!-- If this pull request fixes an issue, add "Fixes #NNN" with the issue number. --> This fixes the import resolution order for stubs from a `-stubs` package and stubs bundled with a `py.typed` package, and fixes #18997. Besides the unit tests, the effectiveness of this fix is also demonstrated at https://github.com/jorenham/mypy-pep561-numpy-issue#mypy-jorenhamfix-18997-with-numtype After investigating a bit more, it looks like mypy's incorrect prioritization of stubs was limited to `__init__.pyi`. I confirmed this by adding `reveal_type(np.dtypes.StringDType())` to the `main.pyi` in https://github.com/jorenham/mypy-pep561-numpy-issue. With `mypy==1.15.0` installed, it correctly showed `numpy.dtypes.StringDType` *without* NumType, and `numpy.dtypes.StringDType[Never]` *with* NumType installed. So both #18997 and this PR only apply to `__init__.pyi`.
1 parent c724a6a commit 7f5a8dd

File tree

10 files changed

+22
-6
lines changed

10 files changed

+22
-6
lines changed

mypy/modulefinder.py

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -506,21 +506,24 @@ def _find_module(self, id: str, use_typeshed: bool) -> ModuleSearchResult:
506506
dir_prefix = base_dir
507507
for _ in range(len(components) - 1):
508508
dir_prefix = os.path.dirname(dir_prefix)
509+
510+
# Stubs-only packages always take precedence over py.typed packages
511+
path_stubs = f"{base_path}-stubs{sepinit}.pyi"
512+
if fscache.isfile_case(path_stubs, dir_prefix):
513+
if verify and not verify_module(fscache, id, path_stubs, dir_prefix):
514+
near_misses.append((path_stubs, dir_prefix))
515+
else:
516+
return path_stubs
517+
509518
# Prefer package over module, i.e. baz/__init__.py* over baz.py*.
510519
for extension in PYTHON_EXTENSIONS:
511520
path = base_path + sepinit + extension
512-
path_stubs = base_path + "-stubs" + sepinit + extension
513521
if fscache.isfile_case(path, dir_prefix):
514522
has_init = True
515523
if verify and not verify_module(fscache, id, path, dir_prefix):
516524
near_misses.append((path, dir_prefix))
517525
continue
518526
return path
519-
elif fscache.isfile_case(path_stubs, dir_prefix):
520-
if verify and not verify_module(fscache, id, path_stubs, dir_prefix):
521-
near_misses.append((path_stubs, dir_prefix))
522-
continue
523-
return path_stubs
524527

525528
# In namespace mode, register a potential namespace package
526529
if self.options and self.options.namespace_packages:

mypy/test/testmodulefinder.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -195,6 +195,9 @@ def test__packages_with_ns(self) -> None:
195195
("pkg_typed.b", self.path("pkg_typed", "b", "__init__.py")),
196196
("pkg_typed.b.c", self.path("pkg_typed", "b", "c.py")),
197197
("pkg_typed.a.a_var", ModuleNotFoundReason.NOT_FOUND),
198+
# Regular package with py.typed, bundled stubs, and external stubs-only package
199+
("pkg_typed_w_stubs", self.path("pkg_typed_w_stubs-stubs", "__init__.pyi")),
200+
("pkg_typed_w_stubs.spam", self.path("pkg_typed_w_stubs-stubs", "spam.pyi")),
198201
# Regular package without py.typed
199202
("pkg_untyped", ModuleNotFoundReason.FOUND_WITHOUT_TYPE_HINTS),
200203
("pkg_untyped.a", ModuleNotFoundReason.FOUND_WITHOUT_TYPE_HINTS),
@@ -250,6 +253,9 @@ def test__packages_without_ns(self) -> None:
250253
("pkg_typed.b", self.path("pkg_typed", "b", "__init__.py")),
251254
("pkg_typed.b.c", self.path("pkg_typed", "b", "c.py")),
252255
("pkg_typed.a.a_var", ModuleNotFoundReason.NOT_FOUND),
256+
# Regular package with py.typed, bundled stubs, and external stubs-only package
257+
("pkg_typed_w_stubs", self.path("pkg_typed_w_stubs-stubs", "__init__.pyi")),
258+
("pkg_typed_w_stubs.spam", self.path("pkg_typed_w_stubs-stubs", "spam.pyi")),
253259
# Regular package without py.typed
254260
("pkg_untyped", ModuleNotFoundReason.FOUND_WITHOUT_TYPE_HINTS),
255261
("pkg_untyped.a", ModuleNotFoundReason.FOUND_WITHOUT_TYPE_HINTS),
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
qux_var: int
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
pkg_typed_w_stubs_var: str = ...
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
spam_var: str
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
pkg_typed_w_stubs_var = "pkg_typed_w_stubs"
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
pkg_typed_w_stubs_var: object

test-data/packages/modulefinder-site-packages/pkg_typed_w_stubs/py.typed

Whitespace-only changes.
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
spam_var = "spam"
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
spam_var: object

0 commit comments

Comments
 (0)