firecube 0.1.4__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- firecube/__init__.py +23 -0
- firecube/__main__.py +20 -0
- firecube/_version.py +46 -0
- firecube/cli/_audience.py +135 -0
- firecube/cli/_command_schemas.py +112 -0
- firecube/cli/_ctx.py +85 -0
- firecube/cli/_errors.py +115 -0
- firecube/cli/_formatter.py +195 -0
- firecube/cli/_product.py +47 -0
- firecube/cli/_rename_hints.py +123 -0
- firecube/cli/_shared_options.py +247 -0
- firecube/cli/_slot_env.py +102 -0
- firecube/cli/_slot_planning.py +95 -0
- firecube/cli/_typed_options.py +181 -0
- firecube/cli/_uri_policy.py +110 -0
- firecube/cli/advise.py +321 -0
- firecube/cli/archive.py +870 -0
- firecube/cli/catalog.py +196 -0
- firecube/cli/chunks/__init__.py +74 -0
- firecube/cli/chunks/_claims.py +229 -0
- firecube/cli/chunks/_common.py +84 -0
- firecube/cli/chunks/_delete.py +376 -0
- firecube/cli/chunks/_list.py +334 -0
- firecube/cli/chunks/_manager.py +161 -0
- firecube/cli/chunks/_runs.py +294 -0
- firecube/cli/chunks/_snapshots.py +165 -0
- firecube/cli/completion.py +73 -0
- firecube/cli/main.py +866 -0
- firecube/cli/parquet.py +289 -0
- firecube/cli/plugins/__init__.py +28 -0
- firecube/cli/plugins/commands.py +262 -0
- firecube/cli/plugins/introspect.py +304 -0
- firecube/cli/plugins/mgmt.py +198 -0
- firecube/cli/plugins/registry.py +132 -0
- firecube/cli/zarr.py +1043 -0
- firecube/core/__init__.py +14 -0
- firecube/core/api.py +88 -0
- firecube/core/cf/__init__.py +23 -0
- firecube/core/cf/check_ids.py +126 -0
- firecube/core/cf/report.py +57 -0
- firecube/core/cf/validator.py +313 -0
- firecube/core/config.py +307 -0
- firecube/core/controlplane/__init__.py +47 -0
- firecube/core/controlplane/_event_processor.py +136 -0
- firecube/core/controlplane/_helpers.py +114 -0
- firecube/core/controlplane/_projection.py +253 -0
- firecube/core/controlplane/_snapshot.py +215 -0
- firecube/core/controlplane/_wal_reader.py +402 -0
- firecube/core/controlplane/_wal_writer.py +589 -0
- firecube/core/controlplane/claims.py +294 -0
- firecube/core/controlplane/deletion.py +704 -0
- firecube/core/controlplane/events.py +219 -0
- firecube/core/controlplane/manager.py +990 -0
- firecube/core/controlplane/metrics.py +97 -0
- firecube/core/controlplane/repo.py +804 -0
- firecube/core/controlplane/repo_utils.py +73 -0
- firecube/core/controlplane/time_dim.py +111 -0
- firecube/core/controlplane/types.py +445 -0
- firecube/core/credentials.py +36 -0
- firecube/core/duckdb/__init__.py +27 -0
- firecube/core/duckdb/bridge.py +82 -0
- firecube/core/duckdb/io.py +30 -0
- firecube/core/errors.py +74 -0
- firecube/core/filesystem/__init__.py +60 -0
- firecube/core/filesystem/_obstore_compat.py +60 -0
- firecube/core/filesystem/fsspec_backend.py +308 -0
- firecube/core/filesystem/instrumentation.py +260 -0
- firecube/core/filesystem/obstore_backend.py +406 -0
- firecube/core/filesystem/ops.py +412 -0
- firecube/core/filesystem/protocol.py +130 -0
- firecube/core/filesystem/store_factory.py +149 -0
- firecube/core/formats/__init__.py +46 -0
- firecube/core/formats/_iso.py +84 -0
- firecube/core/formats/discovery.py +165 -0
- firecube/core/formats/hdf5.py +137 -0
- firecube/core/formats/netcdf.py +226 -0
- firecube/core/formats/zip.py +192 -0
- firecube/core/intake.py +530 -0
- firecube/core/observability/__init__.py +72 -0
- firecube/core/observability/_state.py +24 -0
- firecube/core/observability/logging.py +147 -0
- firecube/core/observability/metrics.py +230 -0
- firecube/core/observability/telemetry/__init__.py +87 -0
- firecube/core/observability/telemetry/pushgateway.py +339 -0
- firecube/core/observability/telemetry/sinks.py +155 -0
- firecube/core/observability/tracing.py +126 -0
- firecube/core/product/__init__.py +45 -0
- firecube/core/product/identity.py +76 -0
- firecube/core/product/resolver.py +121 -0
- firecube/core/product/target.py +120 -0
- firecube/core/runtime.py +187 -0
- firecube/core/slot_index.py +205 -0
- firecube/core/storage/__init__.py +45 -0
- firecube/core/storage/binding.py +39 -0
- firecube/core/storage/cache_key.py +40 -0
- firecube/core/storage/completion.py +245 -0
- firecube/core/storage/driver_config.py +125 -0
- firecube/core/storage/results.py +30 -0
- firecube/core/storage/session.py +431 -0
- firecube/core/storage/transfer.py +142 -0
- firecube/core/storage/uri.py +173 -0
- firecube/core/tensogram/__init__.py +37 -0
- firecube/core/tensogram/_compat.py +36 -0
- firecube/core/tensogram/controlplane_codec.py +230 -0
- firecube/core/tensogram/converter.py +390 -0
- firecube/core/tensogram/metadata.py +274 -0
- firecube/core/tensogram/restore.py +303 -0
- firecube/core/tensogram/schema.py +93 -0
- firecube/core/uris.py +182 -0
- firecube/core/workspaces.py +68 -0
- firecube/core/zarr/__init__.py +69 -0
- firecube/core/zarr/_reserved_attrs.py +47 -0
- firecube/core/zarr/_reserved_root_attrs.py +74 -0
- firecube/core/zarr/io.py +88 -0
- firecube/core/zarr/layers.py +184 -0
- firecube/core/zarr/multires.py +178 -0
- firecube/core/zarr/region_writer.py +782 -0
- firecube/core/zarr/scrub.py +200 -0
- firecube/core/zarr/state.py +258 -0
- firecube/core/zarr/time_decode.py +71 -0
- firecube/core/zarr/validation.py +622 -0
- firecube/ingestor/__init__.py +27 -0
- firecube/ingestor/api.py +215 -0
- firecube/ingestor/config/__init__.py +19 -0
- firecube/ingestor/config/coercion.py +157 -0
- firecube/ingestor/config/engine.py +188 -0
- firecube/ingestor/contracts/__init__.py +14 -0
- firecube/ingestor/contracts/interfaces.py +235 -0
- firecube/ingestor/devtools/_templates/__init__.py +14 -0
- firecube/ingestor/devtools/_templates/ingestor_base.py.tpl +72 -0
- firecube/ingestor/devtools/_templates/ingestor_direct_zarr.py.tpl +66 -0
- firecube/ingestor/devtools/_templates/ingestor_parquet.py.tpl +59 -0
- firecube/ingestor/devtools/_templates/ingestor_zarr.py.tpl +55 -0
- firecube/ingestor/devtools/_templates/readme.md.tpl +40 -0
- firecube/ingestor/devtools/scaffolding.py +229 -0
- firecube/ingestor/errors.py +79 -0
- firecube/ingestor/extensions/_binning.py +152 -0
- firecube/ingestor/extensions/duck.py +153 -0
- firecube/ingestor/extensions/grid.py +287 -0
- firecube/ingestor/extensions/healpix.py +330 -0
- firecube/ingestor/registry/__init__.py +14 -0
- firecube/ingestor/registry/loader.py +144 -0
- firecube/ingestor/registry/metadata.py +97 -0
- firecube/ingestor/registry/version_compat.py +95 -0
- firecube/ingestor/runtime/__init__.py +14 -0
- firecube/ingestor/runtime/aggregation.py +112 -0
- firecube/ingestor/runtime/base.py +836 -0
- firecube/ingestor/runtime/base_hooks.py +129 -0
- firecube/ingestor/runtime/batching.py +161 -0
- firecube/ingestor/runtime/configure.py +195 -0
- firecube/ingestor/runtime/coverage.py +177 -0
- firecube/ingestor/runtime/engine.py +793 -0
- firecube/ingestor/runtime/parallel_evidence.py +75 -0
- firecube/ingestor/runtime/parallel_execution_state.py +28 -0
- firecube/ingestor/runtime/parallel_gate.py +164 -0
- firecube/ingestor/runtime/parallel_run_id.py +48 -0
- firecube/ingestor/runtime/preflight.py +40 -0
- firecube/ingestor/runtime/recording.py +425 -0
- firecube/ingestor/runtime/resume_guard.py +556 -0
- firecube/ingestor/runtime/resume_types.py +65 -0
- firecube/ingestor/runtime/telemetry.py +171 -0
- firecube/ingestor/runtime/tensogram/__init__.py +14 -0
- firecube/ingestor/runtime/tensogram/strategy.py +164 -0
- firecube/ingestor/runtime/workspace.py +220 -0
- firecube/ingestor/runtime/zarr/__init__.py +34 -0
- firecube/ingestor/runtime/zarr/append.py +466 -0
- firecube/ingestor/runtime/zarr/append_services.py +650 -0
- firecube/ingestor/runtime/zarr/batch_runner.py +190 -0
- firecube/ingestor/runtime/zarr/contracts.py +58 -0
- firecube/ingestor/runtime/zarr/existing_cube_check.py +282 -0
- firecube/ingestor/runtime/zarr/resume_cache.py +92 -0
- firecube/ingestor/runtime/zarr/staged_metadata.py +249 -0
- firecube/ingestor/runtime/zarr/strategies/__init__.py +15 -0
- firecube/ingestor/runtime/zarr/strategies/append.py +172 -0
- firecube/ingestor/runtime/zarr/strategies/indexed_region.py +426 -0
- firecube/ingestor/runtime/zarr/write.py +212 -0
- firecube/ingestor/runtime/zarr/write_context.py +139 -0
- firecube/ingestor/templates/__init__.py +19 -0
- firecube/ingestor/templates/config.py +118 -0
- firecube/ingestor/templates/direct_zarr.py +790 -0
- firecube/ingestor/templates/generic.py +462 -0
- firecube/ingestor/templates/generic_tensogram.py +170 -0
- firecube/ingestor/types/__init__.py +19 -0
- firecube/ingestor/types/config.py +89 -0
- firecube/ingestor/types/context.py +409 -0
- firecube/ingestor/types/manifest.py +53 -0
- firecube/ingestor/types/planned_range.py +152 -0
- firecube/ingestor/types/result_metrics.py +386 -0
- firecube/ingestor/utils/__init__.py +14 -0
- firecube/ingestor/utils/duckdb_utils.py +235 -0
- firecube/ingestor/validation.py +55 -0
- firecube-0.1.4.dist-info/METADATA +242 -0
- firecube-0.1.4.dist-info/RECORD +197 -0
- firecube-0.1.4.dist-info/WHEEL +4 -0
- firecube-0.1.4.dist-info/entry_points.txt +2 -0
- firecube-0.1.4.dist-info/licenses/AUTHORS.md +5 -0
- firecube-0.1.4.dist-info/licenses/LICENSE +201 -0
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
# Copyright 2025-2026 EUMETSAT
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
15
|
+
"""Protocols and shared abstractions for the Firecube ingestion SDK.
|
|
16
|
+
|
|
17
|
+
This module defines *interfaces* (Protocols) used by the ingestion engine and
|
|
18
|
+
plugin implementations. It intentionally contains no runtime logic.
|
|
19
|
+
|
|
20
|
+
Key concepts:
|
|
21
|
+
- `SourceFile`: a local-or-remote, file-like input abstraction.
|
|
22
|
+
- `Ingestor`: the minimal plugin contract (run an ingestion with an
|
|
23
|
+
`IngestContext`).
|
|
24
|
+
- `PipelineHost`: the contract implemented by an ingestor that can be executed
|
|
25
|
+
by the pipeline runner (callbacks + batch processing).
|
|
26
|
+
|
|
27
|
+
Plugins should prefer importing public types from `firecube.ingestor.api` rather
|
|
28
|
+
than deep-importing from `firecube.ingestor.*`.
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
from __future__ import annotations
|
|
32
|
+
|
|
33
|
+
import inspect
|
|
34
|
+
import logging
|
|
35
|
+
from collections.abc import Iterable, Mapping, Sequence
|
|
36
|
+
from pathlib import Path
|
|
37
|
+
from typing import Any, BinaryIO, ClassVar, Protocol, runtime_checkable
|
|
38
|
+
|
|
39
|
+
from firecube.core.observability.telemetry import IngestionTelemetry
|
|
40
|
+
from firecube.ingestor.types.context import (
|
|
41
|
+
IngestContext,
|
|
42
|
+
IngestResult,
|
|
43
|
+
PipelineBatch,
|
|
44
|
+
PipelineResult,
|
|
45
|
+
PipelineRunState,
|
|
46
|
+
PluginContext,
|
|
47
|
+
RuntimeIngestContext,
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
@runtime_checkable
|
|
52
|
+
class DatasetProducer(Protocol):
|
|
53
|
+
"""Protocol for batch templates that produce in-memory datasets.
|
|
54
|
+
|
|
55
|
+
Tensogram output is selected by this structural capability rather than by
|
|
56
|
+
requiring a specific Zarr base class. Implementations share the
|
|
57
|
+
``build_dataset`` and ``get_batch_groups`` hooks provided by
|
|
58
|
+
``BaseIngestor``.
|
|
59
|
+
"""
|
|
60
|
+
|
|
61
|
+
def build_dataset(self, group: str, items: list[Any], ctx: PluginContext) -> Any | None:
|
|
62
|
+
"""Convert a sub-batch into a dataset for ``group``."""
|
|
63
|
+
...
|
|
64
|
+
|
|
65
|
+
def get_batch_groups(self, items: Sequence[Any], ctx: PluginContext) -> list[str]:
|
|
66
|
+
"""Return logical dataset groups/messages for ``items``."""
|
|
67
|
+
...
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def is_dataset_producer(cls: type) -> bool:
|
|
71
|
+
"""Return True iff *cls* genuinely satisfies the ``DatasetProducer`` contract.
|
|
72
|
+
|
|
73
|
+
``@runtime_checkable`` Protocol's ``issubclass`` / ``isinstance`` checks
|
|
74
|
+
validate method-name presence only — Python deliberately does NOT check
|
|
75
|
+
signatures. That is structurally unsound for ``DatasetProducer`` because
|
|
76
|
+
``GenericParquetIngestor`` declares
|
|
77
|
+
``build_dataset(self, group, batch: PipelineBatch, ctx)`` which collides
|
|
78
|
+
with the protocol's ``build_dataset(self, group, items: list, ctx)`` by
|
|
79
|
+
name but is invoked incompatibly by ``TensogramWriteStrategy`` (which
|
|
80
|
+
passes a ``list`` of timestamps, not a ``PipelineBatch``).
|
|
81
|
+
|
|
82
|
+
This helper adds a signature-shape check: the second positional parameter
|
|
83
|
+
after ``group`` must NOT be named ``batch`` (the parquet convention).
|
|
84
|
+
Plugins using ``*args`` are accepted.
|
|
85
|
+
"""
|
|
86
|
+
if not issubclass(cls, DatasetProducer):
|
|
87
|
+
return False
|
|
88
|
+
try:
|
|
89
|
+
sig = inspect.signature(cls.build_dataset)
|
|
90
|
+
except (TypeError, ValueError):
|
|
91
|
+
return False
|
|
92
|
+
positional = [
|
|
93
|
+
p
|
|
94
|
+
for p in sig.parameters.values()
|
|
95
|
+
if p.kind
|
|
96
|
+
in (
|
|
97
|
+
inspect.Parameter.POSITIONAL_ONLY,
|
|
98
|
+
inspect.Parameter.POSITIONAL_OR_KEYWORD,
|
|
99
|
+
)
|
|
100
|
+
]
|
|
101
|
+
if len(positional) < 3:
|
|
102
|
+
return any(p.kind is inspect.Parameter.VAR_POSITIONAL for p in sig.parameters.values())
|
|
103
|
+
return positional[2].name != "batch"
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
@runtime_checkable
|
|
107
|
+
class SourceFile(Protocol):
|
|
108
|
+
"""Abstraction for a file-like object that may be local or remote.
|
|
109
|
+
|
|
110
|
+
This allows plugins to accept S3 objects as if they were local files,
|
|
111
|
+
supporting standard python file operations (seek, read).
|
|
112
|
+
"""
|
|
113
|
+
|
|
114
|
+
@property
|
|
115
|
+
def uri(self) -> str:
|
|
116
|
+
"""The canonical URI of the source file."""
|
|
117
|
+
...
|
|
118
|
+
|
|
119
|
+
def open(self) -> BinaryIO:
|
|
120
|
+
"""Open the file in binary read mode. Must be seekable."""
|
|
121
|
+
...
|
|
122
|
+
|
|
123
|
+
def local_path(self) -> Path | None:
|
|
124
|
+
"""Return a local Path if one exists efficiently.
|
|
125
|
+
|
|
126
|
+
Returns None for remote files (unless they are explicitly materialized).
|
|
127
|
+
This allows optimizations for libraries that prefer pathlib.Path only when cheap.
|
|
128
|
+
"""
|
|
129
|
+
...
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
@runtime_checkable
|
|
133
|
+
class Ingestor(Protocol):
|
|
134
|
+
"""Protocol defining the core contract for an ingestor."""
|
|
135
|
+
|
|
136
|
+
def run(self, ctx: IngestContext) -> IngestResult: ...
|
|
137
|
+
|
|
138
|
+
def ingest(self, ctx: IngestContext) -> IngestResult: ...
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
class PipelineHost(Protocol):
|
|
142
|
+
"""Protocol for classes that can be executed by the pipeline runner.
|
|
143
|
+
|
|
144
|
+
The runtime pipeline executor expects these attributes and lifecycle hooks
|
|
145
|
+
to exist on the host object (typically an ingestor instance).
|
|
146
|
+
"""
|
|
147
|
+
|
|
148
|
+
name: str
|
|
149
|
+
_log: logging.Logger
|
|
150
|
+
|
|
151
|
+
def _create_batches(
|
|
152
|
+
self, ctx: RuntimeIngestContext, batch_size: int
|
|
153
|
+
) -> Iterable[PipelineBatch]: ...
|
|
154
|
+
def _aggregate_metrics(
|
|
155
|
+
self, ctx: RuntimeIngestContext, state: PipelineRunState
|
|
156
|
+
) -> Mapping[str, Any]: ...
|
|
157
|
+
|
|
158
|
+
# Lifecycle hooks called by PipelineRunner
|
|
159
|
+
def on_pipeline_start(self, ctx: PluginContext, state: PipelineRunState) -> None: ...
|
|
160
|
+
def on_batch_success(
|
|
161
|
+
self,
|
|
162
|
+
ctx: PluginContext,
|
|
163
|
+
state: PipelineRunState,
|
|
164
|
+
batch: PipelineBatch,
|
|
165
|
+
result: PipelineResult,
|
|
166
|
+
) -> None: ...
|
|
167
|
+
def on_batch_failure(
|
|
168
|
+
self,
|
|
169
|
+
ctx: PluginContext,
|
|
170
|
+
state: PipelineRunState,
|
|
171
|
+
batch: PipelineBatch,
|
|
172
|
+
result: PipelineResult,
|
|
173
|
+
) -> None: ...
|
|
174
|
+
def finalize_pipeline(
|
|
175
|
+
self, ctx: RuntimeIngestContext, state: PipelineRunState
|
|
176
|
+
) -> IngestResult: ...
|
|
177
|
+
def _process_batch(self, batch: PipelineBatch, ctx: PluginContext) -> PipelineResult: ...
|
|
178
|
+
def _resolve_time_dim_name(self) -> str:
|
|
179
|
+
"""Resolve the firecube time-dim coordinate name for this ingestor.
|
|
180
|
+
|
|
181
|
+
Default ``"timestamp"``; plugins may override via
|
|
182
|
+
``time_dim_name: ClassVar[str]`` on their BaseIngestor subclass.
|
|
183
|
+
Consumed by the runtime engine to seed coordinate-array chunks
|
|
184
|
+
in staged mode.
|
|
185
|
+
"""
|
|
186
|
+
...
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
@runtime_checkable
|
|
190
|
+
class SlotRangeCapable(Protocol):
|
|
191
|
+
"""Protocol for hosts that support slot-range parallel ingestion.
|
|
192
|
+
|
|
193
|
+
The engine dispatches to the slot-range scheduling path when the host
|
|
194
|
+
structurally satisfies this protocol *and* ``SUPPORTS_SLOT_RANGE_PARALLELISM``
|
|
195
|
+
is True. Plugins opt in by setting the class variable and implementing the
|
|
196
|
+
three methods below; they do not need to subclass any particular template.
|
|
197
|
+
"""
|
|
198
|
+
|
|
199
|
+
SUPPORTS_SLOT_RANGE_PARALLELISM: ClassVar[bool]
|
|
200
|
+
|
|
201
|
+
def timestamp_to_ts_index(self, group: str, timestamp_val: Any) -> int:
|
|
202
|
+
"""Globally deterministic mapping from the conceptual time axis to ts_index."""
|
|
203
|
+
...
|
|
204
|
+
|
|
205
|
+
def global_expected_time_count(self, ctx: PluginContext) -> dict[str, int] | None:
|
|
206
|
+
"""Return max ts_index + 1 per group across the planned run."""
|
|
207
|
+
...
|
|
208
|
+
|
|
209
|
+
def filter_items_to_slot_range(
|
|
210
|
+
self,
|
|
211
|
+
items: Sequence[Any],
|
|
212
|
+
slot_start: int,
|
|
213
|
+
slot_end: int,
|
|
214
|
+
ctx: PluginContext,
|
|
215
|
+
) -> Sequence[Any]:
|
|
216
|
+
"""Filter source items to those whose ts_index falls in [slot_start, slot_end)."""
|
|
217
|
+
...
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
__all__ = [
|
|
221
|
+
"DatasetProducer",
|
|
222
|
+
"IngestContext",
|
|
223
|
+
"IngestResult",
|
|
224
|
+
"IngestionTelemetry",
|
|
225
|
+
"Ingestor",
|
|
226
|
+
"PipelineBatch",
|
|
227
|
+
"PipelineHost",
|
|
228
|
+
"PipelineResult",
|
|
229
|
+
"PipelineRunState",
|
|
230
|
+
"PluginContext",
|
|
231
|
+
"RuntimeIngestContext",
|
|
232
|
+
"SlotRangeCapable",
|
|
233
|
+
"SourceFile",
|
|
234
|
+
"is_dataset_producer",
|
|
235
|
+
]
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# Copyright 2025-2026 EUMETSAT
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
# Copyright 2025-2026 EUMETSAT
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
15
|
+
"""Ingestor for {plugin_name}."""
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
from collections.abc import Iterable
|
|
19
|
+
from dataclasses import dataclass
|
|
20
|
+
from typing import ClassVar
|
|
21
|
+
|
|
22
|
+
from firecube.core.api import discover_input_files
|
|
23
|
+
from firecube.ingestor.api import (
|
|
24
|
+
BaseIngestor,
|
|
25
|
+
PipelineBatch,
|
|
26
|
+
PipelineResult,
|
|
27
|
+
PipelineRunState,
|
|
28
|
+
PluginConfig,
|
|
29
|
+
PluginContext,
|
|
30
|
+
RuntimeIngestContext,
|
|
31
|
+
merge_batch_metrics,
|
|
32
|
+
register_ingestor,
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
@dataclass
|
|
37
|
+
class {class_name}Config(PluginConfig):
|
|
38
|
+
"""Plugin configuration.
|
|
39
|
+
|
|
40
|
+
To accept ``--option key=value`` flags, add dataclass fields here, e.g.::
|
|
41
|
+
|
|
42
|
+
my_option: str = "default"
|
|
43
|
+
batch_threshold: int = 100
|
|
44
|
+
|
|
45
|
+
Fields appear under ``[PLUGIN]`` in ``firecube plugins describe {plugin_name}``.
|
|
46
|
+
See docs/concepts/plugins/create-a-plugin.md for details.
|
|
47
|
+
"""
|
|
48
|
+
|
|
49
|
+
pass
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
@register_ingestor("{plugin_name}")
|
|
53
|
+
class {class_name}(BaseIngestor):
|
|
54
|
+
PRODUCT_NAME: ClassVar[str] = "{plugin_name}"
|
|
55
|
+
plugin_config_class = {class_name}Config
|
|
56
|
+
|
|
57
|
+
def discover_source_files(self, ctx: PluginContext) -> Iterable[str]:
|
|
58
|
+
# TODO: adjust include_suffixes / sniffing as needed for your input format.
|
|
59
|
+
files = discover_input_files(ctx.source, recursive=True)
|
|
60
|
+
return [str(p) for p in files]
|
|
61
|
+
|
|
62
|
+
def _process_batch(self, batch: PipelineBatch, ctx: PluginContext) -> PipelineResult:
|
|
63
|
+
raise NotImplementedError(
|
|
64
|
+
"{class_name}._process_batch(): implement this hook to process a batch of "
|
|
65
|
+
"source items and return a PipelineResult with outputs=OutputPaths(primary=...). "
|
|
66
|
+
"See docs/concepts/plugins/base-ingestor.md for the contract and examples."
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
def _aggregate_metrics(
|
|
70
|
+
self, ctx: RuntimeIngestContext, state: PipelineRunState
|
|
71
|
+
) -> dict[str, object]:
|
|
72
|
+
return merge_batch_metrics(ctx, state)
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# Copyright 2025-2026 EUMETSAT
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
15
|
+
"""Direct Zarr ingestor for {plugin_name}."""
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
from dataclasses import dataclass
|
|
19
|
+
from typing import ClassVar
|
|
20
|
+
|
|
21
|
+
from firecube.ingestor.api import (
|
|
22
|
+
DirectZarrIngestor,
|
|
23
|
+
PipelineBatch,
|
|
24
|
+
PluginConfig,
|
|
25
|
+
PluginContext,
|
|
26
|
+
WriteIntent,
|
|
27
|
+
ZarrArraySpec,
|
|
28
|
+
ZarrGroupSpec,
|
|
29
|
+
register_ingestor,
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
@dataclass
|
|
34
|
+
class {class_name}Config(PluginConfig):
|
|
35
|
+
"""Plugin configuration.
|
|
36
|
+
|
|
37
|
+
To accept ``--option key=value`` flags, add dataclass fields here.
|
|
38
|
+
See docs/concepts/plugins/create-a-plugin.md.
|
|
39
|
+
"""
|
|
40
|
+
|
|
41
|
+
pass
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
@register_ingestor("{plugin_name}")
|
|
45
|
+
class {class_name}(DirectZarrIngestor):
|
|
46
|
+
PRODUCT_NAME: ClassVar[str] = "{plugin_name}"
|
|
47
|
+
plugin_config_class = {class_name}Config
|
|
48
|
+
|
|
49
|
+
def zarr_schema(self, ctx: PluginContext) -> list[ZarrGroupSpec]:
|
|
50
|
+
raise NotImplementedError(
|
|
51
|
+
"{class_name}.zarr_schema(): implement this hook to declare the Zarr store "
|
|
52
|
+
"layout. Return a list[ZarrGroupSpec] describing groups, arrays, shapes, "
|
|
53
|
+
"dtypes, and chunks. The previous scaffold hard-coded fake shape "
|
|
54
|
+
"(1000, 100, 100) float32 here — you must replace that with your real schema. "
|
|
55
|
+
"See docs/concepts/plugins/direct-zarr.md for examples."
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
def build_write_intents(
|
|
59
|
+
self, batch: PipelineBatch, ctx: PluginContext
|
|
60
|
+
) -> list[WriteIntent]:
|
|
61
|
+
raise NotImplementedError(
|
|
62
|
+
"{class_name}.build_write_intents(): implement this hook to convert a batch "
|
|
63
|
+
"into a list[WriteIntent] describing region writes, 1-D writes, or timestamp "
|
|
64
|
+
"writes. Return an empty list to intentionally skip a batch. "
|
|
65
|
+
"See docs/concepts/plugins/direct-zarr.md for the WriteIntent API."
|
|
66
|
+
)
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# Copyright 2025-2026 EUMETSAT
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
15
|
+
"""Generic Parquet ingestor for {plugin_name}."""
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
from dataclasses import dataclass
|
|
19
|
+
from typing import Any, ClassVar
|
|
20
|
+
|
|
21
|
+
from firecube.ingestor.api import (
|
|
22
|
+
GenericParquetIngestor,
|
|
23
|
+
PipelineBatch,
|
|
24
|
+
PluginConfig,
|
|
25
|
+
PluginContext,
|
|
26
|
+
register_ingestor,
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
@dataclass
|
|
31
|
+
class {class_name}Config(PluginConfig):
|
|
32
|
+
"""Plugin configuration.
|
|
33
|
+
|
|
34
|
+
To accept ``--option key=value`` flags, add dataclass fields here.
|
|
35
|
+
See docs/concepts/plugins/create-a-plugin.md.
|
|
36
|
+
"""
|
|
37
|
+
|
|
38
|
+
pass
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
@register_ingestor("{plugin_name}")
|
|
42
|
+
class {class_name}(GenericParquetIngestor):
|
|
43
|
+
PRODUCT_NAME: ClassVar[str] = "{plugin_name}"
|
|
44
|
+
plugin_config_class = {class_name}Config
|
|
45
|
+
|
|
46
|
+
def build_dataset(
|
|
47
|
+
self, group: str, batch: PipelineBatch, ctx: PluginContext
|
|
48
|
+
) -> Any | None:
|
|
49
|
+
# Return a pyarrow.Table or pandas.DataFrame for this group/batch.
|
|
50
|
+
# Returning None skips writing for this group/batch (intentional skip).
|
|
51
|
+
#
|
|
52
|
+
# If you choose pandas.DataFrame, add `pandas` to your pyproject.toml
|
|
53
|
+
# dependencies.
|
|
54
|
+
raise NotImplementedError(
|
|
55
|
+
"{class_name}.build_dataset(): implement this hook to convert a batch of "
|
|
56
|
+
"source items into a pyarrow.Table or pandas.DataFrame for the given group. "
|
|
57
|
+
"Return None to intentionally skip this group/batch. "
|
|
58
|
+
"See docs/concepts/plugins/generic-parquet.md for examples."
|
|
59
|
+
)
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# Copyright 2025-2026 EUMETSAT
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
15
|
+
"""Generic Zarr ingestor for {plugin_name}."""
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
from dataclasses import dataclass
|
|
19
|
+
from typing import Any, ClassVar
|
|
20
|
+
|
|
21
|
+
import xarray as xr
|
|
22
|
+
|
|
23
|
+
from firecube.ingestor.api import (
|
|
24
|
+
GenericZarrIngestor,
|
|
25
|
+
PluginConfig,
|
|
26
|
+
PluginContext,
|
|
27
|
+
register_ingestor,
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
@dataclass
|
|
32
|
+
class {class_name}Config(PluginConfig):
|
|
33
|
+
"""Plugin configuration.
|
|
34
|
+
|
|
35
|
+
To accept ``--option key=value`` flags, add dataclass fields here.
|
|
36
|
+
See docs/concepts/plugins/create-a-plugin.md.
|
|
37
|
+
"""
|
|
38
|
+
|
|
39
|
+
pass
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
@register_ingestor("{plugin_name}")
|
|
43
|
+
class {class_name}(GenericZarrIngestor):
|
|
44
|
+
PRODUCT_NAME: ClassVar[str] = "{plugin_name}"
|
|
45
|
+
plugin_config_class = {class_name}Config
|
|
46
|
+
|
|
47
|
+
def build_dataset(
|
|
48
|
+
self, group: str, items: list[Any], ctx: PluginContext
|
|
49
|
+
) -> xr.Dataset | None:
|
|
50
|
+
raise NotImplementedError(
|
|
51
|
+
"{class_name}.build_dataset(): implement this hook to convert a batch of "
|
|
52
|
+
"source items into an xarray.Dataset for the given group. Return None to "
|
|
53
|
+
"intentionally skip writing this group/batch. "
|
|
54
|
+
"See docs/concepts/plugins/generic-zarr.md for examples."
|
|
55
|
+
)
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# {start_case_name} Ingestor Plugin
|
|
2
|
+
|
|
3
|
+
> ⚠️ **This scaffold is incomplete.** You must implement `{hook_summary}` before this plugin will produce output. Until then, ingestion runs will raise `NotImplementedError`.
|
|
4
|
+
|
|
5
|
+
## What this plugin does
|
|
6
|
+
|
|
7
|
+
<!-- TODO: write a one-line summary of what {start_case_name} ingests and emits. -->
|
|
8
|
+
|
|
9
|
+
## Development
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
uv sync
|
|
13
|
+
uv run pytest
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
## Install into firecube
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
uv run firecube plugins install --editable .
|
|
20
|
+
uv run firecube plugins describe {plugin_name}
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
`firecube plugins describe` should list `[ENGINE]` options. If you add plugin-specific options via a `PluginConfig` subclass, they will appear under `[PLUGIN]`.
|
|
24
|
+
|
|
25
|
+
## After you implement the hook
|
|
26
|
+
|
|
27
|
+
Once `{hook_summary}` is implemented, run:
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
uv run firecube ingest {plugin_name} \
|
|
31
|
+
--input-data /path/to/your/input \
|
|
32
|
+
--target file:///tmp/{plugin_name}_out.{output_extension} \
|
|
33
|
+
--product-name {plugin_name} \
|
|
34
|
+
--storage-type local \
|
|
35
|
+
--storage-driver fsspec \
|
|
36
|
+
--output-format {output_format} \
|
|
37
|
+
--write-mode {write_mode_default}
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
For S3 targets, swap `--storage-type local` for `--storage-type s3` and use an `s3://bucket/key.{output_extension}` URI.
|