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

49 statements  

« prev     ^ index     » next       coverage.py v7.5.1, created at 2024-05-08 02:51 -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 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__all__ = ( 

30 "ButlerUserError", 

31 "CalibrationLookupError", 

32 "CollectionCycleError", 

33 "CollectionTypeError", 

34 "DataIdValueError", 

35 "DatasetNotFoundError", 

36 "DimensionNameError", 

37 "DatasetTypeNotSupportedError", 

38 "EmptyQueryResultError", 

39 "InconsistentDataIdError", 

40 "InvalidQueryError", 

41 "MissingDatasetTypeError", 

42 "MissingCollectionError", 

43 "ValidationError", 

44) 

45 

46from ._exceptions_legacy import CollectionError, DataIdError, DatasetTypeError 

47 

48 

49class ButlerUserError(Exception): 

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

51 message. 

52 

53 Parameters 

54 ---------- 

55 detail : `str` 

56 Details about the error that occurred. 

57 """ 

58 

59 # When used with Butler server, exceptions inheriting from 

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

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

62 # information. 

63 # 

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

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

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

67 # the library code itself or the underlying databases. 

68 # 

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

70 # _USER_ERROR_TYPES below. 

71 

72 error_type: str 

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

74 information about the error to the client. 

75 """ 

76 

77 def __init__(self, detail: str): 

78 return super().__init__(detail) 

79 

80 

81class CalibrationLookupError(LookupError, ButlerUserError): 

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

83 

84 error_type = "calibration_lookup" 

85 

86 

87class CollectionCycleError(ValueError, ButlerUserError): 

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

89 of itself. 

90 """ 

91 

92 error_type = "collection_cycle" 

93 

94 

95class CollectionTypeError(CollectionError, ButlerUserError): 

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

97 

98 error_type = "collection_type" 

99 

100 

101class DataIdValueError(DataIdError, ButlerUserError): 

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

103 

104 error_type = "data_id_value" 

105 

106 

107class DatasetNotFoundError(LookupError, ButlerUserError): 

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

109 

110 error_type = "dataset_not_found" 

111 

112 

113class DimensionNameError(KeyError, DataIdError, ButlerUserError): 

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

115 or required dimension is not provided. 

116 """ 

117 

118 error_type = "dimension_name" 

119 

120 

121class DimensionValueError(ValueError, ButlerUserError): 

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

123 

124 error_type = "dimension_value" 

125 

126 

127class InconsistentDataIdError(DataIdError, ButlerUserError): 

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

129 according to dimension relationships. 

130 """ 

131 

132 error_type = "inconsistent_data_id" 

133 

134 

135class InvalidQueryError(ButlerUserError): 

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

137 

138 error_type = "invalid_query" 

139 

140 

141class MissingCollectionError(CollectionError, ButlerUserError): 

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

143 does not exist. 

144 """ 

145 

146 error_type = "missing_collection" 

147 

148 

149class MissingDatasetTypeError(DatasetTypeError, KeyError, ButlerUserError): 

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

151 

152 error_type = "missing_dataset_type" 

153 

154 

155class DatasetTypeNotSupportedError(RuntimeError): 

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

157 

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

159 has no formatters associated with it. 

160 """ 

161 

162 pass 

163 

164 

165class ValidationError(RuntimeError): 

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

167 

168 pass 

169 

170 

171class EmptyQueryResultError(Exception): 

172 """Exception raised when query methods return an empty result and `explain` 

173 flag is set. 

174 

175 Parameters 

176 ---------- 

177 reasons : `list` [`str`] 

178 List of possible reasons for an empty query result. 

179 """ 

180 

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

182 self.reasons = reasons 

183 

184 def __str__(self) -> str: 

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

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

187 

188 

189class UnknownButlerUserError(ButlerUserError): 

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

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

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

193 """ 

194 

195 error_type = "unknown" 

196 

197 

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

199 CalibrationLookupError, 

200 CollectionCycleError, 

201 CollectionTypeError, 

202 DimensionNameError, 

203 DimensionValueError, 

204 DataIdValueError, 

205 DatasetNotFoundError, 

206 InconsistentDataIdError, 

207 InvalidQueryError, 

208 MissingCollectionError, 

209 MissingDatasetTypeError, 

210 UnknownButlerUserError, 

211) 

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

213assert len(_USER_ERROR_MAPPING) == len( 

214 _USER_ERROR_TYPES 

215), "Subclasses of ButlerUserError must have unique 'error_type' property" 

216 

217 

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

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

220 ``error_type`` string. 

221 

222 Parameters 

223 ---------- 

224 error_type : `str` 

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

226 subclass you wish to instantiate. 

227 message : `str` 

228 Detailed error message passed to the exception constructor. 

229 """ 

230 cls = _USER_ERROR_MAPPING.get(error_type) 

231 if cls is None: 

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

233 return cls(message)