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

53 statements  

« prev     ^ index     » next       coverage.py v7.14.0, created at 2026-05-20 08:09 +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 "DatasetTypeNotSupportedError", 

38 "DimensionNameError", 

39 "EmptyQueryResultError", 

40 "InconsistentDataIdError", 

41 "InconsistentUniverseError", 

42 "InvalidQueryError", 

43 "MissingCollectionError", 

44 "MissingDatasetTypeError", 

45 "UnknownComponentError", 

46 "ValidationError", 

47) 

48 

49from ._exceptions_legacy import CollectionError, DataIdError, DatasetTypeError 

50 

51 

52class ButlerUserError(Exception): 

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

54 message. 

55 

56 Parameters 

57 ---------- 

58 detail : `str` 

59 Details about the error that occurred. 

60 """ 

61 

62 # When used with Butler server, exceptions inheriting from 

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

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

65 # information. 

66 # 

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

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

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

70 # the library code itself or the underlying databases. 

71 # 

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

73 # _USER_ERROR_TYPES below. 

74 

75 error_type: str 

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

77 information about the error to the client. 

78 """ 

79 

80 def __init__(self, detail: str): 

81 return super().__init__(detail) 

82 

83 

84class CalibrationLookupError(LookupError, ButlerUserError): 

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

86 

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

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

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

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

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

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

93 of an exposure). 

94 """ 

95 

96 error_type = "calibration_lookup" 

97 

98 

99class CollectionCycleError(ValueError, ButlerUserError): 

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

101 of itself. 

102 """ 

103 

104 error_type = "collection_cycle" 

105 

106 

107class CollectionTypeError(CollectionError, ButlerUserError): 

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

109 

110 error_type = "collection_type" 

111 

112 

113class DataIdValueError(DataIdError, ButlerUserError): 

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

115 

116 error_type = "data_id_value" 

117 

118 

119class DatasetNotFoundError(LookupError, ButlerUserError): 

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

121 

122 error_type = "dataset_not_found" 

123 

124 

125class DimensionNameError(KeyError, DataIdError, ButlerUserError): 

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

127 or required dimension is not provided. 

128 """ 

129 

130 error_type = "dimension_name" 

131 

132 

133class DimensionValueError(ValueError, ButlerUserError): 

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

135 

136 error_type = "dimension_value" 

137 

138 

139class InconsistentDataIdError(DataIdError, ButlerUserError): 

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

141 according to dimension relationships. 

142 """ 

143 

144 error_type = "inconsistent_data_id" 

145 

146 

147class InvalidQueryError(ButlerUserError): 

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

149 

150 error_type = "invalid_query" 

151 

152 

153class MissingCollectionError(CollectionError, ButlerUserError): 

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

155 does not exist. 

156 """ 

157 

158 error_type = "missing_collection" 

159 

160 

161class UnimplementedQueryError(NotImplementedError, ButlerUserError): 

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

163 specified by the user. 

164 """ 

165 

166 error_type = "unimplemented_query" 

167 

168 

169class MissingDatasetTypeError(DatasetTypeError, KeyError, ButlerUserError): 

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

171 

172 error_type = "missing_dataset_type" 

173 

174 

175class UnknownComponentError(KeyError, ButlerUserError): 

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

177 known. 

178 """ 

179 

180 error_type = "unknown_component" 

181 

182 

183class DatasetTypeNotSupportedError(RuntimeError): 

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

185 

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

187 has no formatters associated with it. 

188 """ 

189 

190 pass 

191 

192 

193class ValidationError(RuntimeError): 

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

195 

196 pass 

197 

198 

199class EmptyQueryResultError(Exception): 

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

201 ``explain`` flag is set. 

202 

203 Parameters 

204 ---------- 

205 reasons : `list` [`str`] 

206 List of possible reasons for an empty query result. 

207 """ 

208 

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

210 self.reasons = reasons 

211 

212 def __str__(self) -> str: 

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

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

215 

216 

217class UnknownButlerUserError(ButlerUserError): 

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

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

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

221 """ 

222 

223 error_type = "unknown" 

224 

225 

226class InconsistentUniverseError(Exception): 

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

228 incompatible with the target butler universe. 

229 """ 

230 

231 

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

233 CalibrationLookupError, 

234 CollectionCycleError, 

235 CollectionTypeError, 

236 DimensionNameError, 

237 DimensionValueError, 

238 DataIdValueError, 

239 DatasetNotFoundError, 

240 InconsistentDataIdError, 

241 InvalidQueryError, 

242 MissingCollectionError, 

243 MissingDatasetTypeError, 

244 UnimplementedQueryError, 

245 UnknownButlerUserError, 

246 UnknownComponentError, 

247) 

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

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

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

251) 

252 

253 

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

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

256 ``error_type`` string. 

257 

258 Parameters 

259 ---------- 

260 error_type : `str` 

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

262 subclass you wish to instantiate. 

263 message : `str` 

264 Detailed error message passed to the exception constructor. 

265 """ 

266 cls = _USER_ERROR_MAPPING.get(error_type) 

267 if cls is None: 

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

269 return cls(message)