Coverage for python / lsst / dax / apdb / apdb.py: 94%

47 statements  

« prev     ^ index     » next       coverage.py v7.13.5, created at 2026-04-14 23:46 +0000

1# This file is part of dax_apdb. 

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/>. 

21 

22from __future__ import annotations 

23 

24__all__ = ["Apdb", "ApdbConfig"] 

25 

26from abc import ABC, abstractmethod 

27from collections.abc import Iterable, Mapping 

28from typing import TYPE_CHECKING 

29 

30import astropy.time 

31import pandas 

32 

33from lsst.resources import ResourcePathExpression 

34from lsst.sphgeom import Region 

35 

36from .apdbSchema import ApdbTables 

37from .config import ApdbConfig 

38from .factory import make_apdb 

39from .schema_model import Table 

40 

41if TYPE_CHECKING: 

42 from .apdbAdmin import ApdbAdmin 

43 from .apdbMetadata import ApdbMetadata 

44 

45 

46class Apdb(ABC): 

47 """Abstract interface for APDB.""" 

48 

49 @classmethod 

50 def from_config(cls, config: ApdbConfig) -> Apdb: 

51 """Create Ppdb instance from configuration object. 

52 

53 Parameters 

54 ---------- 

55 config : `ApdbConfig` 

56 Configuration object, type of this object determines type of the 

57 Apdb implementation. 

58 

59 Returns 

60 ------- 

61 apdb : `apdb` 

62 Instance of `Apdb` class. 

63 """ 

64 return make_apdb(config) 

65 

66 @classmethod 

67 def from_uri(cls, uri: ResourcePathExpression) -> Apdb: 

68 """Make Apdb instance from a serialized configuration. 

69 

70 Parameters 

71 ---------- 

72 uri : `~lsst.resources.ResourcePathExpression` 

73 URI or local file path pointing to a file with serialized 

74 configuration, or a string with a "label:" prefix. In the latter 

75 case, the configuration will be looked up from an APDB index file 

76 using the label name that follows the prefix. The APDB index file's 

77 location is determined by the ``DAX_APDB_INDEX_URI`` environment 

78 variable. 

79 

80 Returns 

81 ------- 

82 apdb : `apdb` 

83 Instance of `Apdb` class, the type of the returned instance is 

84 determined by configuration. 

85 """ 

86 config = ApdbConfig.from_uri(uri) 

87 return make_apdb(config) 

88 

89 @abstractmethod 

90 def getConfig(self) -> ApdbConfig: 

91 """Return APDB configuration for this instance, including any updates 

92 that may be read from database. 

93 

94 Returns 

95 ------- 

96 config : `ApdbConfig` 

97 APDB configuration. 

98 """ 

99 raise NotImplementedError() 

100 

101 @abstractmethod 

102 def tableDef(self, table: ApdbTables) -> Table | None: 

103 """Return table schema definition for a given table. 

104 

105 Parameters 

106 ---------- 

107 table : `ApdbTables` 

108 One of the known APDB tables. 

109 

110 Returns 

111 ------- 

112 tableSchema : `.schema_model.Table` or `None` 

113 Table schema description, `None` is returned if table is not 

114 defined by this implementation. 

115 """ 

116 raise NotImplementedError() 

117 

118 @abstractmethod 

119 def getDiaObjects(self, region: Region) -> pandas.DataFrame: 

120 """Return catalog of DiaObject instances from a given region. 

121 

122 This method returns only the last version of each DiaObject, 

123 and may return only the subset of the DiaObject columns needed 

124 for AP association. Some 

125 records in a returned catalog may be outside the specified region, it 

126 is up to a client to ignore those records or cleanup the catalog before 

127 futher use. 

128 

129 Parameters 

130 ---------- 

131 region : `lsst.sphgeom.Region` 

132 Region to search for DIAObjects. 

133 

134 Returns 

135 ------- 

136 catalog : `pandas.DataFrame` 

137 Catalog containing DiaObject records for a region that may be a 

138 superset of the specified region. 

139 """ 

140 raise NotImplementedError() 

141 

142 @abstractmethod 

