Skip to content

symfonic.devtools.integrations.checks

checks

Static rules IA-1..IA-5 of the integration-adapter standard (T4.2.1).

Purely static: the source tree is scanned with the T1.2.4 AST scanner and compared against the optional-import map. Nothing here imports the modules it judges, so the checks run identically on an install with no extras. Observation lives in :mod:symfonic.devtools.integrations.introspect; this module only decides.

The behavioural half of the standard -- IA-6 (importing a package must not probe a disabled integration) and IA-7 (every adapter binds its contract suite) -- lives in tests/contracts/integrations/.

adapter_entries_for_family

adapter_entries_for_family(integration_map: IntegrationMap, family: str) -> tuple[AdapterEntry, ...]

Every registered adapter in one family (contract-suite binding aid).

Source code in src/symfonic/devtools/integrations/checks.py
def adapter_entries_for_family(
    integration_map: IntegrationMap, family: str
) -> tuple[AdapterEntry, ...]:
    """Every registered adapter in one family (contract-suite binding aid)."""
    return integration_map.by_family(family)

check_declared_roots

check_declared_roots(modules: dict[str, ModuleInfo], integration_map: IntegrationMap) -> list[Violation]

IA-2 -- declared roots equal the roots the module actually imports.

Source code in src/symfonic/devtools/integrations/checks.py
def check_declared_roots(
    modules: dict[str, ModuleInfo], integration_map: IntegrationMap
) -> list[Violation]:
    """IA-2 -- declared roots equal the roots the module actually imports."""
    violations: list[Violation] = []
    carriers = carrier_modules(modules, integration_map)
    for entry in integration_map.entries:
        observed = carriers.get(entry.module)
        if observed is None:
            continue  # IA-1 already reports this
        declared = set(entry.roots)
        if declared == set(observed):
            continue
        missing = sorted(set(observed) - declared)
        stale = sorted(declared - set(observed))
        violations.append(
            Violation(
                rule="IA-2",
                message=(
                    f"{entry.module} declares roots {sorted(declared)} but "
                    f"imports {sorted(observed)}"
                    + (f"; undeclared: {missing}" if missing else "")
                    + (f"; stale: {stale}" if stale else "")
                ),
                modules=(entry.module,),
            )
        )
    return violations

check_import_style

check_import_style(modules: dict[str, ModuleInfo], integration_map: IntegrationMap) -> list[Violation]

IA-3 -- the declared import style is the one the source implements.

Source code in src/symfonic/devtools/integrations/checks.py
def check_import_style(
    modules: dict[str, ModuleInfo], integration_map: IntegrationMap
) -> list[Violation]:
    """IA-3 -- the declared import style is the one the source implements."""
    violations: list[Violation] = []
    for entry in integration_map.entries:
        info = modules.get(entry.module)
        if info is None:
            continue  # IA-1 already reports this
        actual = observed_style(info, integration_map)
        if actual == entry.import_style:
            continue
        violations.append(
            Violation(
                rule="IA-3",
                message=(
                    f"{entry.module} declares import_style "
                    f"{entry.import_style!r} but its source is {actual!r}"
                ),
                modules=(entry.module,),
            )
        )
    return violations

check_install_hint

check_install_hint(modules: dict[str, ModuleInfo], integration_map: IntegrationMap) -> list[Violation]

IA-4 -- a missing extra is named where the adopter will read it.

Source code in src/symfonic/devtools/integrations/checks.py
def check_install_hint(
    modules: dict[str, ModuleInfo], integration_map: IntegrationMap
) -> list[Violation]:
    """IA-4 -- a missing extra is named where the adopter will read it."""
    violations: list[Violation] = []
    for entry in integration_map.entries:
        if not entry.owes_install_hint:
            continue
        info = modules.get(entry.hint_module)
        if info is None:
            violations.append(
                Violation(
                    rule="IA-4",
                    message=(
                        f"{entry.module} delegates its install hint to "
                        f"{entry.hint_module}, which does not exist"
                    ),
                    modules=(entry.module,),
                )
            )
            continue
        text = info.path.read_text(encoding="utf-8")
        hints = integration_map.install_hints(entry)
        if not any(hint in text for hint in hints):
            violations.append(
                Violation(
                    rule="IA-4",
                    message=(
                        f"{entry.hint_module} never names any of {list(hints)}, "
                        f"so a missing dependency reaches {entry.module}'s "
                        "caller as a bare import error instead of an install "
                        "command"
                    ),
                    modules=(entry.module,),
                )
            )
    return violations

