Coverage for python/lsst/daf/butler/_exceptions.py: 89%

55 statements  

« prev     ^ index     » next       coverage.py v7.16.1, created at 2026-09-26 09:08 +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/>. 

27 

28"""Specialized Butler exceptions.""" 

29 

30__all__ = ( 

31 "ButlerUserError", 

32 "CalibrationLookupError", 

33 "CollectionCycleError", 

34 "CollectionTypeError", 

35 "DataIdValueError", 

36 "DatasetNotFoundError", 

37 "DatasetTypeExpressionError", 

38 "DatasetTypeNotSupportedError", 

39 "DimensionNameError", 

40 "EmptyQueryResultError", 

41 "InconsistentDataIdError", 

42 "InconsistentUniverseError", 

43 "InvalidQueryError", 

44 "MissingCollectionError", 

45 "MissingDatasetTypeError", 

46 "UnknownComponentError", 

47 "ValidationError", 

48) 

49 

50from ._exceptions_legacy import CollectionError, DataIdError, DatasetTypeError 

51 

52 

53class ButlerUserError(Exception): 

54 """Base class for Butler exceptions that contain a user-facing error 

55 message. 

56 

57 Parameters 

58 ---------- 

59 detail : `str` 

60 Details about the error that occurred. 

61 """ 

62 

63 # When used with Butler server, exceptions inheriting from 

64 # this class will be sent to the client side and re-raised by RemoteButler 

65 # there. Be careful that error messages do not contain security-sensitive 

66 # information. 

67 # 

68 # This should only be used for "expected" errors that occur because of 

69 # errors in user-supplied data passed to Butler methods. It should not be 

70 # used for any issues caused by the Butler configuration file, errors in 

71 # the library code itself or the underlying databases. 

72 # 

73 # When you create a new subclass of this type, add it to the list in 

74 # _USER_ERROR_TYPES below. 

75 

76 error_type: str 

77 """Unique name for this error type, used to identify it when sending 

78 information about the error to the client. 

79 """ 

80 

81 def __init__(self, detail: str): 

82 return super().__init__(detail) 

83 

84 

85class CalibrationLookupError(LookupError, ButlerUserError): 

86 """Exception raised for failures to look up a calibration dataset. 

87 

88 For a find-first query involving a calibration dataset to work, either the 

89 query's result rows need to include a temporal dimension or needs to be 

90 constrained temporally, such that each result row corresponds to a unique 

91 calibration dataset. This exception can be raised if those dimensions or 

92 constraint are missing, or if a temporal dimension timespan overlaps 

93 multiple validity ranges (e.g. the recommended bias changes in the middle 

94 of an exposure). 

95 """ 

96 

97 error_type = "calibration_lookup" 

98 

99 

100class CollectionCycleError(ValueError, ButlerUserError): 

101 """Raised when an operation would cause a chained collection to be a child 

102 of itself. 

103 """ 

104 

105 error_type = "collection_cycle" 

106 

107 

108class CollectionTypeError(CollectionError, ButlerUserError): 

109 """Exception raised when type of a collection is incorrect.""" 

110 

111 error_type = "collection_type" 

112 

113 

114class DataIdValueError(DataIdError, ButlerUserError): 

115 """Exception raised when a value specified in a data ID does not exist.""" 

116 

117 error_type = "data_id_value" 

118 

119 

120class DatasetNotFoundError(LookupError, ButlerUserError): 

121 """The requested dataset could not be found.""" 

122 

123 error_type = "dataset_not_found" 

124 

125 

126class DatasetTypeExpressionError(DatasetTypeError, ButlerUserError): 

127 """Exception raised for an incorrect dataset type expression.""" 

128 

129 error_type = "dataset_type_expression" 

130 

131 

132class DimensionNameError(KeyError, DataIdError, ButlerUserError): 

133 """Exception raised when a dimension specified in a data ID does not exist 

134 or required dimension is not provided. 

135 """ 

136 

137 error_type = "dimension_name" 

138 

139 

140class DimensionValueError(ValueError, ButlerUserError): 

141 """Exception raised for issues with dimension values in a data ID.""" 

142 

143 error_type = "dimension_value" 

144 

145 

146class InconsistentDataIdError(DataIdError, ButlerUserError): 

