DataExcept 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- dataexcept/__init__.py +81 -0
- dataexcept/__main__.py +74 -0
- dataexcept/database_exceptions.py +71 -0
- dataexcept/dataengineering_exceptions.py +125 -0
- dataexcept/datascience_exceptions/__init__.py +85 -0
- dataexcept/datascience_exceptions/base.py +17 -0
- dataexcept/datascience_exceptions/ingestion.py +312 -0
- dataexcept/datascience_exceptions/operations.py +130 -0
- dataexcept/datascience_exceptions/training.py +508 -0
- dataexcept/exceptions/__init__.py +36 -0
- dataexcept/exceptions/authentication.py +21 -0
- dataexcept/exceptions/base.py +4 -0
- dataexcept/exceptions/configuration.py +10 -0
- dataexcept/exceptions/external.py +42 -0
- dataexcept/exceptions/lifecycle.py +13 -0
- dataexcept/exceptions/notification.py +49 -0
- dataexcept/exceptions/parsing.py +31 -0
- dataexcept/exceptions/scheduling.py +21 -0
- dataexcept/exceptions/validation.py +12 -0
- dataexcept/io_exceptions.py +66 -0
- dataexcept/job_exceptions.py +59 -0
- dataexcept/logging_helpers.py +76 -0
- dataexcept/network_exceptions.py +102 -0
- dataexcept/pandas_exceptions.py +143 -0
- dataexcept/pipeline_exceptions.py +205 -0
- dataexcept/py.typed +1 -0
- dataexcept/security_exceptions.py +68 -0
- dataexcept-0.1.0.dist-info/METADATA +397 -0
- dataexcept-0.1.0.dist-info/RECORD +32 -0
- dataexcept-0.1.0.dist-info/WHEEL +4 -0
- dataexcept-0.1.0.dist-info/entry_points.txt +3 -0
- dataexcept-0.1.0.dist-info/licenses/LICENSE +22 -0
|
@@ -0,0 +1,508 @@
|
|
|
1
|
+
"""Model training, evaluation, and inference errors."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any, Optional
|
|
6
|
+
|
|
7
|
+
from .base import DataScienceError
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class ModelTrainingError(DataScienceError):
|
|
11
|
+
"""
|
|
12
|
+
Raised when model training fails.
|
|
13
|
+
|
|
14
|
+
Attributes:
|
|
15
|
+
model_type: model class or name.
|
|
16
|
+
epoch: optional epoch index.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
def __init__(
|
|
20
|
+
self,
|
|
21
|
+
model_type: str,
|
|
22
|
+
epoch: Optional[int] = None,
|
|
23
|
+
message: Optional[str] = None,
|
|
24
|
+
) -> None:
|
|
25
|
+
if not isinstance(model_type, str):
|
|
26
|
+
raise TypeError(f"model_type must be str, got {type(model_type).__name__}")
|
|
27
|
+
if epoch is not None and not isinstance(epoch, int):
|
|
28
|
+
raise TypeError(f"epoch must be int or None, got {type(epoch).__name__}")
|
|
29
|
+
if message is not None and not isinstance(message, str):
|
|
30
|
+
raise TypeError(
|
|
31
|
+
f"message must be str or None, got {type(message).__name__}"
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
if message is None:
|
|
35
|
+
msg = f"Training failed for model '{model_type}'"
|
|
36
|
+
if epoch is not None:
|
|
37
|
+
msg += f" at epoch {epoch}" # include epoch
|
|
38
|
+
else:
|
|
39
|
+
msg = message
|
|
40
|
+
|
|
41
|
+
self.model_type = model_type
|
|
42
|
+
self.epoch = epoch
|
|
43
|
+
super().__init__(msg)
|
|
44
|
+
|
|
45
|
+
def __str__(self) -> str:
|
|
46
|
+
base = f"{self.model_type}"
|
|
47
|
+
if self.epoch is not None:
|
|
48
|
+
base += f"@{self.epoch}"
|
|
49
|
+
return f"[ModelTrainingError:{base}] {self.message}"
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class ConvergenceError(ModelTrainingError):
|
|
53
|
+
"""
|
|
54
|
+
Raised when optimization fails to converge.
|
|
55
|
+
|
|
56
|
+
Attributes:
|
|
57
|
+
iterations: number of iterations run.
|
|
58
|
+
"""
|
|
59
|
+
|
|
60
|
+
def __init__(
|
|
61
|
+
self, model_type: str, iterations: int, message: Optional[str] = None
|
|
62
|
+
) -> None:
|
|
63
|
+
if not isinstance(iterations, int):
|
|
64
|
+
raise TypeError(f"iterations must be int, got {type(iterations).__name__}")
|
|
65
|
+
|
|
66
|
+
if message is None:
|
|
67
|
+
message = (
|
|
68
|
+
f"Model '{model_type}' failed to converge after "
|
|
69
|
+
f"{iterations} iterations"
|
|
70
|
+
)
|
|
71
|
+
super().__init__(model_type=model_type, epoch=None, message=message)
|
|
72
|
+
self.iterations = iterations
|
|
73
|
+
|
|
74
|
+
def __str__(self) -> str:
|
|
75
|
+
return f"[ConvergenceError] {self.message}"
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
class TrainingTimeoutError(ModelTrainingError):
|
|
79
|
+
"""Raised when model training exceeds a time limit."""
|
|
80
|
+
|
|
81
|
+
def __init__(self, model_type: str, timeout: float) -> None:
|
|
82
|
+
if not isinstance(timeout, (int, float)):
|
|
83
|
+
raise TypeError(f"timeout must be a number, got {type(timeout).__name__}")
|
|
84
|
+
message = f"Training '{model_type}' exceeded timeout of {timeout} seconds"
|
|
85
|
+
self.timeout = float(timeout)
|
|
86
|
+
super().__init__(model_type=model_type, epoch=None, message=message)
|
|
87
|
+
|
|
88
|
+
def __str__(self) -> str:
|
|
89
|
+
return f"[TrainingTimeoutError] {self.message}"
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
class HyperparameterError(DataScienceError):
|
|
93
|
+
"""
|
|
94
|
+
Raised for invalid hyperparameter settings.
|
|
95
|
+
|
|
96
|
+
Attributes:
|
|
97
|
+
param: name of hyperparameter.
|
|
98
|
+
value: invalid value.
|
|
99
|
+
"""
|
|
100
|
+
|
|
101
|
+
def __init__(self, param: str, value: Any, message: Optional[str] = None) -> None:
|
|
102
|
+
if not isinstance(param, str):
|
|
103
|
+
raise TypeError(f"param must be str, got {type(param).__name__}")
|
|
104
|
+
|
|
105
|
+
if message is None:
|
|
106
|
+
message = f"Invalid hyperparameter '{param}': {value!r}"
|
|
107
|
+
|
|
108
|
+
self.param = param
|
|
109
|
+
self.value = value
|
|
110
|
+
super().__init__(message)
|
|
111
|
+
|
|
112
|
+
def __str__(self) -> str:
|
|
113
|
+
return f"[HyperparameterError:{self.param}] {self.message}"
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
class ModelEvaluationError(DataScienceError):
|
|
117
|
+
"""
|
|
118
|
+
Raised during evaluation metrics computation.
|
|
119
|
+
|
|
120
|
+
Attributes:
|
|
121
|
+
metric: name of the metric.
|
|
122
|
+
value: computed value.
|
|
123
|
+
"""
|
|
124
|
+
|
|
125
|
+
def __init__(
|
|
126
|
+
self, metric: str, value: float, message: Optional[str] = None
|
|
127
|
+
) -> None:
|
|
128
|
+
if not isinstance(metric, str):
|
|
129
|
+
raise TypeError(f"metric must be str, got {type(metric).__name__}")
|
|
130
|
+
if not isinstance(value, (int, float)):
|
|
131
|
+
raise TypeError(f"value must be number, got {type(value).__name__}")
|
|
132
|
+
|
|
133
|
+
if message is None:
|
|
134
|
+
message = f"Failed to compute metric '{metric}', got {value}"
|
|
135
|
+
|
|
136
|
+
self.metric = metric
|
|
137
|
+
self.value = float(value)
|
|
138
|
+
super().__init__(message)
|
|
139
|
+
|
|
140
|
+
def __str__(self) -> str:
|
|
141
|
+
return f"[ModelEvaluationError:{self.metric}] {self.message}"
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
class PredictionError(DataScienceError):
|
|
145
|
+
"""
|
|
146
|
+
Raised when making predictions fails.
|
|
147
|
+
|
|
148
|
+
Attributes:
|
|
149
|
+
model_type: model used.
|
|
150
|
+
inputs: input data snapshot.
|
|
151
|
+
"""
|
|
152
|
+
|
|
153
|
+
def __init__(
|
|
154
|
+
self, model_type: str, inputs: Any, message: Optional[str] = None
|
|
155
|
+
) -> None:
|
|
156
|
+
if not isinstance(model_type, str):
|
|
157
|
+
raise TypeError(f"model_type must be str, got {type(model_type).__name__}")
|
|
158
|
+
|
|
159
|
+
if message is None:
|
|
160
|
+
message = (
|
|
161
|
+
f"Prediction failed for model '{model_type}' " f"with inputs {inputs!r}"
|
|
162
|
+
)
|
|
163
|
+
|
|
164
|
+
self.model_type = model_type
|
|
165
|
+
self.inputs = inputs
|
|
166
|
+
super().__init__(message)
|
|
167
|
+
|
|
168
|
+
def __str__(self) -> str:
|
|
169
|
+
return f"[PredictionError:{self.model_type}] {self.message}"
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
class FeatureSelectionError(DataScienceError):
|
|
173
|
+
"""Failure in feature selection procedure."""
|
|
174
|
+
|
|
175
|
+
def __init__(self, technique: str, details: Optional[str] = None) -> None:
|
|
176
|
+
if not isinstance(technique, str):
|
|
177
|
+
raise TypeError(f"technique must be str, got {type(technique).__name__}")
|
|
178
|
+
msg = f"Feature selection failed using {technique}" + (
|
|
179
|
+
f": {details}" if details else ""
|
|
180
|
+
)
|
|
181
|
+
self.technique = technique
|
|
182
|
+
super().__init__(msg)
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
class DimensionalityReductionError(DataScienceError):
|
|
186
|
+
"""Error applying dimensionality reduction method."""
|
|
187
|
+
|
|
188
|
+
def __init__(self, method: str, components: Optional[int] = None) -> None:
|
|
189
|
+
if not isinstance(method, str):
|
|
190
|
+
raise TypeError(f"method must be str, got {type(method).__name__}")
|
|
191
|
+
if components is not None and not isinstance(components, int):
|
|
192
|
+
raise TypeError(
|
|
193
|
+
("components must be int or None, " f"got {type(components).__name__}")
|
|
194
|
+
)
|
|
195
|
+
msg = f"Dimensionality reduction '{method}' failed" + (
|
|
196
|
+
f" for {components} components" if components else ""
|
|
197
|
+
)
|
|
198
|
+
self.method = method
|
|
199
|
+
self.components = components
|
|
200
|
+
super().__init__(msg)
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
class CrossValidationError(DataScienceError):
|
|
204
|
+
"""Failure during cross-validation procedure."""
|
|
205
|
+
|
|
206
|
+
def __init__(self, folds: int, cause: Optional[str] = None) -> None:
|
|
207
|
+
if not isinstance(folds, int):
|
|
208
|
+
raise TypeError(f"folds must be int, got {type(folds).__name__}")
|
|
209
|
+
msg = f"Cross-validation failed on {folds} folds" + (
|
|
210
|
+
f": {cause}" if cause else ""
|
|
211
|
+
)
|
|
212
|
+
self.folds = folds
|
|
213
|
+
super().__init__(msg)
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
class HyperparameterTuningError(DataScienceError):
|
|
217
|
+
"""Error during hyperparameter search or tuning."""
|
|
218
|
+
|
|
219
|
+
def __init__(self, method: str, details: Optional[str] = None) -> None:
|
|
220
|
+
if not isinstance(method, str):
|
|
221
|
+
raise TypeError(f"method must be str, got {type(method).__name__}")
|
|
222
|
+
msg = f"Hyperparameter tuning ({method}) failed" + (
|
|
223
|
+
f": {details}" if details else ""
|
|
224
|
+
)
|
|
225
|
+
self.method = method
|
|
226
|
+
super().__init__(msg)
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
class ExperimentTrackingError(DataScienceError):
|
|
230
|
+
"""Issues logging or retrieving experiment metadata."""
|
|
231
|
+
|
|
232
|
+
def __init__(self, run_id: str, cause: Optional[str] = None) -> None:
|
|
233
|
+
if not isinstance(run_id, str):
|
|
234
|
+
raise TypeError(f"run_id must be str, got {type(run_id).__name__}")
|
|
235
|
+
msg = f"Experiment tracking failed for run '{run_id}'" + (
|
|
236
|
+
f": {cause}" if cause else ""
|
|
237
|
+
)
|
|
238
|
+
self.run_id = run_id
|
|
239
|
+
super().__init__(msg)
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
class GPUOutOfMemoryError(DataScienceError):
|
|
243
|
+
"""Model or tensor exceeds GPU memory capacity."""
|
|
244
|
+
|
|
245
|
+
def __init__(self, device: str, required: str, available: str) -> None:
|
|
246
|
+
if not all(isinstance(v, str) for v in (device, required, available)):
|
|
247
|
+
raise TypeError("device, required, available must be str")
|
|
248
|
+
msg = f"GPU OOM on {device}: required={required}, available={available}"
|
|
249
|
+
self.device = device
|
|
250
|
+
self.required = required
|
|
251
|
+
self.available = available
|
|
252
|
+
super().__init__(msg)
|
|
253
|
+
|
|
254
|
+
|
|
255
|
+
class ModelInferenceError(DataScienceError):
|
|
256
|
+
"""Raised when model inference fails.
|
|
257
|
+
|
|
258
|
+
Args:
|
|
259
|
+
model_type: Identifier of the model used for inference.
|
|
260
|
+
original: Underlying exception raised by the model.
|
|
261
|
+
"""
|
|
262
|
+
|
|
263
|
+
def __init__(self, model_type: str, original: Exception) -> None:
|
|
264
|
+
if not isinstance(model_type, str):
|
|
265
|
+
raise TypeError(f"model_type must be str, got {type(model_type).__name__}")
|
|
266
|
+
if not isinstance(original, Exception):
|
|
267
|
+
raise TypeError(
|
|
268
|
+
f"original must be Exception, got {type(original).__name__}"
|
|
269
|
+
)
|
|
270
|
+
msg = f"Inference failed for model '{model_type}': {original}"
|
|
271
|
+
self.model_type = model_type
|
|
272
|
+
self.original = original
|
|
273
|
+
super().__init__(msg)
|
|
274
|
+
|
|
275
|
+
def __str__(self) -> str:
|
|
276
|
+
return f"[ModelInferenceError:{self.model_type}] {self.message}"
|
|
277
|
+
|
|
278
|
+
|
|
279
|
+
class ModelCompatibilityError(DataScienceError):
|
|
280
|
+
"""Raised when a model is incompatible with the runtime environment.
|
|
281
|
+
|
|
282
|
+
Args:
|
|
283
|
+
expected_version: Required model version.
|
|
284
|
+
found_version: Detected model version.
|
|
285
|
+
message: Optional custom message.
|
|
286
|
+
"""
|
|
287
|
+
|
|
288
|
+
def __init__(
|
|
289
|
+
self,
|
|
290
|
+
expected_version: str,
|
|
291
|
+
found_version: str,
|
|
292
|
+
message: Optional[str] = None,
|
|
293
|
+
) -> None:
|
|
294
|
+
if not isinstance(expected_version, str):
|
|
295
|
+
raise TypeError(
|
|
296
|
+
"expected_version must be str, got "
|
|
297
|
+
f"{type(expected_version).__name__}"
|
|
298
|
+
)
|
|
299
|
+
if not isinstance(found_version, str):
|
|
300
|
+
raise TypeError(
|
|
301
|
+
"found_version must be str, got " f"{type(found_version).__name__}"
|
|
302
|
+
)
|
|
303
|
+
if message is not None and not isinstance(message, str):
|
|
304
|
+
raise TypeError(
|
|
305
|
+
f"message must be str or None, got {type(message).__name__}"
|
|
306
|
+
)
|
|
307
|
+
|
|
308
|
+
if message is None:
|
|
309
|
+
msg = (
|
|
310
|
+
f"Model requires version {expected_version}, "
|
|
311
|
+
f"but found {found_version}"
|
|
312
|
+
)
|
|
313
|
+
else:
|
|
314
|
+
msg = message
|
|
315
|
+
|
|
316
|
+
self.expected_version = expected_version
|
|
317
|
+
self.found_version = found_version
|
|
318
|
+
super().__init__(msg)
|
|
319
|
+
|
|
320
|
+
def __str__(self) -> str:
|
|
321
|
+
return f"[ModelCompatibilityError] {self.message}"
|
|
322
|
+
|
|
323
|
+
|
|
324
|
+
class OverfittingError(DataScienceError):
|
|
325
|
+
"""Raised when a model is overfitting the training data.
|
|
326
|
+
|
|
327
|
+
Args:
|
|
328
|
+
train_metric: Metric value on the training set.
|
|
329
|
+
val_metric: Metric value on the validation set.
|
|
330
|
+
"""
|
|
331
|
+
|
|
332
|
+
def __init__(self, train_metric: float, val_metric: float) -> None:
|
|
333
|
+
if not isinstance(train_metric, (int, float)):
|
|
334
|
+
raise TypeError(
|
|
335
|
+
("train_metric must be numeric, got " f"{type(train_metric).__name__}")
|
|
336
|
+
)
|
|
337
|
+
if not isinstance(val_metric, (int, float)):
|
|
338
|
+
raise TypeError(
|
|
339
|
+
f"val_metric must be numeric, got {type(val_metric).__name__}"
|
|
340
|
+
)
|
|
341
|
+
|
|
342
|
+
self.train_metric = float(train_metric)
|
|
343
|
+
self.val_metric = float(val_metric)
|
|
344
|
+
msg = (
|
|
345
|
+
f"Overfitting detected: train={self.train_metric}, "
|
|
346
|
+
f"val={self.val_metric}"
|
|
347
|
+
)
|
|
348
|
+
super().__init__(msg)
|
|
349
|
+
|
|
350
|
+
def __str__(self) -> str:
|
|
351
|
+
return f"[OverfittingError] {self.message}"
|
|
352
|
+
|
|
353
|
+
|
|
354
|
+
class UnderfittingError(DataScienceError):
|
|
355
|
+
"""Raised when a model fails to capture patterns in the data.
|
|
356
|
+
|
|
357
|
+
Args:
|
|
358
|
+
train_metric: Metric value on the training set.
|
|
359
|
+
threshold: Minimum acceptable metric value.
|
|
360
|
+
"""
|
|
361
|
+
|
|
362
|
+
def __init__(self, train_metric: float, threshold: float) -> None:
|
|
363
|
+
if not isinstance(train_metric, (int, float)):
|
|
364
|
+
raise TypeError(
|
|
365
|
+
("train_metric must be numeric, got " f"{type(train_metric).__name__}")
|
|
366
|
+
)
|
|
367
|
+
if not isinstance(threshold, (int, float)):
|
|
368
|
+
raise TypeError(
|
|
369
|
+
f"threshold must be numeric, got {type(threshold).__name__}"
|
|
370
|
+
)
|
|
371
|
+
|
|
372
|
+
self.train_metric = float(train_metric)
|
|
373
|
+
self.threshold = float(threshold)
|
|
374
|
+
msg = (
|
|
375
|
+
f"Underfitting detected: training metric {self.train_metric} "
|
|
376
|
+
f"< threshold {self.threshold}"
|
|
377
|
+
)
|
|
378
|
+
super().__init__(msg)
|
|
379
|
+
|
|
380
|
+
def __str__(self) -> str:
|
|
381
|
+
return f"[UnderfittingError] {self.message}"
|
|
382
|
+
|
|
383
|
+
|
|
384
|
+
class EarlyStoppingError(DataScienceError):
|
|
385
|
+
"""Raised when training stops early based on a stopping criterion.
|
|
386
|
+
|
|
387
|
+
Args:
|
|
388
|
+
epoch: Epoch index where training stopped.
|
|
389
|
+
reason: Optional reason for stopping.
|
|
390
|
+
"""
|
|
391
|
+
|
|
392
|
+
def __init__(self, epoch: int, reason: Optional[str] = None) -> None:
|
|
393
|
+
if not isinstance(epoch, int):
|
|
394
|
+
raise TypeError(f"epoch must be int, got {type(epoch).__name__}")
|
|
395
|
+
if reason is not None and not isinstance(reason, str):
|
|
396
|
+
raise TypeError(f"reason must be str or None, got {type(reason).__name__}")
|
|
397
|
+
|
|
398
|
+
msg = f"Training stopped early at epoch {epoch}"
|
|
399
|
+
if reason:
|
|
400
|
+
msg += f": {reason}"
|
|
401
|
+
|
|
402
|
+
self.epoch = epoch
|
|
403
|
+
self.reason = reason
|
|
404
|
+
super().__init__(msg)
|
|
405
|
+
|
|
406
|
+
def __str__(self) -> str:
|
|
407
|
+
return f"[EarlyStoppingError:{self.epoch}] {self.message}"
|
|
408
|
+
|
|
409
|
+
|
|
410
|
+
class BiasDetectionError(DataScienceError):
|
|
411
|
+
"""Raised when algorithmic bias exceeds an acceptable threshold.
|
|
412
|
+
|
|
413
|
+
Args:
|
|
414
|
+
feature: Feature or group where bias was detected.
|
|
415
|
+
bias_score: Calculated bias metric.
|
|
416
|
+
threshold: Maximum acceptable bias metric.
|
|
417
|
+
message: Optional custom message.
|
|
418
|
+
"""
|
|
419
|
+
|
|
420
|
+
def __init__(
|
|
421
|
+
self,
|
|
422
|
+
feature: str,
|
|
423
|
+
bias_score: float,
|
|
424
|
+
threshold: float,
|
|
425
|
+
message: Optional[str] = None,
|
|
426
|
+
) -> None:
|
|
427
|
+
if not isinstance(feature, str):
|
|
428
|
+
raise TypeError(f"feature must be str, got {type(feature).__name__}")
|
|
429
|
+
if not isinstance(bias_score, (int, float)):
|
|
430
|
+
raise TypeError(
|
|
431
|
+
f"bias_score must be numeric, got {type(bias_score).__name__}"
|
|
432
|
+
)
|
|
433
|
+
if not isinstance(threshold, (int, float)):
|
|
434
|
+
raise TypeError(
|
|
435
|
+
f"threshold must be numeric, got {type(threshold).__name__}"
|
|
436
|
+
)
|
|
437
|
+
if message is not None and not isinstance(message, str):
|
|
438
|
+
raise TypeError(
|
|
439
|
+
f"message must be str or None, got {type(message).__name__}"
|
|
440
|
+
)
|
|
441
|
+
|
|
442
|
+
if message is None:
|
|
443
|
+
msg = (
|
|
444
|
+
f"Bias detected in '{feature}': score={bias_score:.3f} > "
|
|
445
|
+
f"threshold={threshold:.3f}"
|
|
446
|
+
)
|
|
447
|
+
else:
|
|
448
|
+
msg = message
|
|
449
|
+
|
|
450
|
+
self.feature = feature
|
|
451
|
+
self.bias_score = float(bias_score)
|
|
452
|
+
self.threshold = float(threshold)
|
|
453
|
+
super().__init__(msg)
|
|
454
|
+
|
|
455
|
+
def __str__(self) -> str:
|
|
456
|
+
return f"[BiasDetectionError:{self.feature}] {self.message}"
|
|
457
|
+
|
|
458
|
+
|
|
459
|
+
class ExplainabilityError(DataScienceError):
|
|
460
|
+
"""Raised when generating model explanations fails.
|
|
461
|
+
|
|
462
|
+
Args:
|
|
463
|
+
method: Explanation technique identifier.
|
|
464
|
+
details: Optional description of the failure.
|
|
465
|
+
"""
|
|
466
|
+
|
|
467
|
+
def __init__(self, method: str, details: Optional[str] = None) -> None:
|
|
468
|
+
if not isinstance(method, str):
|
|
469
|
+
raise TypeError(f"method must be str, got {type(method).__name__}")
|
|
470
|
+
if details is not None and not isinstance(details, str):
|
|
471
|
+
raise TypeError(
|
|
472
|
+
f"details must be str or None, got {type(details).__name__}"
|
|
473
|
+
)
|
|
474
|
+
msg = f"Explainability using '{method}' failed"
|
|
475
|
+
if details:
|
|
476
|
+
msg += f": {details}"
|
|
477
|
+
self.method = method
|
|
478
|
+
self.details = details
|
|
479
|
+
super().__init__(msg)
|
|
480
|
+
|
|
481
|
+
def __str__(self) -> str:
|
|
482
|
+
return f"[ExplainabilityError:{self.method}] {self.message}"
|
|
483
|
+
|
|
484
|
+
|
|
485
|
+
class FeatureScalingError(DataScienceError):
|
|
486
|
+
"""Raised when scaling or standardization of features fails.
|
|
487
|
+
|
|
488
|
+
Args:
|
|
489
|
+
scaler: Name of the scaler or transformation used.
|
|
490
|
+
details: Optional explanation of the failure.
|
|
491
|
+
"""
|
|
492
|
+
|
|
493
|
+
def __init__(self, scaler: str, details: Optional[str] = None) -> None:
|
|
494
|
+
if not isinstance(scaler, str):
|
|
495
|
+
raise TypeError(f"scaler must be str, got {type(scaler).__name__}")
|
|
496
|
+
if details is not None and not isinstance(details, str):
|
|
497
|
+
raise TypeError(
|
|
498
|
+
f"details must be str or None, got {type(details).__name__}"
|
|
499
|
+
)
|
|
500
|
+
msg = f"Feature scaling with '{scaler}' failed"
|
|
501
|
+
if details:
|
|
502
|
+
msg += f": {details}"
|
|
503
|
+
self.scaler = scaler
|
|
504
|
+
self.details = details
|
|
505
|
+
super().__init__(msg)
|
|
506
|
+
|
|
507
|
+
def __str__(self) -> str:
|
|
508
|
+
return f"[FeatureScalingError:{self.scaler}] {self.message}"
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# __init__.py
|
|
2
|
+
from .authentication import AuthenticationError, AuthorizationError
|
|
3
|
+
from .base import JobError
|
|
4
|
+
from .configuration import ConfigurationError
|
|
5
|
+
from .external import (
|
|
6
|
+
ConnectionError,
|
|
7
|
+
DependencyError,
|
|
8
|
+
ResourceNotFoundError,
|
|
9
|
+
TimeoutError,
|
|
10
|
+
)
|
|
11
|
+
from .lifecycle import JobCancellationError
|
|
12
|
+
from .notification import EmailError, NotificationError, WebhookError
|
|
13
|
+
from .parsing import DeserializationError, ParsingError, SerializationError
|
|
14
|
+
from .scheduling import CronExpressionError, ScheduleConflictError
|
|
15
|
+
from .validation import ValidationError
|
|
16
|
+
|
|
17
|
+
__all__ = [
|
|
18
|
+
"JobError",
|
|
19
|
+
"ValidationError",
|
|
20
|
+
"ConfigurationError",
|
|
21
|
+
"ConnectionError",
|
|
22
|
+
"TimeoutError",
|
|
23
|
+
"ResourceNotFoundError",
|
|
24
|
+
"DependencyError",
|
|
25
|
+
"AuthenticationError",
|
|
26
|
+
"AuthorizationError",
|
|
27
|
+
"ParsingError",
|
|
28
|
+
"SerializationError",
|
|
29
|
+
"DeserializationError",
|
|
30
|
+
"ScheduleConflictError",
|
|
31
|
+
"CronExpressionError",
|
|
32
|
+
"NotificationError",
|
|
33
|
+
"EmailError",
|
|
34
|
+
"WebhookError",
|
|
35
|
+
"JobCancellationError",
|
|
36
|
+
]
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# authentication.py
|
|
2
|
+
from .base import JobError
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class AuthenticationError(JobError):
|
|
6
|
+
"""Raised when user authentication fails."""
|
|
7
|
+
|
|
8
|
+
def __init__(self, user: str, message: str | None = None):
|
|
9
|
+
self.user = user
|
|
10
|
+
self.message = message or f"Authentication failed for user '{user}'"
|
|
11
|
+
super().__init__(self.message)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class AuthorizationError(JobError):
|
|
15
|
+
"""Raised when user lacks permission for an action."""
|
|
16
|
+
|
|
17
|
+
def __init__(self, user: str, permission: str):
|
|
18
|
+
self.user = user
|
|
19
|
+
self.permission = permission
|
|
20
|
+
msg = f"User '{user}' lacks permission '{permission}'"
|
|
21
|
+
super().__init__(msg)
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
from .base import JobError
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class ConfigurationError(JobError):
|
|
5
|
+
"""Raised when there is a problem with configuration or settings."""
|
|
6
|
+
|
|
7
|
+
def __init__(self, option: str, message: str | None = None):
|
|
8
|
+
self.option = option
|
|
9
|
+
self.message = message or f"Invalid configuration for '{option}'"
|
|
10
|
+
super().__init__(self.message)
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
from .base import JobError
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class ConnectionError(JobError):
|
|
5
|
+
"""Raised when a connection to an external service fails."""
|
|
6
|
+
|
|
7
|
+
def __init__(self, service_name: str, original_exception: Exception | None = None):
|
|
8
|
+
self.service_name = service_name
|
|
9
|
+
self.original_exception = original_exception
|
|
10
|
+
msg = f"Failed to connect to service '{service_name}'"
|
|
11
|
+
if original_exception:
|
|
12
|
+
msg += f": {original_exception}"
|
|
13
|
+
super().__init__(msg)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class TimeoutError(JobError):
|
|
17
|
+
"""Raised when an operation exceeds its time limit."""
|
|
18
|
+
|
|
19
|
+
def __init__(self, operation: str, timeout: float):
|
|
20
|
+
self.operation = operation
|
|
21
|
+
self.timeout = timeout
|
|
22
|
+
msg = f"Operation '{operation}' timed out after {timeout} seconds"
|
|
23
|
+
super().__init__(msg)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class ResourceNotFoundError(JobError):
|
|
27
|
+
"""Raised when a required resource cannot be found."""
|
|
28
|
+
|
|
29
|
+
def __init__(self, resource_type: str, identifier: str):
|
|
30
|
+
self.resource_type = resource_type
|
|
31
|
+
self.identifier = identifier
|
|
32
|
+
msg = f"{resource_type} with identifier '{identifier}' not found"
|
|
33
|
+
super().__init__(msg)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class DependencyError(JobError):
|
|
37
|
+
"""Raised when a job dependency is missing or fails."""
|
|
38
|
+
|
|
39
|
+
def __init__(self, dependency_name: str, message: str | None = None):
|
|
40
|
+
self.dependency_name = dependency_name
|
|
41
|
+
self.message = message or f"Dependency '{dependency_name}' error"
|
|
42
|
+
super().__init__(self.message)
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
from .base import JobError
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class JobCancellationError(JobError):
|
|
5
|
+
"""Raised when a job is cancelled before completion."""
|
|
6
|
+
|
|
7
|
+
def __init__(self, job_id: str, reason: str | None = None):
|
|
8
|
+
self.job_id = job_id
|
|
9
|
+
self.reason = reason
|
|
10
|
+
msg = f"Job '{job_id}' was cancelled"
|
|
11
|
+
if reason:
|
|
12
|
+
msg += f": {reason}"
|
|
13
|
+
super().__init__(msg)
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# notification.py
|
|
2
|
+
from .base import JobError
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class NotificationError(JobError):
|
|
6
|
+
"""Base exception for notification failures."""
|
|
7
|
+
|
|
8
|
+
def __init__(
|
|
9
|
+
self,
|
|
10
|
+
channel: str,
|
|
11
|
+
original_exception: Exception | None = None,
|
|
12
|
+
message: str | None = None,
|
|
13
|
+
):
|
|
14
|
+
self.channel = channel
|
|
15
|
+
self.original_exception = original_exception
|
|
16
|
+
msg = message or f"Notification via '{channel}' failed"
|
|
17
|
+
if message is None and original_exception:
|
|
18
|
+
msg += f": {original_exception}"
|
|
19
|
+
super().__init__(msg)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class EmailError(NotificationError):
|
|
23
|
+
"""Raised when sending an email fails."""
|
|
24
|
+
|
|
25
|
+
def __init__(
|
|
26
|
+
self,
|
|
27
|
+
recipient: str,
|
|
28
|
+
subject: str,
|
|
29
|
+
original_exception: Exception | None = None,
|
|
30
|
+
):
|
|
31
|
+
self.recipient = recipient
|
|
32
|
+
self.subject = subject
|
|
33
|
+
self.original_exception = original_exception
|
|
34
|
+
msg = f"Email to '{recipient}' with subject '{subject}' failed"
|
|
35
|
+
if original_exception:
|
|
36
|
+
msg += f": {original_exception}"
|
|
37
|
+
super().__init__("email", original_exception, message=msg)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class WebhookError(NotificationError):
|
|
41
|
+
"""Raised when a webhook POST fails."""
|
|
42
|
+
|
|
43
|
+
def __init__(self, url: str, original_exception: Exception | None = None):
|
|
44
|
+
self.url = url
|
|
45
|
+
self.original_exception = original_exception
|
|
46
|
+
msg = f"Webhook to URL '{url}' failed"
|
|
47
|
+
if original_exception:
|
|
48
|
+
msg += f": {original_exception}"
|
|
49
|
+
super().__init__("webhook", original_exception, message=msg)
|