Skip to content

climate_ref.cli.test_cases.migrate #

ref test-cases migrate-manifests.

A one-shot, idempotent maintenance command that backfills every committed manifest.json to the current SCHEMA_VERSION, stamping each case's diagnostic_version from the diagnostic's in-code Diagnostic.version only when the field is absent. An already-recorded diagnostic_version is preserved, so re-running never re-stamps a stale bundle and subverts the gate's staleness check.

It iterates the same _iter_test_cases the CI gate uses, so its coverage is definitionally aligned with the gate (iterator parity). A pre-bump manifest is schema-1 and Manifest.load would reject it, so the raw JSON is read directly and re-serialised through Manifest(...).dump to keep the output byte-identical to what a later mint would write.

migrate_manifests(ctx, provider=None, diagnostic=None, test_case=None) #

Backfill every committed manifest.json to the current schema.

For each test case with an existing manifest, backfills diagnostic_version from the diagnostic's in-code Diagnostic.version when it is absent (an already-recorded value is preserved) and rewrites the manifest at the current SCHEMA_VERSION. The operation is idempotent: re-running on an already-migrated manifest preserves the recorded value and produces identical bytes.

Examples:

ref test-cases migrate-manifests
ref test-cases migrate-manifests --provider ilamb
Source code in packages/climate-ref/src/climate_ref/cli/test_cases/migrate.py
@app.command(name="migrate-manifests")
def migrate_manifests(
    ctx: typer.Context,
    provider: Annotated[
        str | None,
        typer.Option(help="Limit the migration to a single provider slug"),
    ] = None,
    diagnostic: Annotated[
        str | None,
        typer.Option(help="Limit the migration to a single diagnostic slug"),
    ] = None,
    test_case: Annotated[
        str | None,
        typer.Option(help="Limit the migration to a single test case name"),
    ] = None,
) -> None:
    """
    Backfill every committed ``manifest.json`` to the current schema.

    For each test case with an existing manifest, backfills ``diagnostic_version``
    from the diagnostic's in-code ``Diagnostic.version`` when it is absent (an
    already-recorded value is preserved) and rewrites the manifest at the current
    ``SCHEMA_VERSION``. The operation is idempotent: re-running on an already-migrated
    manifest preserves the recorded value and produces identical bytes.

    Examples
    --------
        ref test-cases migrate-manifests
        ref test-cases migrate-manifests --provider ilamb
    """
    from climate_ref.provider_registry import ProviderRegistry
    from climate_ref_core.regression.manifest import SCHEMA_VERSION, Manifest, NativeEntry
    from climate_ref_core.testing import TestCasePaths

    config: Config = ctx.obj.config
    db = ctx.obj.database
    console: Console = ctx.obj.console

    registry = ProviderRegistry.build_from_config(config, db)
    _validate_provider_in_registry(registry, provider)
    _validate_requested_filters(registry, provider=provider, diagnostic=diagnostic, test_case=test_case)
    cases = list(_iter_test_cases(registry, provider=provider, diagnostic=diagnostic, test_case=test_case))

    migrated = 0
    skipped = 0

    for diag, tc in cases:
        case_id = f"{diag.provider.slug}/{diag.slug}/{tc.name}"
        paths = TestCasePaths.from_diagnostic(diag, tc.name)
        if paths is None or not paths.manifest.exists():
            skipped += 1
            continue

        # Manifest.load rejects a schema-1 manifest, so read raw JSON and re-construct.
        data = json.loads(paths.manifest.read_text(encoding="utf-8"))
        native = {
            relpath: NativeEntry(sha256=entry["sha256"], size=entry["size"])
            for relpath, entry in data["native"].items()
        }
        # Preserve an already-recorded value so this stays a pure backfill.
        # After an authorised bump the manifest lags the in-code version until a re-mint,
        # and re-stamping it would mask the gate's staleness check.
        diagnostic_version = data.get("diagnostic_version", diag.version)
        manifest = Manifest(
            schema=SCHEMA_VERSION,
            test_case_version=data["test_case_version"],
            diagnostic_version=diagnostic_version,
            committed=dict(data["committed"]),
            native=native,
            catalog_hash=data.get("catalog_hash"),
        )
        # Serialise via dump so the bytes (sort_keys + unconditional `catalog_hash: null`)
        # match exactly what a later mint would write.
        manifest.dump(paths.manifest)
        migrated += 1
        logger.info(f"Migrated {case_id} -> schema {SCHEMA_VERSION}, diagnostic_version {diagnostic_version}")

    console.print()
    console.print(
        f"[green]Migrated {migrated} manifest(s)[/green]"
        + (f"; skipped {skipped} case(s) with no manifest" if skipped else "")
    )