Skip to content

rendering

rendering

Rendering preview engine for enhanced validation.

Generates previews of how sheets will be rendered at execution time, including expanded prompts, validation paths, and fan-out metadata. Used by mzt validate to show what each sheet will look like before actually running the job.

Attributes

Classes

ExpandedValidation dataclass

ExpandedValidation(index, type, description, raw_path, expanded_path, pattern, condition, applicable)

A single validation rule with expanded paths and applicability.

SheetPreview dataclass

SheetPreview(sheet_num, item_range, rendered_prompt, prompt_snippet, expanded_validations, stage, instance, fan_count, render_error)

Preview of a single sheet's rendered state.

RenderingPreview dataclass

RenderingPreview(sheets, total_sheets, has_fan_out, has_dependencies, render_errors=list())

Complete rendering preview for all sheets in a job.

Functions

generate_preview

generate_preview(config, config_path, *, max_sheets=None)

Generate a rendering preview for all sheets in a job.

Builds each sheet's context, renders its prompt, expands validation paths, and evaluates condition applicability — all without executing anything.

Parameters:

Name Type Description Default
config JobConfig

Parsed job configuration.

required
config_path Path

Path to the YAML config file (for relative path resolution).

required
max_sheets int | None

Cap the number of sheets to preview. None = all.

None

Returns:

Name Type Description
A RenderingPreview

class:RenderingPreview with per-sheet details.

Source code in src/marianne/validation/rendering.py
def generate_preview(
    config: JobConfig,
    config_path: Path,
    *,
    max_sheets: int | None = None,
) -> RenderingPreview:
    """Generate a rendering preview for all sheets in a job.

    Builds each sheet's context, renders its prompt, expands validation
    paths, and evaluates condition applicability — all without executing
    anything.

    Args:
        config: Parsed job configuration.
        config_path: Path to the YAML config file (for relative path resolution).
        max_sheets: Cap the number of sheets to preview. ``None`` = all.

    Returns:
        A :class:`RenderingPreview` with per-sheet details.
    """
    _ = config_path  # available for future relative-path resolution

    total_sheets = config.sheet.total_sheets
    preview_count = min(total_sheets, max_sheets) if max_sheets else total_sheets

    has_fan_out = bool(config.sheet.fan_out_stage_map)
    has_dependencies = bool(config.sheet.dependencies)

    builder = PromptBuilder(config.prompt)
    render_errors: list[str] = []
    sheet_previews: list[SheetPreview] = []

    for sheet_num in range(1, preview_count + 1):
        # 1. Build SheetContext
        context = builder.build_sheet_context(
            sheet_num=sheet_num,
            total_sheets=total_sheets,
            sheet_size=config.sheet.size,
            total_items=config.sheet.total_items,
            start_item=config.sheet.start_item,
            workspace=config.workspace,
        )

        # Populate fan-out metadata
        fan_meta = config.sheet.get_fan_out_metadata(sheet_num)
        context.stage = fan_meta.stage
        context.instance = fan_meta.instance
        context.fan_count = fan_meta.fan_count
        context.total_stages = config.sheet.total_stages

        # 2. Resolve prelude/cadenza files
        injection_items: list[InjectionItem] = list(config.sheet.prelude)
        cadenza_items = config.sheet.cadenzas.get(sheet_num, [])
        injection_items.extend(cadenza_items)

        if injection_items:
            template_vars = context.to_dict()
            inj_warnings = _resolve_injections_preview(
                context, injection_items, template_vars
            )
            render_errors.extend(inj_warnings)

        # 3. Render template
        rendered_prompt: str | None = None
        render_error: str | None = None
        try:
            rendered_prompt = builder.build_sheet_prompt(context)
        except jinja2.TemplateError as exc:
            render_error = str(exc)
            render_errors.append(
                f"Sheet {sheet_num}: template render failed — {exc}"
            )

        # 4. Build snippet
        snippet = ""
        if rendered_prompt:
            snippet = _build_snippet(rendered_prompt)
        elif render_error:
            snippet = f"[render error: {render_error}]"

        # 5. Expand validation paths and check applicability
        # Start with user-defined prompt.variables (lowest precedence),
        # then overlay built-in variables so they always win.
        path_context: dict[str, str] = {
            str(k): str(v) for k, v in config.prompt.variables.items()
        }
        path_context.update({
            "workspace": str(config.workspace),
            SHEET_NUM_KEY: str(sheet_num),
            "start_item": str(context.start_item),
            "end_item": str(context.end_item),
            "stage": str(context.stage if context.stage > 0 else sheet_num),
            "instance": str(context.instance),
            "fan_count": str(context.fan_count),
            "total_sheets": str(total_sheets),
            "total_stages": str(
                context.total_stages if context.total_stages > 0 else total_sheets
            ),
        })

        condition_context: dict[str, int] = {
            SHEET_NUM_KEY: sheet_num,
            "start_item": context.start_item,
            "end_item": context.end_item,
            "stage": context.stage if context.stage > 0 else sheet_num,
            "instance": context.instance,
            "fan_count": context.fan_count,
            "total_sheets": total_sheets,
            "total_stages": (
                context.total_stages if context.total_stages > 0 else total_sheets
            ),
        }

        expanded_validations: list[ExpandedValidation] = []
        for idx, rule in enumerate(config.validations):
            expanded_path: str | None = None
            if rule.path:
                try:
                    expanded_path = _expand_path(rule.path, path_context)
                except (KeyError, ValueError):
                    expanded_path = rule.path  # leave raw on error

            applicable = _check_condition(rule.condition, condition_context)

            expanded_validations.append(
                ExpandedValidation(
                    index=idx,
                    type=rule.type,
                    description=rule.description,
                    raw_path=rule.path,
                    expanded_path=expanded_path,
                    pattern=rule.pattern,
                    condition=rule.condition,
                    applicable=applicable,
                )
            )

        sheet_previews.append(
            SheetPreview(
                sheet_num=sheet_num,
                item_range=(context.start_item, context.end_item),
                rendered_prompt=rendered_prompt,
                prompt_snippet=snippet,
                expanded_validations=expanded_validations,
                stage=fan_meta.stage if has_fan_out else None,
                instance=fan_meta.instance if has_fan_out else None,
                fan_count=fan_meta.fan_count if has_fan_out else None,
                render_error=render_error,
            )
        )

    return RenderingPreview(
        sheets=sheet_previews,
        total_sheets=total_sheets,
        has_fan_out=has_fan_out,
        has_dependencies=has_dependencies,
        render_errors=render_errors,
    )