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 schema validation. Mutate |
|
After schema validation. Inspect the validation report. |
|
After the locator is proposed. Replace or extend |
|
During derived metadata collection. Return a dict to merge into |
|
Just before writing the record. Last chance to mutate metadata. |
|
Just after the record is written. Register side-effects here. |
|
Before committing the transaction. |
|
After committing the transaction (errors here become warnings). |
|
When the operation fails. |
|
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)
For ad hoc notebook workflows, you can also pass any iterable of hook objects
directly as hooks= or plugins=:
catalog = Catalog.open("./my-catalog", hooks=[TitleFromFilenameHook()])
Catalog.open() and Catalog.create() validate hook inputs immediately.
Each supplied hook object must implement at least one supported hook method
listed above, and any matching hook method must be callable. This stricter
validation means inert sentinel objects and no-op objects with no hook methods
are rejected instead of being silently skipped.
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 validationcontext.derived_metadata— mutable during extractioncontext.source_path— source path when availablecontext.planned_locators— list of locators to be resolvedcontext.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.