Coverage for python/lsst/daf/butler/registry/interfaces/_obscore.py: 65%

41 statements  

« prev     ^ index     » next       coverage.py v6.5.0, created at 2023-03-28 04:40 -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 classes that manage obscore table(s) in a `Registry`. 

24""" 

25 

26__all__ = ["ObsCoreTableManager"] 

27 

28from abc import abstractmethod 

29from collections.abc import Iterable, Iterator, Mapping 

30from contextlib import contextmanager 

31from typing import TYPE_CHECKING, Any, Type 

32 

33import sqlalchemy 

34 

35from ._versioning import VersionedExtension 

36 

37if TYPE_CHECKING: 37 ↛ 38line 37 didn't jump to line 38, because the condition on line 37 was never true

38 from lsst.sphgeom import Region 

39 

40 from ...core import DatasetRef, DimensionUniverse 

41 from ..queries import SqlQueryContext 

42 from ._collections import CollectionRecord 

43 from ._database import Database, StaticTablesContext 

44 from ._datasets import DatasetRecordStorageManager 

45 from ._dimensions import DimensionRecordStorageManager 

46 

47 

48class ObsCoreTableManager(VersionedExtension): 

49 """An interface for populating ObsCore tables(s).""" 

50 

51 @classmethod 

52 @abstractmethod 

53 def initialize( 

54 cls, 

55 db: Database, 

56 context: StaticTablesContext, 

57 *, 

58 universe: DimensionUniverse, 

59 config: Mapping, 

60 datasets: Type[DatasetRecordStorageManager], 

61 dimensions: DimensionRecordStorageManager, 

62 ) -> ObsCoreTableManager: 

63 """Construct an instance of the manager. 

64 

65 Parameters 

66 ---------- 

67 db : `Database` 

68 Interface to the underlying database engine and namespace. 

69 context : `StaticTablesContext` 

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

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

72 implemented with this manager. 

73 universe : `DimensionUniverse` 

74 All dimensions known to the registry. 

75 config : `dict` [ `str`, `Any` ] 

76 Configuration of the obscore manager. 

77 datasets : `type` 

78 Type of dataset manager. 

79 dimensions: `DimensionRecordStorageManager` 

80 Manager for Registry dimensions. 

81 

82 Returns 

83 ------- 

84 manager : `ObsCoreTableManager` 

85 An instance of a concrete `ObsCoreTableManager` subclass. 

86 """ 

87 raise NotImplementedError() 

88 

89 @abstractmethod 

90 def config_json(self) -> str: 

91 """Dump configuration in JSON format. 

92 

93 Returns 

94 ------- 

95 json : `str` 

96 Configuration serialized in JSON format. 

97 """ 

98 raise NotImplementedError() 

99 

100 @abstractmethod 

101 def add_datasets(self, refs: Iterable[DatasetRef], context: SqlQueryContext) -> int: 

102 """Possibly add datasets to the obscore table. 

103 

104 This method should be called when new datasets are added to a RUN 

105 collection. 

106 

107 Parameters 

108 ---------- 

109 refs : `iterable` [ `DatasetRef` ] 

110 Dataset refs to add. Dataset refs have to be completely expanded. 

111 If a record with the same dataset ID is already in obscore table, 

112 the dataset is ignored. 

113 context : `SqlQueryContext` 

114 Context used to execute queries for additional dimension metadata. 

115 

116 Returns 

117 ------- 

118 count : `int` 

119 Actual number of records inserted into obscore table. 

120 

121 Notes 

122 ----- 

123 Dataset data types and collection names are checked against configured 

124 list of collections and dataset types, non-matching datasets are 

125 ignored and not added to the obscore table. 

126 

127 When configuration parameter ``collection_type`` is not "RUN", this 

128 method should return immediately. 

129 

130 Note that there is no matching method to remove datasets from obscore 

131 table, we assume that removal happens via foreign key constraint to 

132 dataset table with "ON DELETE CASCADE" option. 

133 """ 

134 raise NotImplementedError() 

135 

136 @abstractmethod 

137 def associate( 

138 self, refs: Iterable[DatasetRef], collection: CollectionRecord, context: SqlQueryContext 

139 ) -> int: 

140 """Possibly add datasets to the obscore table. 

141 

142 This method should be called when existing datasets are associated with 

143 a TAGGED collection. 

144 

145 Parameters 

146 ---------- 

147 refs : `iterable` [ `DatasetRef` ] 

148 Dataset refs to add. Dataset refs have to be completely expanded. 

149 If a record with the same dataset ID is already in obscore table, 

150 the dataset is ignored. 

151 collection : `CollectionRecord` 

152 Collection record for a TAGGED collection. 

153 context : `SqlQueryContext` 

154 Context used to execute queries for additional dimension metadata. 

155 

156 Returns 

157 ------- 

158 count : `int` 

159 Actual number of records inserted into obscore table. 

160 

161 Notes 

162 ----- 

163 Dataset data types and collection names are checked against configured 

164 list of collections and dataset types, non-matching datasets are 

165 ignored and not added to the obscore table. 

166 

167 When configuration parameter ``collection_type`` is not "TAGGED", this 

168 method should return immediately. 

169 """ 

170 raise NotImplementedError() 

171 

172 @abstractmethod 

173 def disassociate(self, refs: Iterable[DatasetRef], collection: CollectionRecord) -> int: 

174 """Possibly remove datasets from the obscore table. 

175 

176 This method should be called when datasets are disassociated from a 

177 TAGGED collection. 

178 

179 Parameters 

180 ---------- 

181 refs : `iterable` [ `DatasetRef` ] 

182 Dataset refs to remove. Dataset refs have to be resolved. 

183 collection : `CollectionRecord` 

184 Collection record for a TAGGED collection. 

185 

186 Returns 

187 ------- 

188 count : `int` 

189 Actual number of records removed from obscore table. 

190 

191 Notes 

192 ----- 

193 Dataset data types and collection names are checked against configured 

194 list of collections and dataset types, non-matching datasets are 

195 ignored and not added to the obscore table. 

196 

197 When configuration parameter ``collection_type`` is not "TAGGED", this 

198 method should return immediately. 

199 """ 

200 raise NotImplementedError() 

201 

202 @abstractmethod 

203 def update_exposure_regions(self, instrument: str, region_data: Iterable[tuple[int, int, Region]]) -> int: 

204 """Update existing exposure records with spatial region data. 

205 

206 Parameters 

207 ---------- 

208 instrument : `str` 

209 Instrument name. 

210 region_data : `Iterable`[`tuple`[`int`, `int`, `~lsst.sphgeom.Region`]] 

211 Sequence of tuples, each tuple contains three values - exposure ID, 

212 detector ID, and corresponding region. 

213 

214 Returns 

215 ------- 

216 count : `int` 

217 Actual number of records updated. 

218 

219 Notes 

220 ----- 

221 This method is needed to update obscore records for raw exposures which 

222 are ingested before their corresponding visits are defined. Exposure 

223 records added when visit is already defined will get their regions 

224 from their matching visits automatically. 

225 """ 

226 raise NotImplementedError() 

227 

228 @abstractmethod 

229 @contextmanager 

230 def query(self, **kwargs: Any) -> Iterator[sqlalchemy.engine.CursorResult]: 

231 """Run a SELECT query against obscore table and return result rows. 

232 

233 Parameters 

234 ---------- 

235 **kwargs 

236 Restriction on values of individual obscore columns. Key is the 

237 column name, value is the required value of the column. Multiple 

238 restrictions are ANDed together. 

239 

240 Returns 

241 ------- 

242 result_context : `sqlalchemy.engine.CursorResult` 

243 Context manager that returns the query result object when entered. 

244 These results are invalidated when the context is exited. 

245 

246 Notes 

247 ----- 

248 This method is intended mostly for tests that need to check the 

249 contents of obscore table. 

250 """ 

251 raise NotImplementedError()