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.
Files changed (197) hide show
  1. firecube/__init__.py +23 -0
  2. firecube/__main__.py +20 -0
  3. firecube/_version.py +46 -0
  4. firecube/cli/_audience.py +135 -0
  5. firecube/cli/_command_schemas.py +112 -0
  6. firecube/cli/_ctx.py +85 -0
  7. firecube/cli/_errors.py +115 -0
  8. firecube/cli/_formatter.py +195 -0
  9. firecube/cli/_product.py +47 -0
  10. firecube/cli/_rename_hints.py +123 -0
  11. firecube/cli/_shared_options.py +247 -0
  12. firecube/cli/_slot_env.py +102 -0
  13. firecube/cli/_slot_planning.py +95 -0
  14. firecube/cli/_typed_options.py +181 -0
  15. firecube/cli/_uri_policy.py +110 -0
  16. firecube/cli/advise.py +321 -0
  17. firecube/cli/archive.py +870 -0
  18. firecube/cli/catalog.py +196 -0
  19. firecube/cli/chunks/__init__.py +74 -0
  20. firecube/cli/chunks/_claims.py +229 -0
  21. firecube/cli/chunks/_common.py +84 -0
  22. firecube/cli/chunks/_delete.py +376 -0
  23. firecube/cli/chunks/_list.py +334 -0
  24. firecube/cli/chunks/_manager.py +161 -0
  25. firecube/cli/chunks/_runs.py +294 -0
  26. firecube/cli/chunks/_snapshots.py +165 -0
  27. firecube/cli/completion.py +73 -0
  28. firecube/cli/main.py +866 -0
  29. firecube/cli/parquet.py +289 -0
  30. firecube/cli/plugins/__init__.py +28 -0
  31. firecube/cli/plugins/commands.py +262 -0
  32. firecube/cli/plugins/introspect.py +304 -0
  33. firecube/cli/plugins/mgmt.py +198 -0
  34. firecube/cli/plugins/registry.py +132 -0
  35. firecube/cli/zarr.py +1043 -0
  36. firecube/core/__init__.py +14 -0
  37. firecube/core/api.py +88 -0
  38. firecube/core/cf/__init__.py +23 -0
  39. firecube/core/cf/check_ids.py +126 -0
  40. firecube/core/cf/report.py +57 -0
  41. firecube/core/cf/validator.py +313 -0
  42. firecube/core/config.py +307 -0
  43. firecube/core/controlplane/__init__.py +47 -0
  44. firecube/core/controlplane/_event_processor.py +136 -0
  45. firecube/core/controlplane/_helpers.py +114 -0
  46. firecube/core/controlplane/_projection.py +253 -0
  47. firecube/core/controlplane/_snapshot.py +215 -0
  48. firecube/core/controlplane/_wal_reader.py +402 -0
  49. firecube/core/controlplane/_wal_writer.py +589 -0
  50. firecube/core/controlplane/claims.py +294 -0
  51. firecube/core/controlplane/deletion.py +704 -0
  52. firecube/core/controlplane/events.py +219 -0
  53. firecube/core/controlplane/manager.py +990 -0
  54. firecube/core/controlplane/metrics.py +97 -0
  55. firecube/core/controlplane/repo.py +804 -0
  56. firecube/core/controlplane/repo_utils.py +73 -0
  57. firecube/core/controlplane/time_dim.py +111 -0
  58. firecube/core/controlplane/types.py +445 -0
  59. firecube/core/credentials.py +36 -0
  60. firecube/core/duckdb/__init__.py +27 -0
  61. firecube/core/duckdb/bridge.py +82 -0
  62. firecube/core/duckdb/io.py +30 -0
  63. firecube/core/errors.py +74 -0
  64. firecube/core/filesystem/__init__.py +60 -0
  65. firecube/core/filesystem/_obstore_compat.py +60 -0
  66. firecube/core/filesystem/fsspec_backend.py +308 -0
  67. firecube/core/filesystem/instrumentation.py +260 -0
  68. firecube/core/filesystem/obstore_backend.py +406 -0
  69. firecube/core/filesystem/ops.py +412 -0
  70. firecube/core/filesystem/protocol.py +130 -0
  71. firecube/core/filesystem/store_factory.py +149 -0
  72. firecube/core/formats/__init__.py +46 -0
  73. firecube/core/formats/_iso.py +84 -0
  74. firecube/core/formats/discovery.py +165 -0
  75. firecube/core/formats/hdf5.py +137 -0
  76. firecube/core/formats/netcdf.py +226 -0
  77. firecube/core/formats/zip.py +192 -0
  78. firecube/core/intake.py +530 -0
  79. firecube/core/observability/__init__.py +72 -0
  80. firecube/core/observability/_state.py +24 -0
  81. firecube/core/observability/logging.py +147 -0
  82. firecube/core/observability/metrics.py +230 -0
  83. firecube/core/observability/telemetry/__init__.py +87 -0
  84. firecube/core/observability/telemetry/pushgateway.py +339 -0
  85. firecube/core/observability/telemetry/sinks.py +155 -0
  86. firecube/core/observability/tracing.py +126 -0
  87. firecube/core/product/__init__.py +45 -0
  88. firecube/core/product/identity.py +76 -0
  89. firecube/core/product/resolver.py +121 -0
  90. firecube/core/product/target.py +120 -0
  91. firecube/core/runtime.py +187 -0
  92. firecube/core/slot_index.py +205 -0
  93. firecube/core/storage/__init__.py +45 -0
  94. firecube/core/storage/binding.py +39 -0
  95. firecube/core/storage/cache_key.py +40 -0
  96. firecube/core/storage/completion.py +245 -0
  97. firecube/core/storage/driver_config.py +125 -0
  98. firecube/core/storage/results.py +30 -0
  99. firecube/core/storage/session.py +431 -0
  100. firecube/core/storage/transfer.py +142 -0
  101. firecube/core/storage/uri.py +173 -0
  102. firecube/core/tensogram/__init__.py +37 -0
  103. firecube/core/tensogram/_compat.py +36 -0
  104. firecube/core/tensogram/controlplane_codec.py +230 -0
  105. firecube/core/tensogram/converter.py +390 -0
  106. firecube/core/tensogram/metadata.py +274 -0
  107. firecube/core/tensogram/restore.py +303 -0
  108. firecube/core/tensogram/schema.py +93 -0
  109. firecube/core/uris.py +182 -0
  110. firecube/core/workspaces.py +68 -0
  111. firecube/core/zarr/__init__.py +69 -0
  112. firecube/core/zarr/_reserved_attrs.py +47 -0
  113. firecube/core/zarr/_reserved_root_attrs.py +74 -0
  114. firecube/core/zarr/io.py +88 -0
  115. firecube/core/zarr/layers.py +184 -0
  116. firecube/core/zarr/multires.py +178 -0
  117. firecube/core/zarr/region_writer.py +782 -0
  118. firecube/core/zarr/scrub.py +200 -0
  119. firecube/core/zarr/state.py +258 -0
  120. firecube/core/zarr/time_decode.py +71 -0
  121. firecube/core/zarr/validation.py +622 -0
  122. firecube/ingestor/__init__.py +27 -0
  123. firecube/ingestor/api.py +215 -0
  124. firecube/ingestor/config/__init__.py +19 -0
  125. firecube/ingestor/config/coercion.py +157 -0
  126. firecube/ingestor/config/engine.py +188 -0
  127. firecube/ingestor/contracts/__init__.py +14 -0
  128. firecube/ingestor/contracts/interfaces.py +235 -0
  129. firecube/ingestor/devtools/_templates/__init__.py +14 -0
  130. firecube/ingestor/devtools/_templates/ingestor_base.py.tpl +72 -0
  131. firecube/ingestor/devtools/_templates/ingestor_direct_zarr.py.tpl +66 -0
  132. firecube/ingestor/devtools/_templates/ingestor_parquet.py.tpl +59 -0
  133. firecube/ingestor/devtools/_templates/ingestor_zarr.py.tpl +55 -0
  134. firecube/ingestor/devtools/_templates/readme.md.tpl +40 -0
  135. firecube/ingestor/devtools/scaffolding.py +229 -0
  136. firecube/ingestor/errors.py +79 -0
  137. firecube/ingestor/extensions/_binning.py +152 -0
  138. firecube/ingestor/extensions/duck.py +153 -0
  139. firecube/ingestor/extensions/grid.py +287 -0
  140. firecube/ingestor/extensions/healpix.py +330 -0
  141. firecube/ingestor/registry/__init__.py +14 -0
  142. firecube/ingestor/registry/loader.py +144 -0
  143. firecube/ingestor/registry/metadata.py +97 -0
  144. firecube/ingestor/registry/version_compat.py +95 -0
  145. firecube/ingestor/runtime/__init__.py +14 -0
  146. firecube/ingestor/runtime/aggregation.py +112 -0
  147. firecube/ingestor/runtime/base.py +836 -0
  148. firecube/ingestor/runtime/base_hooks.py +129 -0
  149. firecube/ingestor/runtime/batching.py +161 -0
  150. firecube/ingestor/runtime/configure.py +195 -0
  151. firecube/ingestor/runtime/coverage.py +177 -0
  152. firecube/ingestor/runtime/engine.py +793 -0
  153. firecube/ingestor/runtime/parallel_evidence.py +75 -0
  154. firecube/ingestor/runtime/parallel_execution_state.py +28 -0
  155. firecube/ingestor/runtime/parallel_gate.py +164 -0
  156. firecube/ingestor/runtime/parallel_run_id.py +48 -0
  157. firecube/ingestor/runtime/preflight.py +40 -0
  158. firecube/ingestor/runtime/recording.py +425 -0
  159. firecube/ingestor/runtime/resume_guard.py +556 -0
  160. firecube/ingestor/runtime/resume_types.py +65 -0
  161. firecube/ingestor/runtime/telemetry.py +171 -0
  162. firecube/ingestor/runtime/tensogram/__init__.py +14 -0
  163. firecube/ingestor/runtime/tensogram/strategy.py +164 -0
  164. firecube/ingestor/runtime/workspace.py +220 -0
  165. firecube/ingestor/runtime/zarr/__init__.py +34 -0
  166. firecube/ingestor/runtime/zarr/append.py +466 -0
  167. firecube/ingestor/runtime/zarr/append_services.py +650 -0
  168. firecube/ingestor/runtime/zarr/batch_runner.py +190 -0
  169. firecube/ingestor/runtime/zarr/contracts.py +58 -0
  170. firecube/ingestor/runtime/zarr/existing_cube_check.py +282 -0
  171. firecube/ingestor/runtime/zarr/resume_cache.py +92 -0
  172. firecube/ingestor/runtime/zarr/staged_metadata.py +249 -0
  173. firecube/ingestor/runtime/zarr/strategies/__init__.py +15 -0
  174. firecube/ingestor/runtime/zarr/strategies/append.py +172 -0
  175. firecube/ingestor/runtime/zarr/strategies/indexed_region.py +426 -0
  176. firecube/ingestor/runtime/zarr/write.py +212 -0
  177. firecube/ingestor/runtime/zarr/write_context.py +139 -0
  178. firecube/ingestor/templates/__init__.py +19 -0
  179. firecube/ingestor/templates/config.py +118 -0
  180. firecube/ingestor/templates/direct_zarr.py +790 -0
  181. firecube/ingestor/templates/generic.py +462 -0
  182. firecube/ingestor/templates/generic_tensogram.py +170 -0
  183. firecube/ingestor/types/__init__.py +19 -0
  184. firecube/ingestor/types/config.py +89 -0
  185. firecube/ingestor/types/context.py +409 -0
  186. firecube/ingestor/types/manifest.py +53 -0
  187. firecube/ingestor/types/planned_range.py +152 -0
  188. firecube/ingestor/types/result_metrics.py +386 -0
  189. firecube/ingestor/utils/__init__.py +14 -0
  190. firecube/ingestor/utils/duckdb_utils.py +235 -0
  191. firecube/ingestor/validation.py +55 -0
  192. firecube-0.1.4.dist-info/METADATA +242 -0
  193. firecube-0.1.4.dist-info/RECORD +197 -0
  194. firecube-0.1.4.dist-info/WHEEL +4 -0
  195. firecube-0.1.4.dist-info/entry_points.txt +2 -0
  196. firecube-0.1.4.dist-info/licenses/AUTHORS.md +5 -0
  197. 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.