Plugin Templates
This reference covers the template classes plugin authors subclass and the
members each template expects. Import all symbols from
firecube.ingestor.api.
Plugins that own the complete batch pipeline subclass BaseIngestor directly;
see Hooks & Lifecycle for that surface.
| Name | Description |
|---|---|
register_ingestor |
Register an ingestor class under a public plugin name. |
GenericZarrIngestor |
Thin facade over AppendStrategy for Zarr-based batch ingestion. |
GenericParquetIngestor |
Generic Pipelined Ingestor for Parquet outputs. |
DirectZarrIngestor |
Abstract template for direct-Zarr region-based ingestors. |
GenericTensogramIngestor |
Template for plugins that write directly to Tensogram .tgm archives. |
Registration
firecube.ingestor.api.register_ingestor
Register an ingestor class under a public plugin name.
Applied as a class decorator. The decorated class is added to the
registry consulted by discover_ingestors() and get_ingestor(),
and its name attribute is set to name.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
name
|
str
|
Public name the plugin is looked up by, e.g. the value passed
to |
required |
Returns:
| Type | Description |
|---|---|
Callable[[type[_IngestorT]], type[_IngestorT]]
|
A class decorator that registers the class and returns it unchanged. |
Raises:
| Type | Description |
|---|---|
TypeError
|
If the decorated class does not satisfy the |
Examples:
Register a plugin under the name used by firecube ingest:
@register_ingestor("my_product")
class MyProductIngestor(GenericZarrIngestor):
PRODUCT_NAME = "my_product"
def build_dataset(self, group, items, ctx):
...
Every concrete plugin class must also declare a non-empty
PRODUCT_NAME: ClassVar[str]. Firecube checks this when the class is defined.
Typed options are declared through PluginConfig
and the template config classes listed in the
Configuration Reference.
GenericZarrIngestor
Subclass GenericZarrIngestor and implement build_dataset. The template
appends the returned dataset to the target Zarr store along the time
dimension, once per write group per batch.
firecube.ingestor.api.GenericZarrIngestor
Thin facade over AppendStrategy for Zarr-based batch ingestion.
Resolves URIs/storage, acquires write claims, then delegates all append
logic to AppendStrategy.write_groups().
Subclasses implement build_dataset(group, items, ctx) -> xr.Dataset | None.
Methods:
build_dataset
abstractmethod
Convert a sub-batch of items into an Xarray Dataset for the given group.
items is the time-grouped slice for this batch iteration.
Returns None to skip writing for this group/batch.
The returned dataset must carry the ingestor's time_dim_name
dimension, ordered on that dimension, with values that do not
overlap another batch; it is appended along that dimension.
Variables, dimensions, coordinates, and data types must remain
compatible across batches.
Examples:
Build one dataset per batch from the discovered items:
def build_dataset(self, group, items, ctx):
paths = [ctx.materialize(item) for item in items]
ds = xr.open_mfdataset(paths, combine="by_coords")
return ds[["temperature"]].sortby(self.time_dim_name)
Route variables per group when get_batch_groups declares
more than one; every group receives the same items:
def build_dataset(self, group, items, ctx):
ds = self._open(items, ctx)
if group == "quality":
return ds[["quality_level"]]
return ds[["temperature"]]
get_batch_groups
Return the logical write groups covered by a batch of items (Hook).
A group names a logical write destination: for Zarr templates it
becomes the group path inside the store, for Parquet it becomes the
output partition (subdirectory). Called once per planned batch;
build_dataset is then invoked once per group with the full batch
item list, and the plugin routes items to the group it is building.
Implementations MUST be deterministic and return a stable, sorted list so grouping stays consistent across runs.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
items
|
Sequence[Any]
|
Items assigned to this batch. |
required |
ctx
|
PluginContext
|
Read-only plugin context. |
required |
Returns:
| Type | Description |
|---|---|
list[str]
|
Group names for the batch. Default: |
Examples:
Write two Zarr groups from the same source items. Each group
name becomes the group path in the store, and build_dataset
selects what belongs in the group it is called for:
def get_batch_groups(self, items, ctx):
return ["quality", "sst"]
def build_dataset(self, group, items, ctx):
ds = self._open(items, ctx)
if group == "sst":
return ds[["sea_surface_temperature"]]
return ds[["quality_level"]]
Options are declared through
ZarrTemplateConfig.
Write Groups
get_batch_groups returns the logical write groups for a batch and defaults
to ["default"]. Each group name is used directly as the Zarr group path in
the store, so nested paths such as "sst/quality" are valid. build_dataset
is called once per group per batch and receives the complete batch item list
for every group; the plugin selects per group which variables or items to
return. Returning None skips the group for that batch. The returned list
must be deterministic across runs.
DirectZarrIngestor derives its groups from the declared schema instead; see
DirectZarrIngestor.
Time Dimension
The inherited time_dim_name: ClassVar[str] selects the append dimension and
defaults to "timestamp". It is a class declaration, not a --option field.
When the target store already exists, Firecube verifies the declared dimension
against the store before writing:
firecube.ingestor.api.verify_dim_compatibility
Verify each group in group_paths has a time dim matching declared_dim.
No-op for groups that do not yet exist on disk (they will be created on
first write) and for a target_uri that does not exist at all (new cube).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
target_uri
|
str
|
URI of the (possibly existing) Zarr store to inspect. |
required |
declared_dim
|
str
|
Time dimension name the plugin declares for its arrays. |
required |
group_paths
|
Sequence[str]
|
Group paths to check within the store. |
required |
storage_config
|
Any
|
Storage configuration or binding used to open the store. |
required |
Raises:
| Type | Description |
|---|---|
ConfigurationError
|
If any existing group uses a different time
dimension than |
GenericParquetIngestor
Subclass GenericParquetIngestor and implement build_dataset. The remaining
methods are optional customizations.
firecube.ingestor.api.GenericParquetIngestor
Generic Pipelined Ingestor for Parquet outputs.
Methods:
build_dataset
abstractmethod
Convert a batch of items into an in-memory tabular dataset for the given group.
Supported return types for the default Parquet writer
pyarrow.Tablepandas.DataFrame(if pandas is installed)
Returns None to skip writing for this group/batch.
Unlike the Zarr template, this hook receives the PipelineBatch
itself rather than a list of items.
Examples:
Return one table per batch:
def build_dataset(self, group, batch, ctx):
rows = []
for item in batch.items:
rows.extend(read_detections(ctx.materialize(item)))
if not rows:
return None
return pyarrow.Table.from_pylist(rows)
get_batch_groups
Return the logical write groups for a batch (Hook).
Each group produces one build_dataset call (receiving the full
batch) and one Parquet file; non-default group names become
subdirectories of the dataset root via output_relpath. Must be
deterministic and stable-sorted. Default: ["default"].
output_relpath
Return a relative output path (within the dataset root) for a group/batch.
ParquetTemplateConfig currently declares parquet_partition_by and
parquet_row_group_size, but the default writer does not apply either field;
see the Configuration Reference.
GenericTensogramIngestor
Subclass GenericTensogramIngestor and implement build_dataset. The
template writes each batch through the Tensogram strategy.
firecube.ingestor.api.GenericTensogramIngestor
Template for plugins that write directly to Tensogram .tgm archives.
Subclasses implement build_dataset(group, items, ctx) returning an
xarray.Dataset (or None to skip); the template writes each
group's dataset to the local .tgm target using the options declared
in TensogramTemplateConfig. Remote targets are not supported.
Methods:
build_dataset
abstractmethod
Convert a sub-batch of items into an xarray Dataset.
Returns None to skip writing for this group/batch.
Examples:
Build the dataset the archive writer receives:
def build_dataset(self, group, items, ctx):
paths = [ctx.materialize(item) for item in items]
if not paths:
return None
return xr.open_mfdataset(paths, combine="by_coords")
Options are declared through
TensogramTemplateConfig.
DirectZarrIngestor
Subclass DirectZarrIngestor and implement index_spec, inspect_item,
zarr_schema, and build_write_intents.
firecube.ingestor.api.DirectZarrIngestor
Abstract template for direct-Zarr region-based ingestors.
Plugins that write directly to Zarr (bypassing xarray) should subclass this template and implement:
zarr_schema— declare groups and arrays.build_write_intents— convert a batch into write operations.
The template orchestrates store setup, write execution via a region write strategy, coverage tracking, and metrics aggregation.
Methods:
index_spec
Override to enable slot-range parallel ingestion.
Return an IndexSpec describing the product's index shape, or
None for serial-only plugins (no --slot-start/--slot-end).
index_spec MUST be resolvable from typed config alone. The
implementation may read self.plugin_config and
self.template_config; it MUST NOT depend on ctx.source
contents (source listing, file peek, or --input-data). Reason:
firecube zarr slots and firecube zarr preallocate call
index_spec without any --input-data; if your product's epoch
or size derives from source, expose an explicit config override (e.g.
MyConfig.time_epoch) and raise ConfigurationError naming the
missing config field when the override is absent.
Default returns None (serial-only plugin).
inspect_item
Override to enable slot-range parallel ingestion.
Called by the engine for each source item to determine its slot index.
Return an ItemInfo with the item's time coordinate, or None
to drop the item from this worker's slot range.
Default raises NotImplementedError — override this method to
enable parallel ingestion.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
item
|
Any
|
A source item from the batch. |
required |
ctx
|
PluginContext
|
The plugin context for this run. |
required |
Returns:
| Type | Description |
|---|---|
ItemInfo | None
|
An |
Raises:
| Type | Description |
|---|---|
NotImplementedError
|
If not overridden. |
resolved_index
Return the resolved index for this run, cached per context.
Raises ConfigurationError if index_spec(ctx) returns None
(serial-only plugin).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
ctx
|
PluginContext
|
The plugin context for this run. |
required |
Returns:
| Type | Description |
|---|---|
ResolvedIndex
|
The |
Raises:
| Type | Description |
|---|---|
ConfigurationError
|
If |
zarr_schema
abstractmethod
Declare the Zarr store layout for this ingestor.
Returns a list of group specifications describing every group and array that the ingestor may write to. Called once per batch to ensure groups and arrays exist before writes begin.
The declared groups are also the ingestor's write groups, so the
schema must cover every group any WriteIntent targets.
Examples:
Declare one group with a time-indexed array and its time axis:
def zarr_schema(self, ctx):
n_times = self.resolved_index(ctx).size("FWI")
# Or use a literal for serial mode.
return [
ZarrGroupSpec(
group="FWI",
arrays=[
ZarrArraySpec(
name="fire_risk",
shape=(n_times, 550, 475),
dtype="float32",
chunks=(1, 550, 475),
dimension_names=("timestamp", "y", "x"),
),
ZarrArraySpec(
name="timestamp",
shape=(n_times,),
dtype="int64",
dimension_names=("timestamp",),
),
],
)
]
build_write_intents
Convert a batch into a list of write operations.
Return one flat list that may freely mix two element types:
IndexedWrite— a coordinate-keyed write. Build it withcoordinate=<timestamp or integer key>and the engine resolves the slot index for you; an unmappable coordinate raisesIndexedWriteCompilationErrorbefore any write.WriteIntent— a fully resolved write. Use it when you have computed the index yourself, and for writes that carry no slot coordinate (WriteIntent.coordinateandWriteIntent.static).
Skip an item by not appending anything for it; emit several writes
for one item by appending several elements. Return an empty list to
skip the whole batch. Every element's group must exist in
zarr_schema(ctx), and resolved indexes must fall inside the
worker's slot range when slot-range parallelism is enabled.
Compilation of IndexedWrite elements runs at the call site,
outside this hook, so no override can bypass it.
For every compiled IndexedWrite, the engine also emits the slot's
time-coordinate verify-write automatically (one per resolved slot,
skipped when the list already carries an explicit
WriteIntent.coordinate for that slot), so plugins on this path
never resolve or emit coordinate writes themselves.
Examples:
One coordinate-keyed write per item plus one static array:
def build_write_intents(self, batch, ctx):
out = []
for item in batch.items:
timestamp, values = read_product(ctx.materialize(item))
out.append(IndexedWrite.slot(
group="data", array="value",
coordinate=timestamp, data=values,
))
out.append(WriteIntent.static(
group="grid", array="lat", data=self._lat_grid,
))
return out
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
batch
|
PipelineBatch
|
The pipeline batch to convert. |
required |
ctx
|
PluginContext
|
The plugin context for this run. |
required |
Returns:
| Type | Description |
|---|---|
Sequence[WriteIntent | IndexedWrite]
|
A list mixing |
Raises:
| Type | Description |
|---|---|
NotImplementedError
|
If the plugin does not override this hook. |
get_batch_groups
Return the sorted set of groups declared by zarr_schema(ctx).
On this template the group set is schema-declared, not item-derived:
writes are routed by WriteIntent.group. Plugins should NOT
override this method — a hand-rolled group list can disagree with
the declared schema and break group/schema agreement.
On this template get_batch_groups is derived from the groups declared in
zarr_schema and is not an override point; overriding it breaks the
agreement between groups and schema.
Use resolved_index(ctx).size(group) when the declared index extent controls
array shape, and resolved_index(ctx).position(group, coordinate) when a write
needs the slot index for a timestamp value.
For parallel writes across disjoint slot ranges, see Parallelism.
Schema And Write Types
firecube.ingestor.api.ZarrGroupSpec
dataclass
Specification for a Zarr group and its arrays.
Returned by DirectZarrIngestor.zarr_schema() to describe the full layout.
attrs is optional, convention-agnostic group-level metadata stamped onto
the group's zarr.json at schema setup — e.g. dataset-level attributes a
plugin chooses to publish. Firecube writes the mapping verbatim and does not
interpret it (no convention is assumed); reserved firecube-internal attribute
names are rejected at write time.
firecube.ingestor.api.ZarrArraySpec
dataclass
Specification for a single Zarr array within a group.
Attributes
chunks
class-attribute
instance-attribute
Chunk shape for the array, or None to use the template default.
fill_value
class-attribute
instance-attribute
Fill value written into unused cells when the array is preallocated.
expected_time_count
class-attribute
instance-attribute
Expected number of time slots for time-indexed arrays, if known.
shards
class-attribute
instance-attribute
Optional Zarr sharding shape for the array, or None to disable sharding.
attrs
class-attribute
instance-attribute
Array-level attributes stamped into zarr.json verbatim.
dimension_names
class-attribute
instance-attribute
Dimension names for the array, ordered to match shape.
time_indexed
class-attribute
instance-attribute
Whether the array participates in time-axis preallocation and slot writes.
filters
class-attribute
instance-attribute
Per-array codec filters.
Each entry is a Zarr v3 ArrayArrayCodec config dict. None inherits the
template default; an explicit tuple overrides the template filter pipeline
for this array only.
serializer
class-attribute
instance-attribute
Per-array serializer codec.
This is a Zarr v3 ArrayBytesCodec config dict. None inherits the
template default; an explicit value overrides the template serializer for
this array only.
firecube.ingestor.api.WriteIntent
dataclass
A single write operation to execute against the Zarr store.
Produced by DirectZarrIngestor.build_write_intents() to describe
what data should be written, where, and at which timestamp index.
The kind field selects the write method on RegionZarrWriter.
Here, "timestamp" means the conceptual time/index axis, not the on-disk
dimension name; the latter is configured separately via
IndexedRegionStrategy.time_coord_name.
"region"→write_region(group, array, ts_index, y_slice, data, channel_index=...)"1d"→write_1d(group, array, ts_index, data)"timestamp"→write_timestamp(group, ts_index, timestamp_val)"static"→write_static(group, array_name, data)(non-time-indexed;ts_indexis ignored)
data may be an eager numpy.ndarray or a zero-arg callable that
returns one. It is resolved exactly once at dispatch time, in dispatch
order. The callable must close over stable inputs (paths, configuration),
not open file handles or per-batch scratch objects. Callable data is
supported for kind="region" and kind="static" only. Passing a
callable for other kinds raises TypeError at construction. Any
callable exception propagates with the same error surface as an eager
payload rejection.
Attributes
timestamp_val
class-attribute
instance-attribute
Conceptual time/index axis value for kind="timestamp" writes.
"timestamp" is a stable plugin-contract token, not the on-disk dim name;
the actual dim/coord name comes from IndexedRegionStrategy.time_coord_name.
Methods:
slot
classmethod
Write a 1-D array slice at a single time slot.
Use this for 1-D arrays that grow along the time axis — per-slot
scalars, per-slot vectors, or any array where each slot contributes
one row. The array must be declared with time_indexed=True in
ZarrArraySpec.
data must be an eager np.ndarray; callable payloads are not
supported for kind="1d" and raise TypeError at construction.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
group
|
str
|
Zarr group name matching a |
required |
array
|
str
|
Array name within the group. |
required |
index
|
int
|
Time-slot index for this write. |
required |
data
|
Any
|
Array data to write at this slot. |
required |
Returns:
| Type | Description |
|---|---|
WriteIntent
|
A |
Examples:
region
classmethod
Write a 2-D spatial region at a single time slot.
Use this for the main image arrays — counts, radiances, quality flags,
pixel times — where each slot contributes a spatial tile. The array
must be declared with time_indexed=True in ZarrArraySpec.
data may be an eager np.ndarray or a zero-arg callable
Callable[[], np.ndarray]; the callable is resolved at dispatch time.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
group
|
str
|
Zarr group name matching a |
required |
array
|
str
|
Array name within the group. |
required |
index
|
int
|
Time-slot index for this write. |
required |
data
|
Any
|
2-D array data, or a callable that returns it. |
required |
y_slice
|
slice
|
Row slice within the array. |
required |
channel_index
|
int | None
|
Channel dimension index, or |
None
|
Returns:
| Type | Description |
|---|---|
WriteIntent
|
A |
Examples:
coordinate
classmethod
Write the time-axis coordinate value for a single slot.
Use this to record the actual timestamp (or integer index) that
corresponds to ts_index. The engine writes it into the time
coordinate array so the output cube is self-describing.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
group
|
str
|
Zarr group name matching a |
required |
index
|
int
|
Time-slot index for this coordinate. |
required |
value
|
Any
|
The coordinate value for this slot — typically a
|
required |
Returns:
| Type | Description |
|---|---|
WriteIntent
|
A |
Raises:
| Type | Description |
|---|---|
ValueError
|
If |
Examples:
static
classmethod
Write a static (non-time-indexed) array — coordinate grids, lookup tables, masks.
Use this for arrays that are the same across every time slot: latitude/
longitude grids, channel names, calibration tables, spatial references.
The array must be declared with time_indexed=False in
ZarrArraySpec; the engine pre-creates it at its declared shape
during schema setup.
Write-once contract: the engine writes the array on the first ingest
run and stamps a marker attribute. On any subsequent run (resume or
re-ingest) the incoming data must be byte-identical to what was already
written, or the ingest fails with SchemaDriftError. There is no
partial-update path for static arrays.
data may be an eager np.ndarray or a zero-arg callable
Callable[[], np.ndarray]; the callable is resolved at dispatch time.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
group
|
str
|
Zarr group name matching a |
required |
array
|
str
|
Array name declared with |
required |
data
|
Any
|
Data to write, or a callable that returns it. |
required |
Returns:
| Type | Description |
|---|---|
WriteIntent
|
A |
Raises:
| Type | Description |
|---|---|
SchemaDriftError
|
On resume, if |
TypeError
|
If |
firecube.ingestor.api.IndexedWrite
dataclass
A coordinate-keyed write intent whose slot index is resolved at compile time.
Returned from DirectZarrIngestor.build_write_intents() to describe a
write whose target slot is expressed as a raw coordinate value (typically
a datetime, numpy.datetime64, or integer) rather than a pre-resolved
integer index. The engine maps coordinate to a slot index at compile
time against the plugin's declared IndexSpec, then materializes an
equivalent WriteIntent.
Use the region classmethod for 2-D spatial region writes and
slot for 1-D per-slot writes. These are the only two builders by
design — there is intentionally no .static() or .coordinate()
factory:
- Static (non-time-indexed) arrays remain
WriteIntent.staticbecause they carry no slot coordinate. - Time-coordinate verify-writes are emitted automatically by the engine for every compiled slot; plugins never resolve or emit them on this path.
For region, data may be an eager numpy.ndarray or a zero-arg
callable Callable[[], np.ndarray] resolved exactly once at dispatch
time, in dispatch order, on the same terms as WriteIntent — so large
payloads are not held in memory while the batch is assembled. slot
requires an eager array; a callable is rejected at compile time.
Attributes
coordinate
instance-attribute
Raw slot key resolved to an integer index at compile time.
Typically a datetime, numpy.datetime64, or int. The engine
dispatches the value through the plugin's declared IndexSpec;
unresolvable coordinates raise at compile time. Must not be None.
data
instance-attribute
Array payload, or a zero-arg callable that returns one.
Callable payloads are resolved exactly once at dispatch time under the
same rules as WriteIntent — the callable must close over stable
inputs (paths, configuration), not open file handles or per-batch scratch.
y_slice
class-attribute
instance-attribute
Row slice within the array; required for region, None for slot.
channel_index
class-attribute
instance-attribute
Channel dimension index for region writes, None otherwise.
Methods:
region
classmethod
Build an indexed 2-D spatial region write.
Use for the main image arrays — counts, radiances, quality flags,
pixel times — where each slot contributes a spatial tile. The array
must be declared with time_indexed=True in ZarrArraySpec.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
group
|
str
|
Zarr group name matching a |
required |
array
|
str
|
Array name within the group. |
required |
coordinate
|
Any
|
Raw slot key (e.g. |
required |
data
|
ndarray | Callable[[], ndarray] | Any
|
2-D array data, or a zero-arg callable that returns it. |
required |
y_slice
|
slice
|
Row slice within the array. |
required |
channel_index
|
int | None
|
Channel dimension index, or |
None
|
Returns:
| Type | Description |
|---|---|
IndexedWrite
|
An |
Examples:
slot
classmethod
Build an indexed 1-D per-slot write.
Use for 1-D arrays that grow along the time axis — per-slot scalars,
per-slot vectors, or any array where each slot contributes one row.
The array must be declared with time_indexed=True in
ZarrArraySpec.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
group
|
str
|
Zarr group name matching a |
required |
array
|
str
|
Array name within the group. |
required |
coordinate
|
Any
|
Raw slot key (e.g. |
required |
data
|
ndarray | Callable[[], ndarray] | Any
|
Eager 1-D array payload. Unlike |
required |
Returns:
| Type | Description |
|---|---|
IndexedWrite
|
An |
Examples: