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(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(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(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(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(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(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(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
|