Coverage for python/lsst/daf/butler/_exceptions.py: 89%
55 statements
« prev ^ index » next coverage.py v7.16.0, created at 2026-09-30 03:56 -0700
« prev ^ index » next coverage.py v7.16.0, created at 2026-09-30 03:56 -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/>.
28"""Specialized Butler exceptions."""
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)
50from ._exceptions_legacy import CollectionError, DataIdError, DatasetTypeError
53class ButlerUserError(Exception):
54 """Base class for Butler exceptions that contain a user-facing error
55 message.
57 Parameters
58 ----------
59 detail : `str`
60 Details about the error that occurred.
61 """
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.
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 """
81 def __init__(self, detail: str):
82 return super().__init__(detail)
85class CalibrationLookupError(LookupError, ButlerUserError):
86 """Exception raised for failures to look up a calibration dataset.
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 """
97 error_type = "calibration_lookup"
100class CollectionCycleError(ValueError, ButlerUserError):
101 """Raised when an operation would cause a chained collection to be a child
102 of itself.
103 """
105 error_type = "collection_cycle"
108class CollectionTypeError(CollectionError, ButlerUserError):
109 """Exception raised when type of a collection is incorrect."""
111 error_type = "collection_type"
114class DataIdValueError(DataIdError, ButlerUserError):
115 """Exception raised when a value specified in a data ID does not exist."""
117 error_type = "data_id_value"
120class DatasetNotFoundError(LookupError, ButlerUserError):
121 """The requested dataset could not be found."""
123 error_type = "dataset_not_found"
126class DatasetTypeExpressionError(DatasetTypeError, ButlerUserError):
127 """Exception raised for an incorrect dataset type expression."""
129 error_type = "dataset_type_expression"
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 """
137 error_type = "dimension_name"
140class DimensionValueError(ValueError, ButlerUserError):
141 """Exception raised for issues with dimension values in a data ID."""
143 error_type = "dimension_value"
146class InconsistentDataIdError(DataIdError, ButlerUserError):
147 """Exception raised when a data ID contains contradictory key-value pairs,
148 according to dimension relationships.
149 """
151 error_type = "inconsistent_data_id"
154class InvalidQueryError(ButlerUserError):
155 """Exception raised when a query is not valid."""
157 error_type = "invalid_query"
160class MissingCollectionError(CollectionError, ButlerUserError):
161 """Exception raised when an operation attempts to use a collection that
162 does not exist.
163 """
165 error_type = "missing_collection"
168class UnimplementedQueryError(NotImplementedError, ButlerUserError):
169 """Exception raised when the query system does not support the query
170 specified by the user.
171 """
173 error_type = "unimplemented_query"
176class MissingDatasetTypeError(DatasetTypeError, KeyError, ButlerUserError):
177 """Exception raised when a dataset type does not exist."""
179 error_type = "missing_dataset_type"
182class UnknownComponentError(KeyError, ButlerUserError):
183 """Exception raised when the requested component of a DatasetType is not
184 known.
185 """
187 error_type = "unknown_component"
190class DatasetTypeNotSupportedError(RuntimeError):
191 """A `DatasetType` is not handled by this routine.
193 This can happen in a `Datastore` when a particular `DatasetType`
194 has no formatters associated with it.
195 """
197 pass
200class ValidationError(RuntimeError):
201 """Some sort of validation error has occurred."""
203 pass
206class EmptyQueryResultError(Exception):
207 """Exception raised when query methods return an empty result and
208 ``explain`` flag is set.
210 Parameters
211 ----------
212 reasons : `list` [`str`]
213 List of possible reasons for an empty query result.
214 """
216 def __init__(self, reasons: list[str]):
217 self.reasons = reasons
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)
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 """
230 error_type = "unknown"
233class InconsistentUniverseError(Exception):
234 """Raised when an imported dataset has a dimension universe that is
235 incompatible with the target butler universe.
236 """
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)
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.
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)