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,312 @@
|
|
|
1
|
+
"""Data ingestion and validation related errors."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any, Optional, Sequence
|
|
6
|
+
|
|
7
|
+
from .base import DataScienceError
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class DataLoadingError(DataScienceError):
|
|
11
|
+
"""
|
|
12
|
+
Raised when loading data fails.
|
|
13
|
+
|
|
14
|
+
Attributes:
|
|
15
|
+
source: data source description (file path, URL).
|
|
16
|
+
original: underlying exception.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
def __init__(self, source: str, original: Exception) -> None:
|
|
20
|
+
if not isinstance(source, str):
|
|
21
|
+
raise TypeError(f"source must be str, got {type(source).__name__}")
|
|
22
|
+
if not isinstance(original, Exception):
|
|
23
|
+
raise TypeError(
|
|
24
|
+
f"original must be Exception, got {type(original).__name__}"
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
message = f"Failed to load data from {source!r}: {original}"
|
|
28
|
+
self.source = source
|
|
29
|
+
self.original = original
|
|
30
|
+
super().__init__(message)
|
|
31
|
+
|
|
32
|
+
def __str__(self) -> str:
|
|
33
|
+
return f"[DataLoadingError] {self.source}"
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class DataFormatError(DataScienceError):
|
|
37
|
+
"""Raised when input data is not in the expected format."""
|
|
38
|
+
|
|
39
|
+
def __init__(self, expected_formats: Sequence[str], found_format: str) -> None:
|
|
40
|
+
if not isinstance(found_format, str):
|
|
41
|
+
raise TypeError(
|
|
42
|
+
f"found_format must be str, got {type(found_format).__name__}"
|
|
43
|
+
)
|
|
44
|
+
if not isinstance(expected_formats, Sequence) or isinstance(
|
|
45
|
+
expected_formats, str
|
|
46
|
+
):
|
|
47
|
+
raise TypeError("expected_formats must be a sequence of strings")
|
|
48
|
+
if not all(isinstance(fmt, str) for fmt in expected_formats):
|
|
49
|
+
raise TypeError("expected_formats must contain strings")
|
|
50
|
+
|
|
51
|
+
self.expected_formats = list(expected_formats)
|
|
52
|
+
self.found_format = found_format
|
|
53
|
+
fmt_list = ", ".join(self.expected_formats)
|
|
54
|
+
message = f"Expected data format {fmt_list}; got {found_format}"
|
|
55
|
+
super().__init__(message)
|
|
56
|
+
|
|
57
|
+
def __str__(self) -> str:
|
|
58
|
+
return f"[DataFormatError] {self.message}"
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
class DataValidationError(DataScienceError):
|
|
62
|
+
"""
|
|
63
|
+
Raised when data fails validation rules.
|
|
64
|
+
|
|
65
|
+
Attributes:
|
|
66
|
+
field: name of invalid field.
|
|
67
|
+
value: the invalid value.
|
|
68
|
+
"""
|
|
69
|
+
|
|
70
|
+
def __init__(self, field: str, value: Any, message: Optional[str] = None) -> None:
|
|
71
|
+
if not isinstance(field, str):
|
|
72
|
+
raise TypeError(f"field must be str, got {type(field).__name__}")
|
|
73
|
+
|
|
74
|
+
if message is None:
|
|
75
|
+
message = f"Invalid value for '{field}': {value!r}"
|
|
76
|
+
elif not isinstance(message, str):
|
|
77
|
+
raise TypeError(f"message must be str, got {type(message).__name__}")
|
|
78
|
+
|
|
79
|
+
self.field = field
|
|
80
|
+
self.value = value
|
|
81
|
+
super().__init__(message)
|
|
82
|
+
|
|
83
|
+
def __str__(self) -> str:
|
|
84
|
+
return f"[DataValidationError:{self.field}] {self.message}"
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
class MissingDataError(DataScienceError):
|
|
88
|
+
"""
|
|
89
|
+
Raised when required data is missing.
|
|
90
|
+
|
|
91
|
+
Attributes:
|
|
92
|
+
feature: name of missing feature.
|
|
93
|
+
"""
|
|
94
|
+
|
|
95
|
+
def __init__(self, feature: str, message: Optional[str] = None) -> None:
|
|
96
|
+
if not isinstance(feature, str):
|
|
97
|
+
raise TypeError(f"feature must be str, got {type(feature).__name__}")
|
|
98
|
+
|
|
99
|
+
if message is None:
|
|
100
|
+
message = f"Missing required feature: {feature!r}"
|
|
101
|
+
elif not isinstance(message, str):
|
|
102
|
+
raise TypeError(f"message must be str, got {type(message).__name__}")
|
|
103
|
+
|
|
104
|
+
self.feature = feature
|
|
105
|
+
super().__init__(message)
|
|
106
|
+
|
|
107
|
+
def __str__(self) -> str:
|
|
108
|
+
return f"[MissingDataError] {self.feature}"
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
class OutlierDetectionError(DataScienceError):
|
|
112
|
+
"""
|
|
113
|
+
Raised when outlier detection fails.
|
|
114
|
+
|
|
115
|
+
Attributes:
|
|
116
|
+
method: detection method name.
|
|
117
|
+
details: optional extra info.
|
|
118
|
+
"""
|
|
119
|
+
|
|
120
|
+
def __init__(self, method: str, details: Optional[str] = None) -> None:
|
|
121
|
+
if not isinstance(method, str):
|
|
122
|
+
raise TypeError(f"method must be str, got {type(method).__name__}")
|
|
123
|
+
if details is not None and not isinstance(details, str):
|
|
124
|
+
raise TypeError(
|
|
125
|
+
f"details must be str or None, got {type(details).__name__}"
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
msg = f"Outlier detection failed using method '{method}'"
|
|
129
|
+
if details:
|
|
130
|
+
msg += f": {details}"
|
|
131
|
+
|
|
132
|
+
self.method = method
|
|
133
|
+
self.details = details
|
|
134
|
+
super().__init__(msg)
|
|
135
|
+
|
|
136
|
+
def __str__(self) -> str:
|
|
137
|
+
return f"[OutlierDetectionError:{self.method}] {self.message}"
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
class SchemaMismatchError(DataScienceError):
|
|
141
|
+
"""
|
|
142
|
+
Raised when data schema does not match expected.
|
|
143
|
+
|
|
144
|
+
Attributes:
|
|
145
|
+
expected: expected schema description.
|
|
146
|
+
found: actual schema description.
|
|
147
|
+
"""
|
|
148
|
+
|
|
149
|
+
def __init__(self, expected: str, found: str) -> None:
|
|
150
|
+
if not isinstance(expected, str):
|
|
151
|
+
raise TypeError(f"expected must be str, got {type(expected).__name__}")
|
|
152
|
+
if not isinstance(found, str):
|
|
153
|
+
raise TypeError(f"found must be str, got {type(found).__name__}")
|
|
154
|
+
|
|
155
|
+
message = f"Schema mismatch. Expected: {expected}, Found: {found}"
|
|
156
|
+
self.expected = expected
|
|
157
|
+
self.found = found
|
|
158
|
+
super().__init__(message)
|
|
159
|
+
|
|
160
|
+
def __str__(self) -> str:
|
|
161
|
+
return f"[SchemaMismatchError] {self.message}"
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
class FeatureEngineeringError(DataScienceError):
|
|
165
|
+
"""
|
|
166
|
+
Raised during feature engineering steps.
|
|
167
|
+
|
|
168
|
+
Attributes:
|
|
169
|
+
step: description of the step that failed.
|
|
170
|
+
cause: optional underlying reason.
|
|
171
|
+
"""
|
|
172
|
+
|
|
173
|
+
def __init__(self, step: str, cause: Optional[str] = None) -> None:
|
|
174
|
+
if not isinstance(step, str):
|
|
175
|
+
raise TypeError(f"step must be str, got {type(step).__name__}")
|
|
176
|
+
if cause is not None and not isinstance(cause, str):
|
|
177
|
+
raise TypeError(f"cause must be str or None, got {type(cause).__name__}")
|
|
178
|
+
|
|
179
|
+
msg = f"Feature engineering failed at step '{step}'"
|
|
180
|
+
if cause:
|
|
181
|
+
msg += f": {cause}"
|
|
182
|
+
|
|
183
|
+
self.step = step
|
|
184
|
+
self.cause = cause
|
|
185
|
+
super().__init__(msg)
|
|
186
|
+
|
|
187
|
+
def __str__(self) -> str:
|
|
188
|
+
return f"[FeatureEngineeringError] {self.message}"
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
class DataNormalizationError(DataScienceError):
|
|
192
|
+
"""Raised when data normalization fails.
|
|
193
|
+
|
|
194
|
+
Args:
|
|
195
|
+
method: Normalization technique identifier.
|
|
196
|
+
details: Optional explanation of the failure.
|
|
197
|
+
"""
|
|
198
|
+
|
|
199
|
+
def __init__(self, method: str, details: Optional[str] = None) -> None:
|
|
200
|
+
if not isinstance(method, str):
|
|
201
|
+
raise TypeError(f"method must be str, got {type(method).__name__}")
|
|
202
|
+
if details is not None and not isinstance(details, str):
|
|
203
|
+
raise TypeError(
|
|
204
|
+
f"details must be str or None, got {type(details).__name__}"
|
|
205
|
+
)
|
|
206
|
+
# Build a helpful error message
|
|
207
|
+
msg = f"Normalization using '{method}' failed"
|
|
208
|
+
if details:
|
|
209
|
+
msg += f": {details}"
|
|
210
|
+
self.method = method
|
|
211
|
+
self.details = details
|
|
212
|
+
super().__init__(msg)
|
|
213
|
+
|
|
214
|
+
def __str__(self) -> str:
|
|
215
|
+
return f"[DataNormalizationError:{self.method}] {self.message}"
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
class DataImbalanceError(DataScienceError):
|
|
219
|
+
"""Raised when class distribution is too imbalanced.
|
|
220
|
+
|
|
221
|
+
Args:
|
|
222
|
+
ratio: Observed minority-to-majority ratio.
|
|
223
|
+
threshold: Minimum acceptable ratio.
|
|
224
|
+
message: Optional custom error message.
|
|
225
|
+
"""
|
|
226
|
+
|
|
227
|
+
def __init__(
|
|
228
|
+
self, ratio: float, threshold: float, message: Optional[str] = None
|
|
229
|
+
) -> None:
|
|
230
|
+
if not isinstance(ratio, (int, float)):
|
|
231
|
+
raise TypeError(f"ratio must be numeric, got {type(ratio).__name__}")
|
|
232
|
+
if not isinstance(threshold, (int, float)):
|
|
233
|
+
raise TypeError(
|
|
234
|
+
f"threshold must be numeric, got {type(threshold).__name__}"
|
|
235
|
+
)
|
|
236
|
+
if message is not None and not isinstance(message, str):
|
|
237
|
+
raise TypeError(
|
|
238
|
+
f"message must be str or None, got {type(message).__name__}"
|
|
239
|
+
)
|
|
240
|
+
self.ratio = float(ratio)
|
|
241
|
+
self.threshold = float(threshold)
|
|
242
|
+
if message is None:
|
|
243
|
+
msg = (
|
|
244
|
+
f"Data imbalance detected: ratio={self.ratio:.3f} < "
|
|
245
|
+
f"threshold={self.threshold:.3f}"
|
|
246
|
+
)
|
|
247
|
+
else:
|
|
248
|
+
msg = message
|
|
249
|
+
super().__init__(msg)
|
|
250
|
+
|
|
251
|
+
def __str__(self) -> str:
|
|
252
|
+
return f"[DataImbalanceError] {self.message}"
|
|
253
|
+
|
|
254
|
+
|
|
255
|
+
class DataAugmentationError(DataScienceError):
|
|
256
|
+
"""Raised when a data augmentation technique fails.
|
|
257
|
+
|
|
258
|
+
Args:
|
|
259
|
+
technique: Name of the augmentation technique.
|
|
260
|
+
details: Optional explanation of the failure.
|
|
261
|
+
"""
|
|
262
|
+
|
|
263
|
+
def __init__(self, technique: str, details: Optional[str] = None) -> None:
|
|
264
|
+
if not isinstance(technique, str):
|
|
265
|
+
raise TypeError(f"technique must be str, got {type(technique).__name__}")
|
|
266
|
+
if details is not None and not isinstance(details, str):
|
|
267
|
+
raise TypeError(
|
|
268
|
+
f"details must be str or None, got {type(details).__name__}"
|
|
269
|
+
)
|
|
270
|
+
|
|
271
|
+
msg = f"Data augmentation '{technique}' failed"
|
|
272
|
+
if details:
|
|
273
|
+
msg += f": {details}"
|
|
274
|
+
|
|
275
|
+
self.technique = technique
|
|
276
|
+
self.details = details
|
|
277
|
+
super().__init__(msg)
|
|
278
|
+
|
|
279
|
+
def __str__(self) -> str:
|
|
280
|
+
return f"[DataAugmentationError:{self.technique}] {self.message}"
|
|
281
|
+
|
|
282
|
+
|
|
283
|
+
class DataLeakageError(DataScienceError):
|
|
284
|
+
"""Raised when data leakage is detected between train and test sets.
|
|
285
|
+
|
|
286
|
+
Args:
|
|
287
|
+
feature: Name of the leaked feature.
|
|
288
|
+
stage: Stage where the leakage occurred.
|
|
289
|
+
message: Optional custom message.
|
|
290
|
+
"""
|
|
291
|
+
|
|
292
|
+
def __init__(self, feature: str, stage: str, message: Optional[str] = None) -> None:
|
|
293
|
+
if not isinstance(feature, str):
|
|
294
|
+
raise TypeError(f"feature must be str, got {type(feature).__name__}")
|
|
295
|
+
if not isinstance(stage, str):
|
|
296
|
+
raise TypeError(f"stage must be str, got {type(stage).__name__}")
|
|
297
|
+
if message is not None and not isinstance(message, str):
|
|
298
|
+
raise TypeError(
|
|
299
|
+
f"message must be str or None, got {type(message).__name__}"
|
|
300
|
+
)
|
|
301
|
+
|
|
302
|
+
if message is None:
|
|
303
|
+
msg = f"Data leakage detected for '{feature}' during {stage}"
|
|
304
|
+
else:
|
|
305
|
+
msg = message
|
|
306
|
+
|
|
307
|
+
self.feature = feature
|
|
308
|
+
self.stage = stage
|
|
309
|
+
super().__init__(msg)
|
|
310
|
+
|
|
311
|
+
def __str__(self) -> str:
|
|
312
|
+
return f"[DataLeakageError:{self.feature}] {self.message}"
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
"""Operational and deployment errors for ML systems."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any, Optional
|
|
6
|
+
|
|
7
|
+
from .base import DataScienceError
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class SerializationError(DataScienceError):
|
|
11
|
+
"""
|
|
12
|
+
Raised when saving or loading a model fails.
|
|
13
|
+
|
|
14
|
+
Attributes:
|
|
15
|
+
path: file path involved.
|
|
16
|
+
original: underlying exception.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
def __init__(self, path: str, original: Exception) -> None:
|
|
20
|
+
if not isinstance(path, str):
|
|
21
|
+
raise TypeError(f"path must be str, got {type(path).__name__}")
|
|
22
|
+
if not isinstance(original, Exception):
|
|
23
|
+
raise TypeError(
|
|
24
|
+
f"original must be Exception, got {type(original).__name__}"
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
message = f"Failed to serialize to {path!r}: {original}"
|
|
28
|
+
self.path = path
|
|
29
|
+
self.original = original
|
|
30
|
+
super().__init__(message)
|
|
31
|
+
|
|
32
|
+
def __str__(self) -> str:
|
|
33
|
+
return f"[SerializationError] {self.path}"
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class DeploymentError(DataScienceError):
|
|
37
|
+
"""
|
|
38
|
+
Raised when deploying a model or pipeline fails.
|
|
39
|
+
|
|
40
|
+
Attributes:
|
|
41
|
+
target: deployment target identifier.
|
|
42
|
+
cause: optional detail.
|
|
43
|
+
"""
|
|
44
|
+
|
|
45
|
+
def __init__(self, target: str, cause: Optional[str] = None) -> None:
|
|
46
|
+
if not isinstance(target, str):
|
|
47
|
+
raise TypeError(f"target must be str, got {type(target).__name__}")
|
|
48
|
+
if cause is not None and not isinstance(cause, str):
|
|
49
|
+
raise TypeError(f"cause must be str or None, got {type(cause).__name__}")
|
|
50
|
+
|
|
51
|
+
msg = f"Deployment failed to '{target}'"
|
|
52
|
+
if cause:
|
|
53
|
+
msg += f": {cause}"
|
|
54
|
+
|
|
55
|
+
self.target = target
|
|
56
|
+
self.cause = cause
|
|
57
|
+
super().__init__(msg)
|
|
58
|
+
|
|
59
|
+
def __str__(self) -> str:
|
|
60
|
+
return f"[DeploymentError] {self.target}"
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
class DataDriftError(DataScienceError):
|
|
64
|
+
"""
|
|
65
|
+
Raised when data drift is detected beyond threshold.
|
|
66
|
+
|
|
67
|
+
Attributes:
|
|
68
|
+
feature: feature name.
|
|
69
|
+
drift_score: computed drift metric.
|
|
70
|
+
"""
|
|
71
|
+
|
|
72
|
+
def __init__(
|
|
73
|
+
self, feature: str, drift_score: float, message: Optional[str] = None
|
|
74
|
+
) -> None:
|
|
75
|
+
if not isinstance(feature, str):
|
|
76
|
+
raise TypeError(f"feature must be str, got {type(feature).__name__}")
|
|
77
|
+
if not isinstance(drift_score, (int, float)):
|
|
78
|
+
raise TypeError(
|
|
79
|
+
f"drift_score must be number, got {type(drift_score).__name__}"
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
self.feature = feature
|
|
83
|
+
self.drift_score = float(drift_score)
|
|
84
|
+
if message is None:
|
|
85
|
+
message = f"Data drift detected on '{feature}', score={drift_score:.4f}"
|
|
86
|
+
|
|
87
|
+
super().__init__(message)
|
|
88
|
+
|
|
89
|
+
def __str__(self) -> str:
|
|
90
|
+
return f"[DataDriftError:{self.feature}] {self.message}"
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
class ResourceLimitError(DataScienceError):
|
|
94
|
+
"""
|
|
95
|
+
Raised when computation exceeds resources (memory, CPU).
|
|
96
|
+
|
|
97
|
+
Attributes:
|
|
98
|
+
resource: 'memory', 'cpu', etc.
|
|
99
|
+
limit: threshold exceeded.
|
|
100
|
+
"""
|
|
101
|
+
|
|
102
|
+
def __init__(self, resource: str, limit: Any) -> None:
|
|
103
|
+
if not isinstance(resource, str):
|
|
104
|
+
raise TypeError(f"resource must be str, got {type(resource).__name__}")
|
|
105
|
+
|
|
106
|
+
message = f"Resource limit exceeded: {resource} at {limit!r}"
|
|
107
|
+
self.resource = resource
|
|
108
|
+
self.limit = limit
|
|
109
|
+
super().__init__(message)
|
|
110
|
+
|
|
111
|
+
def __str__(self) -> str:
|
|
112
|
+
return f"[ResourceLimitError:{self.resource}] {self.message}"
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
class DataExportError(DataScienceError):
|
|
116
|
+
"""Failed to export or write data to destination."""
|
|
117
|
+
|
|
118
|
+
def __init__(self, destination: str, original: Exception) -> None:
|
|
119
|
+
if not isinstance(destination, str):
|
|
120
|
+
raise TypeError(
|
|
121
|
+
f"destination must be str, got {type(destination).__name__}"
|
|
122
|
+
)
|
|
123
|
+
if not isinstance(original, Exception):
|
|
124
|
+
raise TypeError(
|
|
125
|
+
f"original must be Exception, got {type(original).__name__}"
|
|
126
|
+
)
|
|
127
|
+
msg = f"Unable to export data to {destination}: {original}"
|
|
128
|
+
self.destination = destination
|
|
129
|
+
self.original = original
|
|
130
|
+
super().__init__(msg)
|