diff --git a/.github/workflows/mypy.yml b/.github/workflows/mypy.yml index 6d1c6b5b5e6347..212f3e8d70c836 100644 --- a/.github/workflows/mypy.yml +++ b/.github/workflows/mypy.yml @@ -59,4 +59,5 @@ jobs: cache: pip cache-dependency-path: Tools/requirements-dev.txt - run: pip install -r Tools/requirements-dev.txt + - run: python3 Misc/mypy/make_symlinks.py --symlink - run: mypy --config-file ${{ matrix.target }}/mypy.ini diff --git a/Misc/mypy/README.md b/Misc/mypy/README.md index 05eda6c0b82f0a..03eb8bce8d5113 100644 --- a/Misc/mypy/README.md +++ b/Misc/mypy/README.md @@ -4,6 +4,7 @@ This directory stores symlinks to standard library modules and packages that are fully type-annotated and ready to be used in type checking of the rest of the stdlib or Tools/ and so on. +## Why this is necessary Due to most of the standard library being untyped, we prefer not to point mypy directly at `Lib/` for type checking. Additionally, mypy as a tool does not support shadowing typing-related standard libraries @@ -11,6 +12,13 @@ like `types`, `typing`, and `collections.abc`. So instead, we set `mypy_path` to include this directory, which only links modules and packages we know are safe to be -type-checked themselves and used as dependencies. +type-checked themselves and used as dependencies. See +`Lib/_pyrepl/mypy.ini` for a usage example. -See `Lib/_pyrepl/mypy.ini` for an example. \ No newline at end of file +## I want to add a new type-checked module +Add it to `typed-stdlib.txt` and run `make_symlinks.py --symlink`. + +## I don't see any symlinks in this directory +The symlinks in this directory are skipped in source tarballs +in Python releases. This ensures they don't end up in the SBOM. To +recreate them, run the `make_symlinks.py --symlink` script. diff --git a/Misc/mypy/make_symlinks.py b/Misc/mypy/make_symlinks.py new file mode 100755 index 00000000000000..9150604ed7939a --- /dev/null +++ b/Misc/mypy/make_symlinks.py @@ -0,0 +1,60 @@ +#!/usr/bin/env python3 +from __future__ import annotations + +import argparse +import os +from pathlib import Path + +CURRENT_DIR = Path(__file__).parent +MISC_DIR = CURRENT_DIR.parent +REPO_ROOT = MISC_DIR.parent +LIB_DIR = REPO_ROOT / "Lib" +FILE_LIST = CURRENT_DIR / "typed-stdlib.txt" + +parser = argparse.ArgumentParser(prog="make_symlinks.py") +parser.add_argument( + "--symlink", + action="store_true", + help="Create symlinks", +) +parser.add_argument( + "--clean", + action="store_true", + help="Delete any pre-existing symlinks", +) + +args = parser.parse_args() + +if args.clean: + for entry in CURRENT_DIR.glob("*"): + if entry.is_symlink(): + entry_at_root = entry.relative_to(REPO_ROOT) + print(f"removing pre-existing {entry_at_root}") + entry.unlink() + +for link in FILE_LIST.read_text().splitlines(): + link = link.strip() + if not link or link.startswith('#'): + continue + + src = LIB_DIR / link + dst = CURRENT_DIR / link + src_at_root = src.relative_to(REPO_ROOT) + dst_at_root = dst.relative_to(REPO_ROOT) + if ( + dst.is_symlink() + and src.resolve(strict=True) == dst.resolve(strict=True) + ): + continue + + if not args.symlink and args.clean: + # when the user called --clean without --symlink, don't report missing + # symlinks that we just deleted ourselves + continue + + # we specifically want to create relative-path links with .. + src_rel = os.path.relpath(src, CURRENT_DIR) + action = "symlinking" if args.symlink else "missing symlink to" + print(f"{action} {src_at_root} at {dst_at_root}") + if args.symlink: + os.symlink(src_rel, dst) diff --git a/Misc/mypy/typed-stdlib.txt b/Misc/mypy/typed-stdlib.txt new file mode 100644 index 00000000000000..8cd6858b4e591e --- /dev/null +++ b/Misc/mypy/typed-stdlib.txt @@ -0,0 +1,4 @@ +# These libraries in the stdlib can be type-checked. + +_colorize.py +_pyrepl
Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.
Alternative Proxies: