Skip to content

Catalog administration

Generated from the docstrings. Catalog administration walks through rollback, embargo, the catalog's description and prune with examples. The description's JSON form, description_to_json and description_from_json, is in statcatlib.bundle.

CatalogAdmin

CatalogAdmin(
    *args: object,
    _writer: InventoryWriter | None = None,
    **kwargs: object,
)

Operational changes to a catalog's Inventory: rollback, release, cancel, embargo, description and prune.

Open one with open. Every method but editions, products, embargo and prune writes one Inventory version and returns its number; prune writes at most one and returns a PruneReport.

Refuse construction from outside, with a TypeError naming open.

embargo property

embargo: bool

The newest Inventory version's embargo setting; False before the first version.

open classmethod

open(path: str | Path) -> Self

Open the catalog at path for operational changes.

It reads catalog.json and writes nothing until a method is called.

Parameters:

Name Type Description Default
path str | Path

The catalog directory.

required

Returns:

Type Description
Self

The operational object.

Raises:

Type Description
FileNotFoundError

path is not a catalog.

A legacy layout or other files at path raise IncompatibleCatalogError.

products

products() -> list[str]

Every product the newest Inventory version records, in any state, sorted.

editions

editions(product_id: str) -> list[EditionEntry]

The product's editions in every state, as the newest Inventory version records them.

Retired editions are left out: the Inventory keeps only their tombstones.

Parameters:

Name Type Description Default
product_id str

The product.

required

Returns:

Type Description
list[EditionEntry]

The editions in id order, which is the order they were started

list[EditionEntry]

in; empty for a product the Inventory does not record.

rollback

rollback(
    product_id: str,
    *,
    to: str | None = None,
    if_live: str | None = None,
    reason: str,
) -> int

Withdraw the product's live edition and make a superseded one live again.

The rollback is conditional: it is refused when the live edition is no longer if_live when the version is written, such as after a competing release. A working copy built on the withdrawn edition then fails its seal with NonFastForwardError.

Parameters:

Name Type Description Default
product_id str

The product.

required
to str | None

The superseded edition to make live; the most recent superseded one when None.

None
if_live str | None

The edition expected live; the live edition when the call starts when None.

None
reason str

Why, recorded as the version's event.reason.

required

Returns:

Type Description
int

The Inventory version written (event.kind rollback).

Raises:

Type Description
InventoryRuleError

The product is not recorded (rule="unknown"), has no live edition or a live edition other than if_live (rule="if-live"), or to is not a superseded edition (rule="state" or "unknown").

An edition made live that breaks a cross-product rule raises CrossProductRuleError and writes nothing: a data edition cut from a structure edition that is neither live nor embargoed (rule="full-artefacts"), or a structure edition holding a full artefact another structure product holds live (rule="structure-home").

An empty reason raises ValueError. It acts on the live edition at this sdmxlib's store schema; rolling back another schema's live edition arrives in Phase 4.

release

release(
    product_id: str, edition_id: str, *, reason: str
) -> int

Make an embargoed edition live now, superseding the live edition it was built on.

The release time is this machine's clock (design decision P1).

Parameters:

Name Type Description Default
product_id str

The product.

required
edition_id str

The embargoed edition.

required
reason str

Why, recorded as the version's event.reason.

required

Returns:

Type Description
int

The Inventory version written (event.kind release).

A release that breaks a cross-product rule raises CrossProductRuleError and writes nothing: a data edition whose partial or full artefact matches only an embargoed structure edition (rule="publish-order"; release that one first); a stale data edition, or one whose full artefact with no structure home differs from another data product's live copy (rule="full-artefacts"); a structure edition holding a full artefact another structure product holds live (rule="structure-home").

An edition built on an edition that is no longer live raises NonFastForwardError, whose message names both and the fix. An edition that is not recorded (rule="unknown") or not embargoed (rule="state") raises InventoryRuleError, so a withdrawn edition is never released. An empty reason raises ValueError.

cancel

cancel(
    product_id: str, edition_id: str, *, reason: str
) -> int

Withdraw an embargoed edition before its release.

Parameters:

Name Type Description Default
product_id str

The product.

required
edition_id str

The embargoed edition.

required
reason str

Why, recorded as the version's event.reason.

required

Returns:

Type Description
int

The Inventory version written (event.kind withdraw).

An edition that is not recorded (rule="unknown") or not embargoed (rule="state") raises InventoryRuleError, and an empty reason raises ValueError.

set_embargo

