Skip to content

symfonic.cli.component_dependencies

component_dependencies

What each scaffold component cannot be generated without.

A component whose templates import another component's package is not independently selectable: symfonic init --components fastapi,chats wrote app/chats/router.py, which imports app.auth.deps, into an app/ that had no auth/ in it. validate_components checked that every name was known and nothing else, so the CLI accepted the selection, generated the files, and the adopter met the problem at their first import.

Kept beside :mod:symfonic.cli.components rather than inside it because it answers a different question -- not "what components exist" but "which of them depend on which" -- and because the adopter-validation gate reads this declaration to build its covering set of scaffold variants. A separate module gives that reader one file to parse instead of a catalog to search.

unsatisfied_dependencies

unsatisfied_dependencies(components: list[str]) -> str

The failure message for components, or "" when it is satisfiable.

Refuses rather than auto-includes. Adding auth because chats was asked for generates a project the adopter did not ask for and did not read about, and leaves the components they got different from the ones they typed. The message names what to add instead.

Source code in src/symfonic/cli/component_dependencies.py
def unsatisfied_dependencies(components: list[str]) -> str:
    """The failure message for ``components``, or ``""`` when it is satisfiable.

    Refuses rather than auto-includes. Adding ``auth`` because ``chats`` was
    asked for generates a project the adopter did not ask for and did not read
    about, and leaves the components they got different from the ones they
    typed. The message names what to add instead.
    """
    # Callers refuse unknown names before reaching here: a name nobody
    # recognises cannot be reported as an unsatisfied dependency without
    # guessing what it was meant to be.
    selected = set(components)
    missing: list[str] = []
    for component in components:
        for needed in COMPONENT_DEPENDENCIES.get(component, ()):
            if needed not in selected and needed not in missing:
                missing.append(needed)
    if missing:
        wanted = ", ".join(sorted(selected | set(missing)))
        needs = ", ".join(
            f"{c} requires {' and '.join(COMPONENT_DEPENDENCIES[c])}"
            for c in components
            if any(n not in selected for n in COMPONENT_DEPENDENCIES.get(c, ()))
        )
        return (
            f"Unsatisfied component dependencies: {needs}. Without them the "
            f"generated project does not import -- its routers reach into an "
            f"app/ that was never written. Add them: --components {wanted}"
        )
    return ""