mirror of
https://github.com/davidhalter/typeshed.git
synced 2025-12-21 19:32:13 +08:00
Typecheck typeshed's code with pyright (#9793)
Co-authored-by: Alex Waygood <Alex.Waygood@Gmail.com> Co-authored-by: Jelle Zijlstra <jelle.zijlstra@gmail.com>
This commit is contained in:
@@ -10,6 +10,7 @@ import re
|
||||
import sys
|
||||
import urllib.parse
|
||||
from pathlib import Path
|
||||
from typing import TypedDict
|
||||
|
||||
import yaml
|
||||
from packaging.requirements import Requirement
|
||||
@@ -93,7 +94,7 @@ def check_no_symlinks() -> None:
|
||||
|
||||
|
||||
def check_versions() -> None:
|
||||
versions = set()
|
||||
versions = set[str]()
|
||||
with open("stdlib/VERSIONS", encoding="UTF-8") as f:
|
||||
data = f.read().splitlines()
|
||||
for line in data:
|
||||
@@ -115,7 +116,7 @@ def check_versions() -> None:
|
||||
|
||||
|
||||
def _find_stdlib_modules() -> set[str]:
|
||||
modules = set()
|
||||
modules = set[str]()
|
||||
for path, _, files in os.walk("stdlib"):
|
||||
for filename in files:
|
||||
base_module = ".".join(os.path.normpath(path).split(os.sep)[1:])
|
||||
@@ -140,11 +141,21 @@ def get_txt_requirements() -> dict[str, SpecifierSet]:
|
||||
return {requirement.name: requirement.specifier for requirement in requirements}
|
||||
|
||||
|
||||
class PreCommitConfigRepos(TypedDict):
|
||||
hooks: list[dict[str, str]]
|
||||
repo: str
|
||||
rev: str
|
||||
|
||||
|
||||
class PreCommitConfig(TypedDict):
|
||||
repos: list[PreCommitConfigRepos]
|
||||
|
||||
|
||||
def get_precommit_requirements() -> dict[str, SpecifierSet]:
|
||||
with open(".pre-commit-config.yaml", encoding="UTF-8") as precommit_file:
|
||||
precommit = precommit_file.read()
|
||||
yam = yaml.load(precommit, Loader=yaml.Loader)
|
||||
precommit_requirements = {}
|
||||
yam: PreCommitConfig = yaml.load(precommit, Loader=yaml.Loader)
|
||||
precommit_requirements: dict[str, SpecifierSet] = {}
|
||||
for repo in yam["repos"]:
|
||||
if not repo.get("python_requirement", True):
|
||||
continue
|
||||
|
||||
@@ -9,7 +9,7 @@ from pathlib import Path
|
||||
|
||||
|
||||
def check_new_syntax(tree: ast.AST, path: Path, stub: str) -> list[str]:
|
||||
errors = []
|
||||
errors: list[str] = []
|
||||
|
||||
class IfFinder(ast.NodeVisitor):
|
||||
def visit_If(self, node: ast.If) -> None:
|
||||
@@ -31,7 +31,7 @@ def check_new_syntax(tree: ast.AST, path: Path, stub: str) -> list[str]:
|
||||
|
||||
|
||||
def main() -> None:
|
||||
errors = []
|
||||
errors: list[str] = []
|
||||
for path in chain(Path("stdlib").rglob("*.pyi"), Path("stubs").rglob("*.pyi")):
|
||||
with open(path, encoding="UTF-8") as f:
|
||||
stub = f.read()
|
||||
|
||||
@@ -42,7 +42,7 @@ from utils import (
|
||||
|
||||
# Fail early if mypy isn't installed
|
||||
try:
|
||||
import mypy # noqa: F401
|
||||
import mypy # pyright: ignore[reportUnusedImport] # noqa: F401
|
||||
except ImportError:
|
||||
print_error("Cannot import mypy. Did you install it?")
|
||||
sys.exit(1)
|
||||
@@ -57,7 +57,8 @@ VersionTuple: TypeAlias = Tuple[int, int]
|
||||
Platform: TypeAlias = Annotated[str, "Must be one of the entries in SUPPORTED_PLATFORMS"]
|
||||
|
||||
|
||||
class CommandLineArgs(argparse.Namespace):
|
||||
@dataclass(init=False)
|
||||
class CommandLineArgs:
|
||||
verbose: int
|
||||
filter: list[Path]
|
||||
exclude: list[Path] | None
|
||||
@@ -158,7 +159,7 @@ def match(path: Path, args: TestConfig) -> bool:
|
||||
|
||||
|
||||
def parse_versions(fname: StrPath) -> dict[str, tuple[VersionTuple, VersionTuple]]:
|
||||
result = {}
|
||||
result: dict[str, tuple[VersionTuple, VersionTuple]] = {}
|
||||
with open(fname, encoding="UTF-8") as f:
|
||||
for line in f:
|
||||
line = strip_comments(line)
|
||||
@@ -209,7 +210,8 @@ def add_configuration(configurations: list[MypyDistConf], distribution: str) ->
|
||||
with Path("stubs", distribution, "METADATA.toml").open("rb") as f:
|
||||
data = tomli.load(f)
|
||||
|
||||
mypy_tests_conf = data.get("mypy-tests")
|
||||
# TODO: This could be added to parse_metadata.py, but is currently unused
|
||||
mypy_tests_conf: dict[str, dict[str, Any]] = data.get("mypy-tests", {})
|
||||
if not mypy_tests_conf:
|
||||
return
|
||||
|
||||
@@ -221,8 +223,8 @@ def add_configuration(configurations: list[MypyDistConf], distribution: str) ->
|
||||
assert module_name is not None, f"{section_name} should have a module_name key"
|
||||
assert isinstance(module_name, str), f"{section_name} should be a key-value pair"
|
||||
|
||||
values = mypy_section.get("values")
|
||||
assert values is not None, f"{section_name} should have a values section"
|
||||
assert "values" in mypy_section, f"{section_name} should have a values section"
|
||||
values: dict[str, dict[str, Any]] = mypy_section["values"]
|
||||
assert isinstance(values, dict), "values should be a section"
|
||||
|
||||
configurations.append(MypyDistConf(module_name, values.copy()))
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
# This module is made specifically to abstract away those type errors
|
||||
# pyright: reportUnknownVariableType=false, reportUnknownArgumentType=false, reportUnknownMemberType=false
|
||||
|
||||
"""Tools to help parse and validate information stored in METADATA.toml files."""
|
||||
from __future__ import annotations
|
||||
|
||||
@@ -188,7 +191,8 @@ def read_metadata(distribution: str) -> StubMetadata:
|
||||
uploaded_to_pypi = data.get("upload", True)
|
||||
assert type(uploaded_to_pypi) is bool
|
||||
|
||||
tools_settings = data.get("tool", {})
|
||||
empty_tools: dict[str, dict[str, object]] = {}
|
||||
tools_settings = data.get("tool", empty_tools)
|
||||
assert isinstance(tools_settings, dict)
|
||||
assert tools_settings.keys() <= _KNOWN_METADATA_TOOL_FIELDS.keys(), f"Unrecognised tool for {distribution!r}"
|
||||
for tool, tk in _KNOWN_METADATA_TOOL_FIELDS.items():
|
||||
@@ -234,7 +238,8 @@ def read_dependencies(distribution: str) -> PackageDependencies:
|
||||
If a typeshed stub is removed, this function will consider it to be an external dependency.
|
||||
"""
|
||||
pypi_name_to_typeshed_name_mapping = get_pypi_name_to_typeshed_name_mapping()
|
||||
typeshed, external = [], []
|
||||
typeshed: list[str] = []
|
||||
external: list[str] = []
|
||||
for dependency in read_metadata(distribution).requires:
|
||||
maybe_typeshed_dependency = Requirement(dependency).name
|
||||
if maybe_typeshed_dependency in pypi_name_to_typeshed_name_mapping:
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
#!/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.
|
||||
@@ -19,11 +21,14 @@ import traceback
|
||||
from collections.abc import Iterable, Sequence
|
||||
|
||||
import pkg_resources
|
||||
from pytype import config as pytype_config, load_pytd # type: ignore[import]
|
||||
from pytype.imports import typeshed # type: ignore[import]
|
||||
|
||||
from parse_metadata import read_dependencies
|
||||
|
||||
assert sys.platform != "win32"
|
||||
# pytype is not py.typed https://github.com/google/pytype/issues/1325
|
||||
from pytype import config as pytype_config, load_pytd # type: ignore[import] # noqa: E402
|
||||
from pytype.imports import typeshed # type: ignore[import] # noqa: E402
|
||||
|
||||
TYPESHED_SUBDIRS = ["stdlib", "stubs"]
|
||||
TYPESHED_HOME = "TYPESHED_HOME"
|
||||
_LOADERS = {}
|
||||
@@ -155,7 +160,11 @@ def get_missing_modules(files_to_test: Sequence[str]) -> Iterable[str]:
|
||||
for distribution in stub_distributions:
|
||||
for pkg in read_dependencies(distribution).external_pkgs:
|
||||
# See https://stackoverflow.com/a/54853084
|
||||
top_level_file = os.path.join(pkg_resources.get_distribution(pkg).egg_info, "top_level.txt") # type: ignore[attr-defined]
|
||||
top_level_file = os.path.join(
|
||||
# Fixed in #9747
|
||||
pkg_resources.get_distribution(pkg).egg_info, # type: ignore[attr-defined] # pyright: ignore[reportGeneralTypeIssues]
|
||||
"top_level.txt",
|
||||
)
|
||||
with open(top_level_file) as f:
|
||||
missing_modules.update(f.read().splitlines())
|
||||
return missing_modules
|
||||
|
||||
@@ -15,11 +15,12 @@ ReturnCode: TypeAlias = int
|
||||
SUPPORTED_PLATFORMS = ("linux", "darwin", "win32")
|
||||
SUPPORTED_VERSIONS = ("3.11", "3.10", "3.9")
|
||||
DIRECTORIES_TO_TEST = ("scripts", "tests")
|
||||
EMPTY: list[str] = []
|
||||
|
||||
parser = argparse.ArgumentParser(description="Run mypy on typeshed's own code in the `scripts` and `tests` directories.")
|
||||
parser.add_argument(
|
||||
"dir",
|
||||
choices=DIRECTORIES_TO_TEST + ([],),
|
||||
choices=DIRECTORIES_TO_TEST + (EMPTY,),
|
||||
nargs="*",
|
||||
action="extend",
|
||||
help=f"Test only these top-level typeshed directories (defaults to {DIRECTORIES_TO_TEST!r})",
|
||||
|
||||
@@ -135,4 +135,6 @@ def spec_matches_path(spec: pathspec.PathSpec, path: Path) -> bool:
|
||||
normalized_path = path.as_posix()
|
||||
if path.is_dir():
|
||||
normalized_path += "/"
|
||||
return spec.match_file(normalized_path)
|
||||
# pathspec.PathSpec.match_file has partially Unknown file parameter
|
||||
# https://github.com/cpburnz/python-pathspec/pull/75
|
||||
return spec.match_file(normalized_path) # pyright: ignore[reportUnknownMemberType]
|
||||
|
||||
Reference in New Issue
Block a user