Skip to content

symfonic.devtools.archgate.dry

dry

DRY invariants: one invocation pipeline, one normalizer.

Two rule families, because "duplicate path" has two shapes and each is invisible to the other check:

  • single-source — a second module reaches the chokepoint. Detected from import records: a non-owner importing InvocationKernel has just opened a second way into the pipeline, whether or not it copied any code.
  • single-definition — a second module defines the chokepoint. Detected from top-level definitions: a copy-pasted normalize_config imports nothing and would sail through every import rule ever written.

check_single_definition

check_single_definition(facts: dict[str, ModuleFacts], rules: tuple[SingleDefinitionRule, ...]) -> list[Violation]

Non-owner modules that define a declared chokepoint name at top level.

Source code in src/symfonic/devtools/archgate/dry.py
def check_single_definition(
    facts: dict[str, ModuleFacts], rules: tuple[SingleDefinitionRule, ...]
) -> list[Violation]:
    """Non-owner modules that define a declared chokepoint name at top level."""
    violations: list[Violation] = []
    for rule in rules:
        for name in sorted(facts):
            if owns(rule.owners, name):
                continue
            defined = [
                definition
                for definition in facts[name].definitions
                if definition.kind == "function"
                and any(fnmatch.fnmatchcase(definition.name, pat) for pat in rule.names)
            ]
            if not defined:
                continue
            listed = ", ".join(
                f"{definition.name} (line {definition.lineno})" for definition in defined
            )
            violations.append(
                Violation(
                    rule=rule.rule,
                    message=(
                        f"{rule.id}: {name} defines {listed}{rule.description}. "
                        f"Declared owners: {', '.join(rule.owners)}"
                    ),
                    modules=(name,),
                )
            )
    return violations

check_single_source

check_single_source(modules: dict[str, ModuleInfo], rules: tuple[SingleSourceRule, ...]) -> list[Violation]

Non-owner modules that import a declared chokepoint.

Source code in src/symfonic/devtools/archgate/dry.py
def check_single_source(
    modules: dict[str, ModuleInfo], rules: tuple[SingleSourceRule, ...]
) -> list[Violation]:
    """Non-owner modules that import a declared chokepoint."""
    violations: list[Violation] = []
    for rule in rules:
        for name in sorted(modules):
            if owns(rule.owners, name):
                continue
            reaches = [
                record
                for record in modules[name].imports
                if not record.type_checking
                and not (record.lazy and rule.allow_lazy)
                and any(_hits_target(target, record.target) for target in rule.targets)
            ]
            if not reaches:
                continue
            first = min(reaches, key=lambda record: record.lineno)
            violations.append(
                Violation(
                    rule=rule.rule,
                    message=(
                        f"{rule.id}: {name}:{first.lineno} imports {first.target} — "
                        f"{rule.description}. Declared owners: {', '.join(rule.owners)}"
                    ),
                    modules=(name,),
                )
            )
    return violations

owns

owns(patterns: tuple[str, ...], module: str) -> bool

Owner patterns are dotted prefixes, or globs when they contain wildcards.

Source code in src/symfonic/devtools/archgate/dry.py
def owns(patterns: tuple[str, ...], module: str) -> bool:
    """Owner patterns are dotted prefixes, or globs when they contain wildcards."""
    for pattern in patterns:
        if any(ch in pattern for ch in "*?["):
            if fnmatch.fnmatchcase(module, pattern):
                return True
        elif module == pattern or module.startswith(pattern + "."):
            return True
    return False