Hooks and plugins

Hooks let you add domain-specific behaviour to the catalog operation lifecycle without modifying ogcat core.

Hook protocols

Each hook is a plain Python class that implements one or more of the following methods. You only need to implement the methods you care about.

Method

When it runs

before_validate_metadata(context)

Before schema validation. Mutate context.user_metadata here.

after_validate_metadata(context, report)

After schema validation. Inspect the validation report.

resolve_artifact_locator(context)

After the locator is proposed. Replace or extend context.planned_locators.

extract_metadata(context)

During derived metadata collection. Return a dict to merge into context.derived_metadata.

before_record_write(context)

Just before writing the record. Last chance to mutate metadata.

after_record_write(context)

Just after the record is written. Register side-effects here.

before_commit(context)

Before committing the transaction.

after_commit(context)

After committing the transaction (errors here become warnings).

on_error(context, error)

When the operation fails.

on_rollback(context, error)

After rollback has run.

Registering hooks

Pass a plugin registry when creating or opening a catalog:

from ogcat import Catalog, CatalogSpec, PluginRegistry
from ogcat.hooks import OperationContext


class TitleFromFilenameHook:
    """Set title from the source filename when the caller did not supply one."""

    def before_validate_metadata(self, context: OperationContext) -> None:
        if context.source_path is not None:
            context.user_metadata.setdefault("title", context.source_path.stem)


spec = CatalogSpec(catalog_name="files")
plugins = PluginRegistry([TitleFromFilenameHook()])
catalog = Catalog.create("./my-catalog", spec, plugins=plugins)

You can also register hooks after catalog creation:

catalog.hook_manager.register(TitleFromFilenameHook())

Metadata extractor hooks

Implement extract_metadata to add derived metadata:

class ChecksumHook:
    """Compute a SHA-256 checksum and store it in derived metadata."""

    def extract_metadata(self, context: OperationContext) -> dict | None:
        if context.source_path is None:
            return None
        import hashlib
        digest = hashlib.sha256(context.source_path.read_bytes()).hexdigest()
        return {"sha256": digest}

OperationContext

OperationContext is the mutable object passed to every hook. The most commonly used fields are:

  • context.user_metadata — mutable before validation

  • context.derived_metadata — mutable during extraction

  • context.source_path — source path when available

  • context.planned_locators — list of locators to be resolved

  • context.operation_type — name of the current operation

Warnings

Hooks can record non-fatal warnings with context.add_warning():

context.add_warning("title was derived from filename", hook_name="TitleFromFilenameHook")

Warnings appear in after_commit errors and are attached to the operation context. They do not prevent the record from being written.

A complete example

See the intermediate tutorial for a full worked example including a custom extractor hook and smoke tests.