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
firecube/cli/advise.py ADDED
@@ -0,0 +1,321 @@
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
+ """Performance advice helpers (read-only)."""
16
+
17
+ from __future__ import annotations
18
+
19
+ import json as _json
20
+ import sys
21
+ from collections.abc import Callable
22
+
23
+ import click
24
+ import zarr
25
+
26
+ from firecube.cli._ctx import get_storage_config
27
+ from firecube.cli._errors import wrap_user_facing_errors
28
+ from firecube.cli._product import resolve_product_identity
29
+ from firecube.cli._shared_options import (
30
+ product_uri_option,
31
+ storage_driver_option,
32
+ storage_type_option,
33
+ )
34
+ from firecube.cli._uri_policy import apply_smart_default, parse_product_uri
35
+ from firecube.core import observability
36
+ from firecube.core.cf import CFReport
37
+ from firecube.core.cf.validator import validate_cf18
38
+ from firecube.core.storage.binding import StorageBinding
39
+ from firecube.core.storage.driver_config import StorageDriverConfig
40
+ from firecube.core.storage.session import StorageSession
41
+
42
+ _ROOT_GROUP_ALIASES = frozenset({".", "/", ""})
43
+ _COMPLIANCE_PROFILE_VALIDATORS: dict[str, Callable[..., CFReport]] = {
44
+ "cf-18": validate_cf18,
45
+ }
46
+ _COMPLIANCE_PROFILE_LABELS = {
47
+ "cf-18": "CF-1.8",
48
+ }
49
+
50
+
51
+ @click.group(context_settings={"help_option_names": ["-h", "--help"]})
52
+ @click.pass_context
53
+ def advise(ctx: click.Context) -> None:
54
+ """performance advice helpers
55
+
56
+ Read-only helpers for configuring ingestion. Use advise batch-size to get
57
+ a recommended pipeline_batch_size before running large ingestion jobs.
58
+ """
59
+ observability.init_observability("firecube-advise")
60
+ ctx.ensure_object(dict)
61
+
62
+
63
+ @advise.command(
64
+ "batch-size",
65
+ epilog="""\b
66
+ Examples:
67
+ # get recommended batch size for a product group
68
+ firecube advise batch-size -p <product> -g <group>
69
+
70
+ See also: firecube ingest, firecube zarr validate
71
+ """,
72
+ )
73
+ @product_uri_option(tier="inspect")
74
+ @click.option(
75
+ "-g",
76
+ "--group",
77
+ "group_path",
78
+ required=True,
79
+ help="group path inside the product (e.g. F024/FWI)",
80
+ )
81
+ @storage_driver_option(required=False)
82
+ @storage_type_option(required=False)
83
+ @click.pass_context
84
+ @wrap_user_facing_errors
85
+ def batch_size(
86
+ ctx: click.Context,
87
+ product: str,
88
+ group_path: str,
89
+ storage_driver: str | None,
90
+ storage_type: str | None,
91
+ ) -> None:
92
+ """recommend optimal batch size
93
+
94
+ Recommends an optimal pipeline_batch_size based on the Zarr store's time
95
+ chunk shape. Reads the store metadata to compute how many time steps fit
96
+ into memory without partial chunks. Run before a long ingestion job to
97
+ set --option pipeline_batch_size=N.
98
+ """
99
+ parsed_uri = parse_product_uri(product)
100
+ storage_type = apply_smart_default(parsed_uri, storage_type)
101
+ storage_cfg = get_storage_config(
102
+ ctx,
103
+ overrides={"storage_type": storage_type, "storage_driver": storage_driver},
104
+ cache=False,
105
+ )
106
+ driver_config = StorageDriverConfig.from_storage_config(storage_cfg)
107
+ identity = resolve_product_identity(
108
+ parsed_uri.normalized, format="zarr", product_name=parsed_uri.normalized
109
+ )
110
+ session = StorageSession(
111
+ StorageBinding(
112
+ identity=identity,
113
+ driver=driver_config,
114
+ )
115
+ )
116
+
117
+ try:
118
+ root = session.zarr.open_group(identity.product_uri, mode="r")
119
+ grp = root[group_path]
120
+ except Exception as exc:
121
+ click.echo(f"Error opening store: {exc}", err=True)
122
+ sys.exit(1)
123
+
124
+ if not isinstance(grp, zarr.Group):
125
+ click.echo(f"Path '{group_path}' is not a Zarr group.", err=True)
126
+ sys.exit(1)
127
+
128
+ time_chunk_size = _find_time_chunk_size(grp)
129
+
130
+ if time_chunk_size is None:
131
+ click.echo(f"No time dimension found in group '{group_path}'. Any batch size is valid.")
132
+ return
133
+
134
+ if time_chunk_size == 1:
135
+ click.echo(
136
+ "Recommended: --option pipeline_batch_size=10 (any size works; time chunk is 1)\n"
137
+ "Rationale: Time chunk size is 1. Any batch size is safely aligned. Recommend 10 for throughput."
138
+ )
139
+ else:
140
+ click.echo(
141
+ f"Recommended: --option pipeline_batch_size={time_chunk_size}\n"
142
+ f"Rationale: Time chunk size is {time_chunk_size}. "
143
+ "Aligning batch size avoids partial-chunk write overhead."
144
+ )
145
+
146
+
147
+ def _find_time_chunk_size(grp: zarr.Group) -> int | None:
148
+ """Return the time-axis chunk size from the first data array, or *None*."""
149
+ for _name, arr in grp.arrays():
150
+ chunks = arr.chunks
151
+ if not isinstance(chunks, tuple) or len(chunks) == 0:
152
+ continue
153
+ # In this codebase the time dimension is always axis 0 for
154
+ # multi-dimensional data arrays (shape length > 1).
155
+ if len(chunks) > 1:
156
+ return chunks[0]
157
+ return None
158
+
159
+
160
+ @advise.command(
161
+ "compliance",
162
+ epilog="""\b
163
+ Examples:
164
+ # run Tier-1 CF-1.8 checks on a local cube root
165
+ firecube advise compliance --profile cf-18 -p /path/to/cube.zarr -g .
166
+ # check a nested group, emit JSON for downstream tooling
167
+ firecube advise compliance --profile cf-18 -p s3://bucket/x.zarr -g F024/FWI --storage-type s3 --storage-driver fsspec --format json
168
+ # fail (exit 2) on any warning or info finding
169
+ firecube advise compliance --profile cf-18 -p ./cube.zarr -g . --strict
170
+
171
+ See also: firecube zarr validate
172
+ """,
173
+ )
174
+ @click.option(
175
+ "--profile",
176
+ required=True,
177
+ type=click.Choice(sorted(_COMPLIANCE_PROFILE_VALIDATORS)),
178
+ help="compliance profile to run",
179
+ )
180
+ @product_uri_option(tier="inspect")
181
+ @click.option(
182
+ "-g",
183
+ "--group",
184
+ "group_path",
185
+ required=True,
186
+ help="group path inside the cube ('.' or '/' for root, otherwise e.g. F024/FWI)",
187
+ )
188
+ @click.option(
189
+ "--format",
190
+ "output_format",
191
+ type=click.Choice(["text", "json"]),
192
+ default="text",
193
+ show_default=True,
194
+ help="output format for the report",
195
+ )
196
+ @click.option(
197
+ "--strict",
198
+ is_flag=True,
199
+ default=False,
200
+ help="treat any warning or info finding as a non-zero exit (code 2)",
201
+ )
202
+ @storage_driver_option(required=False)
203
+ @storage_type_option(required=False)
204
+ @click.pass_context
205
+ @wrap_user_facing_errors
206
+ def compliance(
207
+ ctx: click.Context,
208
+ profile: str,
209
+ product: str,
210
+ group_path: str,
211
+ output_format: str,
212
+ strict: bool,
213
+ storage_driver: str | None,
214
+ storage_type: str | None,
215
+ ) -> None:
216
+ """run structural compliance checks
217
+
218
+ Opens a Zarr cube and runs the selected compliance profile. Findings are
219
+ emitted as human-readable text (default) or as JSON. Exit codes: 0 on clean
220
+ (or warnings/info without --strict), 1 if any error finding is reported,
221
+ 2 if warnings/info appear under --strict.
222
+ """
223
+ parsed_uri = parse_product_uri(product)
224
+ storage_type = apply_smart_default(parsed_uri, storage_type)
225
+ storage_cfg = get_storage_config(
226
+ ctx,
227
+ overrides={"storage_type": storage_type, "storage_driver": storage_driver},
228
+ cache=False,
229
+ )
230
+ driver_config = StorageDriverConfig.from_storage_config(storage_cfg)
231
+
232
+ identity = resolve_product_identity(
233
+ parsed_uri.normalized, format="zarr", product_name=parsed_uri.normalized
234
+ )
235
+ session = StorageSession(StorageBinding(identity=identity, driver=driver_config))
236
+
237
+ xr_group = "" if group_path in _ROOT_GROUP_ALIASES else group_path
238
+
239
+ try:
240
+ root = session.zarr.open_group(identity.product_uri, mode="r")
241
+ if xr_group:
242
+ target = root[xr_group]
243
+ if not isinstance(target, zarr.Group):
244
+ raise click.ClickException(
245
+ f"Path '{group_path}' is a Zarr array, not a group. "
246
+ "Specify a group path (for example '/' or 'F024/FWI')."
247
+ )
248
+ except KeyError as exc:
249
+ raise click.ClickException(f"Group '{group_path}' not found in Zarr cube.") from exc
250
+
251
+ try:
252
+ ds = session.zarr.open_dataset(
253
+ identity.product_uri,
254
+ group=xr_group,
255
+ decode_times=False,
256
+ )
257
+ except KeyError as exc:
258
+ raise click.ClickException(f"Group '{group_path}' not found in Zarr cube.") from exc
259
+
260
+ try:
261
+ report = _COMPLIANCE_PROFILE_VALIDATORS[profile](ds, product=product, group=group_path)
262
+ finally:
263
+ ds.close()
264
+
265
+ if output_format == "json":
266
+ _render_compliance_json(report, profile=profile)
267
+ else:
268
+ _render_compliance_text(report, profile=profile)
269
+
270
+ summary = report.summary
271
+ if summary.errors > 0:
272
+ sys.exit(1)
273
+ if strict and (summary.warnings > 0 or summary.info > 0):
274
+ sys.exit(2)
275
+
276
+
277
+ def _render_compliance_text(report: CFReport, *, profile: str) -> None:
278
+ label = _COMPLIANCE_PROFILE_LABELS[profile]
279
+ click.echo(f"{label} advisor \u2014 product={report.product} group={report.group}")
280
+
281
+ for finding in report.findings:
282
+ sev_tag = f"[{finding.severity.value}]"
283
+ click.echo(f"{sev_tag:<10}{finding.id}: {finding.message}")
284
+ if finding.suggested_fix:
285
+ click.echo(f" \u2192 {finding.suggested_fix}")
286
+
287
+ summary = report.summary
288
+ if not report.findings:
289
+ click.echo(f"No findings \u2014 {label} structural checks passed.")
290
+ return
291
+
292
+ click.echo(
293
+ f"Summary: {summary.errors} error{'s' if summary.errors != 1 else ''}, "
294
+ f"{summary.warnings} warning{'s' if summary.warnings != 1 else ''}, "
295
+ f"{summary.info} info."
296
+ )
297
+
298
+
299
+ def _render_compliance_json(report: CFReport, *, profile: str) -> None:
300
+ summary = report.summary
301
+ payload = {
302
+ "profile": profile,
303
+ "product": report.product,
304
+ "group": report.group,
305
+ "findings": [
306
+ {
307
+ "id": f.id,
308
+ "severity": f.severity.value,
309
+ "target": f.target,
310
+ "message": f.message,
311
+ "suggested_fix": f.suggested_fix,
312
+ }
313
+ for f in report.findings
314
+ ],
315
+ "summary": {
316
+ "errors": summary.errors,
317
+ "warnings": summary.warnings,
318
+ "info": summary.info,
319
+ },
320
+ }
321
+ click.echo(_json.dumps(payload, indent=2))