lsst-daf-butler 29.0.0rc1__py3-none-any.whl → 29.2025.1100__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/datastores/fileDatastore.py +14 -0
- lsst/daf/butler/direct_query_driver/_driver.py +1 -1
- lsst/daf/butler/queries/_expression_strings.py +7 -0
- lsst/daf/butler/registry/databases/sqlite.py +6 -0
- lsst/daf/butler/registry/datasets/byDimensions/_manager.py +10 -0
- lsst/daf/butler/registry/interfaces/_database.py +1 -1
- lsst/daf/butler/registry/queries/expressions/_predicate.py +26 -19
- lsst/daf/butler/registry/queries/expressions/check.py +19 -10
- lsst/daf/butler/registry/queries/expressions/normalForm.py +4 -0
- lsst/daf/butler/registry/queries/expressions/parser/exprTree.py +23 -0
- lsst/daf/butler/registry/queries/expressions/parser/parserLex.py +9 -0
- lsst/daf/butler/registry/queries/expressions/parser/parserYacc.py +20 -15
- lsst/daf/butler/registry/queries/expressions/parser/treeVisitor.py +14 -2
- lsst/daf/butler/registry/tests/_registry.py +34 -21
- lsst/daf/butler/tests/butler_queries.py +23 -23
- lsst/daf/butler/tests/hybrid_butler_registry.py +10 -0
- lsst/daf/butler/version.py +1 -1
- {lsst_daf_butler-29.0.0rc1.dist-info → lsst_daf_butler-29.2025.1100.dist-info}/METADATA +1 -1
- {lsst_daf_butler-29.0.0rc1.dist-info → lsst_daf_butler-29.2025.1100.dist-info}/RECORD +27 -27
- {lsst_daf_butler-29.0.0rc1.dist-info → lsst_daf_butler-29.2025.1100.dist-info}/WHEEL +1 -1
- {lsst_daf_butler-29.0.0rc1.dist-info → lsst_daf_butler-29.2025.1100.dist-info}/COPYRIGHT +0 -0
- {lsst_daf_butler-29.0.0rc1.dist-info → lsst_daf_butler-29.2025.1100.dist-info}/LICENSE +0 -0
- {lsst_daf_butler-29.0.0rc1.dist-info → lsst_daf_butler-29.2025.1100.dist-info}/bsd_license.txt +0 -0
- {lsst_daf_butler-29.0.0rc1.dist-info → lsst_daf_butler-29.2025.1100.dist-info}/entry_points.txt +0 -0
- {lsst_daf_butler-29.0.0rc1.dist-info → lsst_daf_butler-29.2025.1100.dist-info}/gpl-v3.0.txt +0 -0
- {lsst_daf_butler-29.0.0rc1.dist-info → lsst_daf_butler-29.2025.1100.dist-info}/top_level.txt +0 -0
- {lsst_daf_butler-29.0.0rc1.dist-info → lsst_daf_butler-29.2025.1100.dist-info}/zip-safe +0 -0
|
@@ -2649,6 +2649,7 @@ class FileDatastore(GenericBaseDatastore[StoredFileInfo]):
|
|
|
2649
2649
|
artifact_existence: dict[ResourcePath, bool] | None = None,
|
|
2650
2650
|
dry_run: bool = False,
|
|
2651
2651
|
) -> tuple[set[DatasetRef], set[DatasetRef]]:
|
|
2652
|
+
log.verbose("transfer_from %s to %s", source_datastore.name, self.name)
|
|
2652
2653
|
# Docstring inherited
|
|
2653
2654
|
if type(self) is not type(source_datastore):
|
|
2654
2655
|
raise TypeError(
|
|
@@ -2693,6 +2694,7 @@ class FileDatastore(GenericBaseDatastore[StoredFileInfo]):
|
|
|
2693
2694
|
# What we really want is all the records in the source datastore
|
|
2694
2695
|
# associated with these refs. Or derived ones if they don't exist
|
|
2695
2696
|
# in the source.
|
|
2697
|
+
log.verbose("Looking up source datastore records in %s", source_datastore.name)
|
|
2696
2698
|
source_records = source_datastore._get_stored_records_associated_with_refs(
|
|
2697
2699
|
refs, ignore_datastore_records=True
|
|
2698
2700
|
)
|
|
@@ -2726,6 +2728,9 @@ class FileDatastore(GenericBaseDatastore[StoredFileInfo]):
|
|
|
2726
2728
|
source_records.update(found_records)
|
|
2727
2729
|
|
|
2728
2730
|
# See if we already have these records
|
|
2731
|
+
log.verbose(
|
|
2732
|
+
"Looking up existing datastore records in target %s for %d refs", self.name, len(requested_ids)
|
|
2733
|
+
)
|
|
2729
2734
|
target_records = self._get_stored_records_associated_with_refs(refs, ignore_datastore_records=True)
|
|
2730
2735
|
|
|
2731
2736
|
# The artifacts to register
|
|
@@ -2744,6 +2749,7 @@ class FileDatastore(GenericBaseDatastore[StoredFileInfo]):
|
|
|
2744
2749
|
direct_transfers = []
|
|
2745
2750
|
|
|
2746
2751
|
# Now can transfer the artifacts
|
|
2752
|
+
log.verbose("Transferring artifacts")
|
|
2747
2753
|
for ref in refs:
|
|
2748
2754
|
if not self.constraints.isAcceptable(ref):
|
|
2749
2755
|
# This datastore should not be accepting this dataset.
|
|
@@ -2818,6 +2824,7 @@ class FileDatastore(GenericBaseDatastore[StoredFileInfo]):
|
|
|
2818
2824
|
# to difficulties if the dataset has previously been ingested
|
|
2819
2825
|
# disassembled and is somehow now assembled, or vice versa.
|
|
2820
2826
|
if not dry_run:
|
|
2827
|
+
log.verbose("Registering datastore records in database")
|
|
2821
2828
|
self._register_datasets(artifacts, insert_mode=DatabaseInsertMode.REPLACE)
|
|
2822
2829
|
|
|
2823
2830
|
if already_present:
|
|
@@ -2828,6 +2835,13 @@ class FileDatastore(GenericBaseDatastore[StoredFileInfo]):
|
|
|
2828
2835
|
"" if n_skipped == 1 else "s",
|
|
2829
2836
|
)
|
|
2830
2837
|
|
|
2838
|
+
log.verbose(
|
|
2839
|
+
"Finished transfer_from %s to %s with %d accepted, %d rejected",
|
|
2840
|
+
source_datastore.name,
|
|
2841
|
+
self.name,
|
|
2842
|
+
len(accepted),
|
|
2843
|
+
len(rejected),
|
|
2844
|
+
)
|
|
2831
2845
|
return accepted, rejected
|
|
2832
2846
|
|
|
2833
2847
|
@transactional
|
|
@@ -156,7 +156,7 @@ class DirectQueryDriver(QueryDriver):
|
|
|
156
156
|
self._exit_stack: ExitStack | None = None
|
|
157
157
|
self._raw_page_size = raw_page_size
|
|
158
158
|
self._postprocessing_filter_factor = postprocessing_filter_factor
|
|
159
|
-
self._constant_rows_limit = constant_rows_limit
|
|
159
|
+
self._constant_rows_limit = min(constant_rows_limit, db.get_constant_rows_max())
|
|
160
160
|
self._cursors: set[_Cursor] = set()
|
|
161
161
|
|
|
162
162
|
def __enter__(self) -> None:
|
|
@@ -230,6 +230,13 @@ class _ConversionVisitor(TreeVisitor[_VisitorResult]):
|
|
|
230
230
|
else:
|
|
231
231
|
return _ColExpr(column_expression)
|
|
232
232
|
|
|
233
|
+
def visitBind(self, name: str, node: Node) -> _VisitorResult:
|
|
234
|
+
name = name.lower()
|
|
235
|
+
if name not in self.context.bind:
|
|
236
|
+
raise InvalidQueryError("Name {name!r} is not in the bind map.")
|
|
237
|
+
# Logic in visitIdentifier handles binds.
|
|
238
|
+
return self.visitIdentifier(name, node)
|
|
239
|
+
|
|
233
240
|
def visitNumericLiteral(self, value: str, node: Node) -> _VisitorResult:
|
|
234
241
|
numeric: int | float
|
|
235
242
|
try:
|
|
@@ -398,6 +398,12 @@ class SqliteDatabase(Database):
|
|
|
398
398
|
]
|
|
399
399
|
return sqlalchemy.sql.union_all(*selects).alias(name)
|
|
400
400
|
|
|
401
|
+
def get_constant_rows_max(self) -> int:
|
|
402
|
+
# Docstring inherited.
|
|
403
|
+
# This is the default SQLITE_MAX_COMPOUND_SELECT (see
|
|
404
|
+
# https://www.sqlite.org/limits.html):
|
|
405
|
+
return 500
|
|
406
|
+
|
|
401
407
|
@property
|
|
402
408
|
def has_distinct_on(self) -> bool:
|
|
403
409
|
# Docstring inherited.
|
|
@@ -317,6 +317,16 @@ class ByDimensionsDatasetRecordStorageManagerUUID(DatasetRecordStorageManager):
|
|
|
317
317
|
raise ValueError(
|
|
318
318
|
f"Component dataset types can not be stored in registry. Rejecting {dataset_type.name}"
|
|
319
319
|
)
|
|
320
|
+
|
|
321
|
+
# If database universe and dimension group universe are different it
|
|
322
|
+
# can cause unexpected effects.
|
|
323
|
+
if dataset_type.dimensions.universe is not self._dimensions.universe:
|
|
324
|
+
raise ValueError(
|
|
325
|
+
"Incompatible dimension universe versions - "
|
|
326
|
+
f"database universe: {self._dimensions.universe}, "
|
|
327
|
+
f"dataset type universe: {dataset_type.dimensions.universe}."
|
|
328
|
+
)
|
|
329
|
+
|
|
320
330
|
record = self._fetch_dataset_type_record(dataset_type.name)
|
|
321
331
|
if record is None:
|
|
322
332
|
if (dynamic_tables := self._cache.get_by_dimensions(dataset_type.dimensions)) is None:
|
|
@@ -353,25 +353,7 @@ class PredicateConversionVisitor(TreeVisitor[VisitorResult]):
|
|
|
353
353
|
def visitIdentifier(self, name: str, node: Node) -> VisitorResult:
|
|
354
354
|
# Docstring inherited.
|
|
355
355
|
if name in self.bind:
|
|
356
|
-
|
|
357
|
-
if isinstance(value, list | tuple | Set):
|
|
358
|
-
elements = []
|
|
359
|
-
all_dtypes = set()
|
|
360
|
-
for item in value:
|
|
361
|
-
dtype = type(item)
|
|
362
|
-
all_dtypes.add(dtype)
|
|
363
|
-
elements.append(ColumnExpression.literal(item, dtype=dtype))
|
|
364
|
-
if len(all_dtypes) > 1:
|
|
365
|
-
raise ExpressionTypeError(
|
|
366
|
-
f"Mismatched types in bind iterable: {value} has a mix of {all_dtypes}."
|
|
367
|
-
)
|
|
368
|
-
elif not elements:
|
|
369
|
-
# Empty container
|
|
370
|
-
return ColumnContainer.sequence([])
|
|
371
|
-
else:
|
|
372
|
-
(dtype,) = all_dtypes
|
|
373
|
-
return ColumnContainer.sequence(elements, dtype=dtype)
|
|
374
|
-
return ColumnExpression.literal(value, dtype=type(value))
|
|
356
|
+
return self.visitBind(name, node)
|
|
375
357
|
tag: ColumnTag
|
|
376
358
|
match categorizeConstant(name):
|
|
377
359
|
case ExpressionConstant.INGEST_DATE:
|
|
@@ -403,6 +385,31 @@ class PredicateConversionVisitor(TreeVisitor[VisitorResult]):
|
|
|
403
385
|
assert isinstance(element, Dimension)
|
|
404
386
|
return ColumnExpression.reference(tag, element.primaryKey.getPythonType())
|
|
405
387
|
|
|
388
|
+
def visitBind(self, name: str, node: Node) -> VisitorResult:
|
|
389
|
+
# Docstring inherited.
|
|
390
|
+
if name not in self.bind:
|
|
391
|
+
raise UserExpressionError(f"Name {name!r} is not in the bind map.")
|
|
392
|
+
|
|
393
|
+
value = self.bind[name]
|
|
394
|
+
if isinstance(value, list | tuple | Set):
|
|
395
|
+
elements = []
|
|
396
|
+
all_dtypes = set()
|
|
397
|
+
for item in value:
|
|
398
|
+
dtype = type(item)
|
|
399
|
+
all_dtypes.add(dtype)
|
|
400
|
+
elements.append(ColumnExpression.literal(item, dtype=dtype))
|
|
401
|
+
if len(all_dtypes) > 1:
|
|
402
|
+
raise ExpressionTypeError(
|
|
403
|
+
f"Mismatched types in bind iterable: {value} has a mix of {all_dtypes}."
|
|
404
|
+
)
|
|
405
|
+
elif not elements:
|
|
406
|
+
# Empty container
|
|
407
|
+
return ColumnContainer.sequence([])
|
|
408
|
+
else:
|
|
409
|
+
(dtype,) = all_dtypes
|
|
410
|
+
return ColumnContainer.sequence(elements, dtype=dtype)
|
|
411
|
+
return ColumnExpression.literal(value, dtype=type(value))
|
|
412
|
+
|
|
406
413
|
def visitIsIn(
|
|
407
414
|
self, lhs: VisitorResult, values: list[VisitorResult], not_in: bool, node: Node
|
|
408
415
|
) -> VisitorResult:
|
|
@@ -212,16 +212,8 @@ class InspectionVisitor(TreeVisitor[TreeSummary]):
|
|
|
212
212
|
def visitIdentifier(self, name: str, node: Node) -> TreeSummary:
|
|
213
213
|
# Docstring inherited from TreeVisitor.visitIdentifier
|
|
214
214
|
if name in self.bind:
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
# This can happen on rhs of IN operator, if there is only one
|
|
218
|
-
# element in the list then take it.
|
|
219
|
-
if len(value) == 1:
|
|
220
|
-
return TreeSummary(dataIdValue=next(iter(value)))
|
|
221
|
-
else:
|
|
222
|
-
return TreeSummary()
|
|
223
|
-
else:
|
|
224
|
-
return TreeSummary(dataIdValue=value)
|
|
215
|
+
return self.visitBind(name, node)
|
|
216
|
+
|
|
225
217
|
constant = categorizeConstant(name)
|
|
226
218
|
if constant is ExpressionConstant.INGEST_DATE:
|
|
227
219
|
return TreeSummary(hasIngestDate=True)
|
|
@@ -238,6 +230,23 @@ class InspectionVisitor(TreeVisitor[TreeSummary]):
|
|
|
238
230
|
else:
|
|
239
231
|
return TreeSummary(dimensions=set(element.minimal_group.names), columns={element.name: {column}})
|
|
240
232
|
|
|
233
|
+
def visitBind(self, name: str, node: Node) -> TreeSummary:
|
|
234
|
+
if name not in self.bind:
|
|
235
|
+
raise UserExpressionError(f"Name {name!r} is not in the bind map.")
|
|
236
|
+
value = self.bind[name]
|
|
237
|
+
match value:
|
|
238
|
+
case list() | tuple() | Set():
|
|
239
|
+
# This can happen on rhs of IN operator, if there is only one
|
|
240
|
+
# element in the list then take it.
|
|
241
|
+
match len(value):
|
|
242
|
+
case 1:
|
|
243
|
+
(value,) = value
|
|
244
|
+
return TreeSummary(dataIdValue=value)
|
|
245
|
+
case _:
|
|
246
|
+
return TreeSummary()
|
|
247
|
+
case _:
|
|
248
|
+
return TreeSummary(dataIdValue=value)
|
|
249
|
+
|
|
241
250
|
def visitUnaryOp(self, operator: str, operand: TreeSummary, node: Node) -> TreeSummary:
|
|
242
251
|
# Docstring inherited from TreeVisitor.visitUnaryOp
|
|
243
252
|
return operand
|
|
@@ -1040,6 +1040,10 @@ class TransformationVisitor(TreeVisitor[TransformationWrapper]):
|
|
|
1040
1040
|
# Docstring inherited from TreeVisitor.visitIdentifier
|
|
1041
1041
|
return Opaque(node, PrecedenceTier.TOKEN)
|
|
1042
1042
|
|
|
1043
|
+
def visitBind(self, name: str, node: Node) -> TransformationWrapper:
|
|
1044
|
+
# Docstring inherited from TreeVisitor.visitBind
|
|
1045
|
+
return Opaque(node, PrecedenceTier.TOKEN)
|
|
1046
|
+
|
|
1043
1047
|
def visitUnaryOp(
|
|
1044
1048
|
self,
|
|
1045
1049
|
operator: str,
|
|
@@ -256,6 +256,29 @@ class Identifier(Node):
|
|
|
256
256
|
return "{name}".format(**vars(self))
|
|
257
257
|
|
|
258
258
|
|
|
259
|
+
class BindName(Node):
|
|
260
|
+
"""Node representing a bind name.
|
|
261
|
+
|
|
262
|
+
Value of the bind is its name, which is a simple identifier.
|
|
263
|
+
|
|
264
|
+
Parameters
|
|
265
|
+
----------
|
|
266
|
+
name : str
|
|
267
|
+
Bind name.
|
|
268
|
+
"""
|
|
269
|
+
|
|
270
|
+
def __init__(self, name: str):
|
|
271
|
+
Node.__init__(self)
|
|
272
|
+
self.name = name
|
|
273
|
+
|
|
274
|
+
def visit(self, visitor: TreeVisitor) -> Any:
|
|
275
|
+
# Docstring inherited from Node.visit
|
|
276
|
+
return visitor.visitBind(self.name, self)
|
|
277
|
+
|
|
278
|
+
def __str__(self) -> str:
|
|
279
|
+
return "{name}".format(**vars(self))
|
|
280
|
+
|
|
281
|
+
|
|
259
282
|
class RangeLiteral(Node):
|
|
260
283
|
"""Node representing range literal appearing in `IN` list.
|
|
261
284
|
|
|
@@ -128,6 +128,7 @@ class ParserLex:
|
|
|
128
128
|
# 'DURATION_LITERAL',
|
|
129
129
|
"QUALIFIED_IDENTIFIER",
|
|
130
130
|
"SIMPLE_IDENTIFIER",
|
|
131
|
+
"BIND_NAME",
|
|
131
132
|
"LPAREN",
|
|
132
133
|
"RPAREN",
|
|
133
134
|
"EQ",
|
|
@@ -221,6 +222,14 @@ class ParserLex:
|
|
|
221
222
|
t.type = "SIMPLE_IDENTIFIER"
|
|
222
223
|
return t
|
|
223
224
|
|
|
225
|
+
# we only support ASCII in identifier names
|
|
226
|
+
def t_BIND_NAME(self, t: LexToken) -> LexToken:
|
|
227
|
+
"""[:][a-zA-Z_][a-zA-Z0-9_]*"""
|
|
228
|
+
# Drop colon to get the name.
|
|
229
|
+
t.value = t.value[1:]
|
|
230
|
+
t.type = "BIND_NAME"
|
|
231
|
+
return t
|
|
232
|
+
|
|
224
233
|
def t_error(self, t: LexToken) -> None:
|
|
225
234
|
"""Error handling rule"""
|
|
226
235
|
lexer = t.lexer
|
|
@@ -47,6 +47,7 @@ except ImportError:
|
|
|
47
47
|
|
|
48
48
|
from .exprTree import (
|
|
49
49
|
BinaryOp,
|
|
50
|
+
BindName,
|
|
50
51
|
Identifier,
|
|
51
52
|
IsIn,
|
|
52
53
|
Node,
|
|
@@ -59,7 +60,7 @@ from .exprTree import (
|
|
|
59
60
|
UnaryOp,
|
|
60
61
|
function_call,
|
|
61
62
|
)
|
|
62
|
-
from .parserLex import LexToken, ParserLex
|
|
63
|
+
from .parserLex import LexToken, ParserLex, ParserLexError
|
|
63
64
|
from .ply import yacc
|
|
64
65
|
|
|
65
66
|
|
|
@@ -223,8 +224,7 @@ class ParseError(ParserYaccError):
|
|
|
223
224
|
self.pos = pos
|
|
224
225
|
self.lineno = lineno
|
|
225
226
|
self.posInLine = self._posInLine()
|
|
226
|
-
msg = "Syntax error at or near '{
|
|
227
|
-
msg = msg.format(token, lineno, self.posInLine + 1)
|
|
227
|
+
msg = f"Syntax error at or near '{token}' (line: {lineno}, pos: {self.posInLine + 1})"
|
|
228
228
|
ParserYaccError.__init__(self, msg)
|
|
229
229
|
|
|
230
230
|
def _posInLine(self) -> int:
|
|
@@ -288,7 +288,11 @@ class ParserYacc:
|
|
|
288
288
|
# make lexer
|
|
289
289
|
if lexer is None:
|
|
290
290
|
lexer = ParserLex.make_lexer()
|
|
291
|
-
|
|
291
|
+
try:
|
|
292
|
+
tree = self.parser.parse(input=input, lexer=lexer, debug=debug, tracking=tracking)
|
|
293
|
+
except ParserLexError as exc:
|
|
294
|
+
# Convert it into ParserYaccError
|
|
295
|
+
raise ParserYaccError(str(exc)) from exc
|
|
292
296
|
return tree
|
|
293
297
|
|
|
294
298
|
tokens = ParserLex.tokens[:]
|
|
@@ -371,14 +375,21 @@ class ParserYacc:
|
|
|
371
375
|
def p_literal_or_id_list(cls, p: YaccProduction) -> None:
|
|
372
376
|
"""literal_or_id_list : literal_or_id_list COMMA literal
|
|
373
377
|
| literal_or_id_list COMMA identifier
|
|
378
|
+
| literal_or_id_list COMMA bind_name
|
|
374
379
|
| literal
|
|
375
380
|
| identifier
|
|
381
|
+
| bind_name
|
|
376
382
|
"""
|
|
377
383
|
if len(p) == 2:
|
|
378
384
|
p[0] = [p[1]]
|
|
379
385
|
else:
|
|
380
386
|
p[0] = p[1] + [p[3]]
|
|
381
387
|
|
|
388
|
+
@classmethod
|
|
389
|
+
def p_bind_name(cls, p: YaccProduction) -> None:
|
|
390
|
+
"""bind_name : BIND_NAME"""
|
|
391
|
+
p[0] = BindName(p[1])
|
|
392
|
+
|
|
382
393
|
@classmethod
|
|
383
394
|
def p_bit_expr(cls, p: YaccProduction) -> None:
|
|
384
395
|
"""bit_expr : bit_expr ADD bit_expr
|
|
@@ -395,17 +406,11 @@ class ParserYacc:
|
|
|
395
406
|
|
|
396
407
|
@classmethod
|
|
397
408
|
def p_simple_expr_lit(cls, p: YaccProduction) -> None:
|
|
398
|
-
"""simple_expr : literal
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
"""simple_expr : identifier"""
|
|
404
|
-
p[0] = p[1]
|
|
405
|
-
|
|
406
|
-
@classmethod
|
|
407
|
-
def p_simple_expr_function_call(cls, p: YaccProduction) -> None:
|
|
408
|
-
"""simple_expr : function_call"""
|
|
409
|
+
"""simple_expr : literal
|
|
410
|
+
| identifier
|
|
411
|
+
| bind_name
|
|
412
|
+
| function_call
|
|
413
|
+
"""
|
|
409
414
|
p[0] = p[1]
|
|
410
415
|
|
|
411
416
|
@classmethod
|
|
@@ -29,7 +29,7 @@ from __future__ import annotations
|
|
|
29
29
|
|
|
30
30
|
__all__ = ["TreeVisitor"]
|
|
31
31
|
|
|
32
|
-
from abc import abstractmethod
|
|
32
|
+
from abc import ABC, abstractmethod
|
|
33
33
|
from typing import TYPE_CHECKING, Generic, TypeVar
|
|
34
34
|
|
|
35
35
|
if TYPE_CHECKING:
|
|
@@ -41,7 +41,7 @@ if TYPE_CHECKING:
|
|
|
41
41
|
T = TypeVar("T")
|
|
42
42
|
|
|
43
43
|
|
|
44
|
-
class TreeVisitor(Generic[T]):
|
|
44
|
+
class TreeVisitor(Generic[T], ABC):
|
|
45
45
|
"""Definition of interface for visitor classes.
|
|
46
46
|
|
|
47
47
|
Visitors and tree node classes implement Visitor pattern for tree
|
|
@@ -121,6 +121,18 @@ class TreeVisitor(Generic[T]):
|
|
|
121
121
|
Corresponding tree node, mostly useful for diagnostics.
|
|
122
122
|
"""
|
|
123
123
|
|
|
124
|
+
@abstractmethod
|
|
125
|
+
def visitBind(self, name: str, node: Node) -> T:
|
|
126
|
+
"""Visit BindName node.
|
|
127
|
+
|
|
128
|
+
Parameters
|
|
129
|
+
----------
|
|
130
|
+
name : `str`
|
|
131
|
+
Bind name.
|
|
132
|
+
node : `Node`
|
|
133
|
+
Corresponding tree node, mostly useful for diagnostics.
|
|
134
|
+
"""
|
|
135
|
+
|
|
124
136
|
@abstractmethod
|
|
125
137
|
def visitUnaryOp(self, operator: str, operand: T, node: Node) -> T:
|
|
126
138
|
"""Visit UnaryOp node.
|
|
@@ -75,7 +75,7 @@ from ..._exceptions import (
|
|
|
75
75
|
from ..._exceptions_legacy import DatasetTypeError
|
|
76
76
|
from ..._storage_class import StorageClass
|
|
77
77
|
from ..._timespan import Timespan
|
|
78
|
-
from ...dimensions import DataCoordinate, DataCoordinateSet, SkyPixDimension
|
|
78
|
+
from ...dimensions import DataCoordinate, DataCoordinateSet, DimensionUniverse, SkyPixDimension
|
|
79
79
|
from .._collection_summary import CollectionSummary
|
|
80
80
|
from .._config import RegistryConfig
|
|
81
81
|
from .._exceptions import (
|
|
@@ -326,6 +326,17 @@ class RegistryTests(ABC):
|
|
|
326
326
|
self.assertCountEqual([dt.name for dt in types], ["test", "testNoneTemplate"])
|
|
327
327
|
self.assertEqual(missing, ["notarealdatasettype"])
|
|
328
328
|
|
|
329
|
+
# Trying to register a dataset type with different universe version or
|
|
330
|
+
# namespace will raise.
|
|
331
|
+
wrong_universes = (DimensionUniverse(version=-1), DimensionUniverse(namespace="🔭"))
|
|
332
|
+
for universe in wrong_universes:
|
|
333
|
+
storageClass = StorageClass("testDatasetType")
|
|
334
|
+
dataset_type = DatasetType(
|
|
335
|
+
"wrong_universe", ("instrument", "visit"), storageClass, universe=universe
|
|
336
|
+
)
|
|
337
|
+
with self.assertRaisesRegex(ValueError, "Incompatible dimension universe versions"):
|
|
338
|
+
registry.registerDatasetType(dataset_type)
|
|
339
|
+
|
|
329
340
|
def testDatasetTypeCache(self):
|
|
330
341
|
"""Test for dataset type cache update logic after a cache miss."""
|
|
331
342
|
butler1 = self.make_butler()
|
|
@@ -2801,7 +2812,7 @@ class RegistryTests(ABC):
|
|
|
2801
2812
|
self.assertEqual(len(datasets), expect_len)
|
|
2802
2813
|
|
|
2803
2814
|
# same with bind using datetime or astropy Time
|
|
2804
|
-
where = f"ingest_date {op} ingest_time"
|
|
2815
|
+
where = f"ingest_date {op} :ingest_time"
|
|
2805
2816
|
datasets = list(
|
|
2806
2817
|
registry.queryDatasets(..., collections=..., where=where, bind={"ingest_time": dt})
|
|
2807
2818
|
)
|
|
@@ -2870,33 +2881,33 @@ class RegistryTests(ABC):
|
|
|
2870
2881
|
# the expression.
|
|
2871
2882
|
|
|
2872
2883
|
# t1 is before the start of i1, so this should not include i1.
|
|
2873
|
-
self.assertEqual(ids[:i1], query("visit.timespan OVERLAPS (null, t1)"))
|
|
2884
|
+
self.assertEqual(ids[:i1], query("visit.timespan OVERLAPS (null, :t1)"))
|
|
2874
2885
|
# t2 is exactly at the start of i2, but ends are exclusive, so these
|
|
2875
2886
|
# should not include i2.
|
|
2876
|
-
self.assertEqual(ids[i1:i2], query("(t1, t2) OVERLAPS visit.timespan"))
|
|
2887
|
+
self.assertEqual(ids[i1:i2], query("(:t1, :t2) OVERLAPS visit.timespan"))
|
|
2877
2888
|
if self.supportsExtendedTimeQueryOperators:
|
|
2878
|
-
self.assertEqual(ids[:i2], query("visit.timespan < (t2, t4)"))
|
|
2889
|
+
self.assertEqual(ids[:i2], query("visit.timespan < (:t2, :t4)"))
|
|
2879
2890
|
# t3 is in the middle of i3, so this should include i3.
|
|
2880
|
-
self.assertEqual(ids[i2 : i3 + 1], query("visit.timespan OVERLAPS ts23"))
|
|
2891
|
+
self.assertEqual(ids[i2 : i3 + 1], query("visit.timespan OVERLAPS :ts23"))
|
|
2881
2892
|
# This one should not include t3 by the same reasoning.
|
|
2882
2893
|
if self.supportsExtendedTimeQueryOperators:
|
|
2883
|
-
self.assertEqual(ids[i3 + 1 :], query("visit.timespan > (t1, t3)"))
|
|
2894
|
+
self.assertEqual(ids[i3 + 1 :], query("visit.timespan > (:t1, :t3)"))
|
|
2884
2895
|
# t4 is exactly at the end of i4, so this should include i4.
|
|
2885
|
-
self.assertEqual(ids[i3 : i4 + 1], query(f"visit.timespan OVERLAPS (T'{t3.tai.isot}', t4)"))
|
|
2896
|
+
self.assertEqual(ids[i3 : i4 + 1], query(f"visit.timespan OVERLAPS (T'{t3.tai.isot}', :t4)"))
|
|
2886
2897
|
# i4's upper bound of t4 is exclusive so this should not include t4.
|
|
2887
|
-
self.assertEqual(ids[i4 + 1 :], query("visit.timespan OVERLAPS (t4, NULL)"))
|
|
2898
|
+
self.assertEqual(ids[i4 + 1 :], query("visit.timespan OVERLAPS (:t4, NULL)"))
|
|
2888
2899
|
|
|
2889
2900
|
# Now some timespan vs. time scalar queries.
|
|
2890
|
-
self.assertEqual(ids[i3 : i3 + 1], query("visit.timespan OVERLAPS t3"))
|
|
2901
|
+
self.assertEqual(ids[i3 : i3 + 1], query("visit.timespan OVERLAPS :t3"))
|
|
2891
2902
|
self.assertEqual(ids[i3 : i3 + 1], query(f"T'{t3.tai.isot}' OVERLAPS visit.timespan"))
|
|
2892
2903
|
if self.supportsExtendedTimeQueryOperators:
|
|
2893
|
-
self.assertEqual(ids[:i2], query("visit.timespan < t2"))
|
|
2894
|
-
self.assertEqual(ids[:i2], query("t2 > visit.timespan"))
|
|
2895
|
-
self.assertEqual(ids[i3 + 1 :], query("visit.timespan > t3"))
|
|
2896
|
-
self.assertEqual(ids[i3 + 1 :], query("t3 < visit.timespan"))
|
|
2904
|
+
self.assertEqual(ids[:i2], query("visit.timespan < :t2"))
|
|
2905
|
+
self.assertEqual(ids[:i2], query(":t2 > visit.timespan"))
|
|
2906
|
+
self.assertEqual(ids[i3 + 1 :], query("visit.timespan > :t3"))
|
|
2907
|
+
self.assertEqual(ids[i3 + 1 :], query(":t3 < visit.timespan"))
|
|
2897
2908
|
|
|
2898
2909
|
# Empty timespans should not overlap anything.
|
|
2899
|
-
self.assertEqual([], query("visit.timespan OVERLAPS (t3, t2)"))
|
|
2910
|
+
self.assertEqual([], query("visit.timespan OVERLAPS (:t3, :t2)"))
|
|
2900
2911
|
|
|
2901
2912
|
# Make sure that expanded data IDs include the timespans.
|
|
2902
2913
|
results = list(
|
|
@@ -2973,7 +2984,9 @@ class RegistryTests(ABC):
|
|
|
2973
2984
|
self.load_data(butler, "base.yaml", "datasets.yaml")
|
|
2974
2985
|
self.assertEqual(
|
|
2975
2986
|
set(registry.queryDatasets("flat", band="r", collections=...)),
|
|
2976
|
-
set(
|
|
2987
|
+
set(
|
|
2988
|
+
registry.queryDatasets("flat", where="band=:my_band", bind={"my_band": "r"}, collections=...)
|
|
2989
|
+
),
|
|
2977
2990
|
)
|
|
2978
2991
|
|
|
2979
2992
|
def testQueryIntRangeExpressions(self):
|
|
@@ -3212,7 +3225,7 @@ class RegistryTests(ABC):
|
|
|
3212
3225
|
"(Dimension element name cannot be inferred in this context.)"
|
|
3213
3226
|
"|(Unrecognized identifier 'timespan')",
|
|
3214
3227
|
):
|
|
3215
|
-
list(registry.queryDataIds(["detector"], where="timespan.end < time", bind=bind))
|
|
3228
|
+
list(registry.queryDataIds(["detector"], where="timespan.end < :time", bind=bind))
|
|
3216
3229
|
|
|
3217
3230
|
def testQueryDataIdsOrderBy(self):
|
|
3218
3231
|
"""Test order_by and limit on result returned by queryDataIds()."""
|
|
@@ -3390,13 +3403,13 @@ class RegistryTests(ABC):
|
|
|
3390
3403
|
Test("tract,visit", where="instrument='Cam1' AND skymap='SkyMap5'", exception=DataIdValueError),
|
|
3391
3404
|
Test(
|
|
3392
3405
|
"tract,visit",
|
|
3393
|
-
where="instrument
|
|
3406
|
+
where="instrument=:cam AND skymap=:map",
|
|
3394
3407
|
bind={"cam": "Cam1", "map": "SkyMap1"},
|
|
3395
3408
|
count=6,
|
|
3396
3409
|
),
|
|
3397
3410
|
Test(
|
|
3398
3411
|
"tract,visit",
|
|
3399
|
-
where="instrument
|
|
3412
|
+
where="instrument=:cam AND skymap=:map",
|
|
3400
3413
|
bind={"cam": "Cam", "map": "SkyMap"},
|
|
3401
3414
|
exception=DataIdValueError,
|
|
3402
3415
|
),
|
|
@@ -3530,7 +3543,7 @@ class RegistryTests(ABC):
|
|
|
3530
3543
|
|
|
3531
3544
|
result = registry.queryDimensionRecords("detector", where="instrument='Cam1'")
|
|
3532
3545
|
self.assertEqual(result.count(), 4)
|
|
3533
|
-
result = registry.queryDimensionRecords("detector", where="instrument
|
|
3546
|
+
result = registry.queryDimensionRecords("detector", where="instrument=:instr", bind={"instr": "Cam1"})
|
|
3534
3547
|
self.assertTrue(result.any())
|
|
3535
3548
|
self.assertEqual(result.count(), 4)
|
|
3536
3549
|
|
|
@@ -3557,7 +3570,7 @@ class RegistryTests(ABC):
|
|
|
3557
3570
|
DataIdValueError, "Unknown values specified for governor dimension"
|
|
3558
3571
|
):
|
|
3559
3572
|
result = registry.queryDimensionRecords(
|
|
3560
|
-
"detector", where="instrument
|
|
3573
|
+
"detector", where="instrument=:instr", bind={"instr": "NotCam1"}
|
|
3561
3574
|
)
|
|
3562
3575
|
result.count()
|
|
3563
3576
|
|
|
@@ -219,7 +219,7 @@ class ButlerQueryTests(ABC, TestCaseMixin):
|
|
|
219
219
|
self.check_detector_records(results.where(_x.detector.raft == "B", instrument="Cam1"), [3, 4])
|
|
220
220
|
self.check_detector_records_returned(
|
|
221
221
|
butler.query_dimension_records(
|
|
222
|
-
"detector", where="detector.raft = R", bind={"R": "B"}, instrument="Cam1"
|
|
222
|
+
"detector", where="detector.raft = :R", bind={"R": "B"}, instrument="Cam1"
|
|
223
223
|
),
|
|
224
224
|
ids=[3, 4],
|
|
225
225
|
)
|
|
@@ -572,10 +572,10 @@ class ButlerQueryTests(ABC, TestCaseMixin):
|
|
|
572
572
|
# Check that WHERE accepts astropy time
|
|
573
573
|
with butler.query() as query:
|
|
574
574
|
query = query.join_dataset_search("flat", "imported_g")
|
|
575
|
-
query1 = query.where("flat.ingest_date < before_ingest", bind={"before_ingest": before_ingest})
|
|
575
|
+
query1 = query.where("flat.ingest_date < :before_ingest", bind={"before_ingest": before_ingest})
|
|
576
576
|
rows = list(query1.general(dimensions))
|
|
577
577
|
self.assertEqual(len(rows), 0)
|
|
578
|
-
query1 = query.where("flat.ingest_date >= before_ingest", bind={"before_ingest": before_ingest})
|
|
578
|
+
query1 = query.where("flat.ingest_date >= :before_ingest", bind={"before_ingest": before_ingest})
|
|
579
579
|
rows = list(query1.general(dimensions))
|
|
580
580
|
self.assertEqual(len(rows), 3)
|
|
581
581
|
# Same with a time in string literal.
|
|
@@ -736,7 +736,7 @@ class ButlerQueryTests(ABC, TestCaseMixin):
|
|
|
736
736
|
self.check_detector_records_returned(
|
|
737
737
|
butler.query_dimension_records(
|
|
738
738
|
"detector",
|
|
739
|
-
where="visit_detector_region.region OVERLAPS region",
|
|
739
|
+
where="visit_detector_region.region OVERLAPS :region",
|
|
740
740
|
bind={"region": htm7.pixelization.pixel(253954)},
|
|
741
741
|
visit=1,
|
|
742
742
|
),
|
|
@@ -788,7 +788,7 @@ class ButlerQueryTests(ABC, TestCaseMixin):
|
|
|
788
788
|
self.check_detector_records_returned(
|
|
789
789
|
butler.query_dimension_records(
|
|
790
790
|
"detector",
|
|
791
|
-
where="visit_detector_region.region OVERLAPS region",
|
|
791
|
+
where="visit_detector_region.region OVERLAPS :region",
|
|
792
792
|
bind={"region": htm7.pixelization.pixel(253954)},
|
|
793
793
|
),
|
|
794
794
|
ids=[1, 2, 3, 4],
|
|
@@ -846,7 +846,7 @@ class ButlerQueryTests(ABC, TestCaseMixin):
|
|
|
846
846
|
self.check_detector_records_returned(
|
|
847
847
|
butler.query_dimension_records(
|
|
848
848
|
"detector",
|
|
849
|
-
where="visit_detector_region.region OVERLAPS region",
|
|
849
|
+
where="visit_detector_region.region OVERLAPS :region",
|
|
850
850
|
bind={"region": patch_record.region},
|
|
851
851
|
),
|
|
852
852
|
ids=[1, 2, 3],
|
|
@@ -880,7 +880,7 @@ class ButlerQueryTests(ABC, TestCaseMixin):
|
|
|
880
880
|
self.check_detector_records_returned(
|
|
881
881
|
butler.query_dimension_records(
|
|
882
882
|
"detector",
|
|
883
|
-
where="visit_detector_region.region OVERLAPS region",
|
|
883
|
+
where="visit_detector_region.region OVERLAPS :region",
|
|
884
884
|
bind={"region": patch_record.region},
|
|
885
885
|
order_by="-detector",
|
|
886
886
|
limit=2,
|
|
@@ -900,7 +900,7 @@ class ButlerQueryTests(ABC, TestCaseMixin):
|
|
|
900
900
|
self.check_detector_records_returned(
|
|
901
901
|
butler.query_dimension_records(
|
|
902
902
|
"detector",
|
|
903
|
-
where="visit_detector_region.region OVERLAPS region",
|
|
903
|
+
where="visit_detector_region.region OVERLAPS :region",
|
|
904
904
|
bind={"region": patch_record.region},
|
|
905
905
|
detector=4,
|
|
906
906
|
explain=False,
|
|
@@ -946,20 +946,20 @@ class ButlerQueryTests(ABC, TestCaseMixin):
|
|
|
946
946
|
# string.
|
|
947
947
|
_check_visit_id(
|
|
948
948
|
query.where(
|
|
949
|
-
"visit_detector_region.region OVERLAPS POINT(ra, dec)", bind={"ra": ra, "dec": dec}
|
|
949
|
+
"visit_detector_region.region OVERLAPS POINT(:ra, :dec)", bind={"ra": ra, "dec": dec}
|
|
950
950
|
)
|
|
951
951
|
)
|
|
952
952
|
|
|
953
953
|
# Bind in a point object instead of specifying ra/dec separately.
|
|
954
954
|
_check_visit_id(
|
|
955
955
|
query.where(
|
|
956
|
-
"visit_detector_region.region OVERLAPS my_point",
|
|
956
|
+
"visit_detector_region.region OVERLAPS :my_point",
|
|
957
957
|
bind={"my_point": LonLat.fromDegrees(ra, dec)},
|
|
958
958
|
)
|
|
959
959
|
)
|
|
960
960
|
_check_visit_id(
|
|
961
961
|
query.where(
|
|
962
|
-
"visit_detector_region.region OVERLAPS my_point",
|
|
962
|
+
"visit_detector_region.region OVERLAPS :my_point",
|
|
963
963
|
bind={"my_point": astropy.coordinates.SkyCoord(ra, dec, frame="icrs", unit="deg")},
|
|
964
964
|
)
|
|
965
965
|
)
|
|
@@ -967,7 +967,7 @@ class ButlerQueryTests(ABC, TestCaseMixin):
|
|
|
967
967
|
# handled.
|
|
968
968
|
_check_visit_id(
|
|
969
969
|
query.where(
|
|
970
|
-
"visit_detector_region.region OVERLAPS my_point",
|
|
970
|
+
"visit_detector_region.region OVERLAPS :my_point",
|
|
971
971
|
bind={
|
|
972
972
|
"my_point": astropy.coordinates.SkyCoord(
|
|
973
973
|
ra, dec, frame="icrs", unit="deg"
|
|
@@ -1005,7 +1005,7 @@ class ButlerQueryTests(ABC, TestCaseMixin):
|
|
|
1005
1005
|
)
|
|
1006
1006
|
with self.assertRaisesRegex(ValueError, "Astropy SkyCoord contained an array of points"):
|
|
1007
1007
|
query.where(
|
|
1008
|
-
"visit_detector_region.region OVERLAPS my_point",
|
|
1008
|
+
"visit_detector_region.region OVERLAPS :my_point",
|
|
1009
1009
|
bind={"my_point": array_point},
|
|
1010
1010
|
)
|
|
1011
1011
|
|
|
@@ -1587,7 +1587,7 @@ class ButlerQueryTests(ABC, TestCaseMixin):
|
|
|
1587
1587
|
for record in butler.query_dimension_records(
|
|
1588
1588
|
# In the middle of the timespan.
|
|
1589
1589
|
"visit",
|
|
1590
|
-
where="visit.timespan OVERLAPS(ts)",
|
|
1590
|
+
where="visit.timespan OVERLAPS(:ts)",
|
|
1591
1591
|
bind={"ts": astropy.time.Time("2021-09-09T03:02:30", format="isot", scale="tai")},
|
|
1592
1592
|
)
|
|
1593
1593
|
],
|
|
@@ -1673,7 +1673,7 @@ class ButlerQueryTests(ABC, TestCaseMixin):
|
|
|
1673
1673
|
)
|
|
1674
1674
|
self.check_detector_records_returned(
|
|
1675
1675
|
butler.query_dimension_records(
|
|
1676
|
-
"detector", where="detector IN (det)", bind={"det": [1, 3, 4]}
|
|
1676
|
+
"detector", where="detector IN (:det)", bind={"det": [1, 3, 4]}
|
|
1677
1677
|
),
|
|
1678
1678
|
[1, 3, 4],
|
|
1679
1679
|
)
|
|
@@ -1873,7 +1873,7 @@ class ButlerQueryTests(ABC, TestCaseMixin):
|
|
|
1873
1873
|
with butler.query() as query:
|
|
1874
1874
|
results = query.datasets("calexp", collections=run)
|
|
1875
1875
|
results = results.where(
|
|
1876
|
-
"instrument = 'HSC' AND visit_detector_region.region OVERLAPS(POS)",
|
|
1876
|
+
"instrument = 'HSC' AND visit_detector_region.region OVERLAPS(:POS)",
|
|
1877
1877
|
bind={"POS": Region.from_ivoa_pos(pos)},
|
|
1878
1878
|
)
|
|
1879
1879
|
refs = list(results)
|
|
@@ -1883,7 +1883,7 @@ class ButlerQueryTests(ABC, TestCaseMixin):
|
|
|
1883
1883
|
"calexp",
|
|
1884
1884
|
collections=run,
|
|
1885
1885
|
instrument="HSC",
|
|
1886
|
-
where="visit_detector_region.region OVERLAPS(POS)",
|
|
1886
|
+
where="visit_detector_region.region OVERLAPS(:POS)",
|
|
1887
1887
|
bind={"POS": Region.from_ivoa_pos(pos)},
|
|
1888
1888
|
explain=False,
|
|
1889
1889
|
)
|
|
@@ -1906,20 +1906,20 @@ class ButlerQueryTests(ABC, TestCaseMixin):
|
|
|
1906
1906
|
|
|
1907
1907
|
# Use a time during the middle of a visit.
|
|
1908
1908
|
v_903334 = results.where(
|
|
1909
|
-
"instrument = 'HSC' and visit.timespan OVERLAPS(ts)", bind={"ts": v_903334_mid}
|
|
1909
|
+
"instrument = 'HSC' and visit.timespan OVERLAPS(:ts)", bind={"ts": v_903334_mid}
|
|
1910
1910
|
)
|
|
1911
1911
|
self.assertEqual(len(list(v_903334)), 4)
|
|
1912
1912
|
|
|
1913
1913
|
# Timespan covering first half of the data.
|
|
1914
1914
|
first_half = results.where(
|
|
1915
|
-
"instrument = 'HSC' and visit.timespan OVERLAPS(t1, t2)",
|
|
1915
|
+
"instrument = 'HSC' and visit.timespan OVERLAPS(:t1, :t2)",
|
|
1916
1916
|
bind={"t1": v_903334_pre, "t2": v_904014_pre},
|
|
1917
1917
|
)
|
|
1918
1918
|
self.assertEqual(len(list(first_half)), 17)
|
|
1919
1919
|
|
|
1920
1920
|
# Query using a timespan object.
|
|
1921
1921
|
with_ts = results.where(
|
|
1922
|
-
"instrument = 'HSC' and visit.timespan OVERLAPS(ts)",
|
|
1922
|
+
"instrument = 'HSC' and visit.timespan OVERLAPS(:ts)",
|
|
1923
1923
|
bind={"ts": Timespan(v_904014_pre, v_904014_post)},
|
|
1924
1924
|
)
|
|
1925
1925
|
self.assertEqual(len(list(with_ts)), 16)
|
|
@@ -2136,7 +2136,7 @@ class ButlerQueryTests(ABC, TestCaseMixin):
|
|
|
2136
2136
|
self.assertCountEqual(
|
|
2137
2137
|
butler.query_data_ids(
|
|
2138
2138
|
["detector"],
|
|
2139
|
-
where="(instrument='Cam1' OR instrument='Cam2') AND visit.region OVERLAPS region",
|
|
2139
|
+
where="(instrument='Cam1' OR instrument='Cam2') AND visit.region OVERLAPS :region",
|
|
2140
2140
|
bind={"region": Region.from_ivoa_pos("CIRCLE 320. -0.25 10.")},
|
|
2141
2141
|
explain=False,
|
|
2142
2142
|
),
|
|
@@ -2236,7 +2236,7 @@ class ButlerQueryTests(ABC, TestCaseMixin):
|
|
|
2236
2236
|
self.assertEqual(names, ["Aa"])
|
|
2237
2237
|
|
|
2238
2238
|
result = butler.query_dimension_records(
|
|
2239
|
-
"detector", where="instrument='Cam1' and detector
|
|
2239
|
+
"detector", where="instrument='Cam1' and detector=:an_integer", bind={"an_integer": int64(2)}
|
|
2240
2240
|
)
|
|
2241
2241
|
names = [x.full_name for x in result]
|
|
2242
2242
|
self.assertEqual(names, ["Ab"])
|
|
@@ -2284,7 +2284,7 @@ class ButlerQueryTests(ABC, TestCaseMixin):
|
|
|
2284
2284
|
|
|
2285
2285
|
# bind values work.
|
|
2286
2286
|
results = butler._query_all_datasets(
|
|
2287
|
-
"*", where="detector
|
|
2287
|
+
"*", where="detector=:my_bind and instrument='Cam1'", bind={"my_bind": 1}, find_first=False
|
|
2288
2288
|
)
|
|
2289
2289
|
self.assertCountEqual(detector_1_ids, _ref_uuids(results))
|
|
2290
2290
|
|
|
@@ -144,6 +144,16 @@ class HybridButlerRegistry(Registry):
|
|
|
144
144
|
return self._remote.getCollectionSummary(collection)
|
|
145
145
|
|
|
146
146
|
def registerDatasetType(self, datasetType: DatasetType) -> bool:
|
|
147
|
+
# We need to make sure that dataset type universe is the same as
|
|
148
|
+
# direct registry universe.
|
|
149
|
+
if datasetType.dimensions.universe is self._remote.dimensions:
|
|
150
|
+
datasetType = DatasetType(
|
|
151
|
+
datasetType.name,
|
|
152
|
+
datasetType.dimensions.names,
|
|
153
|
+
datasetType.storageClass,
|
|
154
|
+
universe=self._direct.dimensions,
|
|
155
|
+
isCalibration=datasetType.isCalibration(),
|
|
156
|
+
)
|
|
147
157
|
return self._direct.registerDatasetType(datasetType)
|
|
148
158
|
|
|
149
159
|
def removeDatasetType(self, name: str | tuple[str, ...]) -> None:
|
lsst/daf/butler/version.py
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
__all__ = ["__version__"]
|
|
2
|
-
__version__ = "29.
|
|
2
|
+
__version__ = "29.2025.1100"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.2
|
|
2
2
|
Name: lsst-daf-butler
|
|
3
|
-
Version: 29.
|
|
3
|
+
Version: 29.2025.1100
|
|
4
4
|
Summary: An abstraction layer for reading and writing astronomical data to datastores.
|
|
5
5
|
Author-email: Rubin Observatory Data Management <dm-admin@lists.lsst.org>
|
|
6
6
|
License: BSD 3-Clause License
|
|
@@ -51,7 +51,7 @@ lsst/daf/butler/repo_relocation.py,sha256=Ivhx2xU4slc53Z6RExhNnquMr2Hx-S8h62emml
|
|
|
51
51
|
lsst/daf/butler/time_utils.py,sha256=MVTfOFI2xt3IeA46pa-fWY2kJRwSzaQyq1uzeUABcTM,11805
|
|
52
52
|
lsst/daf/butler/timespan_database_representation.py,sha256=MWDusjIQIL2RH1CDpWSW5sYvdHCJKzAfpg1rm1DfgEU,24302
|
|
53
53
|
lsst/daf/butler/utils.py,sha256=5u50COK5z4u31grOhmQF7mFz55biNLOvSMRdQjEdsjo,5140
|
|
54
|
-
lsst/daf/butler/version.py,sha256=
|
|
54
|
+
lsst/daf/butler/version.py,sha256=sHiUhHRfi72yQHyY-cFkHWLqVyELO1VeQ3Uo0NmIBCc,55
|
|
55
55
|
lsst/daf/butler/_utilities/__init__.py,sha256=vLzPZYAJ-9r1cnqsP64MVpFgSw2166yOpq0iPMSdAvw,1298
|
|
56
56
|
lsst/daf/butler/_utilities/locked_object.py,sha256=3RQf0Ish55mfQAfBy3V4Tfnfq5Q7-cxrwTlQMUhrIno,1931
|
|
57
57
|
lsst/daf/butler/_utilities/named_locks.py,sha256=Zj_u1rZELaiWec3wJfkgmGD_YiZMLVxbMQmdbbVgk5E,2286
|
|
@@ -97,7 +97,7 @@ lsst/daf/butler/datastore/record_data.py,sha256=Jvv2Gpfcr43w97Ub4PT4N0fQQcqPni-w
|
|
|
97
97
|
lsst/daf/butler/datastore/stored_file_info.py,sha256=jdS7IfupaciWiMOVWB8QAMN-qfKPCnr_wFqZwilSteE,13587
|
|
98
98
|
lsst/daf/butler/datastores/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
99
99
|
lsst/daf/butler/datastores/chainedDatastore.py,sha256=u8jz65pKNlpi7Luvq9FIyZOgD3mIXridq0-J3-cdONk,54953
|
|
100
|
-
lsst/daf/butler/datastores/fileDatastore.py,sha256=
|
|
100
|
+
lsst/daf/butler/datastores/fileDatastore.py,sha256=MfjV4HxnffXf9n0UEkddB2T4CH2BKuPqiL4gSMgivD0,132240
|
|
101
101
|
lsst/daf/butler/datastores/fileDatastoreClient.py,sha256=DyLc0SqPMzhOEdfHGeGfgO0Zx9glOeI-l9UcZDfpkg4,3132
|
|
102
102
|
lsst/daf/butler/datastores/inMemoryDatastore.py,sha256=DnVqqKMdWcvml3Bfr2EINDyf630kOqIBWw3PF6MRid4,28730
|
|
103
103
|
lsst/daf/butler/datastores/file_datastore/__init__.py,sha256=vLzPZYAJ-9r1cnqsP64MVpFgSw2166yOpq0iPMSdAvw,1298
|
|
@@ -126,7 +126,7 @@ lsst/daf/butler/direct_butler/__init__.py,sha256=8uPQWbFoKDpP2T2fvjV794JUITtdcDH
|
|
|
126
126
|
lsst/daf/butler/direct_butler/_direct_butler.py,sha256=eex_ul4H_yekaQkekSlom7kOThPoonug_-OmsYhz7sM,107095
|
|
127
127
|
lsst/daf/butler/direct_butler/_direct_butler_collections.py,sha256=6XvdvYqNSPAwva8vgR1bEqF8vYTfaNXUW3sb03NRwOM,8143
|
|
128
128
|
lsst/daf/butler/direct_query_driver/__init__.py,sha256=hurmYGcTsnlqR4Hkh2xO_M544eEpCGeIJte0GXY3RFQ,1443
|
|
129
|
-
lsst/daf/butler/direct_query_driver/_driver.py,sha256=
|
|
129
|
+
lsst/daf/butler/direct_query_driver/_driver.py,sha256=su9R4aExA5tnq6OZZcpEYxDxtJyUXVvLQFr2qGa6EAc,75004
|
|
130
130
|
lsst/daf/butler/direct_query_driver/_postprocessing.py,sha256=CdLrzd0vHHCLaHntZJyMZEv0GlUvu3QZAA7YMVv5X3k,8826
|
|
131
131
|
lsst/daf/butler/direct_query_driver/_predicate_constraints_summary.py,sha256=vdcjLamCEtIrcEFVuMa-HPbuWP0OArxO19tFHkaYdO0,9011
|
|
132
132
|
lsst/daf/butler/direct_query_driver/_query_analysis.py,sha256=Kl_i_438R6LnAqwooSCXi17tEHYg6sZtDghFjMH-lCE,9540
|
|
@@ -150,7 +150,7 @@ lsst/daf/butler/queries/_base.py,sha256=lk_xLoN08nmxj0N5aKCm-qI5gWjq8lY3UaLuCWbd
|
|
|
150
150
|
lsst/daf/butler/queries/_data_coordinate_query_results.py,sha256=S_81y4EkSJw34ekuiHW7RSl0LKNWoV-ZM-C5FLIM-nk,3913
|
|
151
151
|
lsst/daf/butler/queries/_dataset_query_results.py,sha256=SAJsI-KQsEziaNtfu3Zdp0iX4zF2BoDIF4mtb-xvtxQ,5113
|
|
152
152
|
lsst/daf/butler/queries/_dimension_record_query_results.py,sha256=N89s23jBhSMEbr0uxlhMZykiw91O1bLTbdzwv-0Az14,4405
|
|
153
|
-
lsst/daf/butler/queries/_expression_strings.py,sha256=
|
|
153
|
+
lsst/daf/butler/queries/_expression_strings.py,sha256=i4qnhRFRoZNf00l_5rXN1Cshxs2rGmIFbFO45DgGWkE,15078
|
|
154
154
|
lsst/daf/butler/queries/_general_query_results.py,sha256=Q1-BQFS6V_Uihwdv1t77GOV1M8idZr63WpZMOOmDXT8,9780
|
|
155
155
|
lsst/daf/butler/queries/_identifiers.py,sha256=SjsEkgF3B8b2bf9drOzg5ogwa32Efy7MMLfUq7H26dE,7880
|
|
156
156
|
lsst/daf/butler/queries/_query.py,sha256=r2pIwiRvpK4YgwKBzWW125lw_6Pew4uXfrrYwHhXQBU,36171
|
|
@@ -195,11 +195,11 @@ lsst/daf/butler/registry/collections/nameKey.py,sha256=7knssy-9kmynprNdN9309jc7b
|
|
|
195
195
|
lsst/daf/butler/registry/collections/synthIntKey.py,sha256=N27SkPcXIDfo6Qy-xS-M5nSFscNpuBKpThHTKn-aVAg,13179
|
|
196
196
|
lsst/daf/butler/registry/databases/__init__.py,sha256=vLzPZYAJ-9r1cnqsP64MVpFgSw2166yOpq0iPMSdAvw,1298
|
|
197
197
|
lsst/daf/butler/registry/databases/postgresql.py,sha256=-qUH-kynmavIs5jAWCEl93LRhQmslIXX61SQ0NxEIn0,25987
|
|
198
|
-
lsst/daf/butler/registry/databases/sqlite.py,sha256=
|
|
198
|
+
lsst/daf/butler/registry/databases/sqlite.py,sha256=qxMHvQMJ9kNLPdWrMyVaRLeJJxddER7Wz7UnJnbdA_o,18412
|
|
199
199
|
lsst/daf/butler/registry/datasets/__init__.py,sha256=vLzPZYAJ-9r1cnqsP64MVpFgSw2166yOpq0iPMSdAvw,1298
|
|
200
200
|
lsst/daf/butler/registry/datasets/byDimensions/__init__.py,sha256=BG4C7mhKFbCzvfQSI31CIV_iTMc1gYL_LT4Plyu6LdE,1323
|
|
201
201
|
lsst/daf/butler/registry/datasets/byDimensions/_dataset_type_cache.py,sha256=WdbU7ZTAqvpjyouIaKsEEi62P_0Y-zQZjdvzDahKLuw,9653
|
|
202
|
-
lsst/daf/butler/registry/datasets/byDimensions/_manager.py,sha256=
|
|
202
|
+
lsst/daf/butler/registry/datasets/byDimensions/_manager.py,sha256=QrT6suda9h4Yqnf5gpgwWHlKa8Bqqeh4OlnIMKRMtUs,80748
|
|
203
203
|
lsst/daf/butler/registry/datasets/byDimensions/summaries.py,sha256=i0GXbKymSAAPYLfaQdRZ3r4E1ANG5hQYXOqB6ZeLrBo,18494
|
|
204
204
|
lsst/daf/butler/registry/datasets/byDimensions/tables.py,sha256=40Z-kWTHAZNdxO-PxByidOtpEpTvnAtjsZLgChSI4SU,25537
|
|
205
205
|
lsst/daf/butler/registry/dimensions/__init__.py,sha256=vLzPZYAJ-9r1cnqsP64MVpFgSw2166yOpq0iPMSdAvw,1298
|
|
@@ -208,7 +208,7 @@ lsst/daf/butler/registry/interfaces/__init__.py,sha256=IBMBBb1gyAx3o9uTufhQHtMrh
|
|
|
208
208
|
lsst/daf/butler/registry/interfaces/_attributes.py,sha256=z-njEpWLhmKU4S0KOCplrY4QeBGoKUhlPRtSdNS_4uw,7258
|
|
209
209
|
lsst/daf/butler/registry/interfaces/_bridge.py,sha256=Ktc5oNM5DYJejXraAP_kb5nrHxH-adR4JkCMmm1jOBQ,15192
|
|
210
210
|
lsst/daf/butler/registry/interfaces/_collections.py,sha256=7I0e0Pi4UylET5Nr3keRFve7NToafHxJafiwn81lt6U,27008
|
|
211
|
-
lsst/daf/butler/registry/interfaces/_database.py,sha256=
|
|
211
|
+
lsst/daf/butler/registry/interfaces/_database.py,sha256=EKNLyAM969fGI7m3iZt3tmH-l8s-XiExs5ktu9bg8ec,82876
|
|
212
212
|
lsst/daf/butler/registry/interfaces/_database_explain.py,sha256=CkALWwNeyrjRvKizWrxvcGDunIhB77kLtEuXscrXVOY,3052
|
|
213
213
|
lsst/daf/butler/registry/interfaces/_datasets.py,sha256=JoVaPClPbe91PVFeR_taXbPZA-8f8PVK7hXP1ZaYsEM,25889
|
|
214
214
|
lsst/daf/butler/registry/interfaces/_dimensions.py,sha256=GkgJ38ju7oB6SdnWVGmi-08k8gNdHR47hTRAk6n2vk4,17770
|
|
@@ -236,22 +236,22 @@ lsst/daf/butler/registry/queries/_structs.py,sha256=RuLWCw74_6L5BS9QkBbBYOGGzNUD
|
|
|
236
236
|
lsst/daf/butler/registry/queries/butler_sql_engine.py,sha256=AxWDAAu0rGhzwrVQawuV31FpkLNDXWrZt9rf3eQTADY,10182
|
|
237
237
|
lsst/daf/butler/registry/queries/find_first_dataset.py,sha256=Wd4Aa0aJF0OLgScscEhEbUo-jlCk4gLeOqEuTCyJXj4,3682
|
|
238
238
|
lsst/daf/butler/registry/queries/expressions/__init__.py,sha256=7tKQ5LwRhjriICjZBPNubtMG3r4JE8xu-BHe9oQwSGs,1381
|
|
239
|
-
lsst/daf/butler/registry/queries/expressions/_predicate.py,sha256=
|
|
239
|
+
lsst/daf/butler/registry/queries/expressions/_predicate.py,sha256=QIno9yyW8_xdAAv_DSG95Tmx6-jp9MX3weLwQ-hjo9A,22984
|
|
240
240
|
lsst/daf/butler/registry/queries/expressions/categorize.py,sha256=wUUTkDv__T_Rs3v7aO9tw8UIY2R7P5xhQc3vUClCuJI,13972
|
|
241
|
-
lsst/daf/butler/registry/queries/expressions/check.py,sha256=
|
|
242
|
-
lsst/daf/butler/registry/queries/expressions/normalForm.py,sha256=
|
|
241
|
+
lsst/daf/butler/registry/queries/expressions/check.py,sha256=pPL-Mbp_7ORlHVaCvDqNkvBlLi82HRw5rGzl8hEhWIU,23225
|
|
242
|
+
lsst/daf/butler/registry/queries/expressions/normalForm.py,sha256=EPdVP5ccjKmJTkyFLXHqhaWHriqSQFXyqMURElU8azo,41205
|
|
243
243
|
lsst/daf/butler/registry/queries/expressions/parser/__init__.py,sha256=o-z7yiQ4HhZayFOpseBclqqkjgg9VQ2Wq-Rs3gPwLYI,1423
|
|
244
|
-
lsst/daf/butler/registry/queries/expressions/parser/exprTree.py,sha256=
|
|
244
|
+
lsst/daf/butler/registry/queries/expressions/parser/exprTree.py,sha256=z2zAmO08bMR0mCZ-2lnwz_qv13TCu9iHvu0cCTx2pPc,13474
|
|
245
245
|
lsst/daf/butler/registry/queries/expressions/parser/parser.py,sha256=q6fDFG96ClhKtIGXAKYrBR9SF085Bk051epGkjwgmVw,1767
|
|
246
|
-
lsst/daf/butler/registry/queries/expressions/parser/parserLex.py,sha256=
|
|
247
|
-
lsst/daf/butler/registry/queries/expressions/parser/parserYacc.py,sha256=
|
|
248
|
-
lsst/daf/butler/registry/queries/expressions/parser/treeVisitor.py,sha256=
|
|
246
|
+
lsst/daf/butler/registry/queries/expressions/parser/parserLex.py,sha256=RNH9YA_0x2AEGgC6LPkygto_2ob5RCB5HbvBP4wGLuw,6972
|
|
247
|
+
lsst/daf/butler/registry/queries/expressions/parser/parserYacc.py,sha256=dEHhQncJi_NglFPyRwRgdr0NSKRPuhWfNwLNdrqrJwU,15230
|
|
248
|
+
lsst/daf/butler/registry/queries/expressions/parser/treeVisitor.py,sha256=sx1dEp4H4OI1DFoNQ1JQJFNjGWWWNnItJRI-QuLXYAk,8831
|
|
249
249
|
lsst/daf/butler/registry/queries/expressions/parser/ply/__init__.py,sha256=TADR0Z9IftaLFnU7tIHfDE0rwT5uonRbQ6WnU57dWFU,104
|
|
250
250
|
lsst/daf/butler/registry/queries/expressions/parser/ply/lex.py,sha256=Y9SoJv-_0XUNNgVA-4R530PjPZzmxpslTzHs0axKNIY,43384
|
|
251
251
|
lsst/daf/butler/registry/queries/expressions/parser/ply/yacc.py,sha256=aGhbTj66OEYILEApQtwyfDunWoAPJ3QWZu6mD8uEB3s,137077
|
|
252
252
|
lsst/daf/butler/registry/tests/__init__.py,sha256=uWJM-8Yv52Ox6e9rKGTaTVRdJc3fIfAyI5WnZQp81EA,1349
|
|
253
253
|
lsst/daf/butler/registry/tests/_database.py,sha256=0lbzjDUj3n2UPvAktiKcc3G1TE0TK867cASezUISN6I,65218
|
|
254
|
-
lsst/daf/butler/registry/tests/_registry.py,sha256=
|
|
254
|
+
lsst/daf/butler/registry/tests/_registry.py,sha256=h6DI4VYSm5846l7adbPnLQMo-6d1jk34GOL5UkBFy_s,202900
|
|
255
255
|
lsst/daf/butler/remote_butler/__init__.py,sha256=9fCJ-DmQGuktoGnmBBM_BygjIkgFWsROlbJ870uc9ak,1401
|
|
256
256
|
lsst/daf/butler/remote_butler/_authentication.py,sha256=dnc4UGIGIdkkH6RLbq33CLZ2IWwPCEUmDxGHPauxSao,3932
|
|
257
257
|
lsst/daf/butler/remote_butler/_collection_args.py,sha256=_ToE6jplUCzmmqwWvkl1cwrXRJkNU8Qrl2hqwIqHibw,4969
|
|
@@ -311,14 +311,14 @@ lsst/daf/butler/tests/_datasetsHelper.py,sha256=LH1ZPuzSpRijAPtAiwBoSZZPzI-aSaHN
|
|
|
311
311
|
lsst/daf/butler/tests/_dummyRegistry.py,sha256=CowulEeirnWwSJf-AnutvOLvGSr3ITgShlYj9f_IuYk,9509
|
|
312
312
|
lsst/daf/butler/tests/_examplePythonTypes.py,sha256=OkKf8e16T4xSBVjN05qvfrukowqJ6BICTmQZZFdskaI,13508
|
|
313
313
|
lsst/daf/butler/tests/_testRepo.py,sha256=MVkQwXJ5vI86--Npa7SDHRblpJC5pig6vpGxv-dgGvE,23755
|
|
314
|
-
lsst/daf/butler/tests/butler_queries.py,sha256=
|
|
314
|
+
lsst/daf/butler/tests/butler_queries.py,sha256=EoWOR9hUrYmqwzAAAVvWf_ZVpsC98LascueeJ990MP8,111129
|
|
315
315
|
lsst/daf/butler/tests/cliCmdTestBase.py,sha256=G4e264xRYbPW5cXk4xS_yOaoVsfvfmsLmbqlgA5SPxc,6994
|
|
316
316
|
lsst/daf/butler/tests/cliLogTestBase.py,sha256=sgpOMQV21ItF4ElE7Z1rMAi0rzAcBEOfe2Yu62bujBU,18325
|
|
317
317
|
lsst/daf/butler/tests/deferredFormatter.py,sha256=EtUnxNy90n5ouESBbcFBLOt4mrHhmxvHS2vP6Y3dF0w,1920
|
|
318
318
|
lsst/daf/butler/tests/dict_convertible_model.py,sha256=dJ1TDdj8KRuuoZHz3ismfjURWfjY-hgUGGAz5js5u9M,2732
|
|
319
319
|
lsst/daf/butler/tests/hybrid_butler.py,sha256=GCglTVBZozAW9nGNbcOfeUXX8mXg6GHRflLp6Hqe1T0,13854
|
|
320
320
|
lsst/daf/butler/tests/hybrid_butler_collections.py,sha256=XzuZzqKLKkVVY6Y96RdrwkJnAyRkNNEuYVqvKoq2E2I,4604
|
|
321
|
-
lsst/daf/butler/tests/hybrid_butler_registry.py,sha256=
|
|
321
|
+
lsst/daf/butler/tests/hybrid_butler_registry.py,sha256=t6X-kRfDl-tv6H9YskHmMZ-U_ke1gx8qNPfpOsHOlVE,16720
|
|
322
322
|
lsst/daf/butler/tests/postgresql.py,sha256=6-84OPoysRtsborTdsyu5HcVfVWZqHaU8azFPSxu0Wc,4331
|
|
323
323
|
lsst/daf/butler/tests/server.py,sha256=dLnYc6_fzr-SkOxxOpnHKC90k6foPruIdBiDI_74aIg,8012
|
|
324
324
|
lsst/daf/butler/tests/server_utils.py,sha256=sneYxThLhvc7KaFdHJXV15j-R3JtC6yOjDfVptJw6x4,2833
|
|
@@ -328,13 +328,13 @@ lsst/daf/butler/transfers/__init__.py,sha256=M1YcFszSkNB5hB2pZwwGXqbJE2dKt4YXDin
|
|
|
328
328
|
lsst/daf/butler/transfers/_context.py,sha256=h1XDJpdg64R7DRHo7mb9xgaLiHDs_AIJmZbyo66qSSw,17278
|
|
329
329
|
lsst/daf/butler/transfers/_interfaces.py,sha256=Ia1NqcFR5E-Ik4zsXEe2fuMtNCJj5Yfe_gVHLTBtJDw,7490
|
|
330
330
|
lsst/daf/butler/transfers/_yaml.py,sha256=w_0GmrueuHVLfOfAXGHFBbWAl18tX6eSElbTC-2jRoc,32632
|
|
331
|
-
lsst_daf_butler-29.
|
|
332
|
-
lsst_daf_butler-29.
|
|
333
|
-
lsst_daf_butler-29.
|
|
334
|
-
lsst_daf_butler-29.
|
|
335
|
-
lsst_daf_butler-29.
|
|
336
|
-
lsst_daf_butler-29.
|
|
337
|
-
lsst_daf_butler-29.
|
|
338
|
-
lsst_daf_butler-29.
|
|
339
|
-
lsst_daf_butler-29.
|
|
340
|
-
lsst_daf_butler-29.
|
|
331
|
+
lsst_daf_butler-29.2025.1100.dist-info/COPYRIGHT,sha256=k1Vq0-Be_K-puaeW4UZnckPjksEL-MJh4XKiWcjMxJE,312
|
|
332
|
+
lsst_daf_butler-29.2025.1100.dist-info/LICENSE,sha256=pRExkS03v0MQW-neNfIcaSL6aiAnoLxYgtZoFzQ6zkM,232
|
|
333
|
+
lsst_daf_butler-29.2025.1100.dist-info/METADATA,sha256=pN-MFYlaX4VDGQ9CwRHfdoymPz6wfoLYmYDymb9shcM,3243
|
|
334
|
+
lsst_daf_butler-29.2025.1100.dist-info/WHEEL,sha256=52BFRY2Up02UkjOa29eZOS2VxUrpPORXg1pkohGGUS8,91
|
|
335
|
+
lsst_daf_butler-29.2025.1100.dist-info/bsd_license.txt,sha256=7MIcv8QRX9guUtqPSBDMPz2SnZ5swI-xZMqm_VDSfxY,1606
|
|
336
|
+
lsst_daf_butler-29.2025.1100.dist-info/entry_points.txt,sha256=XsRxyTK3c-jGlKVuVnbpch3gtaO0lAA_fS3i2NGS5rw,59
|
|
337
|
+
lsst_daf_butler-29.2025.1100.dist-info/gpl-v3.0.txt,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
|
|
338
|
+
lsst_daf_butler-29.2025.1100.dist-info/top_level.txt,sha256=eUWiOuVVm9wwTrnAgiJT6tp6HQHXxIhj2QSZ7NYZH80,5
|
|
339
|
+
lsst_daf_butler-29.2025.1100.dist-info/zip-safe,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
|
|
340
|
+
lsst_daf_butler-29.2025.1100.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
{lsst_daf_butler-29.0.0rc1.dist-info → lsst_daf_butler-29.2025.1100.dist-info}/bsd_license.txt
RENAMED
|
File without changes
|
{lsst_daf_butler-29.0.0rc1.dist-info → lsst_daf_butler-29.2025.1100.dist-info}/entry_points.txt
RENAMED
|
File without changes
|
|
File without changes
|
{lsst_daf_butler-29.0.0rc1.dist-info → lsst_daf_butler-29.2025.1100.dist-info}/top_level.txt
RENAMED
|
File without changes
|
|
File without changes
|