143 def getDiaSources( 

144 self, 

145 region: Region, 

146 object_ids: Iterable[int] | None, 

147 visit_time: astropy.time.Time, 

148 start_time: astropy.time.Time | None = None, 

149 ) -> pandas.DataFrame | None: 

150 """Return catalog of DiaSource instances from a given region. 

151 

152 Parameters 

153 ---------- 

154 region : `lsst.sphgeom.Region` 

155 Region to search for DIASources. 

156 object_ids : iterable [ `int` ], optional 

157 List of DiaObject IDs to further constrain the set of returned 

158 sources. If `None` then returned sources are not constrained. If 

159 list is empty then empty catalog is returned with a correct 

160 schema. 

161 visit_time : `astropy.time.Time` 

162 Time of the current visit. If APDB contains records later than this 

163 time they may also be returned. 

164 start_time : `astropy.time.Time`, optional 

165 Lower bound of time window for the query. If not specified then 

166 it is calculated using ``visit_time`` and 

167 ``read_forced_sources_months`` configuration parameter. 

168 

169 Returns 

170 ------- 

171 catalog : `pandas.DataFrame`, or `None` 

172 Catalog containing DiaSource records. `None` is returned if 

173 ``start_time`` is not specified and ``read_sources_months`` 

174 configuration parameter is set to 0. 

175 

176 Notes 

177 ----- 

178 This method returns DiaSource catalog for a region with additional 

179 filtering based on DiaObject IDs. Only a subset of DiaSource history 

180 is returned limited by ``read_sources_months`` config parameter, w.r.t. 

181 ``visit_time``. If ``object_ids`` is empty then an empty catalog is 

182 always returned with the correct schema (columns/types). If 

183 ``object_ids`` is `None` then no filtering is performed and some of the 

184 returned records may be outside the specified region. 

185 """ 

186 raise NotImplementedError() 

187 

188 @abstractmethod 

189 def getDiaForcedSources( 

190 self, 

191 region: Region, 

192 object_ids: Iterable[int] | None, 

193 visit_time: astropy.time.Time, 

194 start_time: astropy.time.Time | None = None, 

195 ) -> pandas.DataFrame | None: 

196 """Return catalog of DiaForcedSource instances from a given region. 

197 

198 Parameters 

199 ---------- 

200 region : `lsst.sphgeom.Region` 

201 Region to search for DIASources. 

202 object_ids : iterable [ `int` ], optional 

203 List of DiaObject IDs to further constrain the set of returned 

204 sources. If list is empty then empty catalog is returned with a 

205 correct schema. If `None` then returned sources are not 

206 constrained. 

207 visit_time : `astropy.time.Time` 

208 Time of the current visit. If APDB contains records later than this 

209 time they may also be returned. 

210 start_time : `astropy.time.Time`, optional 

211 Lower bound of time window for the query. If not specified then 

212 it is calculated using ``visit_time`` and 

213 ``read_forced_sources_months`` configuration parameter. 

214 

215 Returns 

216 ------- 

217 catalog : `pandas.DataFrame`, or `None` 

218 Catalog containing DiaForcedSource records. `None` is returned if 

219 ``start_time`` is not specified and ``read_forced_sources_months`` 

220 configuration parameter is set to 0. 

221 

222 Raises 

223 ------ 

224 NotImplementedError 

225 May be raised by some implementations if ``object_ids`` is `None`. 

226 

227 Notes 

228 ----- 

229 This method returns DiaForcedSource catalog for a region with 

230 additional filtering based on DiaObject IDs. Only a subset of DiaSource 

231 history is returned limited by ``read_forced_sources_months`` config 

232 parameter, w.r.t. ``visit_time``. If ``object_ids`` is empty then an 

233 empty catalog is always returned with the correct schema 

234 (columns/types). If ``object_ids`` is `None` then no filtering is 

235 performed and some of the returned records may be outside the specified 

236 region. 

237 """ 

238 raise NotImplementedError() 

239 

240 @abstractmethod 