147 """Exception raised when a data ID contains contradictory key-value pairs, 

148 according to dimension relationships. 

149 """ 

150 

151 error_type = "inconsistent_data_id" 

152 

153 

154class InvalidQueryError(ButlerUserError): 

155 """Exception raised when a query is not valid.""" 

156 

157 error_type = "invalid_query" 

158 

159 

160class MissingCollectionError(CollectionError, ButlerUserError): 

161 """Exception raised when an operation attempts to use a collection that 

162 does not exist. 

163 """ 

164 

165 error_type = "missing_collection" 

166 

167 

168class UnimplementedQueryError(NotImplementedError, ButlerUserError): 

169 """Exception raised when the query system does not support the query 

170 specified by the user. 

171 """ 

172 

173 error_type = "unimplemented_query" 

174 

175 

176class MissingDatasetTypeError(DatasetTypeError, KeyError, ButlerUserError): 

177 """Exception raised when a dataset type does not exist.""" 

178 

179 error_type = "missing_dataset_type" 

180 

181 

182class UnknownComponentError(KeyError, ButlerUserError): 

183 """Exception raised when the requested component of a DatasetType is not 

184 known. 

185 """ 

186 

187 error_type = "unknown_component" 

188 

189 

190class DatasetTypeNotSupportedError(RuntimeError): 

191 """A `DatasetType` is not handled by this routine. 

192 

193 This can happen in a `Datastore` when a particular `DatasetType` 

194 has no formatters associated with it. 

195 """ 

196 

197 pass 

198 

199 

200class ValidationError(RuntimeError): 

201 """Some sort of validation error has occurred.""" 

202 

203 pass 

204 

205 

206class EmptyQueryResultError(Exception): 

207 """Exception raised when query methods return an empty result and 

208 ``explain`` flag is set. 

209 

210 Parameters 

211 ---------- 

212 reasons : `list` [`str`] 

213 List of possible reasons for an empty query result. 

214 """ 

215 

216 def __init__(self, reasons: list[str]): 

217 self.reasons = reasons 

218 

219 def __str__(self) -> str: 

220 # There may be multiple reasons, format them into multiple lines. 

221 return "Possible reasons for empty result:\n" + "\n".join(self.reasons) 

222 

223 

224class UnknownButlerUserError(ButlerUserError): 

225 """Raised when the server sends an ``error_type`` for which we don't know 

226 the corresponding exception type. (This may happen if an old version of 

227 the Butler client library connects to a new server). 

228 """ 

229 

230 error_type = "unknown" 

231 

232 

233class InconsistentUniverseError(Exception): 

234 """Raised when an imported dataset has a dimension universe that is 

235 incompatible with the target butler universe. 

236 """ 

237 

238 

239_USER_ERROR_TYPES: tuple[type[ButlerUserError], ...] = ( 

240 CalibrationLookupError, 

241 CollectionCycleError, 

242 CollectionTypeError, 

243 DimensionNameError, 

244 DimensionValueError, 

245 DataIdValueError, 

246 DatasetNotFoundError, 

247 DatasetTypeExpressionError, 

248 InconsistentDataIdError, 

249 InvalidQueryError, 

250 MissingCollectionError, 

251 MissingDatasetTypeError, 

252 UnimplementedQueryError, 

253 UnknownButlerUserError, 

254 UnknownComponentError, 

255) 

256_USER_ERROR_MAPPING = {e.error_type: e for e in _USER_ERROR_TYPES} 

257assert len(_USER_ERROR_MAPPING) == len(_USER_ERROR_TYPES), ( 

258 "Subclasses of ButlerUserError must have unique 'error_type' property" 

259) 

260 

261 

262def create_butler_user_error(error_type: str, message: str) -> ButlerUserError: 

263 """Instantiate one of the subclasses of `ButlerUserError` based on its 

264 ``error_type`` string. 

265 

266 Parameters 

267 ---------- 

268 error_type : `str` 

269 The value from the ``error_type`` class attribute on the exception 

270 subclass you wish to instantiate. 

271 message : `str` 

272 Detailed error message passed to the exception constructor. 

273 """ 

274 cls = _USER_ERROR_MAPPING.get(error_type) 

275 if cls is None: 

276 raise UnknownButlerUserError(f"Unknown exception type '{error_type}': {message}") 

277 return cls(message)