From 626e64caf48ee822ea2cd20b86585a6dbb9d6140 Mon Sep 17 00:00:00 2001 From: jakkdl Date: Tue, 14 Nov 2023 15:44:56 +0100 Subject: [PATCH 1/2] Remove check_type_completeness and verify_types_*.json, moving pyright --verifytypes directly into check.sh. Fix pyright type_tests location. --- check.sh | 16 +- pyproject.toml | 5 +- src/trio/_core/_asyncgens.py | 2 +- src/trio/_core/_wakeup_socketpair.py | 4 + src/trio/_tests/check_type_completeness.py | 194 --------------------- src/trio/_tests/verify_types_darwin.json | 55 ------ src/trio/_tests/verify_types_linux.json | 55 ------ src/trio/_tests/verify_types_windows.json | 55 ------ 8 files changed, 18 insertions(+), 368 deletions(-) delete mode 100755 src/trio/_tests/check_type_completeness.py delete mode 100644 src/trio/_tests/verify_types_darwin.json delete mode 100644 src/trio/_tests/verify_types_linux.json delete mode 100644 src/trio/_tests/verify_types_windows.json diff --git a/check.sh b/check.sh index a6f41a6d5d..badad99127 100755 --- a/check.sh +++ b/check.sh @@ -96,16 +96,20 @@ fi codespell || EXIT_STATUS=$? +PYRIGHT=0 echo "::group::Pyright interface tests" -python src/trio/_tests/check_type_completeness.py --overwrite-file || EXIT_STATUS=$? -if git status --porcelain src/trio/_tests/verify_types*.json | grep -q "M"; then - echo "* Type completeness changed, please update!" >> "$GITHUB_STEP_SUMMARY" - echo "::error::Type completeness changed, please update!" - git --no-pager diff --color src/trio/_tests/verify_types*.json +pyright --verifytypes --ignoreexternal --pythonplatform=Linux --verifytypes=trio \ + || { echo "* Pyright --verifytypes (Linux) found errors." >> "$GITHUB_STEP_SUMMARY"; PYRIGHT=1; } +pyright --verifytypes --ignoreexternal --pythonplatform=Darwin --verifytypes=trio \ + || { echo "* Pyright --verifytypes (Mac) found errors." >> "$GITHUB_STEP_SUMMARY"; PYRIGHT=1; } +pyright --verifytypes --ignoreexternal --pythonplatform=Windows --verifytypes=trio \ + || { echo "* Pyright --verifytypes (Windows) found errors." >> "$GITHUB_STEP_SUMMARY"; PYRIGHT=1; } +if [ $PYRIGHT -ne 0 ]; then + echo "::error:: Pyright --verifytypes returned errors." EXIT_STATUS=1 fi -pyright trio/_tests/type_tests || EXIT_STATUS=$? +pyright src/trio/_tests/type_tests || EXIT_STATUS=$? echo "::endgroup::" # Finally, leave a really clear warning of any issues and exit diff --git a/pyproject.toml b/pyproject.toml index 9caa39c3af..d455c6ca39 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -155,6 +155,9 @@ disallow_untyped_decorators = true disallow_untyped_defs = true check_untyped_defs = true +[tool.pyright] +pythonVersion = "3.8" + [tool.pytest.ini_options] addopts = ["--strict-markers", "--strict-config"] faulthandler_timeout = 60 @@ -232,8 +235,6 @@ omit = [ "*/trio/_core/_tests/test_multierror_scripts/*", # Omit the generated files in trio/_core starting with _generated_ "*/trio/_core/_generated_*", - # Script used to check type completeness that isn't run in tests - "*/trio/_tests/check_type_completeness.py", ] # The test suite spawns subprocesses to test some stuff, so make sure # this doesn't corrupt the coverage files diff --git a/src/trio/_core/_asyncgens.py b/src/trio/_core/_asyncgens.py index f26f5bd160..30736ac7ad 100644 --- a/src/trio/_core/_asyncgens.py +++ b/src/trio/_core/_asyncgens.py @@ -42,7 +42,7 @@ class AsyncGenerators: # init task starting end-of-run asyncgen finalization. trailing_needs_finalize: _ASYNC_GEN_SET = attr.ib(factory=_ASYNC_GEN_SET) - prev_hooks = attr.ib(init=False) + prev_hooks: sys._asyncgen_hooks = attr.ib(init=False) def install_hooks(self, runner: _run.Runner) -> None: def firstiter(agen: AsyncGeneratorType[object, NoReturn]) -> None: diff --git a/src/trio/_core/_wakeup_socketpair.py b/src/trio/_core/_wakeup_socketpair.py index aff28a1bd8..fb821a23e7 100644 --- a/src/trio/_core/_wakeup_socketpair.py +++ b/src/trio/_core/_wakeup_socketpair.py @@ -11,6 +11,10 @@ class WakeupSocketpair: def __init__(self) -> None: + # explicitly typed to please `pyright --verifytypes` without `--ignoreexternal` + self.wakeup_sock: socket.socket + self.write_sock: socket.socket + self.wakeup_sock, self.write_sock = socket.socketpair() self.wakeup_sock.setblocking(False) self.write_sock.setblocking(False) diff --git a/src/trio/_tests/check_type_completeness.py b/src/trio/_tests/check_type_completeness.py deleted file mode 100755 index 4b46844b60..0000000000 --- a/src/trio/_tests/check_type_completeness.py +++ /dev/null @@ -1,194 +0,0 @@ -#!/usr/bin/env python3 -from __future__ import annotations - -# this file is not run as part of the tests, instead it's run standalone from check.sh -import argparse -import json -import subprocess -import sys -from pathlib import Path -from typing import TYPE_CHECKING - -if TYPE_CHECKING: - from collections.abc import Mapping - -# the result file is not marked in MANIFEST.in so it's not included in the package -failed = False - - -def get_result_file_name(platform: str) -> Path: - return Path(__file__).parent / f"verify_types_{platform.lower()}.json" - - -# TODO: consider checking manually without `--ignoreexternal`, and/or -# removing it from the below call later on. -def run_pyright(platform: str) -> subprocess.CompletedProcess[bytes]: - return subprocess.run( - [ - "pyright", - # Specify a platform and version to keep imported modules consistent. - f"--pythonplatform={platform}", - "--pythonversion=3.8", - "--verifytypes=trio", - "--outputjson", - "--ignoreexternal", - ], - capture_output=True, - ) - - -def check_less_than( - key: str, - current_dict: Mapping[str, int | float], - last_dict: Mapping[str, int | float], - /, - invert: bool = False, -) -> None: - global failed - current = current_dict[key] - last = last_dict[key] - assert isinstance(current, (float, int)) - assert isinstance(last, (float, int)) - if current == last: - return - if (current > last) ^ invert: - failed = True - print("ERROR: ", end="") - strcurrent = f"{current:.4}" if isinstance(current, float) else str(current) - strlast = f"{last:.4}" if isinstance(last, float) else str(last) - print( - f"{key} has gone {'down' if current None: - global failed - if current_dict[key] != 0: - failed = True - print(f"ERROR: {key} is {current_dict[key]}") - - -def check_type(args: argparse.Namespace, platform: str) -> int: - print("*" * 20, "\nChecking type completeness hasn't gone down...") - - res = run_pyright(platform) - current_result = json.loads(res.stdout) - py_typed_file: Path | None = None - - # check if py.typed file was missing - if ( - current_result["generalDiagnostics"] - and current_result["generalDiagnostics"][0]["message"] - == "No py.typed file found" - ): - print("creating py.typed") - py_typed_file = ( - Path(current_result["typeCompleteness"]["packageRootDirectory"]) - / "py.typed" - ) - py_typed_file.write_text("") - - res = run_pyright(platform) - current_result = json.loads(res.stdout) - - if res.stderr: - print(res.stderr) - - last_result = json.loads(get_result_file_name(platform).read_text()) - - for key in "errorCount", "warningCount", "informationCount": - check_zero(key, current_result["summary"]) - - for key, invert in ( - ("missingFunctionDocStringCount", False), - ("missingClassDocStringCount", False), - ("missingDefaultParamCount", False), - ("completenessScore", True), - ): - check_less_than( - key, - current_result["typeCompleteness"], - last_result["typeCompleteness"], - invert=invert, - ) - - for key, invert in ( - ("withUnknownType", False), - ("withAmbiguousType", False), - ("withKnownType", True), - ): - check_less_than( - key, - current_result["typeCompleteness"]["exportedSymbolCounts"], - last_result["typeCompleteness"]["exportedSymbolCounts"], - invert=invert, - ) - - if args.overwrite_file: - print("Overwriting file") - - # don't care about differences in time taken - del current_result["time"] - del current_result["summary"]["timeInSec"] - - # don't fail on version diff so pyright updates can be automerged - del current_result["version"] - - for key in ( - # don't save path (because that varies between machines) - "moduleRootDirectory", - "packageRootDirectory", - "pyTypedPath", - ): - del current_result["typeCompleteness"][key] - - # prune the symbols to only be the name of the symbols with - # errors, instead of saving a huge file. - new_symbols: list[dict[str, str]] = [] - for symbol in current_result["typeCompleteness"]["symbols"]: - if symbol["diagnostics"]: - # function name + message should be enough context for people! - new_symbols.extend( - {"name": symbol["name"], "message": diagnostic["message"]} - for diagnostic in symbol["diagnostics"] - ) - continue - - # Ensure order of arrays does not affect result. - new_symbols.sort(key=lambda module: module.get("name", "")) - current_result["generalDiagnostics"].sort() - current_result["typeCompleteness"]["modules"].sort( - key=lambda module: module.get("name", "") - ) - - del current_result["typeCompleteness"]["symbols"] - current_result["typeCompleteness"]["diagnostics"] = new_symbols - - with open(get_result_file_name(platform), "w") as file: - json.dump(current_result, file, sort_keys=True, indent=2) - # add newline at end of file so it's easier to manually modify - file.write("\n") - - if py_typed_file is not None: - print("deleting py.typed") - py_typed_file.unlink() - - print("*" * 20) - - return int(failed) - - -def main(args: argparse.Namespace) -> int: - res = 0 - for platform in "Linux", "Windows", "Darwin": - res += check_type(args, platform) - return res - - -parser = argparse.ArgumentParser() -parser.add_argument("--overwrite-file", action="store_true", default=False) -parser.add_argument("--full-diagnostics-file", type=Path, default=None) -args = parser.parse_args() - -assert __name__ == "__main__", "This script should be run standalone" -sys.exit(main(args)) diff --git a/src/trio/_tests/verify_types_darwin.json b/src/trio/_tests/verify_types_darwin.json deleted file mode 100644 index 713263afb5..0000000000 --- a/src/trio/_tests/verify_types_darwin.json +++ /dev/null @@ -1,55 +0,0 @@ -{ - "generalDiagnostics": [], - "summary": { - "errorCount": 0, - "filesAnalyzed": 8, - "informationCount": 0, - "warningCount": 0 - }, - "typeCompleteness": { - "completenessScore": 1, - "diagnostics": [], - "exportedSymbolCounts": { - "withAmbiguousType": 0, - "withKnownType": 632, - "withUnknownType": 0 - }, - "ignoreUnknownTypesFromImports": true, - "missingClassDocStringCount": 0, - "missingDefaultParamCount": 0, - "missingFunctionDocStringCount": 0, - "moduleName": "trio", - "modules": [ - { - "name": "trio" - }, - { - "name": "trio.abc" - }, - { - "name": "trio.from_thread" - }, - { - "name": "trio.lowlevel" - }, - { - "name": "trio.socket" - }, - { - "name": "trio.testing" - }, - { - "name": "trio.tests" - }, - { - "name": "trio.to_thread" - } - ], - "otherSymbolCounts": { - "withAmbiguousType": 0, - "withKnownType": 699, - "withUnknownType": 0 - }, - "packageName": "trio" - } -} diff --git a/src/trio/_tests/verify_types_linux.json b/src/trio/_tests/verify_types_linux.json deleted file mode 100644 index 2e8c2e2f44..0000000000 --- a/src/trio/_tests/verify_types_linux.json +++ /dev/null @@ -1,55 +0,0 @@ -{ - "generalDiagnostics": [], - "summary": { - "errorCount": 0, - "filesAnalyzed": 8, - "informationCount": 0, - "warningCount": 0 - }, - "typeCompleteness": { - "completenessScore": 1, - "diagnostics": [], - "exportedSymbolCounts": { - "withAmbiguousType": 0, - "withKnownType": 629, - "withUnknownType": 0 - }, - "ignoreUnknownTypesFromImports": true, - "missingClassDocStringCount": 0, - "missingDefaultParamCount": 0, - "missingFunctionDocStringCount": 0, - "moduleName": "trio", - "modules": [ - { - "name": "trio" - }, - { - "name": "trio.abc" - }, - { - "name": "trio.from_thread" - }, - { - "name": "trio.lowlevel" - }, - { - "name": "trio.socket" - }, - { - "name": "trio.testing" - }, - { - "name": "trio.tests" - }, - { - "name": "trio.to_thread" - } - ], - "otherSymbolCounts": { - "withAmbiguousType": 0, - "withKnownType": 699, - "withUnknownType": 0 - }, - "packageName": "trio" - } -} diff --git a/src/trio/_tests/verify_types_windows.json b/src/trio/_tests/verify_types_windows.json deleted file mode 100644 index f0de7469a5..0000000000 --- a/src/trio/_tests/verify_types_windows.json +++ /dev/null @@ -1,55 +0,0 @@ -{ - "generalDiagnostics": [], - "summary": { - "errorCount": 0, - "filesAnalyzed": 8, - "informationCount": 0, - "warningCount": 0 - }, - "typeCompleteness": { - "completenessScore": 1, - "diagnostics": [], - "exportedSymbolCounts": { - "withAmbiguousType": 0, - "withKnownType": 632, - "withUnknownType": 0 - }, - "ignoreUnknownTypesFromImports": true, - "missingClassDocStringCount": 0, - "missingDefaultParamCount": 0, - "missingFunctionDocStringCount": 0, - "moduleName": "trio", - "modules": [ - { - "name": "trio" - }, - { - "name": "trio.abc" - }, - { - "name": "trio.from_thread" - }, - { - "name": "trio.lowlevel" - }, - { - "name": "trio.socket" - }, - { - "name": "trio.testing" - }, - { - "name": "trio.tests" - }, - { - "name": "trio.to_thread" - } - ], - "otherSymbolCounts": { - "withAmbiguousType": 0, - "withKnownType": 691, - "withUnknownType": 0 - }, - "packageName": "trio" - } -} From a1cd7b41bbb08faaac98efa5d826bf3d7be59f7f Mon Sep 17 00:00:00 2001 From: jakkdl Date: Tue, 14 Nov 2023 16:08:29 +0100 Subject: [PATCH 2/2] remove autogeneration of py.typed --- src/trio/_tests/test_exports.py | 22 ---------------------- 1 file changed, 22 deletions(-) diff --git a/src/trio/_tests/test_exports.py b/src/trio/_tests/test_exports.py index bd04436640..8afb710eb6 100644 --- a/src/trio/_tests/test_exports.py +++ b/src/trio/_tests/test_exports.py @@ -146,13 +146,6 @@ def no_underscores(symbols: Iterable[str]) -> set[str]: if getattr(module, name, None) is getattr(__future__, name): runtime_names.remove(name) - if tool in ("mypy", "pyright_verifytypes"): - # create py.typed file - py_typed_path = Path(trio.__file__).parent / "py.typed" - py_typed_exists = py_typed_path.exists() - if not py_typed_exists: # pragma: no branch - py_typed_path.write_text("") - if tool == "pylint": try: from pylint.lint import PyLinter @@ -236,10 +229,6 @@ def no_underscores(symbols: Iterable[str]) -> set[str]: else: # pragma: no cover raise AssertionError() - # remove py.typed file - if tool in ("mypy", "pyright_verifytypes") and not py_typed_exists: - py_typed_path.unlink() - # mypy handles errors with an `assert` in its branch if tool == "mypy": return @@ -289,16 +278,9 @@ def no_hidden(symbols: Iterable[str]) -> set[str]: if (not symbol.startswith("_")) or symbol.startswith("__") } - py_typed_path = Path(trio.__file__).parent / "py.typed" - py_typed_exists = py_typed_path.exists() - if tool == "mypy": if sys.implementation.name != "cpython": pytest.skip("mypy not installed in tests on pypy") - # create py.typed file - # remove this logic when trio is marked with py.typed proper - if not py_typed_exists: # pragma: no branch - py_typed_path.write_text("") cache = Path.cwd() / ".mypy_cache" @@ -536,10 +518,6 @@ def lookup_symbol(symbol: str) -> dict[str, str]: "extra": extra, } - # clean up created py.typed file - if tool == "mypy" and not py_typed_exists: - py_typed_path.unlink() - # `assert not errors` will not print the full content of errors, even with # `--verbose`, so we manually print it if errors: # pragma: no cover