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

34 statements  

« prev     ^ index     » next       coverage.py v7.2.7, created at 2023-06-07 02:10 -0700

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 typing import TYPE_CHECKING, Any, Iterable, Iterator, Mapping, Optional 

31 

32from ...core.ddl import TableSpec 

33from ._database import Database, StaticTablesContext 

34from ._versioning import VersionedExtension, VersionTuple 

35 

36if TYPE_CHECKING: 

37 from ...core.datastore import DatastoreTransaction 

38 

39 

40class OpaqueTableStorage(ABC): 

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

42 opaque table in a `Registry`. 

43 

44 Parameters 

45 ---------- 

46 name : `str` 

47 Name of the opaque table. 

48 """ 

49 

50 def __init__(self, name: str): 

51 self.name = name 

52 

53 @abstractmethod 

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

55 """Insert records into the table 

56 

57 Parameters 

58 ---------- 

59 *data 

60 Each additional positional argument is a dictionary that represents 

61 a single row to be added. 

62 transaction : `DatastoreTransaction`, optional 

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

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

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

66 `None` if no external transaction is available. 

67 """ 

68 raise NotImplementedError() 

69 

70 @abstractmethod 

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

72 """Retrieve records from an opaque table. 

73 

74 Parameters 

75 ---------- 

76 **where 

77 Additional keyword arguments are interpreted as equality 

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

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

80 must have. 

81 

82 Yields 

83 ------ 

84 row : `dict` 

85 A dictionary representing a single result row. 

86 """ 

87 raise NotImplementedError() 

88 

89 @abstractmethod 

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

91 """Remove records from an opaque table. 

92 

93 Parameters 

94 ---------- 

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

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

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

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

99 *rows 

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

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

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

103 """ 

104 raise NotImplementedError() 

105 

106 name: str 

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

108 """ 

109 

110 

111class OpaqueTableStorageManager(VersionedExtension): 

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

113 

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

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

116 for a different (logical) opaque table. 

117 

118 Notes 

119 ----- 

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

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

122 limited to this. 

123 

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

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

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

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

128 """ 

129 

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

131 super().__init__(registry_schema_version=registry_schema_version) 

132 

133 @classmethod 

134 @abstractmethod 

135 def initialize( 

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

137 ) -> OpaqueTableStorageManager: 

138 """Construct an instance of the manager. 

139 

140 Parameters 

141 ---------- 

142 db : `Database` 

143 Interface to the underlying database engine and namespace. 

144 context : `StaticTablesContext` 

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

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

147 implemented with this manager. 

148 registry_schema_version : `VersionTuple` or `None` 

149 Schema version of this extension as defined in registry. 

150 

151 Returns 

152 ------- 

153 manager : `OpaqueTableStorageManager` 

154 An instance of a concrete `OpaqueTableStorageManager` subclass. 

155 """ 

156 raise NotImplementedError() 

157 

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

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

160 `None` on failure. 

161 """ 

162 r = self.get(name) 

163 if r is None: 

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

165 return r 

166 

167 @abstractmethod 

168 def get(self, name: str) -> Optional[OpaqueTableStorage]: 

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

170 an opaque logical table. 

171 

172 Parameters 

173 ---------- 

174 name : `str` 

175 Name of the logical table. 

176 

177 Returns 

178 ------- 

179 records : `OpaqueTableStorage` or `None` 

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

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

182 layer. 

183 

184 Notes 

185 ----- 

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

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

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

189 obtained from an existing data repository. 

190 """ 

191 raise NotImplementedError() 

192 

193 @abstractmethod 

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

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

196 table, creating new tables as necessary. 

197 

198 Parameters 

199 ---------- 

200 name : `str` 

201 Name of the logical table. 

202 spec : `TableSpec` 

203 Schema specification for the table to be created. 

204 

205 Returns 

206 ------- 

207 records : `OpaqueTableStorage` 

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

209 layer. 

210 

211 Notes 

212 ----- 

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

214 """ 

215 raise NotImplementedError()