Source code for cursus.cli.config_cli

"""
CLI commands for inspecting the configuration a pipeline DAG requires.

`cursus config requirements` loads a DAG JSON and reports the configuration fields each
node needs (name, type, required, default, description) — the stateless introspection
side of ``api.factory.DAGConfigFactory``. Engine imports are lazy.

Note: actually *generating* a populated config set is an interactive, stateful workflow
(DAGConfigFactory.set_base_config / set_step_config across many calls, then
generate_all_configs), which does not map to a single one-shot command — use the
Python API or the config widget for that. This command covers the "what do I need to
provide?" question.

Examples:
    cursus config requirements dag.json
    cursus config requirements dag.json --step XGBoostTraining
    cursus config requirements dag.json --format json
"""

import logging

import click

from ._shared import echo_json

logging.basicConfig(level=logging.INFO, format="%(levelname)s: %(message)s")
logger = logging.getLogger(__name__)


@click.group(name="config")
def config_cli():
    """Inspect the configuration a pipeline DAG requires."""


@config_cli.command(name="requirements")
@click.argument("dag_file", type=click.Path(exists=True, dir_okay=False))
@click.option(
    "--step",
    "step_name",
    help="Show requirements for one step only (default: base + all steps).",
)
@click.option(
    "--format",
    type=click.Choice(["text", "json"], case_sensitive=False),
    default="text",
    help="Output format.",
)
def requirements(dag_file: str, step_name, format):
    """
    Show the configuration fields a DAG (or one of its steps) requires.

    DAG_FILE: path to a serialized DAG JSON file.
    """
    fmt = format.lower()
    try:
        from ..api.dag import import_dag_from_json
        from ..api.factory import DAGConfigFactory

        dag = import_dag_from_json(dag_file)
        factory = DAGConfigFactory(dag)

        if step_name:
            data = {
                "step": step_name,
                "fields": factory.get_step_requirements(step_name),
            }
        else:
            data = {
                "base": factory.get_base_config_requirements(),
                "steps": {
                    node: factory.get_step_requirements(node) for node in dag.nodes
                },
            }
    except Exception as e:
        click.echo(
            f"❌ Failed to read config requirements from '{dag_file}': {e}", err=True
        )
        logger.error("config requirements error", exc_info=True)
        raise SystemExit(1)

    if fmt == "json":
        echo_json(data)
        return

    def _print_fields(fields):
        for f in fields:
            req = "required" if f.get("required") else "optional"
            line = f"    - {f.get('name')} ({f.get('type')}) [{req}]"
            click.echo(line)
            if f.get("description"):
                click.echo(f"        {f['description']}")

    if step_name:
        click.echo(f"Config requirements for step '{step_name}':")
        _print_fields(data["fields"])
        return

    click.echo(f"Config requirements for DAG: {dag_file}\n")
    click.echo("Base pipeline config:")
    _print_fields(data["base"])
    click.echo("\nPer-step config:")
    for node, fields in data["steps"].items():
        click.echo(f"  {node}:")
        _print_fields(fields)


[docs] def main(): """Main entry point for the config CLI.""" return config_cli()
if __name__ == "__main__": main()