AST-based source scanner: modules, imports, sizes, and export surfaces.
Purely static (never imports scanned code). Each import records whether it is
lazy (inside a function body — the sanctioned form for root cells) and
whether it lives under if TYPE_CHECKING: (excluded from runtime cycle
detection).
resolve_internal(target: str, known_modules: frozenset[str]) -> str | None
Map an import target onto the longest known internal module prefix.
Source code in src/symfonic/devtools/archcheck/scan.py
| def resolve_internal(target: str, known_modules: frozenset[str]) -> str | None:
"""Map an import target onto the longest known internal module prefix."""
candidate = target
while candidate:
if candidate in known_modules:
return candidate
candidate = candidate.rpartition(".")[0]
return None
|
scan_source_tree(src_root: Path, *, packages: tuple[str, ...] | None = None) -> dict[str, ModuleInfo]
Scan every *.py under src_root into a name -> ModuleInfo map.
packages restricts the walk to those top-level packages while keeping
module names relative to src_root. A repository checkout's src/
holds nothing but the framework, but an installed wheel's root is
site-packages, where walking everything means AST-parsing every
dependency in the environment.
Source code in src/symfonic/devtools/archcheck/scan.py
| def scan_source_tree(
src_root: Path, *, packages: tuple[str, ...] | None = None
) -> dict[str, ModuleInfo]:
"""Scan every ``*.py`` under ``src_root`` into a name -> ModuleInfo map.
``packages`` restricts the walk to those top-level packages while keeping
module names relative to ``src_root``. A repository checkout's ``src/``
holds nothing but the framework, but an installed wheel's root is
``site-packages``, where walking everything means AST-parsing every
dependency in the environment.
"""
modules: dict[str, ModuleInfo] = {}
roots = (
[src_root] if packages is None else [src_root / package for package in packages]
)
for root in roots:
if not root.is_dir():
continue
for path in sorted(root.rglob("*.py")):
rel = path.relative_to(src_root).with_suffix("")
parts = rel.parts
is_package = parts[-1] == "__init__"
if is_package:
parts = parts[:-1]
if not parts:
continue
name = ".".join(parts)
scanned = _scan_module(name, path, is_package)
if scanned is not None:
modules[name] = scanned
return modules
|