Skip to content

symfonic.devtools.integrations.introspect

introspect

Static facts about how a module reaches its optional distributions.

Split out of :mod:symfonic.devtools.integrations.checks so the rules read as rules: everything here answers "what does this file actually do?" from the AST alone, and nothing here decides whether that is allowed.

carrier_modules

carrier_modules(modules: dict[str, ModuleInfo], integration_map: IntegrationMap) -> dict[str, dict[str, bool]]

Every scanned module that reaches an optional root, with its roots.

Source code in src/symfonic/devtools/integrations/introspect.py
def carrier_modules(
    modules: dict[str, ModuleInfo], integration_map: IntegrationMap
) -> dict[str, dict[str, bool]]:
    """Every scanned module that reaches an optional root, with its roots."""
    carriers: dict[str, dict[str, bool]] = {}
    for name, info in modules.items():
        roots = observed_roots(info, integration_map)
        if roots:
            carriers[name] = roots
    return carriers

guarded_import_lines

guarded_import_lines(path: Path) -> set[int]

Line numbers of imports sitting inside a try/except ImportError.

Source code in src/symfonic/devtools/integrations/introspect.py
def guarded_import_lines(path: Path) -> set[int]:
    """Line numbers of imports sitting inside a ``try/except ImportError``."""
    try:
        tree = ast.parse(path.read_text(encoding="utf-8"))
    except (OSError, SyntaxError):  # pragma: no cover - unparsable source
        return set()
    lines: set[int] = set()
    for node in ast.walk(tree):
        if not isinstance(node, ast.Try):
            continue
        if not any(_catches_import_error(h) for h in node.handlers):
            continue
        for child in node.body:
            for sub in ast.walk(child):
                if isinstance(sub, (ast.Import, ast.ImportFrom)):
                    lines.add(sub.lineno)
    return lines

observed_roots

observed_roots(info: ModuleInfo, integration_map: IntegrationMap) -> dict[str, bool]

Optional roots this module imports -> True when at module level.

Type-checking-only imports are ignored: they cost nothing at runtime, which is the only thing the optional-dependency contract is about.

Source code in src/symfonic/devtools/integrations/introspect.py
def observed_roots(info: ModuleInfo, integration_map: IntegrationMap) -> dict[str, bool]:
    """Optional roots this module imports -> ``True`` when at module level.

    Type-checking-only imports are ignored: they cost nothing at runtime,
    which is the only thing the optional-dependency contract is about.
    """
    out: dict[str, bool] = {}
    for record in info.imports:
        if record.type_checking:
            continue
        root = integration_map.root_of(record.target)
        if root is None:
            continue
        module_level = not record.lazy
        out[root] = out.get(root, False) or module_level
    return out

observed_style

observed_style(info: ModuleInfo, integration_map: IntegrationMap) -> str

lazy / module-guarded / module-required for one module.

Source code in src/symfonic/devtools/integrations/introspect.py
def observed_style(
    info: ModuleInfo, integration_map: IntegrationMap
) -> str:
    """``lazy`` / ``module-guarded`` / ``module-required`` for one module."""
    module_level = [
        record
        for record in info.imports
        if not record.lazy
        and not record.type_checking
        and integration_map.root_of(record.target) is not None
    ]
    if not module_level:
        return "lazy"
    guarded = guarded_import_lines(info.path)
    if all(record.lineno in guarded for record in module_level):
        return "module-guarded"
    return "module-required"