mirror of
https://github.com/davidhalter/typeshed.git
synced 2026-05-08 22:36:18 +08:00
Remove support for pytype (#14463)
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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"]
|
||||
}
|
||||
]
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -23,7 +23,6 @@ exclude = [
|
||||
# cache directories, etc.:
|
||||
".git",
|
||||
".mypy_cache",
|
||||
".pytype",
|
||||
]
|
||||
|
||||
[tool.ruff.lint]
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
@@ -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()
|
||||
@@ -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:
|
||||
|
||||
Reference in New Issue
Block a user