Skip to content

Commit d91e5f0

Browse files
authored
Further enhance check_consistent.py (#8604)
1 parent 8cf125d commit d91e5f0

File tree

2 files changed

+65
-10
lines changed

2 files changed

+65
-10
lines changed

requirements-tests.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,4 +14,6 @@ tomlkit==0.11.4
1414
# must match .pre-commit-config.yaml
1515
pycln==2.1.1
1616
packaging==21.3
17+
pyyaml==6.0
1718
termcolor
19+
types-pyyaml

tests/check_consistent.py

Lines changed: 63 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -11,15 +11,18 @@
1111
from pathlib import Path
1212

1313
import tomli
14+
import yaml
1415
from packaging.requirements import Requirement
16+
from packaging.specifiers import SpecifierSet
1517
from packaging.version import Version
1618

1719
metadata_keys = {"version", "requires", "extra_description", "obsolete_since", "no_longer_updated", "tool"}
1820
tool_keys = {"stubtest": {"skip", "apt_dependencies", "extras", "ignore_missing_stub"}}
21+
extension_descriptions = {".pyi": "stub", ".py": ".py"}
1922

2023

21-
def assert_stubs_only(directory: Path, allowed: set[str]) -> None:
22-
"""Check that given directory contains only valid stub files."""
24+
def assert_consistent_filetypes(directory: Path, *, kind: str, allowed: set[str]) -> None:
25+
"""Check that given directory contains only valid Python files of a certain kind."""
2326
allowed_paths = {Path(f) for f in allowed}
2427
contents = list(directory.iterdir())
2528
while contents:
@@ -28,15 +31,16 @@ def assert_stubs_only(directory: Path, allowed: set[str]) -> None:
2831
# Note if a subdirectory is allowed, we will not check its contents
2932
continue
3033
if entry.is_file():
31-
assert entry.stem.isidentifier(), f"Files must be valid modules, got: {entry}"
32-
assert entry.suffix == ".pyi", f"Only stub files allowed, got: {entry}"
34+
assert entry.stem.isidentifier(), f'Files must be valid modules, got: "{entry}"'
35+
bad_filetype = f'Only {extension_descriptions[kind]!r} files allowed in the "{directory}" directory; got: {entry}'
36+
assert entry.suffix == kind, bad_filetype
3337
else:
3438
assert entry.name.isidentifier(), f"Directories must be valid packages, got: {entry}"
3539
contents.extend(entry.iterdir())
3640

3741

3842
def check_stdlib() -> None:
39-
assert_stubs_only(Path("stdlib"), allowed={"_typeshed/README.md", "VERSIONS"})
43+
assert_consistent_filetypes(Path("stdlib"), kind=".pyi", allowed={"_typeshed/README.md", "VERSIONS"})
4044

4145

4246
def check_stubs() -> None:
@@ -46,14 +50,21 @@ def check_stubs() -> None:
4650
valid_dist_name = "^([A-Z0-9]|[A-Z0-9][A-Z0-9._-]*[A-Z0-9])$" # courtesy of PEP 426
4751
assert re.fullmatch(
4852
valid_dist_name, dist.name, re.IGNORECASE
49-
), f"Directory name must have valid distribution name: {dist}"
53+
), f"Directory name must be a valid distribution name: {dist}"
5054
assert not dist.name.startswith("types-"), f"Directory name not allowed to start with 'types-': {dist}"
5155

5256
allowed = {"METADATA.toml", "README", "README.md", "README.rst", "@tests"}
53-
assert_stubs_only(dist, allowed)
57+
assert_consistent_filetypes(dist, kind=".pyi", allowed=allowed)
5458

5559

56-
def check_same_files() -> None:
60+
def check_test_cases() -> None:
61+
assert_consistent_filetypes(Path("test_cases"), kind=".py", allowed={"README.md"})
62+
bad_test_case_filename = 'Files in the `test_cases` directory must have names starting with "test_"; got "{}"'
63+
for file in Path("test_cases").rglob("*.py"):
64+
assert file.stem.startswith("test_"), bad_test_case_filename.format(file)
65+
66+
67+
def check_no_symlinks() -> None:
5768
files = [os.path.join(root, file) for root, _, files in os.walk(".") for file in files]
5869
no_symlink = "You cannot use symlinks in typeshed, please copy {} to its link."
5970
for file in files:
@@ -65,12 +76,16 @@ def check_same_files() -> None:
6576
_VERSIONS_RE = re.compile(r"^([a-zA-Z_][a-zA-Z0-9_.]*): [23]\.\d{1,2}-(?:[23]\.\d{1,2})?$")
6677

6778

79+
def strip_comments(text: str) -> str:
80+
return text.split("#")[0].strip()
81+
82+
6883
def check_versions() -> None:
6984
versions = set()
7085
with open("stdlib/VERSIONS") as f:
7186
data = f.read().splitlines()
7287
for line in data:
73-
line = line.split("#")[0].strip()
88+
line = strip_comments(line)
7489
if line == "":
7590
continue
7691
m = _VERSIONS_RE.match(line)
@@ -126,10 +141,48 @@ def check_metadata() -> None:
126141
assert key in tk, f"Unrecognised {tool} key {key} for {distribution}"
127142

128143

144+
def get_txt_requirements() -> dict[str, SpecifierSet]:
145+
with open("requirements-tests.txt") as requirements_file:
146+
stripped_lines = map(strip_comments, requirements_file)
147+
requirements = map(Requirement, filter(None, stripped_lines))
148+
return {requirement.name: requirement.specifier for requirement in requirements}
149+
150+
151+
def get_precommit_requirements() -> dict[str, SpecifierSet]:
152+
with open(".pre-commit-config.yaml") as precommit_file:
153+
precommit = precommit_file.read()
154+
yam = yaml.load(precommit, Loader=yaml.Loader)
155+
precommit_requirements = {}
156+
for repo in yam["repos"]:
157+
hook = repo["hooks"][0]
158+
package_name, package_rev = hook["id"], repo["rev"]
159+
package_specifier = SpecifierSet(f"=={package_rev.removeprefix('v')}")
160+
precommit_requirements[package_name] = package_specifier
161+
for additional_req in hook.get("additional_dependencies", []):
162+
req = Requirement(additional_req)
163+
precommit_requirements[req.name] = req.specifier
164+
return precommit_requirements
165+
166+
167+
def check_requirements() -> None:
168+
requirements_txt_requirements = get_txt_requirements()
169+
precommit_requirements = get_precommit_requirements()
170+
no_txt_entry_msg = "All pre-commit requirements must also be listed in `requirements-tests.txt` (missing {requirement!r})"
171+
for requirement, specifier in precommit_requirements.items():
172+
assert requirement in requirements_txt_requirements, no_txt_entry_msg.format(requirement)
173+
specifier_mismatch = (
174+
f'Specifier "{specifier}" for {requirement!r} in `.pre-commit-config.yaml` '
175+
f'does not match specifier "{requirements_txt_requirements[requirement]}" in `requirements-tests.txt`'
176+
)
177+
assert specifier == requirements_txt_requirements[requirement], specifier_mismatch
178+
179+
129180
if __name__ == "__main__":
130181
assert sys.version_info >= (3, 9), "Python 3.9+ is required to run this test"
131182
check_stdlib()
132183
check_versions()
133184
check_stubs()
134185
check_metadata()
135-
check_same_files()
186+
check_no_symlinks()
187+
check_test_cases()
188+
check_requirements()

0 commit comments

Comments
 (0)