set_embargo(*, on: bool, reason: str) -> int

Turn the catalog's embargo on or off.

With embargo on, a seal records its edition embargoed, and only release makes it live. A seal that computed its record with embargo off and lost the race to this version re-reads the head, and records its edition embargoed.

Parameters:

Name Type Description Default
on bool

The new setting, keyword-only: set_embargo(on=True, reason=...).

required
reason str

Why, recorded as the version's event.reason.

required

Returns:

Type Description
int

The Inventory version written (event.kind embargo-change).

Setting the embargo it already has raises ValueError naming the current state and writes no version; the check runs against the head the version would land on, and the embargo is off before the first version. An empty reason raises ValueError too.

describe

describe(
    description: CatalogDescription, *, reason: str
) -> int

Record the catalog's description in the Inventory root's extensions.statcat.

The section becomes {"format_version": "3", "description": ...}, with the catalog-level fields statcatlib.bundle.description_to_json writes; any other key a later statcatlib put there is kept. Every later version carries the description unchanged, and Catalog.description reads it.

Parameters:

Name Type Description Default
description CatalogDescription

The description.

required
reason str

Why, recorded as the version's event.reason.

required

Returns:

Type Description
int

The Inventory version written (event.kind describe).

An empty reason raises ValueError.

prune

prune(
    *,
    older_than: timedelta,
    reason: str,
    dry_run: bool = True,
) -> PruneReport

Retire old editions and delete the files no edition needs (design decision P7).

It takes one window, older_than, and runs three steps:

  1. It retires the superseded and withdrawn editions whose state_changed_at is older than the window, in one Inventory version (event.kind retire), re-applied to the new head after a lost race.
  2. It deletes the directory of each edition tombstoned for longer than the window.
  3. It deletes a file under a product's data/ when no manifest.json on disk under the product lists it and its mtime is older than the window.

A product with a working copy or a seal journal entry is skipped in all three steps and reported in PruneReport.skipped_products, and so is one with an edition whose manifest cannot be read. Prune deletes no Inventory root or leaf, never expires a tombstone, and never deletes an edition the Inventory does not tombstone, so a live, embargoed or unrecorded edition stays. It never retires a structure edition that a live or embargoed data edition's partial_of cites (specification ยง11.4); when a manifest that decision needs cannot be read, it retires no structure edition. An edition retired in step 1 keeps its directory until a run after its tombstone has aged past the window.

With dry_run=True, the default, it retires and deletes nothing and reports what a real run would do. A real run logs every deleted path at INFO. A deletion that fails is recorded in PruneReport.failed, logged at WARNING, and the run continues; the path is tried again on the next run.

Parameters:

Name Type Description Default
older_than timedelta

The window, a positive timedelta.

required
reason str

Why, recorded as the retire version's event.reason. A dry run requires one too.

required
dry_run bool

Report only, the default; False retires and deletes.

True

Returns:

Type Description
PruneReport

The report.

Raises:

Type Description
ValueError

older_than is not positive, or reason is blank.

A retirement sdmxlib's rule refuses raises InventoryRuleError, and InventoryContentionError follows MAX_RACES lost races; neither deletes anything.

EditionEntry

One edition as the newest Inventory version records it, in any state.

Attributes:

Name Type Description
edition_id str

The edition's id, a UUIDv7.

product_id str

Its product.

previous str | None

The edition it was built on, or None.

state State

embargoed, live, superseded or withdrawn.

manifest_sha256 str

The SHA-256 of its manifest.json.

schema_version str

The sdmxlib store schema it was written at.

state_changed_at datetime

When it entered state.

release_at datetime | None

For an embargoed edition, when it may be released.

PruneReport

What CatalogAdmin.prune retired and deleted, or would have in a dry run.

Every path is /-separated, from the catalog directory.

Attributes:

Name Type Description
retired tuple[str, ...]

Each edition retired, as <product>/<edition_id>.

deleted_dirs tuple[str, ...]

Each tombstoned edition's directory deleted, as products/<product>/<edition_id>.

deleted_files tuple[str, ...]

Each data/ file deleted, as products/<product>/data/....

skipped_products dict[str, str]

Each product left untouched, with the reason: a working copy, a seal journal entry, or a manifest that cannot be read.

failed dict[str, str]

Each path whose deletion failed, with the error. The next run tries it again.

root Path

The catalog directory.

dry_run bool

Whether this is a dry run, which retired and deleted nothing.

seq int | None

The Inventory version the retire commit wrote, or None when nothing was retired or the run was dry.