|
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