Coverage for python/lsst/daf/butler/tests/_dummyRegistry.py: 84%
113 statements
« prev ^ index » next coverage.py v7.16.2, created at 2026-11-06 01:43 -0800
« prev ^ index » next coverage.py v7.16.2, created at 2026-11-06 01:43 -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__ = ("DummyRegistry",)
31from collections.abc import Iterable, Iterator
32from typing import Any
34from lsst.daf.butler import DimensionUniverse, ddl
35from lsst.daf.butler.registry import ConflictingDefinitionError
36from lsst.daf.butler.registry.bridge.ephemeral import EphemeralDatastoreRegistryBridge
37from lsst.daf.butler.registry.interfaces import (
38 Database,
39 DatabaseInsertMode,
40 DatasetIdRef,
41 DatasetRecordStorageManager,
42 DatastoreRegistryBridge,
43 DatastoreRegistryBridgeManager,
44 OpaqueTableStorage,
45 OpaqueTableStorageManager,
46 StaticTablesContext,
47 VersionTuple,
48)
50from ..datastore import DatastoreTransaction
53class DummyOpaqueTableStorage(OpaqueTableStorage):
54 def __init__(self, name: str, spec: ddl.TableSpec) -> None:
55 super().__init__(name=name)
56 self._rows: list[dict] = []
57 self._spec = spec
59 def insert(self, *data: dict, transaction: DatastoreTransaction | None = None) -> None:
60 # Docstring inherited from OpaqueTableStorage.
61 self._insert(*data, transaction=transaction, insert_mode=DatabaseInsertMode.INSERT)
63 def replace(self, *data: dict, transaction: DatastoreTransaction | None = None) -> None:
64 # Docstring inherited from OpaqueTableStorage.
65 self._insert(*data, transaction=transaction, insert_mode=DatabaseInsertMode.REPLACE)
67 def ensure(self, *data: dict, transaction: DatastoreTransaction | None = None) -> None:
68 # Docstring inherited from OpaqueTableStorage.
69 self._insert(*data, transaction=transaction, insert_mode=DatabaseInsertMode.ENSURE)
71 def _insert(
72 self,
73 *data: dict,
74 transaction: DatastoreTransaction | None = None,
75 insert_mode: DatabaseInsertMode = DatabaseInsertMode.INSERT,
76 ) -> None:
77 uniqueConstraints = list(self._spec.unique)
78 uniqueConstraints.append(tuple(field.name for field in self._spec.fields if field.primaryKey))
79 for d in data:
80 skipping = False
81 for constraint in uniqueConstraints:
82 matching = list(self.fetch(**{k: d[k] for k in constraint}))
83 if len(matching) != 0:
84 match insert_mode:
85 case DatabaseInsertMode.INSERT: 85 ↛ 86line 85 didn't jump to line 86 because the pattern on line 85 never matched
86 raise ConflictingDefinitionError(
87 f"Table {self.name} already contains matching record(s): {matching}"
88 )
89 case DatabaseInsertMode.ENSURE: 89 ↛ 91line 89 didn't jump to line 91 because the pattern on line 89 never matched
90 # Row already exists. Skip.
91 skipping = True
92 case DatabaseInsertMode.REPLACE: 92 ↛ 96line 92 didn't jump to line 96 because the pattern on line 92 always matched
93 # Should try to put these rows back on transaction
94 # rollback...
95 self.delete([], *matching)
96 case _:
97 raise ValueError(f"Unrecognized insert mode: {insert_mode}.")
99 if skipping: 99 ↛ 100line 99 didn't jump to line 100 because the condition on line 99 was never true
100 continue
101 self._rows.append(d)
102 if transaction is not None:
103 transaction.registerUndo("insert", self.delete, [], d)
105 def fetch(self, **where: Any) -> Iterator[dict]:
106 # Docstring inherited from OpaqueTableStorage.
107 where = where.copy() # May need to modify it.
109 # Can support an IN operator if given list.
110 wherein = {}
111 for k in list(where):
112 if isinstance(where[k], tuple | list | set):
113 wherein[k] = set(where[k])
114 del where[k]
116 for d in self._rows:
117 if all(d[k] == v for k, v in where.items()):
118 if wherein:
119 match = True
120 for k, v in wherein.items():
121 if d[k] not in v:
122 match = False
123 break
124 if match:
125 yield d
126 else:
127 yield d
129 def fetch_batches(self, **where: Any) -> Iterator[list[dict]]:
130 # Docstring inherited from OpaqueTableStorage.
131 yield list(self.fetch(**where))
133 def delete(self, columns: Iterable[str], *rows: dict) -> None:
134 # Docstring inherited from OpaqueTableStorage.
135 kept_rows = []
136 for table_row in self._rows:
137 for where_row in rows:
138 if all(table_row[k] == v for k, v in where_row.items()):
139 break
140 else:
141 kept_rows.append(table_row)
142 self._rows = kept_rows
145class DummyOpaqueTableStorageManager(OpaqueTableStorageManager):
146 def __init__(self, registry_schema_version: VersionTuple | None = None) -> None:
147 super().__init__(registry_schema_version=registry_schema_version)
148 self._storages: dict[str, DummyOpaqueTableStorage] = {}
150 def clone(self, db: Database) -> OpaqueTableStorageManager:
151 return self
153 @classmethod
154 def initialize(
155 cls, db: Database, context: StaticTablesContext, registry_schema_version: VersionTuple | None = None
156 ) -> OpaqueTableStorageManager:
157 # Docstring inherited from OpaqueTableStorageManager.
158 # Not used, but needed to satisfy ABC requirement.
159 return cls(registry_schema_version=registry_schema_version)
161 def get(self, name: str) -> OpaqueTableStorage | None:
162 # Docstring inherited from OpaqueTableStorageManager.
163 return self._storages.get(name)
165 def register(self, name: str, spec: ddl.TableSpec) -> OpaqueTableStorage:
166 # Docstring inherited from OpaqueTableStorageManager.
167 return self._storages.setdefault(name, DummyOpaqueTableStorage(name, spec))
169 @classmethod
170 def currentVersions(cls) -> list[VersionTuple]:
171 # Docstring inherited from VersionedExtension.
172 return []
175class DummyDatastoreRegistryBridgeManager(DatastoreRegistryBridgeManager):
176 def __init__(
177 self,
178 opaque: OpaqueTableStorageManager,
179 universe: DimensionUniverse,
180 registry_schema_version: VersionTuple | None = None,
181 ):
182 super().__init__(
183 opaque=opaque,
184 universe=universe,
185 registry_schema_version=registry_schema_version,
186 )
187 self._bridges: dict[str, EphemeralDatastoreRegistryBridge] = {}
189 def clone(self, *, db: Database, opaque: OpaqueTableStorageManager) -> DatastoreRegistryBridgeManager:
190 return DummyDatastoreRegistryBridgeManager(
191 opaque=opaque,
192 universe=self.universe,
193 registry_schema_version=self._registry_schema_version,
194 )
196 @classmethod
197 def initialize(
198 cls,
199 db: Database,
200 context: StaticTablesContext,
201 *,
202 opaque: OpaqueTableStorageManager,
203 datasets: type[DatasetRecordStorageManager],
204 universe: DimensionUniverse,
205 registry_schema_version: VersionTuple | None = None,
206 ) -> DatastoreRegistryBridgeManager:
207 # Docstring inherited from DatastoreRegistryBridgeManager
208 # Not used, but needed to satisfy ABC requirement.
209 return cls(
210 opaque=opaque,
211 universe=universe,
212 registry_schema_version=registry_schema_version,
213 )
215 def refresh(self) -> None:
216 # Docstring inherited from DatastoreRegistryBridgeManager
217 pass
219 def register(self, name: str, *, ephemeral: bool = False) -> DatastoreRegistryBridge:
220 # Docstring inherited from DatastoreRegistryBridgeManager
221 return self._bridges.setdefault(name, EphemeralDatastoreRegistryBridge(name))
223 def findDatastores(self, ref: DatasetIdRef) -> Iterable[str]:
224 # Docstring inherited from DatastoreRegistryBridgeManager
225 for name, bridge in self._bridges.items():
226 if ref in bridge:
227 yield name
229 @classmethod
230 def currentVersions(cls) -> list[VersionTuple]:
231 # Docstring inherited from VersionedExtension.
232 return []
235class DummyRegistry:
236 """Dummy Registry, for Datastore test purposes."""
238 def __init__(self) -> None:
239 self._opaque = DummyOpaqueTableStorageManager()
240 self.dimensions = DimensionUniverse()
241 self._datastoreBridges = DummyDatastoreRegistryBridgeManager(self._opaque, self.dimensions, None)
243 def getDatastoreBridgeManager(self) -> DatastoreRegistryBridgeManager:
244 return self._datastoreBridges