Remove support for pytype (#14463)

This commit is contained in:
Sebastian Rittau
2025-07-27 11:47:37 +02:00
committed by GitHub
parent df8de9e5ac
commit aebefb1754
12 changed files with 9 additions and 356 deletions
-1
View File
@@ -4,6 +4,5 @@
# Set linguist-language to support comments syntax highlight
**/stubtest_allowlist*.txt linguist-language=ini
**/stubtest_allowlists/*.txt linguist-language=ini
tests/pytype_exclude_list.txt linguist-language=ini
pyrightconfig*.json linguist-language=jsonc
.vscode/*.json linguist-language=jsonc
+4 -4
View File
@@ -24,15 +24,15 @@
{
groupName: "most test/lint dependencies",
matchManagers: ["pip_requirements", "pre-commit"],
matchPackageNames: ["!pytype", "!pyright"],
matchPackageNames: ["!pyright"],
description: "Quarterly update of most test dependencies",
schedule: ["every 3 months on the first day of the month"]
},
{
"groupName": "pytype and pyright",
"groupName": "pyright",
"matchManagers": ["pip_requirements"],
"matchPackageNames": ["pytype", "pyright"],
"description": "Daily update of pyright and pytype",
"matchPackageNames": ["pyright"],
"description": "Daily update of pyright",
"schedule": ["before 4am"]
}
]
-23
View File
@@ -34,29 +34,6 @@ jobs:
- run: uv pip install -r requirements-tests.txt --system
- run: python ./tests/check_typeshed_structure.py
pytype:
name: "pytype: Check stubs"
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
# Max supported Python version as of pytype 2024.10.11
python-version: "3.12"
- uses: astral-sh/setup-uv@v6
with:
version-file: "requirements-tests.txt"
- run: uv pip install -r requirements-tests.txt --system
- name: Install external dependencies for 3rd-party stubs
run: |
DEPENDENCIES=$( python tests/get_external_stub_requirements.py )
if [ -n "$DEPENDENCIES" ]; then
printf "Installing packages:\n $(echo $DEPENDENCIES | sed 's/ /\n /g')\n"
uv pip install --system $DEPENDENCIES
fi
- run: uv pip freeze
- run: ./tests/pytype_test.py --print-stderr
mypy:
name: "mypy: Check stubs"
runs-on: ubuntu-latest
-11
View File
@@ -83,13 +83,6 @@ Note that some tests require extra setup steps to install the required dependenc
(.venv) > pip install -r requirements-tests.txt
```
To be able to run pytype tests, you'll also need to install it manually
as it's currently excluded from the requirements file:
```powershell
(.venv) > pip install -U pytype
```
</td>
</tr>
<tr><!-- disables zebra striping --></tr>
@@ -104,10 +97,6 @@ as it's currently excluded from the requirements file:
uv pip install -r requirements-tests.txt
```
```shell
uv pip install -U pytype
```
</td>
</tr>
</table>
+3 -3
View File
@@ -25,9 +25,9 @@ Typeshed supports Python versions 3.9 to 3.14.
## Using
If you're just using a type checker ([mypy](https://github.com/python/mypy/),
[pyright](https://github.com/microsoft/pyright),
[pytype](https://github.com/google/pytype/), PyCharm, ...), as opposed to
If you're just using a type checker (e.g. [mypy](https://github.com/python/mypy/),
[pyright](https://github.com/microsoft/pyright), or PyCharm's built-in type
checker), as opposed to
developing it, you don't need to interact with the typeshed repo at
all: a copy of standard library part of typeshed is bundled with type checkers.
And type stubs for third party packages and modules you are using can
-1
View File
@@ -23,7 +23,6 @@ exclude = [
# cache directories, etc.:
".git",
".mypy_cache",
".pytype",
]
[tool.ruff.lint]
-2
View File
@@ -2,8 +2,6 @@
# be pinned to a specific version to make failure reproducible.
mypy==1.16.1
pyright==1.1.403
# pytype can be installed on Windows, but requires building wheels, let's not do that on the CI
pytype==2024.10.11; platform_system != "Windows" and python_version >= "3.10" and python_version < "3.13"
# Libraries used by our various scripts.
# TODO (2025-05-09): Installing this on Python 3.14 on Windows fails at
+1 -15
View File
@@ -1,8 +1,6 @@
This directory contains several tests:
- `tests/mypy_test.py`
tests the stubs with [mypy](https://github.com/python/mypy/)
- `tests/pytype_test.py` tests the stubs with
[pytype](https://github.com/google/pytype/).
- `tests/pyright_test.py` tests the stubs with
[pyright](https://github.com/microsoft/pyright).
- `tests/regr_test.py` runs mypy against the test cases for typeshed's
@@ -19,7 +17,7 @@ in the `tests` and `scripts` directories.
To run the tests, follow the [setup instructions](../CONTRIBUTING.md#preparing-the-environment)
in the `CONTRIBUTING.md` document. In particular, you have to run with Python 3.9+.
In order for `pytype_test` and `pyright_test` to work correctly, some third-party stubs
In order for `pyright_test` to work correctly, some third-party stubs
may require extra dependencies external to typeshed to be installed in your virtual environment
prior to running the test.
You can list or install all of a stubs package's external dependencies using the following script:
@@ -67,18 +65,6 @@ imported but doesn't check whether stubs match their implementation
Run `python tests/mypy_test.py --help` for information on the various configuration options
for this script.
## pytype\_test.py
Note: This test cannot be run on Python version < 3.13 as pytype does not yet support
Python 3.13 and above.
Run using:
```bash
(.venv3)$ python3 tests/pytype_test.py
```
This test works similarly to `mypy_test.py`, except it uses `pytype`.
## pyright\_test.py
This test requires [Node.js](https://nodejs.org) to be installed. Although
+1 -1
View File
@@ -25,7 +25,7 @@ extension_descriptions = {".pyi": "stub", ".py": ".py"}
# These type checkers and linters must have exact versions in the requirements file to ensure
# consistent CI runs.
linters = {"mypy", "pyright", "pytype", "ruff"}
linters = {"mypy", "pyright", "ruff"}
ALLOWED_PY_FILES_IN_TESTS_DIR = {
"django_settings.py" # This file contains Django settings used by the mypy_django_plugin during stubtest execution.
-28
View File
@@ -1,28 +0,0 @@
# Pytype exclude list. Files will not be tested with pytype.
# pytype has its own version of these files, and thus doesn't mind if it
# can't parse the typeshed version:
stdlib/builtins.pyi
stdlib/typing.pyi
# can't subscript memoryview for some reason
stdlib/asyncio/sslproto.pyi
stdlib/asyncio/transports.pyi
# errors about import statements
stubs/mysqlclient/MySQLdb/__init__.pyi
stubs/mysqlclient/MySQLdb/connections.pyi
stubs/mysqlclient/MySQLdb/cursors.pyi
# twisted not installed during tests
stubs/pika/pika/adapters/twisted_connection.pyi
# pytype.pytd.visitors.ContainerError: Conflicting values for TypeVar
stubs/openpyxl/openpyxl/descriptors/nested.pyi
# matplotlib not installed during tests
stubs/shapely/shapely/plotting.pyi
# matplotlib et al not installed during tests
stubs/geopandas/geopandas/plotting.pyi
stubs/geopandas/geopandas/explore.pyi
-249
View File
@@ -1,249 +0,0 @@
#!/usr/bin/env python3
# Lack of pytype typing
# pyright: reportUnknownVariableType=false, reportUnknownMemberType=false, reportUnknownArgumentType=false, reportMissingTypeStubs=false
"""Test runner for typeshed.
Depends on pytype being installed.
If pytype is installed:
1. For every pyi, do nothing if it is in pytype_exclude_list.txt.
2. Otherwise, call 'pytype.io.parse_pyi'.
Option two will load the file and all the builtins, typeshed dependencies. This
will also discover incorrect usage of imported modules.
"""
from __future__ import annotations
import sys
from typing import TYPE_CHECKING
if TYPE_CHECKING:
assert sys.platform != "win32", "pytype isn't yet installed in CI, but wheels can be built on Windows"
from _typeshed import StrPath
if sys.version_info >= (3, 13):
print("pytype does not support Python 3.13+ yet.", file=sys.stderr)
sys.exit(1)
import argparse
import importlib.metadata
import inspect
import os
import traceback
from collections.abc import Iterable, Sequence
from pathlib import Path
# pytype is not py.typed https://github.com/google/pytype/issues/1325
from pytype import config as pytype_config, load_pytd # type: ignore[import]
from pytype.imports import typeshed # type: ignore[import]
from ts_utils.metadata import read_dependencies
from ts_utils.paths import STDLIB_PATH, STUBS_PATH, TS_BASE_PATH
from ts_utils.utils import SupportedVersionsDict, parse_stdlib_versions_file, supported_versions_for_module
TYPESHED_SUBDIRS = [STDLIB_PATH.absolute(), STUBS_PATH.absolute()]
TYPESHED_HOME = "TYPESHED_HOME"
EXCLUDE_LIST = TS_BASE_PATH / "tests" / "pytype_exclude_list.txt"
_LOADERS: dict[str, tuple[pytype_config.Options, load_pytd.Loader]] = {}
def main() -> None:
args = create_parser().parse_args()
typeshed_location = Path(args.typeshed_location) or Path.cwd()
check_subdirs_discoverable(TYPESHED_SUBDIRS)
old_typeshed_home = os.environ.get(TYPESHED_HOME)
os.environ[TYPESHED_HOME] = str(typeshed_location)
files_to_test = determine_files_to_test(paths=[Path(file) for file in args.files] or TYPESHED_SUBDIRS)
run_all_tests(files_to_test=files_to_test, print_stderr=args.print_stderr, dry_run=args.dry_run)
if old_typeshed_home is None:
del os.environ[TYPESHED_HOME]
else:
os.environ[TYPESHED_HOME] = old_typeshed_home
def create_parser() -> argparse.ArgumentParser:
parser = argparse.ArgumentParser(description="Pytype/typeshed tests.")
parser.add_argument("-n", "--dry-run", action="store_true", default=False, help="Don't actually run tests")
# Default to '' so that symlinking typeshed subdirs in cwd will work.
parser.add_argument("--typeshed-location", type=str, default="", help="Path to typeshed installation.")
# Set to true to print a stack trace every time an exception is thrown.
parser.add_argument(
"--print-stderr", action="store_true", default=False, help="Print stderr every time an error is encountered."
)
parser.add_argument(
"files", metavar="FILE", type=str, nargs="*", help="Files or directories to check. (Default: Check all files.)"
)
return parser
def run_pytype(*, filename: StrPath, python_version: str, missing_modules: Iterable[str]) -> str | None:
"""Run pytype, returning the stderr if any."""
if python_version not in _LOADERS:
options = pytype_config.Options.create("", parse_pyi=True, python_version=python_version)
# For simplicity, pretends missing modules are part of the stdlib.
missing_modules = tuple(str(STDLIB_PATH / m) for m in missing_modules)
loader = load_pytd.create_loader(options, missing_modules)
_LOADERS[python_version] = (options, loader)
options, loader = _LOADERS[python_version]
stderr: str | None
try:
with pytype_config.verbosity_from(options):
ast = loader.load_file(_get_module_name(filename), filename)
loader.finish_and_verify_ast(ast)
except Exception:
stderr = traceback.format_exc()
else:
stderr = None
return stderr
def _get_relative(filename: StrPath) -> Path:
filepath = Path(filename)
for d in TYPESHED_SUBDIRS:
try:
return filepath.absolute().relative_to(d.parent)
except ValueError:
continue
raise ValueError(f"{filepath} not relative to {TYPESHED_SUBDIRS}")
def _get_module_name(filename: StrPath) -> str:
"""Convert a filename {subdir}/m.n/module/foo to module.foo."""
parts = _get_relative(filename).parts
if parts[0] == "stdlib":
module_parts = parts[1:]
else:
assert parts[0] == "stubs"
module_parts = parts[2:]
return ".".join(module_parts).replace(".pyi", "").replace(".__init__", "")
def check_subdirs_discoverable(subdir_paths: Iterable[Path]) -> None:
for p in subdir_paths:
if not p.is_dir():
raise SystemExit(f"Cannot find typeshed subdir at {p} (specify parent dir via --typeshed-location)")
def determine_files_to_test(*, paths: Iterable[Path]) -> list[Path]:
"""Determine all files to test.
Checks for files in the pytype exclude list and for the stdlib VERSIONS file.
"""
filenames = find_stubs_in_paths(paths)
ts = typeshed.Typeshed()
exclude_list = set(ts.read_blacklist())
stdlib_module_versions = parse_stdlib_versions_file()
return [
f
for f in sorted(filenames)
if _get_relative(f).as_posix() not in exclude_list and _is_supported_stdlib_version(stdlib_module_versions, f)
]
def find_stubs_in_paths(paths: Iterable[Path]) -> list[Path]:
filenames: list[Path] = []
for path in paths:
if path.is_dir():
for root, _, fns in os.walk(path):
filenames.extend(Path(root, fn) for fn in fns if fn.endswith(".pyi"))
else:
filenames.append(path)
return filenames
def _is_supported_stdlib_version(module_versions: SupportedVersionsDict, filename: StrPath) -> bool:
parts = _get_relative(filename).parts
if parts[0] != "stdlib":
return True
module_name = _get_module_name(filename)
min_version, max_version = supported_versions_for_module(module_versions, module_name)
return min_version <= sys.version_info <= max_version
def _get_pkgs_associated_with_requirement(req_name: str) -> list[str]:
try:
dist = importlib.metadata.distribution(req_name)
except importlib.metadata.PackageNotFoundError:
# The package wasn't installed, probably because an environment
# marker excluded it.
return []
toplevel_txt_contents = dist.read_text("top_level.txt")
if toplevel_txt_contents is None:
if dist.files is None:
raise RuntimeError(f"Can't read find the packages associated with requirement {req_name!r}")
maybe_modules = [f.parts[0] if len(f.parts) > 1 else inspect.getmodulename(f) for f in dist.files]
packages = [name for name in maybe_modules if name is not None and "." not in name]
else:
packages = toplevel_txt_contents.split()
# https://peps.python.org/pep-0561/#stub-only-packages
return sorted({package.removesuffix("-stubs") for package in packages})
def get_missing_modules(files_to_test: Iterable[Path]) -> Iterable[str]:
"""Get names of modules that should be treated as missing.
Some typeshed stubs depend on dependencies outside of typeshed. Since pytype
isn't able to read such dependencies, we instead declare them as "missing"
modules, so that no errors are reported for them.
Similarly, pytype cannot parse files on its exclude list, so we also treat
those as missing.
"""
stub_distributions = set[str]()
for fi in files_to_test:
parts = fi.parts
try:
idx = parts.index("stubs")
except ValueError:
continue
stub_distributions.add(parts[idx + 1])
missing_modules = {
associated_package
for distribution in stub_distributions
for external_req in read_dependencies(distribution).external_pkgs
for associated_package in _get_pkgs_associated_with_requirement(external_req.name)
}
with EXCLUDE_LIST.open() as f:
for line in f:
if not line.startswith("stubs/"):
# Skips comments, empty lines, and stdlib files, which are in
# the exclude list because pytype has its own version.
continue
_ts_subdir, _distribution, module_path = line.strip().split("/", 2)
missing_modules.add(module_path.removesuffix(".pyi"))
return missing_modules
def run_all_tests(*, files_to_test: Sequence[Path], print_stderr: bool, dry_run: bool) -> None:
bad: list[tuple[StrPath, str, str]] = []
errors = 0
total_tests = len(files_to_test)
missing_modules = get_missing_modules(files_to_test)
python_version = f"{sys.version_info.major}.{sys.version_info.minor}"
print("Testing files with pytype...")
for i, file_to_test in enumerate(files_to_test):
if dry_run:
stderr = None
else:
stderr = run_pytype(filename=file_to_test, python_version=python_version, missing_modules=missing_modules)
if stderr:
if print_stderr:
print(f"\n{stderr}")
errors += 1
stacktrace_final_line = stderr.rstrip().rsplit("\n", 1)[-1]
bad.append((_get_relative(file_to_test), python_version, stacktrace_final_line))
runs = i + 1
if runs % 25 == 0:
print(f" {runs:3d}/{total_tests:d} with {errors:3d} errors")
print(f"Ran pytype with {total_tests:d} pyis, got {errors:d} errors.")
for f, v, err in bad:
print(f"\n{f} ({v}): {err}")
if errors:
raise SystemExit("\nRun again with --print-stderr to get the full stacktrace.")
if __name__ == "__main__":
main()
-18
View File
@@ -6,7 +6,6 @@ import json
import re
import subprocess
import sys
from importlib.util import find_spec
from pathlib import Path
from ts_utils.metadata import get_oldest_supported_python, read_metadata
@@ -77,7 +76,6 @@ def main() -> None:
python_version = get_oldest_supported_python()
stubtest_result: subprocess.CompletedProcess[bytes] | None = None
pytype_result: subprocess.CompletedProcess[bytes] | None = None
print("\nRunning pre-commit...")
pre_commit_result = subprocess.run(["pre-commit", "run", "--files", *path.rglob("*")], check=False)
@@ -125,17 +123,6 @@ def main() -> None:
else:
print(colored("\nSkipping stubtest since mypy failed.", "yellow"))
if find_spec("pytype"):
print("\nRunning pytype...")
pytype_result = subprocess.run([sys.executable, "tests/pytype_test.py", path], check=False)
else:
print(
colored(
f"\nSkipping pytype on Windows. You need to install it first: `{sys.executable} -m pip install pytype` .",
"yellow",
)
)
cases_path = test_cases_path(stub if folder == "stubs" else "stdlib")
if not cases_path.exists():
# No test means they all ran successfully (0 out of 0). Not all 3rd-party stubs have regression tests.
@@ -186,7 +173,6 @@ def main() -> None:
pyright_returncode,
mypy_result.returncode,
getattr(stubtest_result, "returncode", 0),
getattr(pytype_result, "returncode", 0),
pyright_testcases_returncode,
regr_test_returncode,
]
@@ -218,10 +204,6 @@ def main() -> None:
print("stubtest:", _SKIPPED)
else:
print("stubtest:", _SUCCESS if stubtest_result.returncode == 0 else _FAILED)
if not pytype_result:
print("pytype:", _SKIPPED)
else:
print("pytype:", _SUCCESS if pytype_result.returncode == 0 else _FAILED)
if pyright_testcases_skipped:
print("Pyright regression tests:", _SKIPPED)
else: