diff --git a/mypy/modulefinder.py b/mypy/modulefinder.py index ad856aef2714..40f2db91adf1 100644 --- a/mypy/modulefinder.py +++ b/mypy/modulefinder.py @@ -198,9 +198,7 @@ def find_module(self, id: str, *, fast_path: bool = False) -> ModuleSearchResult top_level = id.partition('.')[0] use_typeshed = True if top_level in self.stdlib_py_versions: - min_version = self.stdlib_py_versions[top_level] - use_typeshed = (self.options is None - or typeshed_py_version(self.options) >= min_version) + use_typeshed = self._typeshed_has_version(top_level) self.results[id] = self._find_module(id, use_typeshed) if (not fast_path and self.results[id] is ModuleNotFoundReason.NOT_FOUND @@ -208,6 +206,13 @@ def find_module(self, id: str, *, fast_path: bool = False) -> ModuleSearchResult self.results[id] = ModuleNotFoundReason.WRONG_WORKING_DIRECTORY return self.results[id] + def _typeshed_has_version(self, module: str) -> bool: + if not self.options: + return True + version = typeshed_py_version(self.options) + min_version, max_version = self.stdlib_py_versions[module] + return version >= min_version and (max_version is None or version <= max_version) + def _find_module_non_stub_helper(self, components: List[str], pkg_dir: str) -> Union[OnePackageDir, ModuleNotFoundReason]: plausible_match = False @@ -735,10 +740,14 @@ def compute_search_paths(sources: List[BuildSource], typeshed_path=tuple(lib_path)) -def load_stdlib_py_versions(custom_typeshed_dir: Optional[str]) -> Dict[str, Tuple[int, int]]: - """Return dict with minimum Python versions of stdlib modules. +def load_stdlib_py_versions(custom_typeshed_dir: Optional[str] + ) -> Dict[str, Tuple[Tuple[int, int], Optional[Tuple[int, int]]]]: + """Return dict with minimum and maximum Python versions of stdlib modules. + + The contents look like + {..., 'secrets': ((3, 6), None), 'symbol': (2, 7), (3, 9)), ...} - The contents look like {..., 'secrets': 3.6, 're': 2.7, ...}. + None means there is no maximum version. """ typeshed_dir = custom_typeshed_dir or os.path.join(os.path.dirname(__file__), "typeshed") stdlib_dir = os.path.join(typeshed_dir, "stdlib") @@ -751,20 +760,29 @@ def load_stdlib_py_versions(custom_typeshed_dir: Optional[str]) -> Dict[str, Tup line = line.strip() if line.startswith("#") or line == "": continue - module, version = line.split(":") - major, minor = version.strip().split(".") - result[module] = int(major), int(minor) + module, version_range = line.split(":") + versions = version_range.split("-") + min_version = parse_version(versions[0]) + max_version = (parse_version(versions[1]) + if len(versions) >= 2 and versions[1].strip() else None) + result[module] = min_version, max_version # Modules that are Python 2 only or have separate Python 2 stubs # have stubs in @python2/ and may need an override. python2_dir = os.path.join(stdlib_dir, PYTHON2_STUB_DIR) for fnam in os.listdir(python2_dir): fnam = fnam.replace(".pyi", "") - result[fnam] = (2, 7) + max_version = result.get(fnam, ((2, 7), None))[1] + result[fnam] = (2, 7), max_version return result +def parse_version(version: str) -> Tuple[int, int]: + major, minor = version.strip().split(".") + return int(major), int(minor) + + def typeshed_py_version(options: Options) -> Tuple[int, int]: """Return Python version used for checking whether module supports typeshed.""" # Typeshed no longer covers Python 3.x versions before 3.6, so 3.6 is diff --git a/mypy/stubtest.py b/mypy/stubtest.py index db8666c94e3e..7915e14f1551 100644 --- a/mypy/stubtest.py +++ b/mypy/stubtest.py @@ -1074,8 +1074,9 @@ def get_typeshed_stdlib_modules(custom_typeshed_dir: Optional[str]) -> List[str] version_info = (3, 6) else: version_info = sys.version_info[0:2] - for module, minver in stdlib_py_versions.items(): - if version_info >= minver: + for module, versions in stdlib_py_versions.items(): + minver, maxver = versions + if version_info >= minver and (maxver is None or version_info <= maxver): packages.add(module) if custom_typeshed_dir: