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
dataexcept/__init__.py
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
"""Top-level package for DataExcept.
|
|
2
|
+
|
|
3
|
+
Exposes the common job-related exceptions directly and also provides
|
|
4
|
+
access to data science specific exceptions via the ``datascience_exceptions``
|
|
5
|
+
module.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
|
|
10
|
+
try: # Python >=3.11
|
|
11
|
+
import tomllib # type: ignore
|
|
12
|
+
except ModuleNotFoundError: # pragma: no cover - fallback for Python <3.11
|
|
13
|
+
import tomli as tomllib # type: ignore
|
|
14
|
+
|
|
15
|
+
from importlib import import_module, metadata
|
|
16
|
+
from typing import Any
|
|
17
|
+
|
|
18
|
+
from . import (
|
|
19
|
+
database_exceptions,
|
|
20
|
+
dataengineering_exceptions,
|
|
21
|
+
datascience_exceptions,
|
|
22
|
+
)
|
|
23
|
+
from . import exceptions as _exceptions
|
|
24
|
+
from . import (
|
|
25
|
+
io_exceptions,
|
|
26
|
+
network_exceptions,
|
|
27
|
+
pandas_exceptions,
|
|
28
|
+
pipeline_exceptions,
|
|
29
|
+
security_exceptions,
|
|
30
|
+
)
|
|
31
|
+
from .exceptions import ( # noqa: F401
|
|
32
|
+
AuthenticationError,
|
|
33
|
+
AuthorizationError,
|
|
34
|
+
ConfigurationError,
|
|
35
|
+
ConnectionError,
|
|
36
|
+
CronExpressionError,
|
|
37
|
+
DependencyError,
|
|
38
|
+
DeserializationError,
|
|
39
|
+
EmailError,
|
|
40
|
+
JobCancellationError,
|
|
41
|
+
JobError,
|
|
42
|
+
NotificationError,
|
|
43
|
+
ParsingError,
|
|
44
|
+
ResourceNotFoundError,
|
|
45
|
+
ScheduleConflictError,
|
|
46
|
+
SerializationError,
|
|
47
|
+
TimeoutError,
|
|
48
|
+
ValidationError,
|
|
49
|
+
WebhookError,
|
|
50
|
+
)
|
|
51
|
+
from .logging_helpers import log_and_raise, log_exception, log_then_raise
|
|
52
|
+
|
|
53
|
+
try:
|
|
54
|
+
__version__ = metadata.version("DataExcept")
|
|
55
|
+
except metadata.PackageNotFoundError: # pragma: no cover - fallback during dev
|
|
56
|
+
_root = Path(__file__).resolve().parents[1]
|
|
57
|
+
with open(_root / "pyproject.toml", "rb") as _f:
|
|
58
|
+
__version__ = tomllib.load(_f)["tool"]["poetry"]["version"]
|
|
59
|
+
|
|
60
|
+
__all__ = list(_exceptions.__all__) + [
|
|
61
|
+
"datascience_exceptions",
|
|
62
|
+
"job_exceptions",
|
|
63
|
+
"pipeline_exceptions",
|
|
64
|
+
"dataengineering_exceptions",
|
|
65
|
+
"network_exceptions",
|
|
66
|
+
"io_exceptions",
|
|
67
|
+
"database_exceptions",
|
|
68
|
+
"security_exceptions",
|
|
69
|
+
"pandas_exceptions",
|
|
70
|
+
"log_exception",
|
|
71
|
+
"log_and_raise",
|
|
72
|
+
"log_then_raise",
|
|
73
|
+
]
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def __getattr__(name: str) -> Any:
|
|
77
|
+
if name == "job_exceptions":
|
|
78
|
+
module = import_module("dataexcept.job_exceptions")
|
|
79
|
+
globals()[name] = module
|
|
80
|
+
return module
|
|
81
|
+
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
|
dataexcept/__main__.py
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
"""Command line interface for the DataExcept package."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import argparse
|
|
6
|
+
import pkgutil
|
|
7
|
+
import sys
|
|
8
|
+
from importlib import import_module
|
|
9
|
+
from types import ModuleType
|
|
10
|
+
from typing import Iterable
|
|
11
|
+
|
|
12
|
+
from . import __path__ as _PKG_PATH
|
|
13
|
+
from . import __version__
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def _iter_exception_modules() -> Iterable[ModuleType]:
|
|
17
|
+
"""Yield every submodule that explicitly defines ``__all__``."""
|
|
18
|
+
allowed_suffixes = ("exceptions", "_exceptions")
|
|
19
|
+
|
|
20
|
+
for module_info in pkgutil.walk_packages(
|
|
21
|
+
_PKG_PATH, prefix="dataexcept.", onerror=lambda name: None
|
|
22
|
+
):
|
|
23
|
+
if not module_info.name.endswith(allowed_suffixes):
|
|
24
|
+
continue
|
|
25
|
+
try:
|
|
26
|
+
module = import_module(module_info.name)
|
|
27
|
+
except ImportError as exc: # pragma: no cover - defensive guard
|
|
28
|
+
print(
|
|
29
|
+
f"dataexcept: failed to import {module_info.name}: {exc}",
|
|
30
|
+
file=sys.stderr,
|
|
31
|
+
)
|
|
32
|
+
continue
|
|
33
|
+
if getattr(module, "__all__", None):
|
|
34
|
+
yield module
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def _iter_exception_names() -> Iterable[str]:
|
|
38
|
+
seen: set[str] = set()
|
|
39
|
+
for module in _iter_exception_modules():
|
|
40
|
+
names = getattr(module, "__all__", None)
|
|
41
|
+
if not names:
|
|
42
|
+
continue
|
|
43
|
+
for name in names:
|
|
44
|
+
if name.endswith("Error") and name not in seen:
|
|
45
|
+
seen.add(name)
|
|
46
|
+
yield name
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def _list_exceptions() -> None:
|
|
50
|
+
for name in sorted(_iter_exception_names()):
|
|
51
|
+
print(name)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def main(argv: list[str] | None = None) -> None:
|
|
55
|
+
"""Entry point for the ``dataexcept`` command."""
|
|
56
|
+
parser = argparse.ArgumentParser(description="Utilities for DataExcept")
|
|
57
|
+
parser.add_argument(
|
|
58
|
+
"--version",
|
|
59
|
+
action="version",
|
|
60
|
+
version=f"%(prog)s {__version__}",
|
|
61
|
+
)
|
|
62
|
+
subparsers = parser.add_subparsers(dest="command")
|
|
63
|
+
subparsers.add_parser("list", help="List available exception classes")
|
|
64
|
+
|
|
65
|
+
args = parser.parse_args(argv)
|
|
66
|
+
|
|
67
|
+
if args.command == "list":
|
|
68
|
+
_list_exceptions()
|
|
69
|
+
else: # pragma: no cover - help message
|
|
70
|
+
parser.print_help()
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
if __name__ == "__main__": # pragma: no cover - manual invocation
|
|
74
|
+
main()
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
"""Custom exceptions for database operations."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class DatabaseError(Exception):
|
|
7
|
+
"""Base exception for database-related errors."""
|
|
8
|
+
|
|
9
|
+
pass
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class DatabaseConnectionError(DatabaseError):
|
|
13
|
+
"""Raised when connecting to the database fails."""
|
|
14
|
+
|
|
15
|
+
def __init__(self, db_url: str, message: str | None = None) -> None:
|
|
16
|
+
"""Initialize DatabaseConnectionError.
|
|
17
|
+
|
|
18
|
+
Args:
|
|
19
|
+
db_url: Database connection URL.
|
|
20
|
+
message: Optional custom error message.
|
|
21
|
+
"""
|
|
22
|
+
self.db_url = db_url
|
|
23
|
+
default = f"Failed to connect to database at '{db_url}'"
|
|
24
|
+
super().__init__(message or default)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class QueryExecutionError(DatabaseError):
|
|
28
|
+
"""Raised when a database query execution fails."""
|
|
29
|
+
|
|
30
|
+
def __init__(self, query: str, original: Exception | None = None) -> None:
|
|
31
|
+
"""Initialize QueryExecutionError.
|
|
32
|
+
|
|
33
|
+
Args:
|
|
34
|
+
query: SQL query string.
|
|
35
|
+
original: Optional underlying exception.
|
|
36
|
+
"""
|
|
37
|
+
self.query = query
|
|
38
|
+
self.original = original
|
|
39
|
+
msg = f"Query failed: {query}"
|
|
40
|
+
if original:
|
|
41
|
+
msg += f" ({original})"
|
|
42
|
+
super().__init__(msg)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class TransactionError(DatabaseError):
|
|
46
|
+
"""Raised when a database transaction fails."""
|
|
47
|
+
|
|
48
|
+
def __init__(
|
|
49
|
+
self,
|
|
50
|
+
transaction_id: str | None = None,
|
|
51
|
+
message: str | None = None,
|
|
52
|
+
) -> None:
|
|
53
|
+
"""Initialize TransactionError.
|
|
54
|
+
|
|
55
|
+
Args:
|
|
56
|
+
transaction_id: Identifier for the transaction.
|
|
57
|
+
message: Optional custom error message.
|
|
58
|
+
"""
|
|
59
|
+
self.transaction_id = transaction_id
|
|
60
|
+
default = "Database transaction failed"
|
|
61
|
+
if transaction_id:
|
|
62
|
+
default += f" (id={transaction_id})"
|
|
63
|
+
super().__init__(message or default)
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
__all__ = [
|
|
67
|
+
"DatabaseError",
|
|
68
|
+
"DatabaseConnectionError",
|
|
69
|
+
"QueryExecutionError",
|
|
70
|
+
"TransactionError",
|
|
71
|
+
]
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
"""Custom exceptions for data engineering workflows."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Optional
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class DataEngineeringError(Exception):
|
|
9
|
+
"""Base exception for data engineering errors."""
|
|
10
|
+
|
|
11
|
+
pass
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class ETLJobError(DataEngineeringError):
|
|
15
|
+
"""Raised when an ETL job fails to complete successfully."""
|
|
16
|
+
|
|
17
|
+
def __init__(self, job_name: str, message: Optional[str] = None) -> None:
|
|
18
|
+
"""Initialize ETLJobError.
|
|
19
|
+
|
|
20
|
+
Args:
|
|
21
|
+
job_name: Name of the ETL job.
|
|
22
|
+
message: Optional custom error message.
|
|
23
|
+
"""
|
|
24
|
+
self.job_name = job_name
|
|
25
|
+
default = f"ETL job '{job_name}' failed"
|
|
26
|
+
super().__init__(message or default)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class SchemaEvolutionError(DataEngineeringError):
|
|
30
|
+
"""Raised when database schema evolution fails."""
|
|
31
|
+
|
|
32
|
+
def __init__(self, schema_version: str, reason: Optional[str] = None) -> None:
|
|
33
|
+
"""Initialize SchemaEvolutionError.
|
|
34
|
+
|
|
35
|
+
Args:
|
|
36
|
+
schema_version: Version of the schema being applied.
|
|
37
|
+
reason: Optional explanation of the failure.
|
|
38
|
+
"""
|
|
39
|
+
self.schema_version = schema_version
|
|
40
|
+
self.reason = reason
|
|
41
|
+
msg = f"Schema evolution to {schema_version} failed"
|
|
42
|
+
if reason:
|
|
43
|
+
msg += f": {reason}"
|
|
44
|
+
super().__init__(msg)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class DataTransformationError(DataEngineeringError):
|
|
48
|
+
"""Raised when a data transformation step fails."""
|
|
49
|
+
|
|
50
|
+
def __init__(self, step: str, details: Optional[str] = None) -> None:
|
|
51
|
+
"""Initialize DataTransformationError.
|
|
52
|
+
|
|
53
|
+
Args:
|
|
54
|
+
step: Name of the transformation step.
|
|
55
|
+
details: Optional details about the failure.
|
|
56
|
+
"""
|
|
57
|
+
self.step = step
|
|
58
|
+
self.details = details
|
|
59
|
+
msg = f"Data transformation '{step}' failed"
|
|
60
|
+
if details:
|
|
61
|
+
msg += f": {details}"
|
|
62
|
+
super().__init__(msg)
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
class BatchProcessingError(DataEngineeringError):
|
|
66
|
+
"""Raised when processing a data batch fails."""
|
|
67
|
+
|
|
68
|
+
def __init__(self, batch_id: str, original: Optional[Exception] = None) -> None:
|
|
69
|
+
"""Initialize BatchProcessingError.
|
|
70
|
+
|
|
71
|
+
Args:
|
|
72
|
+
batch_id: Identifier of the batch being processed.
|
|
73
|
+
original: Optional underlying exception.
|
|
74
|
+
"""
|
|
75
|
+
self.batch_id = batch_id
|
|
76
|
+
self.original = original
|
|
77
|
+
msg = f"Batch '{batch_id}' processing failed"
|
|
78
|
+
if original:
|
|
79
|
+
msg += f": {original}"
|
|
80
|
+
super().__init__(msg)
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
class DataWarehouseConnectionError(DataEngineeringError):
|
|
84
|
+
"""Raised when a connection to a data warehouse cannot be established."""
|
|
85
|
+
|
|
86
|
+
def __init__(self, warehouse: str, message: Optional[str] = None) -> None:
|
|
87
|
+
"""Initialize DataWarehouseConnectionError.
|
|
88
|
+
|
|
89
|
+
Args:
|
|
90
|
+
warehouse: Identifier of the data warehouse.
|
|
91
|
+
message: Optional custom error message.
|
|
92
|
+
"""
|
|
93
|
+
self.warehouse = warehouse
|
|
94
|
+
default = f"Failed to connect to warehouse '{warehouse}'"
|
|
95
|
+
super().__init__(message or default)
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
class MissingPartitionError(DataEngineeringError):
|
|
99
|
+
"""Raised when a required data partition is missing."""
|
|
100
|
+
|
|
101
|
+
def __init__(
|
|
102
|
+
self, partition: str, location: str, message: Optional[str] = None
|
|
103
|
+
) -> None:
|
|
104
|
+
"""Initialize MissingPartitionError.
|
|
105
|
+
|
|
106
|
+
Args:
|
|
107
|
+
partition: Name of the missing partition.
|
|
108
|
+
location: Data location checked for the partition.
|
|
109
|
+
message: Optional custom error message.
|
|
110
|
+
"""
|
|
111
|
+
self.partition = partition
|
|
112
|
+
self.location = location
|
|
113
|
+
default = f"Partition '{partition}' not found at {location}"
|
|
114
|
+
super().__init__(message or default)
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
__all__ = [
|
|
118
|
+
"DataEngineeringError",
|
|
119
|
+
"ETLJobError",
|
|
120
|
+
"SchemaEvolutionError",
|
|
121
|
+
"DataTransformationError",
|
|
122
|
+
"BatchProcessingError",
|
|
123
|
+
"DataWarehouseConnectionError",
|
|
124
|
+
"MissingPartitionError",
|
|
125
|
+
]
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
"""Custom exceptions for data science workflows."""
|
|
2
|
+
|
|
3
|
+
from .base import DataScienceError
|
|
4
|
+
from .ingestion import (
|
|
5
|
+
DataAugmentationError,
|
|
6
|
+
DataFormatError,
|
|
7
|
+
DataImbalanceError,
|
|
8
|
+
DataLeakageError,
|
|
9
|
+
DataLoadingError,
|
|
10
|
+
DataNormalizationError,
|
|
11
|
+
DataValidationError,
|
|
12
|
+
FeatureEngineeringError,
|
|
13
|
+
MissingDataError,
|
|
14
|
+
OutlierDetectionError,
|
|
15
|
+
SchemaMismatchError,
|
|
16
|
+
)
|
|
17
|
+
from .operations import (
|
|
18
|
+
DataDriftError,
|
|
19
|
+
DataExportError,
|
|
20
|
+
DeploymentError,
|
|
21
|
+
ResourceLimitError,
|
|
22
|
+
SerializationError,
|
|
23
|
+
)
|
|
24
|
+
from .training import (
|
|
25
|
+
BiasDetectionError,
|
|
26
|
+
ConvergenceError,
|
|
27
|
+
CrossValidationError,
|
|
28
|
+
DimensionalityReductionError,
|
|
29
|
+
EarlyStoppingError,
|
|
30
|
+
ExperimentTrackingError,
|
|
31
|
+
ExplainabilityError,
|
|
32
|
+
FeatureScalingError,
|
|
33
|
+
FeatureSelectionError,
|
|
34
|
+
GPUOutOfMemoryError,
|
|
35
|
+
HyperparameterError,
|
|
36
|
+
HyperparameterTuningError,
|
|
37
|
+
ModelCompatibilityError,
|
|
38
|
+
ModelEvaluationError,
|
|
39
|
+
ModelInferenceError,
|
|
40
|
+
ModelTrainingError,
|
|
41
|
+
OverfittingError,
|
|
42
|
+
PredictionError,
|
|
43
|
+
TrainingTimeoutError,
|
|
44
|
+
UnderfittingError,
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
__all__ = [
|
|
48
|
+
"DataScienceError",
|
|
49
|
+
"DataLoadingError",
|
|
50
|
+
"DataFormatError",
|
|
51
|
+
"DataValidationError",
|
|
52
|
+
"MissingDataError",
|
|
53
|
+
"OutlierDetectionError",
|
|
54
|
+
"SchemaMismatchError",
|
|
55
|
+
"FeatureEngineeringError",
|
|
56
|
+
"ModelTrainingError",
|
|
57
|
+
"ConvergenceError",
|
|
58
|
+
"TrainingTimeoutError",
|
|
59
|
+
"HyperparameterError",
|
|
60
|
+
"ModelEvaluationError",
|
|
61
|
+
"PredictionError",
|
|
62
|
+
"SerializationError",
|
|
63
|
+
"DeploymentError",
|
|
64
|
+
"DataDriftError",
|
|
65
|
+
"ResourceLimitError",
|
|
66
|
+
"DataExportError",
|
|
67
|
+
"FeatureSelectionError",
|
|
68
|
+
"DimensionalityReductionError",
|
|
69
|
+
"CrossValidationError",
|
|
70
|
+
"HyperparameterTuningError",
|
|
71
|
+
"ExperimentTrackingError",
|
|
72
|
+
"GPUOutOfMemoryError",
|
|
73
|
+
"DataNormalizationError",
|
|
74
|
+
"DataImbalanceError",
|
|
75
|
+
"ModelInferenceError",
|
|
76
|
+
"DataAugmentationError",
|
|
77
|
+
"DataLeakageError",
|
|
78
|
+
"OverfittingError",
|
|
79
|
+
"UnderfittingError",
|
|
80
|
+
"EarlyStoppingError",
|
|
81
|
+
"BiasDetectionError",
|
|
82
|
+
"ExplainabilityError",
|
|
83
|
+
"FeatureScalingError",
|
|
84
|
+
"ModelCompatibilityError",
|
|
85
|
+
]
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"""Base exception shared across data science errors."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class DataScienceError(Exception):
|
|
7
|
+
"""Base exception for data science errors."""
|
|
8
|
+
|
|
9
|
+
def __init__(self, message: str) -> None:
|
|
10
|
+
# Ensure message is a string
|
|
11
|
+
if not isinstance(message, str):
|
|
12
|
+
raise TypeError(f"message must be str, got {type(message).__name__}")
|
|
13
|
+
self.message = message
|
|
14
|
+
super().__init__(message)
|
|
15
|
+
|
|
16
|
+
def __str__(self) -> str:
|
|
17
|
+
return f"[DataScienceError] {self.message}"
|