From 3facd78f9817f6dd4da86918a97c83661e2296ac Mon Sep 17 00:00:00 2001 From: hauntsaninja Date: Thu, 1 Jun 2023 13:58:04 -0700 Subject: [PATCH 1/4] Fix over eager types-google-cloud-ndb suggestion Fixes #15343 --- mypy/build.py | 22 +++++++++++++--------- mypy/modulefinder.py | 9 ++------- mypy/stubinfo.py | 4 ++-- mypy/util.py | 11 ----------- test-data/unit/check-modules.test | 18 +++++++++--------- 5 files changed, 26 insertions(+), 38 deletions(-) diff --git a/mypy/build.py b/mypy/build.py index c239afb56236..8eef02aead5e 100644 --- a/mypy/build.py +++ b/mypy/build.py @@ -57,7 +57,6 @@ DecodeError, decode_python_encoding, get_mypy_comments, - get_top_two_prefixes, hash_digest, is_stub_package_file, is_sub_path, @@ -97,7 +96,7 @@ is_legacy_bundled_package, legacy_bundled_packages, non_bundled_packages, - stub_package_name, + stub_distribution_name, ) from mypy.types import Type from mypy.typestate import reset_global_state, type_state @@ -2664,14 +2663,15 @@ def find_module_and_diagnose( # search path or the module has not been installed. ignore_missing_imports = options.ignore_missing_imports - top_level, second_level = get_top_two_prefixes(id) + + id_components = id.split(".") # Don't honor a global (not per-module) ignore_missing_imports # setting for modules that used to have bundled stubs, as # otherwise updating mypy can silently result in new false # negatives. (Unless there are stubs but they are incomplete.) global_ignore_missing_imports = manager.options.ignore_missing_imports if ( - (is_legacy_bundled_package(top_level) or is_legacy_bundled_package(second_level)) + any(".".join(id_components[:i]) in legacy_bundled_packages for i in range(len(id_components), 0, -1)) and global_ignore_missing_imports and not options.ignore_missing_imports_per_module and result is ModuleNotFoundReason.APPROVED_STUBS_NOT_INSTALLED @@ -2780,15 +2780,19 @@ def module_not_found( daemon = manager.options.fine_grained_incremental msg, notes = reason.error_message_templates(daemon) errors.report(line, 0, msg.format(module=target), code=codes.IMPORT) - top_level, second_level = get_top_two_prefixes(target) - if second_level in legacy_bundled_packages or second_level in non_bundled_packages: - top_level = second_level + + components = target.split(".") + for i in range(len(components), 0, -1): + module = ".".join(components[:i]) + if module in legacy_bundled_packages or module in non_bundled_packages: + break + for note in notes: if "{stub_dist}" in note: - note = note.format(stub_dist=stub_package_name(top_level)) + note = note.format(stub_dist=stub_distribution_name(module)) errors.report(line, 0, note, severity="note", only_once=True, code=codes.IMPORT) if reason is ModuleNotFoundReason.APPROVED_STUBS_NOT_INSTALLED: - manager.missing_stub_packages.add(stub_package_name(top_level)) + manager.missing_stub_packages.add(stub_distribution_name(module)) errors.set_import_context(save_import_context) diff --git a/mypy/modulefinder.py b/mypy/modulefinder.py index e0406bffcc7b..155df441dd64 100644 --- a/mypy/modulefinder.py +++ b/mypy/modulefinder.py @@ -337,14 +337,9 @@ def _find_module_non_stub_helper( # If this is not a directory then we can't traverse further into it if not self.fscache.isdir(dir_path): break - if approved_stub_package_exists(components[0]): - if len(components) == 1 or ( - self.find_module(components[0]) - is ModuleNotFoundReason.APPROVED_STUBS_NOT_INSTALLED - ): + for i in range(len(components), 0, -1): + if approved_stub_package_exists(".".join(components[:i])): return ModuleNotFoundReason.APPROVED_STUBS_NOT_INSTALLED - if approved_stub_package_exists(".".join(components[:2])): - return ModuleNotFoundReason.APPROVED_STUBS_NOT_INSTALLED if plausible_match: return ModuleNotFoundReason.FOUND_WITHOUT_TYPE_HINTS else: diff --git a/mypy/stubinfo.py b/mypy/stubinfo.py index e6e549ad280f..7bf32d3b0180 100644 --- a/mypy/stubinfo.py +++ b/mypy/stubinfo.py @@ -9,7 +9,7 @@ def approved_stub_package_exists(prefix: str) -> bool: return is_legacy_bundled_package(prefix) or prefix in non_bundled_packages -def stub_package_name(prefix: str) -> str: +def stub_distribution_name(prefix: str) -> str: return legacy_bundled_packages.get(prefix) or non_bundled_packages[prefix] @@ -116,7 +116,7 @@ def stub_package_name(prefix: str) -> str: "flask_sqlalchemy": "types-Flask-SQLAlchemy", "fpdf": "types-fpdf2", "gdb": "types-gdb", - "google.cloud": "types-google-cloud-ndb", + "google.cloud.ndb": "types-google-cloud-ndb", "hdbcli": "types-hdbcli", "html5lib": "types-html5lib", "httplib2": "types-httplib2", diff --git a/mypy/util.py b/mypy/util.py index 2c225c7fe651..1fd2b2e71f7c 100644 --- a/mypy/util.py +++ b/mypy/util.py @@ -308,17 +308,6 @@ def get_prefix(fullname: str) -> str: return fullname.rsplit(".", 1)[0] -def get_top_two_prefixes(fullname: str) -> tuple[str, str]: - """Return one and two component prefixes of a fully qualified name. - - Given 'a.b.c.d', return ('a', 'a.b'). - - If fullname has only one component, return (fullname, fullname). - """ - components = fullname.split(".", 3) - return components[0], ".".join(components[:2]) - - def correct_relative_import( cur_mod_id: str, relative: int, target: str, is_cur_package_init_file: bool ) -> tuple[str, bool]: diff --git a/test-data/unit/check-modules.test b/test-data/unit/check-modules.test index 8a30237843a5..65a142e50c54 100644 --- a/test-data/unit/check-modules.test +++ b/test-data/unit/check-modules.test @@ -3134,17 +3134,17 @@ import google.cloud from google.cloud import x [case testErrorFromGoogleCloud] -import google.cloud +import google.cloud # E: Cannot find implementation or library stub for module named "google.cloud" \ + # E: Cannot find implementation or library stub for module named "google" from google.cloud import x -import google.non_existent +import google.non_existent # E: Cannot find implementation or library stub for module named "google.non_existent" from google.non_existent import x -[out] -main:1: error: Library stubs not installed for "google.cloud" -main:1: note: Hint: "python3 -m pip install types-google-cloud-ndb" -main:1: note: (or run "mypy --install-types" to install all missing stub packages) -main:1: note: See https://mypy.readthedocs.io/en/stable/running_mypy.html#missing-imports -main:1: error: Cannot find implementation or library stub for module named "google" -main:3: error: Cannot find implementation or library stub for module named "google.non_existent" + +import google.cloud.ndb # E: Library stubs not installed for "google.cloud.ndb" \ + # N: Hint: "python3 -m pip install types-google-cloud-ndb" \ + # N: (or run "mypy --install-types" to install all missing stub packages) \ + # N: See https://mypy.readthedocs.io/en/stable/running_mypy.html#missing-imports +from google.cloud import ndb [case testMissingSubmoduleOfInstalledStubPackage] import bleach.xyz From 47b820c5e18caccadd44b6a114fccb179be1fddf Mon Sep 17 00:00:00 2001 From: hauntsaninja Date: Thu, 10 Aug 2023 02:17:05 -0700 Subject: [PATCH 2/4] remove comment --- mypy/stubinfo.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/mypy/stubinfo.py b/mypy/stubinfo.py index 7bf32d3b0180..0d76a6215238 100644 --- a/mypy/stubinfo.py +++ b/mypy/stubinfo.py @@ -16,8 +16,6 @@ def stub_distribution_name(prefix: str) -> str: # Stubs for these third-party packages used to be shipped with mypy. # # Map package name to PyPI stub distribution name. -# -# Package name can have one or two components ('a' or 'a.b'). legacy_bundled_packages = { "aiofiles": "types-aiofiles", "bleach": "types-bleach", From 927333d767a50c3ee6d616de60cee03d92d03938 Mon Sep 17 00:00:00 2001 From: hauntsaninja Date: Thu, 10 Aug 2023 02:20:24 -0700 Subject: [PATCH 3/4] . --- mypy/build.py | 6 ++++-- test-data/unit/check-modules.test | 6 ++++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/mypy/build.py b/mypy/build.py index 5d05cdbd14a1..9d0e60631909 100644 --- a/mypy/build.py +++ b/mypy/build.py @@ -91,7 +91,6 @@ from mypy.renaming import LimitedVariableRenameVisitor, VariableRenameVisitor from mypy.stats import dump_type_stats from mypy.stubinfo import ( - is_legacy_bundled_package, legacy_bundled_packages, non_bundled_packages, stub_distribution_name, @@ -2672,7 +2671,10 @@ def find_module_and_diagnose( # negatives. (Unless there are stubs but they are incomplete.) global_ignore_missing_imports = manager.options.ignore_missing_imports if ( - any(".".join(id_components[:i]) in legacy_bundled_packages for i in range(len(id_components), 0, -1)) + any( + ".".join(id_components[:i]) in legacy_bundled_packages + for i in range(len(id_components), 0, -1) + ) and global_ignore_missing_imports and not options.ignore_missing_imports_per_module and result is ModuleNotFoundReason.APPROVED_STUBS_NOT_INSTALLED diff --git a/test-data/unit/check-modules.test b/test-data/unit/check-modules.test index 36e15ce18e83..3da5996ed274 100644 --- a/test-data/unit/check-modules.test +++ b/test-data/unit/check-modules.test @@ -3138,9 +3138,11 @@ import bleach.xyz from bleach.abc import fgh [file bleach/__init__.pyi] [out] -main:1: error: Cannot find implementation or library stub for module named "bleach.xyz" +main:1: error: Library stubs not installed for "bleach.xyz" +main:1: note: Hint: "python3 -m pip install types-bleach" +main:1: note: (or run "mypy --install-types" to install all missing stub packages) main:1: note: See https://mypy.readthedocs.io/en/stable/running_mypy.html#missing-imports -main:2: error: Cannot find implementation or library stub for module named "bleach.abc" +main:2: error: Library stubs not installed for "bleach.abc" [case testMissingSubmoduleOfInstalledStubPackageIgnored] # flags: --ignore-missing-imports From ef17fd25d53a43893c9d491db1d8462c2b875b1f Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 10 Aug 2023 09:21:04 +0000 Subject: [PATCH 4/4] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- mypy/build.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/mypy/build.py b/mypy/build.py index 9d0e60631909..525d5f436e7e 100644 --- a/mypy/build.py +++ b/mypy/build.py @@ -90,11 +90,7 @@ from mypy.plugins.default import DefaultPlugin from mypy.renaming import LimitedVariableRenameVisitor, VariableRenameVisitor from mypy.stats import dump_type_stats -from mypy.stubinfo import ( - legacy_bundled_packages, - non_bundled_packages, - stub_distribution_name, -) +from mypy.stubinfo import legacy_bundled_packages, non_bundled_packages, stub_distribution_name from mypy.types import Type from mypy.typestate import reset_global_state, type_state from mypy.version import __version__