Coverage for python/lsst/daf/butler/registry/interfaces/_opaque.py: 95%
42 statements
« prev ^ index » next coverage.py v7.17.2, created at 2026-11-06 09:46 +0000
« prev ^ index » next coverage.py v7.17.2, created at 2026-11-06 09:46 +0000
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/>.
28"""Interfaces for the objects that manage opaque (logical) tables within a
29`Registry`.
30"""
32from __future__ import annotations
34__all__ = ["OpaqueTableStorage", "OpaqueTableStorageManager"]
36from abc import ABC, abstractmethod
37from collections.abc import Iterable, Iterator, Mapping, Sequence
38from typing import TYPE_CHECKING, Any
40from ...ddl import TableSpec
41from ._database import Database, StaticTablesContext
42from ._versioning import VersionedExtension, VersionTuple
44if TYPE_CHECKING:
45 from ...datastore import DatastoreTransaction
48class OpaqueTableStorage(ABC):
49 """An interface that manages the records associated with a particular
50 opaque table in a `Registry`.
52 Parameters
53 ----------
54 name : `str`
55 Name of the opaque table.
56 """
58 def __init__(self, name: str):
59 self.name = name
61 @abstractmethod
62 def insert(self, *data: dict, transaction: DatastoreTransaction | None = None) -> None:
63 """Insert records into the table.
65 Parameters
66 ----------
67 *data
68 Each additional positional argument is a dictionary that represents
69 a single row to be added.
70 transaction : `DatastoreTransaction`, optional
71 Transaction object that can be used to enable an explicit rollback
72 of the insert to be registered. Can be ignored if rollback is
73 handled via a different mechanism, such as by a database. Can be
74 `None` if no external transaction is available.
76 Raises
77 ------
78 ConflictingDefinitionError
79 Raised if the record with a matching primary or unique key already
80 exists.
81 """
82 raise NotImplementedError()
84 @abstractmethod
85 def ensure(self, *data: dict, transaction: DatastoreTransaction | None = None) -> None:
86 """Insert records into the table, skipping rows that already exist.
88 Parameters
89 ----------
90 *data
91 Each additional positional argument is a dictionary that represents
92 a single row to be added.
93 transaction : `DatastoreTransaction`, optional
94 Transaction object that can be used to enable an explicit rollback
95 of the insert to be registered. Can be ignored if rollback is
96 handled via a different mechanism, such as by a database. Can be
97 `None` if no external transaction is available.
98 """
99 raise NotImplementedError()
101 @abstractmethod
102 def replace(self, *data: dict, transaction: DatastoreTransaction | None = None) -> None:
103 """Insert records into the table, replacing if previously existing
104 but different.
106 Parameters
107 ----------
108 *data
109 Each additional positional argument is a dictionary that represents
110 a single row to be added.
111 transaction : `DatastoreTransaction`, optional
112 Transaction object that can be used to enable an explicit rollback
113 of the insert to be registered. Can be ignored if rollback is
114 handled via a different mechanism, such as by a database. Can be
115 `None` if no external transaction is available.
116 """
117 raise NotImplementedError()
119 @abstractmethod
120 def fetch(self, **where: Any) -> Iterator[Mapping[Any, Any]]:
121 """Retrieve records from an opaque table.
123 Parameters
124 ----------
125 **where
126 Additional keyword arguments are interpreted as equality
127 constraints that restrict the returned rows (combined with AND);
128 keyword arguments are column names and values are the values they
129 must have.
131 Yields
132 ------
133 row : `dict`
134 A dictionary representing a single result row.
135 """
136 raise NotImplementedError()
138 @abstractmethod
139 def fetch_batches(
140 self,
141 **where: Any,
142 ) -> Iterator[Sequence[Mapping]]:
143 """Retrieve records from an opaque table in batches.
145 Parameters
146 ----------
147 **where
148 Same as ``OpaqueTableStorage.fetch``.
150 Yields
151 ------
152 batch
153 A batch of mappings representing a series of result rows.
154 """
155 raise NotImplementedError()
157 @abstractmethod
158 def delete(self, columns: Iterable[str], *rows: dict) -> None:
159 """Remove records from an opaque table.
161 Parameters
162 ----------
163 columns : `~collections.abc.Iterable` of `str`
164 The names of columns that will be used to constrain the rows to
165 be deleted; these will be combined via ``AND`` to form the
166 ``WHERE`` clause of the delete query.
167 *rows
168 Positional arguments are the keys of rows to be deleted, as
169 dictionaries mapping column name to value. The keys in all
170 dictionaries must be exactly the names in ``columns``.
171 """
172 raise NotImplementedError()
174 name: str
175 """The name of the logical table this instance manages (`str`).
176 """
179class OpaqueTableStorageManager(VersionedExtension):
180 """An interface that manages the opaque tables in a `Registry`.
182 `OpaqueTableStorageManager` primarily serves as a container and factory for
183 `OpaqueTableStorage` instances, which each provide access to the records
184 for a different (logical) opaque table.
186 Parameters
187 ----------
188 registry_schema_version : `VersionTuple` or `None`, optional
189 Version of registry schema.
191 Notes
192 -----
193 Opaque tables are primarily used by `Datastore` instances to manage their
194 internal data in the same database that hold the `Registry`, but are not
195 limited to this.
197 While an opaque table in a multi-layer `Registry` may in fact be the union
198 of multiple tables in different layers, we expect this to be rare, as
199 `Registry` layers will typically correspond to different leaf `Datastore`
200 instances (each with their own opaque table) in a `ChainedDatastore`.
201 """
203 def __init__(self, *, registry_schema_version: VersionTuple | None = None):
204 super().__init__(registry_schema_version=registry_schema_version)
206 @classmethod
207 @abstractmethod
208 def initialize(
209 cls, db: Database, context: StaticTablesContext, registry_schema_version: VersionTuple | None = None
210 ) -> OpaqueTableStorageManager:
211 """Construct an instance of the manager.
213 Parameters
214 ----------
215 db : `Database`
216 Interface to the underlying database engine and namespace.
217 context : `StaticTablesContext`
218 Context object obtained from `Database.declareStaticTables`; used
219 to declare any tables that should always be present in a layer
220 implemented with this manager.
221 registry_schema_version : `VersionTuple` or `None`
222 Schema version of this extension as defined in registry.
224 Returns
225 -------
226 manager : `OpaqueTableStorageManager`
227 An instance of a concrete `OpaqueTableStorageManager` subclass.
228 """
229 raise NotImplementedError()
231 def __getitem__(self, name: str) -> OpaqueTableStorage:
232 """Interface to `get` that raises `LookupError` instead of returning
233 `None` on failure.
234 """
235 r = self.get(name)
236 if r is None: 236 ↛ 237line 236 didn't jump to line 237 because the condition on line 236 was never true
237 raise LookupError(f"No logical table with name '{name}' found.")
238 return r
240 @abstractmethod
241 def get(self, name: str) -> OpaqueTableStorage | None:
242 """Return an object that provides access to the records associated with
243 an opaque logical table.
245 Parameters
246 ----------
247 name : `str`
248 Name of the logical table.
250 Returns
251 -------
252 records : `OpaqueTableStorage` or `None`
253 The object representing the records for the given table in this
254 layer, or `None` if there are no records for that table in this
255 layer.
257 Notes
258 -----
259 Opaque tables must be registered with the layer (see `register`) by
260 the same client before they can safely be retrieved with `get`.
261 Unlike most other manager classes, the set of opaque tables cannot be
262 obtained from an existing data repository.
263 """
264 raise NotImplementedError()
266 @abstractmethod
267 def register(self, name: str, spec: TableSpec) -> OpaqueTableStorage:
268 """Ensure that this layer can hold records for the given opaque logical
269 table, creating new tables as necessary.
271 Parameters
272 ----------
273 name : `str`
274 Name of the logical table.
275 spec : `TableSpec`
276 Schema specification for the table to be created.
278 Returns
279 -------
280 records : `OpaqueTableStorage`
281 The object representing the records for the given element in this
282 layer.
284 Notes
285 -----
286 This operation may not be invoked within a transaction context block.
287 """
288 raise NotImplementedError()
290 @abstractmethod
291 def clone(self, db: Database) -> OpaqueTableStorageManager:
292 """Make an independent copy of this manager instance bound to a new
293 `Database` instance.
295 Parameters
296 ----------
297 db : `Database`
298 New `Database` object to use when instantiating the manager.
300 Returns
301 -------
302 instance : `OpaqueTableStorageManager`
303 New manager instance with the same configuration as this instance,
304 but bound to a new Database object.
305 """
306 raise NotImplementedError()