lsst-daf-butler 29.0.1rc1__py3-none-any.whl → 29.1.0__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.
- lsst/daf/butler/__init__.py +1 -0
- lsst/daf/butler/_butler.py +57 -10
- lsst/daf/butler/_butler_collections.py +4 -0
- lsst/daf/butler/_butler_instance_options.py +3 -0
- lsst/daf/butler/_butler_metrics.py +117 -0
- lsst/daf/butler/_config.py +1 -1
- lsst/daf/butler/_dataset_ref.py +99 -16
- lsst/daf/butler/_file_dataset.py +78 -3
- lsst/daf/butler/_limited_butler.py +42 -3
- lsst/daf/butler/_quantum_backed.py +23 -4
- lsst/daf/butler/arrow_utils.py +7 -9
- lsst/daf/butler/cli/butler.py +1 -1
- lsst/daf/butler/cli/cmd/_remove_runs.py +2 -0
- lsst/daf/butler/cli/cmd/commands.py +25 -1
- lsst/daf/butler/cli/utils.py +33 -5
- lsst/daf/butler/column_spec.py +77 -34
- lsst/daf/butler/configs/datastores/formatters.yaml +1 -0
- lsst/daf/butler/configs/storageClasses.yaml +2 -0
- lsst/daf/butler/datastore/__init__.py +1 -0
- lsst/daf/butler/datastore/_datastore.py +48 -19
- lsst/daf/butler/datastore/_transfer.py +102 -0
- lsst/daf/butler/datastore/generic_base.py +2 -2
- lsst/daf/butler/datastore/stored_file_info.py +34 -0
- lsst/daf/butler/datastores/chainedDatastore.py +112 -95
- lsst/daf/butler/datastores/fileDatastore.py +296 -151
- lsst/daf/butler/datastores/file_datastore/transfer.py +104 -0
- lsst/daf/butler/datastores/inMemoryDatastore.py +33 -5
- lsst/daf/butler/dimensions/_coordinate.py +7 -15
- lsst/daf/butler/dimensions/_group.py +15 -5
- lsst/daf/butler/dimensions/_record_set.py +469 -4
- lsst/daf/butler/dimensions/_record_table.py +1 -1
- lsst/daf/butler/dimensions/_records.py +127 -6
- lsst/daf/butler/dimensions/_universe.py +12 -8
- lsst/daf/butler/dimensions/record_cache.py +1 -2
- lsst/daf/butler/direct_butler/_direct_butler.py +429 -245
- lsst/daf/butler/direct_query_driver/_driver.py +30 -11
- lsst/daf/butler/direct_query_driver/_query_builder.py +74 -17
- lsst/daf/butler/direct_query_driver/_sql_column_visitor.py +28 -1
- lsst/daf/butler/formatters/parquet.py +7 -3
- lsst/daf/butler/pydantic_utils.py +26 -0
- lsst/daf/butler/queries/_expression_strings.py +24 -0
- lsst/daf/butler/queries/_identifiers.py +4 -1
- lsst/daf/butler/queries/_query.py +48 -1
- lsst/daf/butler/queries/expression_factory.py +16 -0
- lsst/daf/butler/queries/overlaps.py +1 -1
- lsst/daf/butler/{direct_query_driver/_predicate_constraints_summary.py → queries/predicate_constraints_summary.py} +2 -2
- lsst/daf/butler/queries/tree/_column_expression.py +39 -0
- lsst/daf/butler/queries/tree/_column_set.py +1 -1
- lsst/daf/butler/queries/tree/_predicate.py +19 -9
- lsst/daf/butler/registry/bridge/ephemeral.py +16 -6
- lsst/daf/butler/registry/bridge/monolithic.py +78 -37
- lsst/daf/butler/registry/collections/_base.py +23 -6
- lsst/daf/butler/registry/connectionString.py +5 -10
- lsst/daf/butler/registry/databases/postgresql.py +50 -0
- lsst/daf/butler/registry/databases/sqlite.py +46 -0
- lsst/daf/butler/registry/datasets/byDimensions/_manager.py +77 -64
- lsst/daf/butler/registry/datasets/byDimensions/summaries.py +4 -4
- lsst/daf/butler/registry/dimensions/static.py +20 -8
- lsst/daf/butler/registry/interfaces/_bridge.py +13 -1
- lsst/daf/butler/registry/interfaces/_database.py +22 -2
- lsst/daf/butler/registry/interfaces/_datasets.py +4 -16
- lsst/daf/butler/registry/interfaces/_dimensions.py +7 -2
- lsst/daf/butler/registry/obscore/_config.py +5 -0
- lsst/daf/butler/registry/obscore/_records.py +4 -2
- lsst/daf/butler/registry/queries/expressions/_predicate.py +35 -19
- lsst/daf/butler/registry/queries/expressions/check.py +29 -10
- lsst/daf/butler/registry/queries/expressions/normalForm.py +15 -0
- lsst/daf/butler/registry/queries/expressions/parser/exprTree.py +136 -23
- lsst/daf/butler/registry/queries/expressions/parser/parserLex.py +10 -1
- lsst/daf/butler/registry/queries/expressions/parser/parserYacc.py +47 -24
- lsst/daf/butler/registry/queries/expressions/parser/treeVisitor.py +49 -10
- lsst/daf/butler/registry/sql_registry.py +17 -45
- lsst/daf/butler/registry/tests/_registry.py +60 -32
- lsst/daf/butler/remote_butler/_http_connection.py +21 -5
- lsst/daf/butler/remote_butler/_query_driver.py +5 -7
- lsst/daf/butler/remote_butler/_registry.py +3 -2
- lsst/daf/butler/remote_butler/_remote_butler.py +55 -27
- lsst/daf/butler/remote_butler/_remote_file_transfer_source.py +124 -0
- lsst/daf/butler/remote_butler/server/_config.py +68 -13
- lsst/daf/butler/remote_butler/server/_dependencies.py +68 -3
- lsst/daf/butler/remote_butler/server/_factory.py +4 -0
- lsst/daf/butler/remote_butler/server/_gafaelfawr.py +125 -0
- lsst/daf/butler/remote_butler/server/_server.py +11 -4
- lsst/daf/butler/remote_butler/server/_telemetry.py +105 -0
- lsst/daf/butler/remote_butler/server/handlers/_external.py +100 -5
- lsst/daf/butler/remote_butler/server/handlers/_query_serialization.py +5 -7
- lsst/daf/butler/remote_butler/server/handlers/_query_streaming.py +7 -3
- lsst/daf/butler/remote_butler/server/handlers/_utils.py +15 -1
- lsst/daf/butler/remote_butler/server_models.py +17 -1
- lsst/daf/butler/script/ingest_zip.py +13 -1
- lsst/daf/butler/script/queryCollections.py +185 -29
- lsst/daf/butler/script/removeRuns.py +2 -5
- lsst/daf/butler/script/retrieveArtifacts.py +1 -0
- lsst/daf/butler/script/transferDatasets.py +5 -0
- lsst/daf/butler/tests/butler_queries.py +236 -23
- lsst/daf/butler/tests/cliCmdTestBase.py +1 -1
- lsst/daf/butler/tests/hybrid_butler.py +42 -9
- lsst/daf/butler/tests/hybrid_butler_registry.py +15 -2
- lsst/daf/butler/tests/server.py +28 -3
- lsst/daf/butler/version.py +1 -1
- {lsst_daf_butler-29.0.1rc1.dist-info → lsst_daf_butler-29.1.0.dist-info}/METADATA +1 -1
- {lsst_daf_butler-29.0.1rc1.dist-info → lsst_daf_butler-29.1.0.dist-info}/RECORD +110 -104
- {lsst_daf_butler-29.0.1rc1.dist-info → lsst_daf_butler-29.1.0.dist-info}/WHEEL +1 -1
- {lsst_daf_butler-29.0.1rc1.dist-info → lsst_daf_butler-29.1.0.dist-info}/entry_points.txt +0 -0
- {lsst_daf_butler-29.0.1rc1.dist-info → lsst_daf_butler-29.1.0.dist-info}/licenses/COPYRIGHT +0 -0
- {lsst_daf_butler-29.0.1rc1.dist-info → lsst_daf_butler-29.1.0.dist-info}/licenses/LICENSE +0 -0
- {lsst_daf_butler-29.0.1rc1.dist-info → lsst_daf_butler-29.1.0.dist-info}/licenses/bsd_license.txt +0 -0
- {lsst_daf_butler-29.0.1rc1.dist-info → lsst_daf_butler-29.1.0.dist-info}/licenses/gpl-v3.0.txt +0 -0
- {lsst_daf_butler-29.0.1rc1.dist-info → lsst_daf_butler-29.1.0.dist-info}/top_level.txt +0 -0
- {lsst_daf_butler-29.0.1rc1.dist-info → lsst_daf_butler-29.1.0.dist-info}/zip-safe +0 -0
|
@@ -43,6 +43,7 @@ import pydantic
|
|
|
43
43
|
from lsst.resources import ResourcePath, ResourcePathExpression
|
|
44
44
|
|
|
45
45
|
from ._butler_config import ButlerConfig
|
|
46
|
+
from ._butler_metrics import ButlerMetrics
|
|
46
47
|
from ._config import Config
|
|
47
48
|
from ._dataset_provenance import DatasetProvenance
|
|
48
49
|
from ._dataset_ref import DatasetId, DatasetRef
|
|
@@ -118,6 +119,8 @@ class QuantumBackedButler(LimitedButler):
|
|
|
118
119
|
Object managing all storage class definitions.
|
|
119
120
|
dataset_types : `~collections.abc.Mapping` [`str`, `DatasetType`]
|
|
120
121
|
The registry dataset type definitions, indexed by name.
|
|
122
|
+
metrics : `lsst.daf.butler.ButlerMetrics` or `None`, optional
|
|
123
|
+
Metrics object for tracking butler statistics.
|
|
121
124
|
|
|
122
125
|
Notes
|
|
123
126
|
-----
|
|
@@ -164,6 +167,7 @@ class QuantumBackedButler(LimitedButler):
|
|
|
164
167
|
datastore: Datastore,
|
|
165
168
|
storageClasses: StorageClassFactory,
|
|
166
169
|
dataset_types: Mapping[str, DatasetType] | None = None,
|
|
170
|
+
metrics: ButlerMetrics | None = None,
|
|
167
171
|
):
|
|
168
172
|
self._dimensions = dimensions
|
|
169
173
|
self._predicted_inputs = set(predicted_inputs)
|
|
@@ -175,6 +179,7 @@ class QuantumBackedButler(LimitedButler):
|
|
|
175
179
|
self._datastore = datastore
|
|
176
180
|
self.storageClasses = storageClasses
|
|
177
181
|
self._dataset_types: Mapping[str, DatasetType] = {}
|
|
182
|
+
self._metrics = metrics if metrics is not None else ButlerMetrics()
|
|
178
183
|
if dataset_types is not None:
|
|
179
184
|
self._dataset_types = dataset_types
|
|
180
185
|
self._datastore.set_retrieve_dataset_type_method(self._retrieve_dataset_type)
|
|
@@ -190,6 +195,7 @@ class QuantumBackedButler(LimitedButler):
|
|
|
190
195
|
BridgeManagerClass: type[DatastoreRegistryBridgeManager] = MonolithicDatastoreRegistryBridgeManager,
|
|
191
196
|
search_paths: list[str] | None = None,
|
|
192
197
|
dataset_types: Mapping[str, DatasetType] | None = None,
|
|
198
|
+
metrics: ButlerMetrics | None = None,
|
|
193
199
|
) -> QuantumBackedButler:
|
|
194
200
|
"""Construct a new `QuantumBackedButler` from repository configuration
|
|
195
201
|
and helper types.
|
|
@@ -219,6 +225,8 @@ class QuantumBackedButler(LimitedButler):
|
|
|
219
225
|
dataset_types : `~collections.abc.Mapping` [`str`, `DatasetType`], \
|
|
220
226
|
optional
|
|
221
227
|
Mapping of the dataset type name to its registry definition.
|
|
228
|
+
metrics : `lsst.daf.butler.ButlerMetrics` or `None`, optional
|
|
229
|
+
Metrics object for gathering butler statistics.
|
|
222
230
|
"""
|
|
223
231
|
predicted_inputs = [ref.id for ref in itertools.chain.from_iterable(quantum.inputs.values())]
|
|
224
232
|
predicted_inputs += [ref.id for ref in quantum.initInputs.values()]
|
|
@@ -234,6 +242,7 @@ class QuantumBackedButler(LimitedButler):
|
|
|
234
242
|
BridgeManagerClass=BridgeManagerClass,
|
|
235
243
|
search_paths=search_paths,
|
|
236
244
|
dataset_types=dataset_types,
|
|
245
|
+
metrics=metrics,
|
|
237
246
|
)
|
|
238
247
|
|
|
239
248
|
@classmethod
|
|
@@ -249,6 +258,7 @@ class QuantumBackedButler(LimitedButler):
|
|
|
249
258
|
BridgeManagerClass: type[DatastoreRegistryBridgeManager] = MonolithicDatastoreRegistryBridgeManager,
|
|
250
259
|
search_paths: list[str] | None = None,
|
|
251
260
|
dataset_types: Mapping[str, DatasetType] | None = None,
|
|
261
|
+
metrics: ButlerMetrics | None = None,
|
|
252
262
|
) -> QuantumBackedButler:
|
|
253
263
|
"""Construct a new `QuantumBackedButler` from sets of input and output
|
|
254
264
|
dataset IDs.
|
|
@@ -281,6 +291,8 @@ class QuantumBackedButler(LimitedButler):
|
|
|
281
291
|
dataset_types : `~collections.abc.Mapping` [`str`, `DatasetType`], \
|
|
282
292
|
optional
|
|
283
293
|
Mapping of the dataset type name to its registry definition.
|
|
294
|
+
metrics : `lsst.daf.butler.ButlerMetrics` or `None`, optional
|
|
295
|
+
Metrics object for gathering butler statistics.
|
|
284
296
|
"""
|
|
285
297
|
return cls._initialize(
|
|
286
298
|
config=config,
|
|
@@ -293,6 +305,7 @@ class QuantumBackedButler(LimitedButler):
|
|
|
293
305
|
BridgeManagerClass=BridgeManagerClass,
|
|
294
306
|
search_paths=search_paths,
|
|
295
307
|
dataset_types=dataset_types,
|
|
308
|
+
metrics=metrics,
|
|
296
309
|
)
|
|
297
310
|
|
|
298
311
|
@classmethod
|
|
@@ -309,6 +322,7 @@ class QuantumBackedButler(LimitedButler):
|
|
|
309
322
|
BridgeManagerClass: type[DatastoreRegistryBridgeManager] = MonolithicDatastoreRegistryBridgeManager,
|
|
310
323
|
search_paths: list[str] | None = None,
|
|
311
324
|
dataset_types: Mapping[str, DatasetType] | None = None,
|
|
325
|
+
metrics: ButlerMetrics | None = None,
|
|
312
326
|
) -> QuantumBackedButler:
|
|
313
327
|
"""Initialize quantum-backed butler.
|
|
314
328
|
|
|
@@ -341,6 +355,8 @@ class QuantumBackedButler(LimitedButler):
|
|
|
341
355
|
Additional search paths for butler configuration.
|
|
342
356
|
dataset_types : `~collections.abc.Mapping` [`str`, `DatasetType`]
|
|
343
357
|
Mapping of the dataset type name to its registry definition.
|
|
358
|
+
metrics : `lsst.daf.butler.ButlerMetrics` or `None`, optional
|
|
359
|
+
Metrics object for gathering butler statistics.
|
|
344
360
|
"""
|
|
345
361
|
butler_config = ButlerConfig(config, searchPaths=search_paths)
|
|
346
362
|
butler_root = butler_config.get("root", butler_config.configDir)
|
|
@@ -373,6 +389,7 @@ class QuantumBackedButler(LimitedButler):
|
|
|
373
389
|
datastore,
|
|
374
390
|
storageClasses=storageClasses,
|
|
375
391
|
dataset_types=dataset_types,
|
|
392
|
+
metrics=metrics,
|
|
376
393
|
)
|
|
377
394
|
|
|
378
395
|
def _retrieve_dataset_type(self, name: str) -> DatasetType | None:
|
|
@@ -459,8 +476,9 @@ class QuantumBackedButler(LimitedButler):
|
|
|
459
476
|
# Docstring inherited.
|
|
460
477
|
if ref.id not in self._predicted_outputs:
|
|
461
478
|
raise RuntimeError("Cannot `put` dataset that was not predicted as an output.")
|
|
462
|
-
self.
|
|
463
|
-
|
|
479
|
+
with self._metrics.instrument_put(log=_LOG, msg="Put QBB dataset"):
|
|
480
|
+
self._datastore.put(obj, ref, provenance=provenance)
|
|
481
|
+
self._actual_output_refs.add(ref)
|
|
464
482
|
return ref
|
|
465
483
|
|
|
466
484
|
def pruneDatasets(
|
|
@@ -498,8 +516,9 @@ class QuantumBackedButler(LimitedButler):
|
|
|
498
516
|
self._actual_output_refs.discard(ref)
|
|
499
517
|
|
|
500
518
|
if unstore:
|
|
501
|
-
# Point of no return for removing artifacts
|
|
502
|
-
|
|
519
|
+
# Point of no return for removing artifacts. Only try to remove
|
|
520
|
+
# refs associated with this pruning.
|
|
521
|
+
self._datastore.emptyTrash(refs=refs)
|
|
503
522
|
|
|
504
523
|
def retrieve_artifacts_zip(
|
|
505
524
|
self,
|
lsst/daf/butler/arrow_utils.py
CHANGED
|
@@ -383,7 +383,7 @@ class _ToArrowTimespan(ToArrow):
|
|
|
383
383
|
# Docstring inherited.
|
|
384
384
|
return TimespanArrowType()
|
|
385
385
|
|
|
386
|
-
def append(self, value: Timespan | None, column: list[
|
|
386
|
+
def append(self, value: Timespan | None, column: list[dict[str, int] | None]) -> None:
|
|
387
387
|
# Docstring inherited.
|
|
388
388
|
column.append({"begin_nsec": value.nsec[0], "end_nsec": value.nsec[1]} if value is not None else None)
|
|
389
389
|
|
|
@@ -432,12 +432,10 @@ class _ToArrowDateTime(ToArrow):
|
|
|
432
432
|
|
|
433
433
|
@final
|
|
434
434
|
class UUIDArrowType(pa.ExtensionType):
|
|
435
|
-
"""An Arrow extension type for `
|
|
436
|
-
nanoseconds since 1970-01-01.
|
|
437
|
-
"""
|
|
435
|
+
"""An Arrow extension type for `uuid.UUID`, stored as 16 bytes."""
|
|
438
436
|
|
|
439
437
|
def __init__(self) -> None:
|
|
440
|
-
super().__init__(
|
|
438
|
+
super().__init__(_ToArrowUUID.storage_type, "uuid.UUID")
|
|
441
439
|
|
|
442
440
|
def __arrow_ext_serialize__(self) -> bytes:
|
|
443
441
|
return b""
|
|
@@ -458,7 +456,7 @@ class UUIDArrowScalar(pa.ExtensionScalar):
|
|
|
458
456
|
instance.
|
|
459
457
|
"""
|
|
460
458
|
|
|
461
|
-
def as_py(self) ->
|
|
459
|
+
def as_py(self, **_unused: Any) -> uuid.UUID:
|
|
462
460
|
return uuid.UUID(bytes=self.value.as_py())
|
|
463
461
|
|
|
464
462
|
|
|
@@ -487,7 +485,7 @@ class RegionArrowScalar(pa.ExtensionScalar):
|
|
|
487
485
|
Use the standard `as_py` method to convert to an actual region.
|
|
488
486
|
"""
|
|
489
487
|
|
|
490
|
-
def as_py(self) -> Region:
|
|
488
|
+
def as_py(self, **_unused: Any) -> Region:
|
|
491
489
|
return Region.decode(self.value.as_py())
|
|
492
490
|
|
|
493
491
|
|
|
@@ -516,7 +514,7 @@ class TimespanArrowScalar(pa.ExtensionScalar):
|
|
|
516
514
|
Use the standard `as_py` method to convert to an actual timespan.
|
|
517
515
|
"""
|
|
518
516
|
|
|
519
|
-
def as_py(self) -> Timespan | None:
|
|
517
|
+
def as_py(self, **_unused: Any) -> Timespan | None:
|
|
520
518
|
if self.value is None:
|
|
521
519
|
return None
|
|
522
520
|
else:
|
|
@@ -554,7 +552,7 @@ class DateTimeArrowScalar(pa.ExtensionScalar):
|
|
|
554
552
|
instance.
|
|
555
553
|
"""
|
|
556
554
|
|
|
557
|
-
def as_py(self) -> astropy.time.Time:
|
|
555
|
+
def as_py(self, **_unused: Any) -> astropy.time.Time:
|
|
558
556
|
return TimeConverter().nsec_to_astropy(self.value.as_py())
|
|
559
557
|
|
|
560
558
|
|
lsst/daf/butler/cli/butler.py
CHANGED
|
@@ -102,7 +102,7 @@ class PluginCommand:
|
|
|
102
102
|
"""Where the command came from (`str`)."""
|
|
103
103
|
|
|
104
104
|
|
|
105
|
-
class LoaderCLI(click.
|
|
105
|
+
class LoaderCLI(click.Group, abc.ABC):
|
|
106
106
|
"""Extends `click.MultiCommand`, which dispatches to subcommands, to load
|
|
107
107
|
subcommands at runtime.
|
|
108
108
|
|
|
@@ -81,7 +81,9 @@ def _print_remove(will: bool, runs: Sequence[script.RemoveRun], datasets: Mappin
|
|
|
81
81
|
else:
|
|
82
82
|
print(run.name)
|
|
83
83
|
print("\n" + willRemoveDatasetsMsg if will else didRemoveDatasetsMsg)
|
|
84
|
+
total = sum(datasets.values())
|
|
84
85
|
print(", ".join([f"{i[0]}({i[1]})" for i in datasets.items()]))
|
|
86
|
+
print("Total number of datasets to remove: ", total)
|
|
85
87
|
|
|
86
88
|
|
|
87
89
|
def _print_requires_confirmation(runs: Sequence[script.RemoveRun], datasets: Mapping[str, int]) -> None:
|
|
@@ -424,6 +424,21 @@ def prune_datasets(**kwargs: Any) -> None:
|
|
|
424
424
|
case_sensitive=False,
|
|
425
425
|
),
|
|
426
426
|
)
|
|
427
|
+
@click.option(
|
|
428
|
+
"-t",
|
|
429
|
+
"--show-dataset-types",
|
|
430
|
+
is_flag=True,
|
|
431
|
+
help="Also show the dataset types registered within each collection.",
|
|
432
|
+
)
|
|
433
|
+
@click.option(
|
|
434
|
+
"--exclude-dataset-types",
|
|
435
|
+
type=click.STRING,
|
|
436
|
+
multiple=True,
|
|
437
|
+
default=["*_config,*_log,*_metadata,packages"],
|
|
438
|
+
callback=split_commas,
|
|
439
|
+
show_default=True,
|
|
440
|
+
help="Dataset types (comma-separated) to exclude. Only valid with --show-dataset-types.",
|
|
441
|
+
)
|
|
427
442
|
@options_file_option()
|
|
428
443
|
def query_collections(*args: Any, **kwargs: Any) -> None:
|
|
429
444
|
"""Get the collections whose names match an expression."""
|
|
@@ -454,7 +469,7 @@ def query_dataset_types(*args: Any, **kwargs: Any) -> None:
|
|
|
454
469
|
"""Get the dataset types in a repository."""
|
|
455
470
|
table = script.queryDatasetTypes(*args, **kwargs)
|
|
456
471
|
if table:
|
|
457
|
-
table.pprint_all()
|
|
472
|
+
table.pprint_all(align="<")
|
|
458
473
|
else:
|
|
459
474
|
print("No results. Try --help for more information.")
|
|
460
475
|
|
|
@@ -638,6 +653,9 @@ def retrieve_artifacts(**kwargs: Any) -> None:
|
|
|
638
653
|
@transfer_option()
|
|
639
654
|
@register_dataset_types_option()
|
|
640
655
|
@transfer_dimensions_option()
|
|
656
|
+
@click.option(
|
|
657
|
+
"--dry-run/--no-dry-run", default=False, help="Enable dry run mode and do not transfer any datasets."
|
|
658
|
+
)
|
|
641
659
|
@options_file_option()
|
|
642
660
|
def transfer_datasets(**kwargs: Any) -> None:
|
|
643
661
|
"""Transfer datasets from a source butler to a destination butler.
|
|
@@ -826,6 +844,12 @@ def export_calibs(*args: Any, **kwargs: Any) -> None:
|
|
|
826
844
|
@repo_argument(required=True)
|
|
827
845
|
@click.argument("zip", required=True)
|
|
828
846
|
@transfer_option()
|
|
847
|
+
@transfer_dimensions_option(
|
|
848
|
+
default=False, help="Attempt to register missing dimension records during ingest."
|
|
849
|
+
)
|
|
850
|
+
@click.option(
|
|
851
|
+
"--dry-run/--no-dry-run", default=False, help="Enable dry run mode and do not ingest any datasets."
|
|
852
|
+
)
|
|
829
853
|
def ingest_zip(**kwargs: Any) -> None:
|
|
830
854
|
"""Ingest a Zip file created by retrieve-artifacts.
|
|
831
855
|
|
lsst/daf/butler/cli/utils.py
CHANGED
|
@@ -55,6 +55,7 @@ __all__ = (
|
|
|
55
55
|
)
|
|
56
56
|
|
|
57
57
|
|
|
58
|
+
import importlib.metadata
|
|
58
59
|
import itertools
|
|
59
60
|
import logging
|
|
60
61
|
import os
|
|
@@ -76,6 +77,7 @@ import click
|
|
|
76
77
|
import click.exceptions
|
|
77
78
|
import click.testing
|
|
78
79
|
import yaml
|
|
80
|
+
from packaging.version import Version
|
|
79
81
|
|
|
80
82
|
from lsst.utils.iteration import ensure_iterable
|
|
81
83
|
|
|
@@ -87,6 +89,12 @@ if TYPE_CHECKING:
|
|
|
87
89
|
|
|
88
90
|
from lsst.daf.butler import Dimension
|
|
89
91
|
|
|
92
|
+
_click_version = Version(importlib.metadata.version("click"))
|
|
93
|
+
if _click_version >= Version("8.2.0"):
|
|
94
|
+
_click_make_metavar_has_context = True
|
|
95
|
+
else:
|
|
96
|
+
_click_make_metavar_has_context = False
|
|
97
|
+
|
|
90
98
|
log = logging.getLogger(__name__)
|
|
91
99
|
|
|
92
100
|
# This is used as the metavar argument to Options that accept multiple string
|
|
@@ -741,9 +749,16 @@ class MWPath(click.Path):
|
|
|
741
749
|
class MWOption(click.Option):
|
|
742
750
|
"""Overrides click.Option with desired behaviors."""
|
|
743
751
|
|
|
744
|
-
def make_metavar(self) -> str:
|
|
752
|
+
def make_metavar(self, ctx: click.Context | None = None) -> str:
|
|
745
753
|
"""Make the metavar for the help menu.
|
|
746
754
|
|
|
755
|
+
Parameters
|
|
756
|
+
----------
|
|
757
|
+
ctx : `click.Context` or `None`
|
|
758
|
+
Context from the command.
|
|
759
|
+
|
|
760
|
+
Notes
|
|
761
|
+
-----
|
|
747
762
|
Overrides `click.Option.make_metavar`.
|
|
748
763
|
Adds a space and an ellipsis after the metavar name if
|
|
749
764
|
the option accepts multiple inputs, otherwise defers to the base
|
|
@@ -758,7 +773,10 @@ class MWOption(click.Option):
|
|
|
758
773
|
transformation that must apply to all types should be applied in
|
|
759
774
|
get_help_record.
|
|
760
775
|
"""
|
|
761
|
-
|
|
776
|
+
if _click_make_metavar_has_context:
|
|
777
|
+
metavar = super().make_metavar(ctx=ctx) # type: ignore
|
|
778
|
+
else:
|
|
779
|
+
metavar = super().make_metavar() # type: ignore
|
|
762
780
|
if self.multiple and self.nargs == 1:
|
|
763
781
|
metavar += " ..."
|
|
764
782
|
elif self.nargs != 1:
|
|
@@ -769,9 +787,16 @@ class MWOption(click.Option):
|
|
|
769
787
|
class MWArgument(click.Argument):
|
|
770
788
|
"""Overrides click.Argument with desired behaviors."""
|
|
771
789
|
|
|
772
|
-
def make_metavar(self) -> str:
|
|
790
|
+
def make_metavar(self, ctx: click.Context | None = None) -> str:
|
|
773
791
|
"""Make the metavar for the help menu.
|
|
774
792
|
|
|
793
|
+
Parameters
|
|
794
|
+
----------
|
|
795
|
+
ctx : `click.Context` or `None`
|
|
796
|
+
Context from the command.
|
|
797
|
+
|
|
798
|
+
Notes
|
|
799
|
+
-----
|
|
775
800
|
Overrides `click.Option.make_metavar`.
|
|
776
801
|
Always adds a space and an ellipsis (' ...') after the
|
|
777
802
|
metavar name if the option accepts multiple inputs.
|
|
@@ -784,7 +809,10 @@ class MWArgument(click.Argument):
|
|
|
784
809
|
metavar : `str`
|
|
785
810
|
The metavar value.
|
|
786
811
|
"""
|
|
787
|
-
|
|
812
|
+
if _click_make_metavar_has_context:
|
|
813
|
+
metavar = super().make_metavar(ctx=ctx) # type: ignore
|
|
814
|
+
else:
|
|
815
|
+
metavar = super().make_metavar() # type: ignore
|
|
788
816
|
if self.nargs != 1:
|
|
789
817
|
metavar = f"{metavar[:-3]} ..."
|
|
790
818
|
return metavar
|
|
@@ -1029,7 +1057,7 @@ class MWCommand(click.Command):
|
|
|
1029
1057
|
return ret
|
|
1030
1058
|
|
|
1031
1059
|
@epilog.setter
|
|
1032
|
-
def epilog(self, val: str) -> None:
|
|
1060
|
+
def epilog(self, val: str | None) -> None:
|
|
1033
1061
|
self._epilog = val
|
|
1034
1062
|
|
|
1035
1063
|
|
lsst/daf/butler/column_spec.py
CHANGED
|
@@ -39,12 +39,24 @@ __all__ = (
|
|
|
39
39
|
"StringColumnSpec",
|
|
40
40
|
"TimespanColumnSpec",
|
|
41
41
|
"UUIDColumnSpec",
|
|
42
|
+
"make_tuple_type_adapter",
|
|
42
43
|
)
|
|
43
44
|
|
|
44
45
|
import textwrap
|
|
45
46
|
import uuid
|
|
46
47
|
from abc import ABC, abstractmethod
|
|
47
|
-
from
|
|
48
|
+
from collections.abc import Iterable
|
|
49
|
+
from typing import (
|
|
50
|
+
TYPE_CHECKING,
|
|
51
|
+
Annotated,
|
|
52
|
+
Any,
|
|
53
|
+
ClassVar,
|
|
54
|
+
Literal,
|
|
55
|
+
Optional,
|
|
56
|
+
TypeAlias,
|
|
57
|
+
Union,
|
|
58
|
+
final,
|
|
59
|
+
)
|
|
48
60
|
|
|
49
61
|
import astropy.time
|
|
50
62
|
import pyarrow as pa
|
|
@@ -54,7 +66,7 @@ from lsst.sphgeom import Region
|
|
|
54
66
|
|
|
55
67
|
from . import arrow_utils, ddl
|
|
56
68
|
from ._timespan import Timespan
|
|
57
|
-
from .pydantic_utils import SerializableRegion, SerializableTime
|
|
69
|
+
from .pydantic_utils import SerializableBytesHex, SerializableRegion, SerializableTime
|
|
58
70
|
|
|
59
71
|
if TYPE_CHECKING:
|
|
60
72
|
from .name_shrinker import NameShrinker
|
|
@@ -125,18 +137,6 @@ class ColumnValueSerializer(ABC):
|
|
|
125
137
|
raise NotImplementedError
|
|
126
138
|
|
|
127
139
|
|
|
128
|
-
class _DefaultColumnValueSerializer(ColumnValueSerializer):
|
|
129
|
-
"""Default implementation of serializer for basic types."""
|
|
130
|
-
|
|
131
|
-
def serialize(self, value: Any) -> Any:
|
|
132
|
-
# Docstring inherited.
|
|
133
|
-
return value
|
|
134
|
-
|
|
135
|
-
def deserialize(self, value: Any) -> Any:
|
|
136
|
-
# Docstring inherited.
|
|
137
|
-
return value
|
|
138
|
-
|
|
139
|
-
|
|
140
140
|
class _TypeAdapterColumnValueSerializer(ColumnValueSerializer):
|
|
141
141
|
"""Implementation of serializer that uses pydantic type adapter."""
|
|
142
142
|
|
|
@@ -156,6 +156,8 @@ class _TypeAdapterColumnValueSerializer(ColumnValueSerializer):
|
|
|
156
156
|
class _BaseColumnSpec(pydantic.BaseModel, ABC):
|
|
157
157
|
"""Base class for descriptions of table columns."""
|
|
158
158
|
|
|
159
|
+
pytype: ClassVar[type]
|
|
160
|
+
|
|
159
161
|
name: str = pydantic.Field(description="""Name of the column.""")
|
|
160
162
|
|
|
161
163
|
doc: str = pydantic.Field(default="", description="Documentation for the column.")
|
|
@@ -200,7 +202,6 @@ class _BaseColumnSpec(pydantic.BaseModel, ABC):
|
|
|
200
202
|
"""
|
|
201
203
|
raise NotImplementedError()
|
|
202
204
|
|
|
203
|
-
@abstractmethod
|
|
204
205
|
def serializer(self) -> ColumnValueSerializer:
|
|
205
206
|
"""Return object that converts values of this column to or from
|
|
206
207
|
serializable format.
|
|
@@ -210,7 +211,7 @@ class _BaseColumnSpec(pydantic.BaseModel, ABC):
|
|
|
210
211
|
serializer : `ColumnValueSerializer`
|
|
211
212
|
A converter instance.
|
|
212
213
|
"""
|
|
213
|
-
|
|
214
|
+
return _TypeAdapterColumnValueSerializer(pydantic.TypeAdapter(self.annotated_type))
|
|
214
215
|
|
|
215
216
|
def display(self, level: int = 0, tab: str = " ") -> list[str]:
|
|
216
217
|
"""Return a human-reader-focused string description of this column as
|
|
@@ -243,6 +244,48 @@ class _BaseColumnSpec(pydantic.BaseModel, ABC):
|
|
|
243
244
|
def __str__(self) -> str:
|
|
244
245
|
return "\n".join(self.display())
|
|
245
246
|
|
|
247
|
+
@property
|
|
248
|
+
def annotated_type(self) -> Any:
|
|
249
|
+
"""Return a Pydantic-friendly type annotation for this column type.
|
|
250
|
+
|
|
251
|
+
Since this is a runtime object and most type annotations must be
|
|
252
|
+
static, this is really only useful for `pydantic.TypeAdapter`
|
|
253
|
+
construction and dynamic `pydantic.create_model` construction.
|
|
254
|
+
"""
|
|
255
|
+
base = self._get_base_annotated_type()
|
|
256
|
+
if self.nullable:
|
|
257
|
+
return Optional[base]
|
|
258
|
+
return base
|
|
259
|
+
|
|
260
|
+
@abstractmethod
|
|
261
|
+
def _get_base_annotated_type(self) -> Any:
|
|
262
|
+
"""Return the base annotated type (not taking into account `nullable`)
|
|
263
|
+
for this column type.
|
|
264
|
+
"""
|
|
265
|
+
raise NotImplementedError()
|
|
266
|
+
|
|
267
|
+
|
|
268
|
+
def make_tuple_type_adapter(
|
|
269
|
+
columns: Iterable[ColumnSpec],
|
|
270
|
+
) -> pydantic.TypeAdapter[tuple[Any, ...]]:
|
|
271
|
+
"""Return a `pydantic.TypeAdapter` for a `tuple` with types defined by an
|
|
272
|
+
iterable of `ColumnSpec` objects.
|
|
273
|
+
|
|
274
|
+
Parameters
|
|
275
|
+
----------
|
|
276
|
+
columns : `~collections.abc.Iterable` [ `ColumnSpec` ]
|
|
277
|
+
Iterable of column specifications.
|
|
278
|
+
|
|
279
|
+
Returns
|
|
280
|
+
-------
|
|
281
|
+
adapter : `pydantic.TypeAdapter`
|
|
282
|
+
A Pydantic type adapter for the `tuple` representation of a row with
|
|
283
|
+
the given columns.
|
|
284
|
+
"""
|
|
285
|
+
# Static type-checkers don't like this runtime use of static-typing
|
|
286
|
+
# constructs, but that's how Pydantic works.
|
|
287
|
+
return pydantic.TypeAdapter(tuple[*[spec.annotated_type for spec in columns]]) # type: ignore
|
|
288
|
+
|
|
246
289
|
|
|
247
290
|
@final
|
|
248
291
|
class IntColumnSpec(_BaseColumnSpec):
|
|
@@ -256,9 +299,9 @@ class IntColumnSpec(_BaseColumnSpec):
|
|
|
256
299
|
# Docstring inherited.
|
|
257
300
|
return arrow_utils.ToArrow.for_primitive(self.name, pa.uint64(), nullable=self.nullable)
|
|
258
301
|
|
|
259
|
-
def
|
|
302
|
+
def _get_base_annotated_type(self) -> Any:
|
|
260
303
|
# Docstring inherited.
|
|
261
|
-
return
|
|
304
|
+
return pydantic.StrictInt
|
|
262
305
|
|
|
263
306
|
|
|
264
307
|
@final
|
|
@@ -280,9 +323,9 @@ class StringColumnSpec(_BaseColumnSpec):
|
|
|
280
323
|
# Docstring inherited.
|
|
281
324
|
return arrow_utils.ToArrow.for_primitive(self.name, pa.string(), nullable=self.nullable)
|
|
282
325
|
|
|
283
|
-
def
|
|
326
|
+
def _get_base_annotated_type(self) -> Any:
|
|
284
327
|
# Docstring inherited.
|
|
285
|
-
return
|
|
328
|
+
return pydantic.StrictStr
|
|
286
329
|
|
|
287
330
|
|
|
288
331
|
@final
|
|
@@ -310,9 +353,9 @@ class HashColumnSpec(_BaseColumnSpec):
|
|
|
310
353
|
nullable=self.nullable,
|
|
311
354
|
)
|
|
312
355
|
|
|
313
|
-
def
|
|
356
|
+
def _get_base_annotated_type(self) -> Any:
|
|
314
357
|
# Docstring inherited.
|
|
315
|
-
return
|
|
358
|
+
return SerializableBytesHex
|
|
316
359
|
|
|
317
360
|
|
|
318
361
|
@final
|
|
@@ -328,9 +371,9 @@ class FloatColumnSpec(_BaseColumnSpec):
|
|
|
328
371
|
assert self.nullable is not None, "nullable=None should be resolved by validators"
|
|
329
372
|
return arrow_utils.ToArrow.for_primitive(self.name, pa.float64(), nullable=self.nullable)
|
|
330
373
|
|
|
331
|
-
def
|
|
374
|
+
def _get_base_annotated_type(self) -> Any:
|
|
332
375
|
# Docstring inherited.
|
|
333
|
-
return
|
|
376
|
+
return pydantic.StrictFloat
|
|
334
377
|
|
|
335
378
|
|
|
336
379
|
@final
|
|
@@ -345,9 +388,9 @@ class BoolColumnSpec(_BaseColumnSpec):
|
|
|
345
388
|
# Docstring inherited.
|
|
346
389
|
return arrow_utils.ToArrow.for_primitive(self.name, pa.bool_(), nullable=self.nullable)
|
|
347
390
|
|
|
348
|
-
def
|
|
391
|
+
def _get_base_annotated_type(self) -> Any:
|
|
349
392
|
# Docstring inherited.
|
|
350
|
-
return
|
|
393
|
+
return pydantic.StrictBool
|
|
351
394
|
|
|
352
395
|
|
|
353
396
|
@final
|
|
@@ -363,9 +406,9 @@ class UUIDColumnSpec(_BaseColumnSpec):
|
|
|
363
406
|
assert self.nullable is not None, "nullable=None should be resolved by validators"
|
|
364
407
|
return arrow_utils.ToArrow.for_uuid(self.name, nullable=self.nullable)
|
|
365
408
|
|
|
366
|
-
def
|
|
409
|
+
def _get_base_annotated_type(self) -> Any:
|
|
367
410
|
# Docstring inherited.
|
|
368
|
-
return
|
|
411
|
+
return uuid.UUID
|
|
369
412
|
|
|
370
413
|
|
|
371
414
|
@final
|
|
@@ -386,9 +429,9 @@ class RegionColumnSpec(_BaseColumnSpec):
|
|
|
386
429
|
assert self.nullable is not None, "nullable=None should be resolved by validators"
|
|
387
430
|
return arrow_utils.ToArrow.for_region(self.name, nullable=self.nullable)
|
|
388
431
|
|
|
389
|
-
def
|
|
432
|
+
def _get_base_annotated_type(self) -> Any:
|
|
390
433
|
# Docstring inherited.
|
|
391
|
-
return
|
|
434
|
+
return SerializableRegion
|
|
392
435
|
|
|
393
436
|
|
|
394
437
|
@final
|
|
@@ -405,9 +448,9 @@ class TimespanColumnSpec(_BaseColumnSpec):
|
|
|
405
448
|
# Docstring inherited.
|
|
406
449
|
return arrow_utils.ToArrow.for_timespan(self.name, nullable=self.nullable)
|
|
407
450
|
|
|
408
|
-
def
|
|
451
|
+
def _get_base_annotated_type(self) -> Any:
|
|
409
452
|
# Docstring inherited.
|
|
410
|
-
return
|
|
453
|
+
return Timespan
|
|
411
454
|
|
|
412
455
|
|
|
413
456
|
@final
|
|
@@ -425,9 +468,9 @@ class DateTimeColumnSpec(_BaseColumnSpec):
|
|
|
425
468
|
assert self.nullable is not None, "nullable=None should be resolved by validators"
|
|
426
469
|
return arrow_utils.ToArrow.for_datetime(self.name, nullable=self.nullable)
|
|
427
470
|
|
|
428
|
-
def
|
|
471
|
+
def _get_base_annotated_type(self) -> Any:
|
|
429
472
|
# Docstring inherited.
|
|
430
|
-
return
|
|
473
|
+
return SerializableTime
|
|
431
474
|
|
|
432
475
|
|
|
433
476
|
ColumnSpec = Annotated[
|
|
@@ -94,3 +94,4 @@ Timespan: lsst.daf.butler.formatters.json.JsonFormatter
|
|
|
94
94
|
RegionTimeInfo: lsst.daf.butler.formatters.json.JsonFormatter
|
|
95
95
|
QPEnsemble: lsst.meas.pz.qp_formatter.QPFormatter
|
|
96
96
|
PZModel: lsst.meas.pz.model_formatter.ModelFormatter
|
|
97
|
+
VisitBackgroundModel: lsst.daf.butler.formatters.json.JsonFormatter
|