From 48fa12a8b2df16b53f0aa8ee7f8bb786583b2aac Mon Sep 17 00:00:00 2001 From: jorenham Date: Wed, 30 Apr 2025 00:04:30 +0200 Subject: [PATCH 1/3] reproduce #18997 --- mypy/test/testmodulefinder.py | 4 ++++ .../packages/modulefinder-site-packages/foo-stubs/qux.pyi | 1 + .../pkg_typed_w_stubs-stubs/__init__.pyi | 1 + .../modulefinder-site-packages/pkg_typed_w_stubs/__init__.py | 1 + .../modulefinder-site-packages/pkg_typed_w_stubs/__init__.pyi | 1 + .../modulefinder-site-packages/pkg_typed_w_stubs/py.typed | 0 6 files changed, 8 insertions(+) create mode 100644 test-data/packages/modulefinder-site-packages/foo-stubs/qux.pyi create mode 100644 test-data/packages/modulefinder-site-packages/pkg_typed_w_stubs-stubs/__init__.pyi create mode 100644 test-data/packages/modulefinder-site-packages/pkg_typed_w_stubs/__init__.py create mode 100644 test-data/packages/modulefinder-site-packages/pkg_typed_w_stubs/__init__.pyi create mode 100644 test-data/packages/modulefinder-site-packages/pkg_typed_w_stubs/py.typed diff --git a/mypy/test/testmodulefinder.py b/mypy/test/testmodulefinder.py index 65d9a66c5fa0d..1f4ae407635e8 100644 --- a/mypy/test/testmodulefinder.py +++ b/mypy/test/testmodulefinder.py @@ -195,6 +195,8 @@ def test__packages_with_ns(self) -> None: ("pkg_typed.b", self.path("pkg_typed", "b", "__init__.py")), ("pkg_typed.b.c", self.path("pkg_typed", "b", "c.py")), ("pkg_typed.a.a_var", ModuleNotFoundReason.NOT_FOUND), + # Regular package with py.typed, bundled stubs, and external stubs-only package + ("pkg_typed_w_stubs", self.path("pkg_typed_w_stubs-stubs", "__init__.pyi")), # Regular package without py.typed ("pkg_untyped", ModuleNotFoundReason.FOUND_WITHOUT_TYPE_HINTS), ("pkg_untyped.a", ModuleNotFoundReason.FOUND_WITHOUT_TYPE_HINTS), @@ -250,6 +252,8 @@ def test__packages_without_ns(self) -> None: ("pkg_typed.b", self.path("pkg_typed", "b", "__init__.py")), ("pkg_typed.b.c", self.path("pkg_typed", "b", "c.py")), ("pkg_typed.a.a_var", ModuleNotFoundReason.NOT_FOUND), + # Regular package with py.typed, bundled stubs, and external stubs-only package + ("pkg_typed_w_stubs", self.path("pkg_typed_w_stubs-stubs", "__init__.pyi")), # Regular package without py.typed ("pkg_untyped", ModuleNotFoundReason.FOUND_WITHOUT_TYPE_HINTS), ("pkg_untyped.a", ModuleNotFoundReason.FOUND_WITHOUT_TYPE_HINTS), diff --git a/test-data/packages/modulefinder-site-packages/foo-stubs/qux.pyi b/test-data/packages/modulefinder-site-packages/foo-stubs/qux.pyi new file mode 100644 index 0000000000000..5605b14540396 --- /dev/null +++ b/test-data/packages/modulefinder-site-packages/foo-stubs/qux.pyi @@ -0,0 +1 @@ +qux_var: int diff --git a/test-data/packages/modulefinder-site-packages/pkg_typed_w_stubs-stubs/__init__.pyi b/test-data/packages/modulefinder-site-packages/pkg_typed_w_stubs-stubs/__init__.pyi new file mode 100644 index 0000000000000..579a7556fdd1e --- /dev/null +++ b/test-data/packages/modulefinder-site-packages/pkg_typed_w_stubs-stubs/__init__.pyi @@ -0,0 +1 @@ +pkg_typed_w_stubs_var: str = ... diff --git a/test-data/packages/modulefinder-site-packages/pkg_typed_w_stubs/__init__.py b/test-data/packages/modulefinder-site-packages/pkg_typed_w_stubs/__init__.py new file mode 100644 index 0000000000000..11fa3635a2c7a --- /dev/null +++ b/test-data/packages/modulefinder-site-packages/pkg_typed_w_stubs/__init__.py @@ -0,0 +1 @@ +pkg_typed_w_stubs_var = "pkg_typed_w_stubs" diff --git a/test-data/packages/modulefinder-site-packages/pkg_typed_w_stubs/__init__.pyi b/test-data/packages/modulefinder-site-packages/pkg_typed_w_stubs/__init__.pyi new file mode 100644 index 0000000000000..3a03f395d0144 --- /dev/null +++ b/test-data/packages/modulefinder-site-packages/pkg_typed_w_stubs/__init__.pyi @@ -0,0 +1 @@ +pkg_typed_w_stubs_var: object diff --git a/test-data/packages/modulefinder-site-packages/pkg_typed_w_stubs/py.typed b/test-data/packages/modulefinder-site-packages/pkg_typed_w_stubs/py.typed new file mode 100644 index 0000000000000..e69de29bb2d1d From e85acf6b92d64856b272187462e7e50e06e3b0ba Mon Sep 17 00:00:00 2001 From: jorenham Date: Wed, 30 Apr 2025 00:47:26 +0200 Subject: [PATCH 2/3] prioritize stubs from `-stubs` packages over bundled stubs --- mypy/modulefinder.py | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/mypy/modulefinder.py b/mypy/modulefinder.py index ca21cc6a71994..8365575906233 100644 --- a/mypy/modulefinder.py +++ b/mypy/modulefinder.py @@ -506,21 +506,24 @@ def _find_module(self, id: str, use_typeshed: bool) -> ModuleSearchResult: dir_prefix = base_dir for _ in range(len(components) - 1): dir_prefix = os.path.dirname(dir_prefix) + + # Stubs-only packages always take precedence over py.typed packages + path_stubs = f"{base_path}-stubs{sepinit}.pyi" + if fscache.isfile_case(path_stubs, dir_prefix): + if verify and not verify_module(fscache, id, path_stubs, dir_prefix): + near_misses.append((path_stubs, dir_prefix)) + else: + return path_stubs + # 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, dir_prefix): has_init = True if verify and not verify_module(fscache, id, path, dir_prefix): near_misses.append((path, dir_prefix)) continue return path - elif fscache.isfile_case(path_stubs, dir_prefix): - if verify and not verify_module(fscache, id, path_stubs, dir_prefix): - near_misses.append((path_stubs, dir_prefix)) - continue - return path_stubs # In namespace mode, register a potential namespace package if self.options and self.options.namespace_packages: From 561fb7e5d8e15bf784260fcbeb94c510e5b9888d Mon Sep 17 00:00:00 2001 From: jorenham Date: Wed, 30 Apr 2025 09:13:25 +0200 Subject: [PATCH 3/3] validate PEP 561 precendence for `.pyi` modules --- mypy/test/testmodulefinder.py | 2 ++ .../modulefinder-site-packages/pkg_typed_w_stubs-stubs/spam.pyi | 1 + .../modulefinder-site-packages/pkg_typed_w_stubs/spam.py | 1 + .../modulefinder-site-packages/pkg_typed_w_stubs/spam.pyi | 1 + 4 files changed, 5 insertions(+) create mode 100644 test-data/packages/modulefinder-site-packages/pkg_typed_w_stubs-stubs/spam.pyi create mode 100644 test-data/packages/modulefinder-site-packages/pkg_typed_w_stubs/spam.py create mode 100644 test-data/packages/modulefinder-site-packages/pkg_typed_w_stubs/spam.pyi diff --git a/mypy/test/testmodulefinder.py b/mypy/test/testmodulefinder.py index 1f4ae407635e8..d4ee3af041c5e 100644 --- a/mypy/test/testmodulefinder.py +++ b/mypy/test/testmodulefinder.py @@ -197,6 +197,7 @@ def test__packages_with_ns(self) -> None: ("pkg_typed.a.a_var", ModuleNotFoundReason.NOT_FOUND), # Regular package with py.typed, bundled stubs, and external stubs-only package ("pkg_typed_w_stubs", self.path("pkg_typed_w_stubs-stubs", "__init__.pyi")), + ("pkg_typed_w_stubs.spam", self.path("pkg_typed_w_stubs-stubs", "spam.pyi")), # Regular package without py.typed ("pkg_untyped", ModuleNotFoundReason.FOUND_WITHOUT_TYPE_HINTS), ("pkg_untyped.a", ModuleNotFoundReason.FOUND_WITHOUT_TYPE_HINTS), @@ -254,6 +255,7 @@ def test__packages_without_ns(self) -> None: ("pkg_typed.a.a_var", ModuleNotFoundReason.NOT_FOUND), # Regular package with py.typed, bundled stubs, and external stubs-only package ("pkg_typed_w_stubs", self.path("pkg_typed_w_stubs-stubs", "__init__.pyi")), + ("pkg_typed_w_stubs.spam", self.path("pkg_typed_w_stubs-stubs", "spam.pyi")), # Regular package without py.typed ("pkg_untyped", ModuleNotFoundReason.FOUND_WITHOUT_TYPE_HINTS), ("pkg_untyped.a", ModuleNotFoundReason.FOUND_WITHOUT_TYPE_HINTS), diff --git a/test-data/packages/modulefinder-site-packages/pkg_typed_w_stubs-stubs/spam.pyi b/test-data/packages/modulefinder-site-packages/pkg_typed_w_stubs-stubs/spam.pyi new file mode 100644 index 0000000000000..e3ef9cce59055 --- /dev/null +++ b/test-data/packages/modulefinder-site-packages/pkg_typed_w_stubs-stubs/spam.pyi @@ -0,0 +1 @@ +spam_var: str diff --git a/test-data/packages/modulefinder-site-packages/pkg_typed_w_stubs/spam.py b/test-data/packages/modulefinder-site-packages/pkg_typed_w_stubs/spam.py new file mode 100644 index 0000000000000..0aff1579b57f7 --- /dev/null +++ b/test-data/packages/modulefinder-site-packages/pkg_typed_w_stubs/spam.py @@ -0,0 +1 @@ +spam_var = "spam" diff --git a/test-data/packages/modulefinder-site-packages/pkg_typed_w_stubs/spam.pyi b/test-data/packages/modulefinder-site-packages/pkg_typed_w_stubs/spam.pyi new file mode 100644 index 0000000000000..8eca196a79817 --- /dev/null +++ b/test-data/packages/modulefinder-site-packages/pkg_typed_w_stubs/spam.pyi @@ -0,0 +1 @@ +spam_var: object