Skip to content

symfonic.devtools.archcheck.scan

scan

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

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

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