Skip to content

validate

validate

Validate command for Marianne CLI.

This module implements the mzt validate command for comprehensive configuration validation before score execution.

★ Insight ───────────────────────────────────── 1. Multi-layer validation: The validate command performs 3 distinct validation layers: YAML syntax (parseable), Pydantic schema (structural), and extended checks (semantic). Each layer catches different classes of errors.

  1. Exit code convention: Exit codes follow a convention (0=valid, 1=errors, 2=cannot parse). This enables CI/CD integration where scripts can differentiate between "has validation errors" vs "cannot even validate" scenarios.

  2. DAG visualization: When sheet dependencies are configured, the command visualizes the execution graph. This helps users understand parallel execution potential and identify dependency bottlenecks. ─────────────────────────────────────────────────

Classes

Functions

validate

validate(config_file=Argument(..., help='Path to YAML score configuration file', exists=True, readable=True), json_output=Option(False, '--json', '-j', help='Output validation results as JSON'), verbose=Option(False, '--verbose', '-v', help='Show detailed validation output'))

Validate a score configuration file.

Performs comprehensive validation including: - YAML syntax and Pydantic schema validation - Jinja template syntax checking - Path existence verification - Regex pattern compilation - Configuration completeness checks

Exit codes

0: Valid (warnings/info OK) 1: Invalid (one or more errors) 2: Cannot validate (file not found, YAML unparseable)

Source code in src/marianne/cli/commands/validate.py
def validate(
    config_file: Path = typer.Argument(
        ...,
        help="Path to YAML score configuration file",
        exists=True,
        readable=True,
    ),
    json_output: bool = typer.Option(
        False,
        "--json",
        "-j",
        help="Output validation results as JSON",
    ),
    verbose: bool = typer.Option(
        False,
        "--verbose",
        "-v",
        help="Show detailed validation output",
    ),
) -> None:
    """Validate a score configuration file.

    Performs comprehensive validation including:
    - YAML syntax and Pydantic schema validation
    - Jinja template syntax checking
    - Path existence verification
    - Regex pattern compilation
    - Configuration completeness checks

    Exit codes:
      0: Valid (warnings/info OK)
      1: Invalid (one or more errors)
      2: Cannot validate (file not found, YAML unparseable)
    """
    configure_global_logging(console)

    # First try to read and parse YAML
    try:
        raw_yaml = config_file.read_text()
    except Exception as e:
        output_error(
            f"Cannot read score file: {e}",
            hints=["Check that the file exists and you have read permission."],
            json_output=json_output,
        )
        raise typer.Exit(2) from None

    # Try to parse YAML
    try:
        parsed = yaml.safe_load(raw_yaml)
    except yaml.YAMLError as e:
        output_error(
            f"YAML syntax error: {e}",
            hints=["Check for indentation issues or invalid YAML characters."],
            json_output=json_output,
        )
        raise typer.Exit(2) from None

    # Ensure parsed YAML is a mapping (dict), not a scalar or list
    if not isinstance(parsed, dict):
        got_type = type(parsed).__name__ if parsed is not None else "empty file"
        output_error(
            f"Score must be a YAML mapping (key-value pairs), got: {got_type}",
            hints=[
                "A Marianne score needs key-value fields like: name: my-score",
                "Check that your file isn't plain text, a list, or empty.",
                "See: docs/score-writing-guide.md",
            ],
            json_output=json_output,
        )
        raise typer.Exit(2) from None

    # Try Pydantic validation
    try:
        config = JobConfig.from_yaml(config_file)
    except Exception as e:
        hints = _schema_error_hints(str(e))
        output_error(
            f"Schema validation failed: {e}",
            hints=hints,
            json_output=json_output,
        )
        raise typer.Exit(2) from None

    # Show basic info first
    if not json_output:
        console.print(f"\nValidating [cyan]{config.name}[/cyan]...")
        console.print()
        console.print("[green]✓[/green] YAML syntax valid")
        console.print("[green]✓[/green] Schema validation passed (Pydantic)")
        console.print()
        console.print("Running extended validation checks...")

    # Run extended validation checks
    runner = ValidationRunner(create_default_checks())
    issues = runner.validate(config, config_file, raw_yaml)

    # Output results
    reporter = ValidationReporter(console)

    if json_output:
        import json as json_mod

        from marianne.validation.rendering import generate_preview

        # Build combined JSON with validation issues and rendering preview
        validation_data = json_mod.loads(reporter.report_json(issues))
        preview = generate_preview(config, config_file)
        validation_data["rendering"] = reporter.report_rendering_json(preview)
        console.print(
            json_mod.dumps(validation_data, indent=2),
            soft_wrap=True,
            highlight=False,
        )
    else:
        reporter.report_terminal(issues, config.name)

        # Show config summary if no errors
        if not runner.has_errors(issues):
            console.print()
            console.print("[dim]Configuration summary:[/dim]")
            console.print(f"  Sheets: {config.sheet.total_sheets}")
            instrument_display = config.instrument or config.backend.type
            console.print(f"  Instrument: {instrument_display}")
            console.print(f"  Validations: {len(config.validations)}")
            console.print(f"  Notifications: {len(config.notifications)}")

            # Show DAG visualization if dependencies configured (v17 evolution)
            if config.sheet.dependencies:
                _show_dag_visualization(config, verbose)

            # Show rendering preview when validation passes
            from marianne.validation.rendering import generate_preview

            preview = generate_preview(
                config,
                config_file,
                max_sheets=1 if not verbose else None,
            )
            reporter.report_rendering_terminal(preview, verbose=verbose)

            # Report any rendering errors
            for err in preview.render_errors:
                console.print(f"  [red]Rendering error:[/red] {err}")
            for sheet in preview.sheets:
                if sheet.render_error:
                    console.print(
                        f"  [red]Sheet {sheet.sheet_num} render error:[/red]"
                        f" {sheet.render_error}"
                    )

    # Exit with appropriate code
    exit_code = runner.get_exit_code(issues)
    if exit_code != 0:
        raise typer.Exit(exit_code) from None