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 == []