Coverage for python/lsst/daf/butler/registry/queries/_sql_query_backend.py: 36%
59 statements
« prev ^ index » next coverage.py v7.11.0, created at 2025-11-06 01:32 -0800
« prev ^ index » next coverage.py v7.11.0, created at 2025-11-06 01:32 -0800
1# This file is part of daf_butler.
2#
3# Developed for the LSST Data Management System.
4# This product includes software developed by the LSST Project
5# (http://www.lsst.org).
6# See the COPYRIGHT file at the top-level directory of this distribution
7# for details of code ownership.
8#
9# This software is dual licensed under the GNU General Public License and also
10# under a 3-clause BSD license. Recipients may choose which of these licenses
11# to use; please see the files gpl-3.0.txt and/or bsd_license.txt,
12# respectively. If you choose the GPL option then the following text applies
13# (but note that there is still no warranty even if you opt for BSD instead):
14#
15# This program is free software: you can redistribute it and/or modify
16# it under the terms of the GNU General Public License as published by
17# the Free Software Foundation, either version 3 of the License, or
18# (at your option) any later version.
19#
20# This program is distributed in the hope that it will be useful,
21# but WITHOUT ANY WARRANTY; without even the implied warranty of
22# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
23# GNU General Public License for more details.
24#
25# You should have received a copy of the GNU General Public License
26# along with this program. If not, see <http://www.gnu.org/licenses/>.
27from __future__ import annotations
29__all__ = ("SqlQueryBackend",)
31import warnings
32from collections.abc import Iterable, Mapping, Sequence, Set
33from contextlib import AbstractContextManager
34from typing import TYPE_CHECKING, Any, cast
36from lsst.utils.introspection import find_outside_stacklevel
38from ..._collection_type import CollectionType
39from ..._dataset_type import DatasetType
40from ..._exceptions import DataIdValueError
41from ...dimensions import DimensionGroup, DimensionRecordSet, DimensionUniverse
42from ...dimensions.record_cache import DimensionRecordCache
43from ..interfaces import CollectionRecord, Database
44from ._query_backend import QueryBackend
45from ._sql_query_context import SqlQueryContext
47if TYPE_CHECKING:
48 from ..managers import RegistryManagerInstances
51class SqlQueryBackend(QueryBackend[SqlQueryContext]):
52 """An implementation of `QueryBackend` for `SqlRegistry`.
54 Parameters
55 ----------
56 db : `Database`
57 Object that abstracts the database engine.
58 managers : `RegistryManagerInstances`
59 Struct containing the manager objects that back a `SqlRegistry`.
60 dimension_record_cache : `DimensionRecordCache`
61 Cache of all records for dimension elements with
62 `~DimensionElement.is_cached` `True`.
63 """
65 def __init__(
66 self, db: Database, managers: RegistryManagerInstances, dimension_record_cache: DimensionRecordCache
67 ):
68 self._db = db
69 self._managers = managers
70 self._dimension_record_cache = dimension_record_cache
72 @property
73 def universe(self) -> DimensionUniverse:
74 # Docstring inherited.
75 return self._managers.dimensions.universe
77 def caching_context(self) -> AbstractContextManager[None]:
78 # Docstring inherited.
79 return self._managers.caching_context_manager()
81 def context(self) -> SqlQueryContext:
82 # Docstring inherited.
83 return SqlQueryContext(self._db, self._managers.column_types)
85 def get_collection_name(self, key: Any) -> str:
86 assert self._managers.caching_context.collection_records is not None, (
87 "Collection-record caching should already been enabled any time this is called."
88 )
89 return self._managers.collections[key].name
91 def resolve_collection_wildcard(
92 self,
93 expression: Any,
94 *,
95 collection_types: Set[CollectionType] = CollectionType.all(),
96 flatten_chains: bool = True,
97 include_chains: bool | None = None,
98 ) -> list[CollectionRecord]:
99 # Docstring inherited.
100 return self._managers.collections.resolve_wildcard(
101 expression,
102 collection_types=collection_types,
103 flatten_chains=flatten_chains,
104 include_chains=include_chains,
105 )
107 def resolve_dataset_type_wildcard(
108 self,
109 expression: Any,
110 missing: list[str] | None = None,
111 explicit_only: bool = False,
112 ) -> list[DatasetType]:
113 # Docstring inherited.
114 return self._managers.datasets.resolve_wildcard(
115 expression,
116 missing,
117 explicit_only,
118 )
120 def filter_dataset_collections(
121 self,
122 dataset_types: Iterable[DatasetType],
123 collections: Sequence[CollectionRecord],
124 *,
125 governor_constraints: Mapping[str, Set[str]],
126 rejections: list[str] | None = None,
127 ) -> dict[DatasetType, list[CollectionRecord]]:
128 # Docstring inherited.
129 result: dict[DatasetType, list[CollectionRecord]] = {
130 dataset_type: [] for dataset_type in dataset_types
131 }
132 summaries = self._managers.datasets.fetch_summaries(collections, result.keys())
133 for dataset_type, filtered_collections in result.items():
134 for collection_record in collections:
135 if not dataset_type.isCalibration() and collection_record.type is CollectionType.CALIBRATION:
136 if rejections is not None:
137 rejections.append(
138 f"Not searching for non-calibration dataset of type {dataset_type.name!r} "
139 f"in CALIBRATION collection {collection_record.name!r}."
140 )
141 else:
142 collection_summary = summaries[collection_record.key]
143 if collection_summary.is_compatible_with(
144 dataset_type,
145 governor_constraints,
146 rejections=rejections,
147 name=collection_record.name,
148 ):
149 filtered_collections.append(collection_record)
150 return result
152 def resolve_governor_constraints(
153 self, dimensions: DimensionGroup, constraints: Mapping[str, Set[str]]
154 ) -> Mapping[str, Set[str]]:
155 # Docstring inherited.
156 result: dict[str, Set[str]] = {}
157 for dimension_name in dimensions.governors:
158 all_values = {
159 cast(str, record.dataId[dimension_name])
160 for record in self._dimension_record_cache[dimension_name]
161 }
162 if (constraint_values := constraints.get(dimension_name)) is not None:
163 if not (constraint_values <= all_values):
164 warnings.warn(
165 "DataIdValueError will no longer be raised for invalid governor dimension values."
166 " Instead, an empty list will be returned. Will be changed after v28.",
167 FutureWarning,
168 stacklevel=find_outside_stacklevel("lsst.daf.butler"),
169 )
170 raise DataIdValueError(
171 f"Unknown values specified for governor dimension {dimension_name}: "
172 f"{constraint_values - all_values}."
173 )
174 result[dimension_name] = constraint_values
175 else:
176 result[dimension_name] = all_values
177 return result
179 def get_dimension_record_cache(self, element_name: str) -> DimensionRecordSet | None:
180 return (
181 self._dimension_record_cache[element_name]
182 if element_name in self._dimension_record_cache
183 else None
184 )