File size: 3,270 Bytes
dc72977 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 | import ast
from pathlib import Path
ROOT = Path(__file__).parents[1]
def python_runtime_files() -> list[Path]:
return sorted((ROOT / "src").rglob("*.py")) + sorted((ROOT / "scripts").glob("*.py"))
def test_runtime_code_avoids_python311_datetime_utc_symbol() -> None:
"""Catch imports that break Python 3.9/3.10 baseline environments."""
violations: list[str] = []
for path in python_runtime_files():
tree = ast.parse(path.read_text(encoding="utf-8"), filename=str(path))
for node in ast.walk(tree):
if isinstance(node, ast.ImportFrom) and node.module == "datetime":
for alias in node.names:
if alias.name == "UTC":
violations.append(f"{path.relative_to(ROOT)} imports datetime.UTC")
if (
isinstance(node, ast.Attribute)
and node.attr == "UTC"
and isinstance(node.value, ast.Name)
and node.value.id == "datetime"
):
violations.append(f"{path.relative_to(ROOT)} uses datetime.UTC")
assert violations == []
def test_runtime_pep604_annotations_are_future_gated_for_python39() -> None:
"""Catch imports that evaluate A | None annotations under Python 3.9."""
violations: list[str] = []
for path in python_runtime_files():
text = path.read_text(encoding="utf-8")
if "| None" not in text and " | " not in text:
continue
tree = ast.parse(text, filename=str(path))
future_annotations = any(
isinstance(node, ast.ImportFrom)
and node.module == "__future__"
and any(alias.name == "annotations" for alias in node.names)
for node in tree.body
)
if not future_annotations:
violations.append(f"{path.relative_to(ROOT)} uses PEP 604 annotations")
assert violations == []
def test_runtime_type_aliases_avoid_pep604_expressions_for_python39() -> None:
"""Catch top-level A | B aliases that execute before Python 3.10."""
violations: list[str] = []
for path in python_runtime_files():
tree = ast.parse(path.read_text(encoding="utf-8"), filename=str(path))
for node in tree.body:
if isinstance(node, ast.Assign) and isinstance(node.value, ast.BinOp):
if isinstance(node.value.op, ast.BitOr):
violations.append(
f"{path.relative_to(ROOT)} has runtime PEP 604 type alias"
)
assert violations == []
def test_runtime_zip_calls_avoid_strict_keyword_for_python39() -> None:
"""Catch zip(strict=...) calls in code executed by Python 3.9 environments."""
violations: list[str] = []
for path in python_runtime_files():
tree = ast.parse(path.read_text(encoding="utf-8"), filename=str(path))
for node in ast.walk(tree):
if not isinstance(node, ast.Call):
continue
if not isinstance(node.func, ast.Name) or node.func.id != "zip":
continue
if any(keyword.arg == "strict" for keyword in node.keywords):
violations.append(f"{path.relative_to(ROOT)} calls zip(strict=...)")
assert violations == []
|