check_no_eager_package_import

check_no_eager_package_import(modules: dict[str, ModuleInfo], integration_map: IntegrationMap) -> list[Violation]

IA-5 -- no package __init__ eagerly imports a probing adapter.

This is the static half of "a disabled integration is neither imported nor probed". A package __init__ runs for every import of anything beneath it, so an adapter reached from there is loaded by code that never asked for the integration. Whether that costs a probe depends on the adapter's import style: a lazy adapter keeps its distribution behind a function call and is free to be exported eagerly, while a module-guarded or module-required adapter attempts the import the moment it loads -- which is what IA-6's recorder observes.

Source code in src/symfonic/devtools/integrations/checks.py
def check_no_eager_package_import(
    modules: dict[str, ModuleInfo], integration_map: IntegrationMap
) -> list[Violation]:
    """IA-5 -- no package ``__init__`` eagerly imports a *probing* adapter.

    This is the static half of "a disabled integration is neither imported
    nor probed". A package ``__init__`` runs for every import of anything
    beneath it, so an adapter reached from there is loaded by code that
    never asked for the integration. Whether that costs a probe depends on
    the adapter's import style: a ``lazy`` adapter keeps its distribution
    behind a function call and is free to be exported eagerly, while a
    ``module-guarded`` or ``module-required`` adapter attempts the import
    the moment it loads -- which is what IA-6's recorder observes.
    """
    violations: list[Violation] = []
    probing = {
        entry.module: entry
        for entry in integration_map.entries
        if entry.import_style != "lazy"
    }
    seen: set[tuple[str, str]] = set()
    for name, info in sorted(modules.items()):
        if not info.is_package:
            continue
        for record in info.imports:
            if record.lazy or record.type_checking:
                continue
            target = _probing_target(record.target, probing)
            if target is None or (name, target) in seen:
                continue
            seen.add((name, target))
            entry = probing[target]
            violations.append(
                Violation(
                    rule="IA-5",
                    message=(
                        f"package {name} imports {entry.import_style} adapter "
                        f"{target} at module level (line {record.lineno}); every "
                        f"import beneath {name} then probes "
                        f"{', '.join(entry.roots)}. Export it through a module "
                        "__getattr__ instead"
                    ),
                    modules=(name, target),
                )
            )
    return violations

check_registration

check_registration(modules: dict[str, ModuleInfo], integration_map: IntegrationMap) -> list[Violation]

IA-1 -- the map covers exactly the modules that reach an extra.

Source code in src/symfonic/devtools/integrations/checks.py
def check_registration(
    modules: dict[str, ModuleInfo], integration_map: IntegrationMap
) -> list[Violation]:
    """IA-1 -- the map covers exactly the modules that reach an extra."""
    violations: list[Violation] = []
    carriers = carrier_modules(modules, integration_map)
    for name in sorted(set(carriers) - integration_map.modules):
        roots = ", ".join(sorted(carriers[name]))
        violations.append(
            Violation(
                rule="IA-1",
                message=(
                    f"{name} imports optional root(s) {roots} but is absent "
                    "from the optional-import map; add an [[adapter]] entry to "
                    "integration-adapters.toml"
                ),
                modules=(name,),
            )
        )
    for name in sorted(integration_map.modules - set(carriers)):
        reason = (
            "the module no longer exists"
            if name not in modules
            else "it no longer imports any optional root"
        )
        violations.append(
            Violation(
                rule="IA-1",
                message=(
                    f"{name} is registered in the optional-import map but "
                    f"{reason}; drop the stale entry"
                ),
                modules=(name,),
            )
        )
    return violations

collect_violations

collect_violations(modules: dict[str, ModuleInfo], integration_map: IntegrationMap) -> list[Violation]

Run every static rule and return the violations, rule-ordered.

Source code in src/symfonic/devtools/integrations/checks.py
def collect_violations(
    modules: dict[str, ModuleInfo], integration_map: IntegrationMap
) -> list[Violation]:
    """Run every static rule and return the violations, rule-ordered."""
    violations: list[Violation] = []
    violations.extend(check_registration(modules, integration_map))
    violations.extend(check_declared_roots(modules, integration_map))
    violations.extend(check_import_style(modules, integration_map))
    violations.extend(check_install_hint(modules, integration_map))
    violations.extend(check_no_eager_package_import(modules, integration_map))
    violations.sort(key=lambda violation: (violation.rule, violation.modules))
    return violations