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 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}"