|
16 | 16 | import os
|
17 | 17 | import sys
|
18 | 18 | import traceback
|
19 |
| -from collections.abc import Sequence |
| 19 | +from collections.abc import Iterable, Sequence |
20 | 20 |
|
| 21 | +import pkg_resources |
| 22 | +import utils |
21 | 23 | from pytype import config as pytype_config, load_pytd # type: ignore[import]
|
22 | 24 | from pytype.imports import typeshed # type: ignore[import]
|
23 | 25 |
|
@@ -56,11 +58,13 @@ def create_parser() -> argparse.ArgumentParser:
|
56 | 58 | return parser
|
57 | 59 |
|
58 | 60 |
|
59 |
| -def run_pytype(*, filename: str, python_version: str) -> str | None: |
| 61 | +def run_pytype(*, filename: str, python_version: str, missing_modules: Iterable[str]) -> str | None: |
60 | 62 | """Runs pytype, returning the stderr if any."""
|
61 | 63 | if python_version not in _LOADERS:
|
62 | 64 | options = pytype_config.Options.create("", parse_pyi=True, python_version=python_version)
|
63 |
| - loader = load_pytd.create_loader(options) |
| 65 | + # For simplicity, pretends missing modules are part of the stdlib. |
| 66 | + missing_modules = tuple(os.path.join("stdlib", m) for m in missing_modules) |
| 67 | + loader = load_pytd.create_loader(options, missing_modules) |
64 | 68 | _LOADERS[python_version] = (options, loader)
|
65 | 69 | options, loader = _LOADERS[python_version]
|
66 | 70 | stderr: str | None
|
@@ -131,14 +135,43 @@ def find_stubs_in_paths(paths: Sequence[str]) -> list[str]:
|
131 | 135 | return filenames
|
132 | 136 |
|
133 | 137 |
|
| 138 | +def get_missing_modules(files_to_test: Sequence[str]) -> Iterable[str]: |
| 139 | + """Gets module names provided by typeshed-external dependencies. |
| 140 | +
|
| 141 | + Some typeshed stubs depend on dependencies outside of typeshed. Since pytype |
| 142 | + isn't able to read such dependencies, we instead declare them as "missing" |
| 143 | + modules, so that no errors are reported for them. |
| 144 | + """ |
| 145 | + stub_distributions = set() |
| 146 | + for fi in files_to_test: |
| 147 | + parts = fi.split(os.sep) |
| 148 | + try: |
| 149 | + idx = parts.index("stubs") |
| 150 | + except ValueError: |
| 151 | + continue |
| 152 | + stub_distributions.add(parts[idx + 1]) |
| 153 | + missing_modules = set() |
| 154 | + for distribution in stub_distributions: |
| 155 | + for pkg in utils.read_dependencies(distribution).external_pkgs: |
| 156 | + # See https://stackoverflow.com/a/54853084 |
| 157 | + top_level_file = os.path.join(pkg_resources.get_distribution(pkg).egg_info, "top_level.txt") # type: ignore[attr-defined] |
| 158 | + with open(top_level_file) as f: |
| 159 | + missing_modules.update(f.read().splitlines()) |
| 160 | + return missing_modules |
| 161 | + |
| 162 | + |
134 | 163 | def run_all_tests(*, files_to_test: Sequence[str], print_stderr: bool, dry_run: bool) -> None:
|
135 | 164 | bad = []
|
136 | 165 | errors = 0
|
137 | 166 | total_tests = len(files_to_test)
|
| 167 | + missing_modules = get_missing_modules(files_to_test) |
138 | 168 | print("Testing files with pytype...")
|
139 | 169 | for i, f in enumerate(files_to_test):
|
140 | 170 | python_version = "{0.major}.{0.minor}".format(sys.version_info)
|
141 |
| - stderr = run_pytype(filename=f, python_version=python_version) if not dry_run else None |
| 171 | + if dry_run: |
| 172 | + stderr = None |
| 173 | + else: |
| 174 | + stderr = run_pytype(filename=f, python_version=python_version, missing_modules=missing_modules) |
142 | 175 | if stderr:
|
143 | 176 | if print_stderr:
|
144 | 177 | print(f"\n{stderr}")
|
|
0 commit comments