241 def containsVisitDetector( 

242 self, 

243 visit: int, 

244 detector: int, 

245 region: Region, 

246 visit_time: astropy.time.Time, 

247 ) -> bool: 

248 """Test whether any sources for a given visit-detector are present in 

249 the APDB. 

250 

251 Parameters 

252 ---------- 

253 visit, detector : `int` 

254 The ID of the visit-detector to search for. 

255 region : `lsst.sphgeom.Region` 

256 Region corresponding to the visit/detector combination. 

257 visit_time : `astropy.time.Time` 

258 Visit time (as opposed to visit processing time). This can be any 

259 timestamp in the visit timespan, e.g. its begin or end time. 

260 

261 Returns 

262 ------- 

263 present : `bool` 

264 `True` if at least one DiaSource or DiaForcedSource record 

265 may exist for the specified observation, `False` otherwise. 

266 """ 

267 raise NotImplementedError() 

268 

269 @abstractmethod 

270 def store( 

271 self, 

272 visit_time: astropy.time.Time, 

273 objects: pandas.DataFrame, 

274 sources: pandas.DataFrame | None = None, 

275 forced_sources: pandas.DataFrame | None = None, 

276 ) -> None: 

277 """Store all three types of catalogs in the database. 

278 

279 Parameters 

280 ---------- 

281 visit_time : `astropy.time.Time` 

282 Time of the visit. 

283 objects : `pandas.DataFrame` 

284 Catalog with DiaObject records. 

285 sources : `pandas.DataFrame`, optional 

286 Catalog with DiaSource records. 

287 forced_sources : `pandas.DataFrame`, optional 

288 Catalog with DiaForcedSource records. 

289 

290 Notes 

291 ----- 

292 This methods takes DataFrame catalogs, their schema must be 

293 compatible with the schema of APDB table: 

294 

295 - column names must correspond to database table columns 

296 - types and units of the columns must match database definitions, 

297 no unit conversion is performed presently 

298 - columns that have default values in database schema can be 

299 omitted from catalog 

300 - this method knows how to fill interval-related columns of DiaObject 

301 (validityStart, validityEnd) they do not need to appear in a 

302 catalog 

303 - source catalogs have ``diaObjectId`` column associating sources 

304 with objects 

305 

306 This operation need not be atomic, but DiaSources and DiaForcedSources 

307 will not be stored until all DiaObjects are stored. 

308 """ 

309 raise NotImplementedError() 

310 

311 @abstractmethod 

312 def reassignDiaSources(self, idMap: Mapping[int, int]) -> None: 

313 """Associate DiaSources with SSObjects, dis-associating them 

314 from DiaObjects. 

315 

316 Parameters 

317 ---------- 

318 idMap : `Mapping` 

319 Maps DiaSource IDs to their new SSObject IDs. 

320 

321 Raises 

322 ------ 

323 ValueError 

324 Raised if DiaSource ID does not exist in the database. 

325 """ 

326 raise NotImplementedError() 

327 

328 @abstractmethod 

329 def dailyJob(self) -> None: 

330 """Implement daily activities like cleanup/vacuum. 

331 

332 What should be done during daily activities is determined by 

333 specific implementation. 

334 """ 

335 raise NotImplementedError() 

336 

337 @abstractmethod 

338 def countUnassociatedObjects(self) -> int: 

339 """Return the number of DiaObjects that have only one DiaSource 

340 associated with them. 

341 

342 Used as part of ap_verify metrics. 

343 

344 Returns 

345 ------- 

346 count : `int` 

347 Number of DiaObjects with exactly one associated DiaSource. 

348 

349 Notes 

350 ----- 

351 This method can be very inefficient or slow in some implementations. 

352 """ 

353 raise NotImplementedError() 

354 

355 @property 

356 @abstractmethod 

357 def metadata(self) -> ApdbMetadata: 

358 """Object controlling access to APDB metadata (`ApdbMetadata`).""" 

359 raise NotImplementedError() 

360 

361 @property 

362 @abstractmethod 

363 def admin(self) -> ApdbAdmin: 

364 """Object providing adminitrative interface for APDB (`ApdbAdmin`).""" 

365 raise NotImplementedError()