|
5 | 5 | import pytest |
6 | 6 |
|
7 | 7 | from puyapy.awst_build import pytypes |
8 | | -from tests import VCS_ROOT |
| 8 | +from tests import STUBS_DIR |
| 9 | +from tests.utils import get_module_name_from_path |
9 | 10 |
|
10 | 11 |
|
11 | | -def module_name_from_path(file_path: Path, stubs_root: Path) -> str: |
12 | | - rel = file_path.relative_to(stubs_root) |
13 | | - parts = list(rel.parts) |
14 | | - if parts[-1] == "__init__.pyi": |
15 | | - mod_parts = parts[:-1] |
16 | | - else: |
17 | | - # strip .pyi suffix |
18 | | - mod_parts = parts[:-1] + [parts[-1][:-4]] |
19 | | - return ".".join(mod_parts) |
20 | | - |
21 | | - |
22 | | -def collect_from_file(file_path: Path) -> tuple[set[str], set[str]]: |
23 | | - """Parse a single .pyi file and return (classes, aliases) sets of names.""" |
| 12 | +def _symbols_from_file(file_path: Path) -> list[str]: |
| 13 | + """ |
| 14 | + Parse a single .pyi file and return class names, |
| 15 | + and module level assignments which are (probably) type annotations. |
| 16 | + """ |
24 | 17 | text = file_path.read_text(encoding="utf8") |
25 | | - try: |
26 | | - tree = ast.parse(text, filename=str(file_path)) |
27 | | - except SyntaxError: |
28 | | - # Some stub files may use syntax not supported by the running Python |
29 | | - # interpreter (rare). Skip files we can't parse. |
30 | | - return set(), set() |
31 | | - |
32 | | - classes: set[str] = set() |
33 | | - aliases: set[str] = set() |
34 | | - |
35 | | - for node in ast.iter_child_nodes(tree): |
36 | | - if isinstance(node, ast.ClassDef): |
37 | | - classes.add(node.name) |
38 | | - elif isinstance(node, ast.AnnAssign): |
39 | | - # annotated assignment: name: TypeAlias = ... |
40 | | - target = node.target |
41 | | - if isinstance(target, ast.Name) and node.annotation is not None: |
42 | | - ann = node.annotation |
43 | | - ann_id = getattr(ann, "id", None) or getattr(ann, "attr", None) |
44 | | - if ann_id == "TypeAlias": |
45 | | - aliases.add(target.id) |
46 | | - return classes, aliases |
47 | | - |
48 | | - |
49 | | -def stub_class_names_and_predefined_aliases() -> list[str]: |
50 | | - root_module = "algopy" |
51 | | - stubs_root = (VCS_ROOT / "stubs" / "algopy-stubs").resolve() |
52 | | - results: set[str] = set() |
| 18 | + tree = ast.parse(text, filename=str(file_path)) |
| 19 | + symbols = [] |
| 20 | + for stmt in tree.body: |
| 21 | + match stmt: |
| 22 | + case ast.ClassDef(name=class_name): |
| 23 | + symbols.append(class_name) |
| 24 | + case ( |
| 25 | + ast.AnnAssign(target=ast.Name(id=alias_name)) |
| 26 | + | ast.Assign(targets=[ast.Name(id=alias_name)]) |
| 27 | + ) if stmt.value is None or not _is_ellipsis(stmt.value): |
| 28 | + symbols.append(alias_name) |
| 29 | + return symbols |
| 30 | + |
| 31 | + |
| 32 | +def _is_ellipsis(expr: ast.expr) -> bool: |
| 33 | + match expr: |
| 34 | + case ast.Constant(value=value): |
| 35 | + return value is Ellipsis |
| 36 | + case _: |
| 37 | + return False |
| 38 | + |
| 39 | + |
| 40 | +def _stub_class_names_and_predefined_aliases() -> list[str]: |
| 41 | + stubs_root = STUBS_DIR.parent.resolve() |
| 42 | + results = [] |
53 | 43 | for path in stubs_root.rglob("*.pyi"): |
54 | | - # compute module name |
55 | | - try: |
56 | | - module = module_name_from_path(path, stubs_root) |
57 | | - except Exception: |
58 | | - continue |
59 | | - classes, aliases = collect_from_file(path) |
60 | | - for name in classes | aliases: |
61 | | - if name.startswith("_"): |
62 | | - continue |
63 | | - if module: |
64 | | - results.add(f"{root_module}.{module}.{name}") |
65 | | - else: |
66 | | - results.add(f"{root_module}.{name}") |
67 | | - |
68 | | - return sorted(results) |
| 44 | + module = get_module_name_from_path(path, stubs_root) |
| 45 | + symbols = _symbols_from_file(path) |
| 46 | + qualified_symbols = [f"{module}.{name}" for name in symbols if not name.startswith("_")] |
| 47 | + results.extend(qualified_symbols) |
| 48 | + return results |
69 | 49 |
|
70 | 50 |
|
71 | 51 | @pytest.fixture(scope="session") |
72 | 52 | def builtins_registry() -> Mapping[str, pytypes.PyType]: |
73 | 53 | return pytypes.builtins_registry() |
74 | 54 |
|
75 | 55 |
|
76 | | -@pytest.mark.parametrize( |
77 | | - "fullname", |
78 | | - stub_class_names_and_predefined_aliases(), |
79 | | - ids=str, |
80 | | -) |
| 56 | +@pytest.mark.parametrize("fullname", _stub_class_names_and_predefined_aliases(), ids=str) |
81 | 57 | def test_stub_class_names_lookup( |
82 | 58 | builtins_registry: Mapping[str, pytypes.PyType], fullname: str |
83 | 59 | ) -> None: |
|
0 commit comments