Coverage for python/lsst/daf/butler/registry/interfaces/_opaque.py: 78%

35 statements  

« prev     ^ index     » next       coverage.py v7.2.7, created at 2023-06-15 09:13 +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 program is free software: you can redistribute it and/or modify 

10# it under the terms of the GNU General Public License as published by 

11# the Free Software Foundation, either version 3 of the License, or 

12# (at your option) any later version. 

13# 

14# This program is distributed in the hope that it will be useful, 

15# but WITHOUT ANY WARRANTY; without even the implied warranty of 

16# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 

17# GNU General Public License for more details. 

18# 

19# You should have received a copy of the GNU General Public License 

20# along with this program. If not, see <http://www.gnu.org/licenses/>. 

21from __future__ import annotations 

22 

23"""Interfaces for the objects that manage opaque (logical) tables within a 

24`Registry`. 

25""" 

26 

27__all__ = ["OpaqueTableStorageManager", "OpaqueTableStorage"] 

28 

29from abc import ABC, abstractmethod 

30from collections.abc import Iterable, Iterator, Mapping 

31from typing import TYPE_CHECKING, Any 

32 

33from ...core.ddl import TableSpec 

34from ._database import Database, StaticTablesContext 

35from ._versioning import VersionedExtension, VersionTuple 

36 

37if TYPE_CHECKING: 

38 from ...core.datastore import DatastoreTransaction 

39 

40 

41class OpaqueTableStorage(ABC): 

42 """An interface that manages the records associated with a particular 

43 opaque table in a `Registry`. 

44 

45 Parameters 

46 ---------- 

47 name : `str` 

48 Name of the opaque table. 

49 """ 

50 

51 def __init__(self, name: str): 

52 self.name = name 

53 

54 @abstractmethod 

55 def insert(self, *data: dict, transaction: DatastoreTransaction | None = None) -> None: 

56 """Insert records into the table 

57 

58 Parameters 

59 ---------- 

60 *data 

61 Each additional positional argument is a dictionary that represents 

62 a single row to be added. 

63 transaction : `DatastoreTransaction`, optional 

64 Transaction object that can be used to enable an explicit rollback 

65 of the insert to be registered. Can be ignored if rollback is 

66 handled via a different mechanism, such as by a database. Can be 

67 `None` if no external transaction is available. 

68 """ 

69 raise NotImplementedError() 

70 

71 @abstractmethod 

72 def fetch(self, **where: Any) -> Iterator[Mapping[Any, Any]]: 

73 """Retrieve records from an opaque table. 

74 

75 Parameters 

76 ---------- 

77 **where 

78 Additional keyword arguments are interpreted as equality 

79 constraints that restrict the returned rows (combined with AND); 

80 keyword arguments are column names and values are the values they 

81 must have. 

82 

83 Yields 

84 ------ 

85 row : `dict` 

86 A dictionary representing a single result row. 

87 """ 

88 raise NotImplementedError() 

89 

90 @abstractmethod 

91 def delete(self, columns: Iterable[str], *rows: dict) -> None: 

92 """Remove records from an opaque table. 

93 

94 Parameters 

95 ---------- 

96 columns: `~collections.abc.Iterable` of `str` 

97 The names of columns that will be used to constrain the rows to 

98 be deleted; these will be combined via ``AND`` to form the 

99 ``WHERE`` clause of the delete query. 

100 *rows 

101 Positional arguments are the keys of rows to be deleted, as 

102 dictionaries mapping column name to value. The keys in all 

103 dictionaries must be exactly the names in ``columns``. 

104 """ 

105 raise NotImplementedError() 

106 

107 name: str 

108 """The name of the logical table this instance manages (`str`). 

109 """ 

110 

111 

112class OpaqueTableStorageManager(VersionedExtension): 

113 """An interface that manages the opaque tables in a `Registry`. 

114 

115 `OpaqueTableStorageManager` primarily serves as a container and factory for 

116 `OpaqueTableStorage` instances, which each provide access to the records 

117 for a different (logical) opaque table. 

118 

119 Notes 

120 ----- 

121 Opaque tables are primarily used by `Datastore` instances to manage their 

122 internal data in the same database that hold the `Registry`, but are not 

123 limited to this. 

124 

125 While an opaque table in a multi-layer `Registry` may in fact be the union 

126 of multiple tables in different layers, we expect this to be rare, as 

127 `Registry` layers will typically correspond to different leaf `Datastore` 

128 instances (each with their own opaque table) in a `ChainedDatastore`. 

129 """ 

130 

131 def __init__(self, *, registry_schema_version: VersionTuple | None = None): 

132 super().__init__(registry_schema_version=registry_schema_version) 

133 

134 @classmethod 

135 @abstractmethod 

136 def initialize( 

137 cls, db: Database, context: StaticTablesContext, registry_schema_version: VersionTuple | None = None 

138 ) -> OpaqueTableStorageManager: 

139 """Construct an instance of the manager. 

140 

141 Parameters 

142 ---------- 

143 db : `Database` 

144 Interface to the underlying database engine and namespace. 

145 context : `StaticTablesContext` 

146 Context object obtained from `Database.declareStaticTables`; used 

147 to declare any tables that should always be present in a layer 

148 implemented with this manager. 

149 registry_schema_version : `VersionTuple` or `None` 

150 Schema version of this extension as defined in registry. 

151 

152 Returns 

153 ------- 

154 manager : `OpaqueTableStorageManager` 

155 An instance of a concrete `OpaqueTableStorageManager` subclass. 

156 """ 

157 raise NotImplementedError() 

158 

159 def __getitem__(self, name: str) -> OpaqueTableStorage: 

160 """Interface to `get` that raises `LookupError` instead of returning 

161 `None` on failure. 

162 """ 

163 r = self.get(name) 

164 if r is None: 

165 raise LookupError(f"No logical table with name '{name}' found.") 

166 return r 

167 

168 @abstractmethod 

169 def get(self, name: str) -> OpaqueTableStorage | None: 

170 """Return an object that provides access to the records associated with 

171 an opaque logical table. 

172 

173 Parameters 

174 ---------- 

175 name : `str` 

176 Name of the logical table. 

177 

178 Returns 

179 ------- 

180 records : `OpaqueTableStorage` or `None` 

181 The object representing the records for the given table in this 

182 layer, or `None` if there are no records for that table in this 

183 layer. 

184 

185 Notes 

186 ----- 

187 Opaque tables must be registered with the layer (see `register`) by 

188 the same client before they can safely be retrieved with `get`. 

189 Unlike most other manager classes, the set of opaque tables cannot be 

190 obtained from an existing data repository. 

191 """ 

192 raise NotImplementedError() 

193 

194 @abstractmethod 

195 def register(self, name: str, spec: TableSpec) -> OpaqueTableStorage: 

196 """Ensure that this layer can hold records for the given opaque logical 

197 table, creating new tables as necessary. 

198 

199 Parameters 

200 ---------- 

201 name : `str` 

202 Name of the logical table. 

203 spec : `TableSpec` 

204 Schema specification for the table to be created. 

205 

206 Returns 

207 ------- 

208 records : `OpaqueTableStorage` 

209 The object representing the records for the given element in this 

210 layer. 

211 

212 Notes 

213 ----- 

214 This operation may not be invoked within a transaction context block. 

215 """ 

216 raise NotImplementedError()