diff --git a/mypy/build.py b/mypy/build.py index cbf7b0b6f5b5..bc5fc822df5c 100644 --- a/mypy/build.py +++ b/mypy/build.py @@ -35,7 +35,7 @@ from mypy.errors import Errors, CompileError, ErrorInfo, report_internal_error from mypy.util import ( DecodeError, decode_python_encoding, is_sub_path, get_mypy_comments, module_prefix, - read_py_file, hash_digest, is_typeshed_file, is_stub_package_file + read_py_file, hash_digest, is_typeshed_file, is_stub_package_file, get_top_two_prefixes ) if TYPE_CHECKING: from mypy.report import Reports # Avoid unconditional slow import @@ -2443,13 +2443,13 @@ def find_module_and_diagnose(manager: BuildManager, # search path or the module has not been installed. ignore_missing_imports = options.ignore_missing_imports - top_level = file_id.partition('.')[0] + top_level, second_level = get_top_two_prefixes(file_id) # 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. global_ignore_missing_imports = manager.options.ignore_missing_imports - if (top_level in legacy_bundled_packages + if ((top_level in legacy_bundled_packages or second_level in legacy_bundled_packages) and global_ignore_missing_imports and not options.ignore_missing_imports_per_module): ignore_missing_imports = False @@ -2553,7 +2553,9 @@ def module_not_found(manager: BuildManager, line: int, caller_state: State, msg, notes = reason.error_message_templates(daemon) pyver = '%d.%d' % manager.options.python_version errors.report(line, 0, msg.format(module=target, pyver=pyver), code=codes.IMPORT) - top_level = target.partition('.')[0] + top_level, second_level = get_top_two_prefixes(target) + if second_level in legacy_bundled_packages: + top_level = second_level for note in notes: if '{stub_dist}' in note: note = note.format(stub_dist=legacy_bundled_packages[top_level]) diff --git a/mypy/modulefinder.py b/mypy/modulefinder.py index 41e60cedb5cd..f9450c562ee3 100644 --- a/mypy/modulefinder.py +++ b/mypy/modulefinder.py @@ -230,7 +230,8 @@ def _find_module_non_stub_helper(self, components: List[str], elif not plausible_match and (self.fscache.isdir(dir_path) or self.fscache.isfile(dir_path + ".py")): plausible_match = True - if components[0] in legacy_bundled_packages: + if (components[0] in legacy_bundled_packages + or '.'.join(components[:2]) in legacy_bundled_packages): return ModuleNotFoundReason.APPROVED_STUBS_NOT_INSTALLED elif plausible_match: return ModuleNotFoundReason.FOUND_WITHOUT_TYPE_HINTS diff --git a/mypy/stubinfo.py b/mypy/stubinfo.py index 284dcaf16bf5..06be24541ed1 100644 --- a/mypy/stubinfo.py +++ b/mypy/stubinfo.py @@ -1,6 +1,8 @@ # 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', 'atomicwrites': 'types-atomicwrites', @@ -36,7 +38,7 @@ 'frozendict': 'types-frozendict', 'geoip2': 'types-geoip2', 'gflags': 'types-python-gflags', - 'google': 'types-protobuf', + 'google.protobuf': 'types-protobuf', 'ipaddress': 'types-ipaddress', 'itsdangerous': 'types-itsdangerous', 'jinja2': 'types-Jinja2', diff --git a/mypy/util.py b/mypy/util.py index e34dffcd3ab0..79475972a57a 100644 --- a/mypy/util.py +++ b/mypy/util.py @@ -284,6 +284,17 @@ 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, diff --git a/test-data/unit/check-modules.test b/test-data/unit/check-modules.test index 643488ec4158..9a68b00b6007 100644 --- a/test-data/unit/check-modules.test +++ b/test-data/unit/check-modules.test @@ -3094,3 +3094,16 @@ ignore_missing_imports = true \[[tool.mypy.overrides]] module = "foobar1" ignore_missing_imports = true + +[case testIgnoreErrorFromGoogleCloud] +# flags: --ignore-missing-imports +import google.cloud +from google.cloud import x + +[case testErrorFromGoogleCloud] +import google.cloud +from google.cloud import x +[out] +main:1: error: Cannot find implementation or library stub for module named "google.cloud" +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"