From 96e5c5831e955e9d618bf844ddf31058920c71ab Mon Sep 17 00:00:00 2001 From: Jukka Lehtosalo Date: Fri, 4 Jun 2021 16:51:44 +0100 Subject: [PATCH] Make ignore_missing_imports work for libs which had bundled stubs It has to be specified as a per-module option, if the library used to have bundled stubs. This way mypy won't silently start ignoring these missing stubs. This makes `ignore_missing_imports` special by making it work a bit differently, depending on whether it's set globally or per module. The new behavior is less surprising. This makes it possible to ignore arbitrary missing third-party stub packages, even those which used to have bundled stubs. Fixes #10283. --- mypy/build.py | 4 +++- mypy/options.py | 6 ++++++ test-data/unit/check-modules.test | 32 ++++++++++++++++++++++++++++++- 3 files changed, 40 insertions(+), 2 deletions(-) diff --git a/mypy/build.py b/mypy/build.py index 499f027d8fae..cbf7b0b6f5b5 100644 --- a/mypy/build.py +++ b/mypy/build.py @@ -2449,7 +2449,9 @@ def find_module_and_diagnose(manager: BuildManager, # 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 and global_ignore_missing_imports: + if (top_level in legacy_bundled_packages + and global_ignore_missing_imports + and not options.ignore_missing_imports_per_module): ignore_missing_imports = False if skip_diagnose: diff --git a/mypy/options.py b/mypy/options.py index 099d998f2e46..aa92b1cea9ff 100644 --- a/mypy/options.py +++ b/mypy/options.py @@ -82,6 +82,8 @@ def __init__(self) -> None: self.no_silence_site_packages = False self.no_site_packages = False self.ignore_missing_imports = False + # Is ignore_missing_imports set in a per-module section + self.ignore_missing_imports_per_module = False self.follow_imports = 'normal' # normal|silent|skip|error # Whether to respect the follow_imports setting even for stub files. # Intended to be used for disabling specific stubs. @@ -325,6 +327,10 @@ def apply_changes(self, changes: Dict[str, object]) -> 'Options': replace_object_state(new_options, self, copy_dict=True) for key, value in changes.items(): setattr(new_options, key, value) + if changes.get("ignore_missing_imports"): + # This is the only option for which a per-module and a global + # option sometimes beheave differently. + new_options.ignore_missing_imports_per_module = True return new_options def build_per_module_cache(self) -> None: diff --git a/test-data/unit/check-modules.test b/test-data/unit/check-modules.test index d06dfb413275..643488ec4158 100644 --- a/test-data/unit/check-modules.test +++ b/test-data/unit/check-modules.test @@ -3011,7 +3011,6 @@ certifi.x # E: Expression has type "Any" certifi.x # E: Expression has type "Any" 1() # E: "int" not callable - [case testDoNotLimitImportErrorVolume] # flags: --disallow-any-expr --soft-error-limit=3 import xyz1 # E: Cannot find implementation or library stub for module named "xyz1" \ @@ -3064,3 +3063,34 @@ certifi.x # E: Expression has type "Any" certifi.x # E: Expression has type "Any" certifi.x # E: Expression has type "Any" certifi.x # E: Expression has type "Any" + +[case testIgnoreErrorFromMissingStubs1] +# flags: --config-file tmp/pyproject.toml +import certifi +from foobar1 import x +import foobar2 +[file pyproject.toml] +\[tool.mypy] +ignore_missing_imports = true +\[[tool.mypy.overrides]] +module = "certifi" +ignore_missing_imports = true +\[[tool.mypy.overrides]] +module = "foobar1" +ignore_missing_imports = true + +[case testIgnoreErrorFromMissingStubs2] +# flags: --config-file tmp/pyproject.toml +import certifi +from foobar1 import x +import foobar2 # E: Cannot find implementation or library stub for module named "foobar2" \ + # N: See https://mypy.readthedocs.io/en/stable/running_mypy.html#missing-imports +[file pyproject.toml] +\[tool.mypy] +ignore_missing_imports = false +\[[tool.mypy.overrides]] +module = "certifi" +ignore_missing_imports = true +\[[tool.mypy.overrides]] +module = "foobar1" +ignore_missing_imports = true