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.
Files changed (27) hide show
  1. lsst/daf/butler/datastores/fileDatastore.py +14 -0
  2. lsst/daf/butler/direct_query_driver/_driver.py +1 -1
  3. lsst/daf/butler/queries/_expression_strings.py +7 -0
  4. lsst/daf/butler/registry/databases/sqlite.py +6 -0
  5. lsst/daf/butler/registry/datasets/byDimensions/_manager.py +10 -0
  6. lsst/daf/butler/registry/interfaces/_database.py +1 -1
  7. lsst/daf/butler/registry/queries/expressions/_predicate.py +26 -19
  8. lsst/daf/butler/registry/queries/expressions/check.py +19 -10
  9. lsst/daf/butler/registry/queries/expressions/normalForm.py +4 -0
  10. lsst/daf/butler/registry/queries/expressions/parser/exprTree.py +23 -0
  11. lsst/daf/butler/registry/queries/expressions/parser/parserLex.py +9 -0
  12. lsst/daf/butler/registry/queries/expressions/parser/parserYacc.py +20 -15
  13. lsst/daf/butler/registry/queries/expressions/parser/treeVisitor.py +14 -2
  14. lsst/daf/butler/registry/tests/_registry.py +34 -21
  15. lsst/daf/butler/tests/butler_queries.py +23 -23
  16. lsst/daf/butler/tests/hybrid_butler_registry.py +10 -0
  17. lsst/daf/butler/version.py +1 -1
  18. {lsst_daf_butler-29.0.0rc1.dist-info → lsst_daf_butler-29.2025.1100.dist-info}/METADATA +1 -1
  19. {lsst_daf_butler-29.0.0rc1.dist-info → lsst_daf_butler-29.2025.1100.dist-info}/RECORD +27 -27
  20. {lsst_daf_butler-29.0.0rc1.dist-info → lsst_daf_butler-29.2025.1100.dist-info}/WHEEL +1 -1
  21. {lsst_daf_butler-29.0.0rc1.dist-info → lsst_daf_butler-29.2025.1100.dist-info}/COPYRIGHT +0 -0
  22. {lsst_daf_butler-29.0.0rc1.dist-info → lsst_daf_butler-29.2025.1100.dist-info}/LICENSE +0 -0
  23. {lsst_daf_butler-29.0.0rc1.dist-info → lsst_daf_butler-29.2025.1100.dist-info}/bsd_license.txt +0 -0
  24. {lsst_daf_butler-29.0.0rc1.dist-info → lsst_daf_butler-29.2025.1100.dist-info}/entry_points.txt +0 -0
  25. {lsst_daf_butler-29.0.0rc1.dist-info → lsst_daf_butler-29.2025.1100.dist-info}/gpl-v3.0.txt +0 -0
  26. {lsst_daf_butler-29.0.0rc1.dist-info → lsst_daf_butler-29.2025.1100.dist-info}/top_level.txt +0 -0
  27. {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:
@@ -1918,7 +1918,7 @@ class Database(ABC):
1918
1918
  This should reflect typical performance profiles (or a guess at these),
1919
1919
  not just hard database engine limits.
1920
1920
  """
1921
- return 100
1921
+ return 1000
1922
1922
 
1923
1923
  @property
1924
1924
  @abstractmethod
@@ -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
- value = self.bind[name]
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
- value = self.bind[name]
216
- if isinstance(value, list | tuple | Set):
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 '{0}' (line: {1}, pos: {2})"
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
- tree = self.parser.parse(input=input, lexer=lexer, debug=debug, tracking=tracking)
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
- p[0] = p[1]
400
-
401
- @classmethod
402
- def p_simple_expr_id(cls, p: YaccProduction) -> None:
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(registry.queryDatasets("flat", where="band=my_band", bind={"my_band": "r"}, collections=...)),
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=cam AND skymap=map",
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=cam AND skymap=map",
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=instr", bind={"instr": "Cam1"})
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=instr", bind={"instr": "NotCam1"}
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=an_integer", bind={"an_integer": int64(2)}
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=my_bind and instrument='Cam1'", bind={"my_bind": 1}, find_first=False
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:
@@ -1,2 +1,2 @@
1
1
  __all__ = ["__version__"]
2
- __version__ = "29.0.0rc1"
2
+ __version__ = "29.2025.1100"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: lsst-daf-butler
3
- Version: 29.0.0rc1
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=7RDErOq5RGNAoQY53vvlgNSl38poXunVsozwg-tQkrU,52
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=6vhVbG9aJxKZVjDLSxQSDcyU5_KexxlHLuM-zQOv2rc,131598
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=X0LitlwV0-zMWTduPcV0GI3iy7ILIo4nEm2jdMa8Phk,74971
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=npGnjkwmVpvsCs8VyvT_ixeV9eAVJqBDMvdKZOVXobc,14766
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=SVZ-4HspSFxedOlyJpopKs_iedeXj3IY14wxhnn15YE,18208
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=H7UsDZEijOP42tfoaL6jDok339I5CldiTy22sX9_3kY,80299
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=otEEp9bCjoOXQ0C4TZ1I2JeRk2f3ILayLizq8bwGp8g,82875
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=2KLKGLWVA6YQQeoLbeX3jsDKSbR6B2l3D3IJ3B3TdZQ,22802
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=4RvpZL-FkmjOvlcnuNHyR19kGHIK2leuD_AaJHz9wUg,22923
242
- lsst/daf/butler/registry/queries/expressions/normalForm.py,sha256=Nv3bTwyH246OS5zMzQhmvE3HjaBscegxyUryQKZ-Quc,41024
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=yrMJWHOoIrcd0ahoqxKB55RpwvJiQziF1j-45NVR5M8,12961
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=mJKC-PnPT3kumzlBN6ixhd-uSzUqUKJkNkKgrjW_TOo,6696
247
- lsst/daf/butler/registry/queries/expressions/parser/parserYacc.py,sha256=502aiNPpcWnDrD5gxcxz8XC1VKwWsiHNpcZtU9AXZXo,15087
248
- lsst/daf/butler/registry/queries/expressions/parser/treeVisitor.py,sha256=xO-cchum-qLKRDV98Z8CafYcsmHCyhIg4uff94SulNk,8530
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=0WGCCq0c-aB8ra-4g84n5m1bm6ng-FNyAfpQQJ31FOM,202217
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=l_ZaCooxXOYVCScil70bggn9J9VvKgCSC7rIddFD8cc,111104
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=1BaKeOJChQO5-IkRqPudEvryTp7VD9fk34VXIWrCz40,16256
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.0.0rc1.dist-info/COPYRIGHT,sha256=k1Vq0-Be_K-puaeW4UZnckPjksEL-MJh4XKiWcjMxJE,312
332
- lsst_daf_butler-29.0.0rc1.dist-info/LICENSE,sha256=pRExkS03v0MQW-neNfIcaSL6aiAnoLxYgtZoFzQ6zkM,232
333
- lsst_daf_butler-29.0.0rc1.dist-info/METADATA,sha256=RfM--L9bMCLZU_M69mYGOgj0GUtrRmC7UMVSGFmeXbU,3240
334
- lsst_daf_butler-29.0.0rc1.dist-info/WHEEL,sha256=jB7zZ3N9hIM9adW7qlTAyycLYW9npaWKLRzaoVcLKcM,91
335
- lsst_daf_butler-29.0.0rc1.dist-info/bsd_license.txt,sha256=7MIcv8QRX9guUtqPSBDMPz2SnZ5swI-xZMqm_VDSfxY,1606
336
- lsst_daf_butler-29.0.0rc1.dist-info/entry_points.txt,sha256=XsRxyTK3c-jGlKVuVnbpch3gtaO0lAA_fS3i2NGS5rw,59
337
- lsst_daf_butler-29.0.0rc1.dist-info/gpl-v3.0.txt,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
338
- lsst_daf_butler-29.0.0rc1.dist-info/top_level.txt,sha256=eUWiOuVVm9wwTrnAgiJT6tp6HQHXxIhj2QSZ7NYZH80,5
339
- lsst_daf_butler-29.0.0rc1.dist-info/zip-safe,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
340
- lsst_daf_butler-29.0.0rc1.dist-info/RECORD,,
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,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (75.8.2)
2
+ Generator: setuptools (76.0.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5