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.
@@ -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,4 @@
1
+ class JobError(Exception):
2
+ """Base exception for all job-related errors."""
3
+
4
+ pass
@@ -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)