datacoolie 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.
Files changed (68) hide show
  1. datacoolie/__init__.py +242 -0
  2. datacoolie/core/__init__.py +81 -0
  3. datacoolie/core/constants.py +223 -0
  4. datacoolie/core/exceptions.py +114 -0
  5. datacoolie/core/models.py +1202 -0
  6. datacoolie/core/registry.py +208 -0
  7. datacoolie/core/secret_provider.py +233 -0
  8. datacoolie/core/secret_resolver.py +125 -0
  9. datacoolie/destinations/__init__.py +37 -0
  10. datacoolie/destinations/base.py +383 -0
  11. datacoolie/destinations/delta_writer.py +377 -0
  12. datacoolie/destinations/file_writer.py +143 -0
  13. datacoolie/destinations/iceberg_writer.py +167 -0
  14. datacoolie/destinations/load_strategies.py +323 -0
  15. datacoolie/engines/__init__.py +28 -0
  16. datacoolie/engines/base.py +1038 -0
  17. datacoolie/engines/polars_engine.py +2768 -0
  18. datacoolie/engines/spark_engine.py +1777 -0
  19. datacoolie/engines/spark_session_builder.py +78 -0
  20. datacoolie/logging/__init__.py +48 -0
  21. datacoolie/logging/base.py +561 -0
  22. datacoolie/logging/context.py +37 -0
  23. datacoolie/logging/etl_logger.py +769 -0
  24. datacoolie/logging/system_logger.py +110 -0
  25. datacoolie/metadata/__init__.py +34 -0
  26. datacoolie/metadata/api_client.py +632 -0
  27. datacoolie/metadata/base.py +743 -0
  28. datacoolie/metadata/database_provider.py +677 -0
  29. datacoolie/metadata/file_provider.py +757 -0
  30. datacoolie/orchestration/__init__.py +23 -0
  31. datacoolie/orchestration/driver.py +910 -0
  32. datacoolie/orchestration/job_distributor.py +147 -0
  33. datacoolie/orchestration/parallel_executor.py +326 -0
  34. datacoolie/orchestration/retry_handler.py +149 -0
  35. datacoolie/orchestration/utils.py +52 -0
  36. datacoolie/platforms/__init__.py +15 -0
  37. datacoolie/platforms/aws_platform.py +665 -0
  38. datacoolie/platforms/base.py +313 -0
  39. datacoolie/platforms/databricks_platform.py +630 -0
  40. datacoolie/platforms/fabric_platform.py +365 -0
  41. datacoolie/platforms/local_platform.py +294 -0
  42. datacoolie/sources/__init__.py +24 -0
  43. datacoolie/sources/api_reader.py +1270 -0
  44. datacoolie/sources/base.py +425 -0
  45. datacoolie/sources/database_reader.py +237 -0
  46. datacoolie/sources/delta_reader.py +94 -0
  47. datacoolie/sources/file_reader.py +440 -0
  48. datacoolie/sources/iceberg_reader.py +75 -0
  49. datacoolie/sources/python_function_reader.py +179 -0
  50. datacoolie/transformers/__init__.py +29 -0
  51. datacoolie/transformers/base.py +198 -0
  52. datacoolie/transformers/column_adder.py +124 -0
  53. datacoolie/transformers/column_name_sanitizer.py +89 -0
  54. datacoolie/transformers/deduplicator.py +84 -0
  55. datacoolie/transformers/partition_handler.py +76 -0
  56. datacoolie/transformers/schema_converter.py +107 -0
  57. datacoolie/utils/__init__.py +45 -0
  58. datacoolie/utils/converters.py +261 -0
  59. datacoolie/utils/helpers.py +165 -0
  60. datacoolie/utils/path_utils.py +60 -0
  61. datacoolie/watermark/__init__.py +19 -0
  62. datacoolie/watermark/base.py +172 -0
  63. datacoolie/watermark/watermark_manager.py +69 -0
  64. datacoolie-0.1.0.dist-info/METADATA +200 -0
  65. datacoolie-0.1.0.dist-info/RECORD +68 -0
  66. datacoolie-0.1.0.dist-info/WHEEL +4 -0
  67. datacoolie-0.1.0.dist-info/entry_points.txt +44 -0
  68. datacoolie-0.1.0.dist-info/licenses/LICENSE +661 -0
datacoolie/__init__.py ADDED
@@ -0,0 +1,242 @@
1
+ """DataCoolie — Metadata-driven, engine-unified, cloud-agnostic ETL framework."""
2
+
3
+ __version__ = "0.1.0"
4
+
5
+ __all__ = [
6
+ # Base classes
7
+ "PluginRegistry",
8
+ "BaseSecretResolver",
9
+ "BaseEngine",
10
+ "BasePlatform",
11
+ "BaseSourceReader",
12
+ "BaseDestinationWriter",
13
+ "BaseTransformer",
14
+ # Registries
15
+ "engine_registry",
16
+ "platform_registry",
17
+ "source_registry",
18
+ "destination_registry",
19
+ "transformer_registry",
20
+ "resolver_registry",
21
+ # Factory functions
22
+ "create_engine",
23
+ "create_platform",
24
+ "create_source",
25
+ "create_destination",
26
+ "create_transformer",
27
+ "create_resolver",
28
+ ]
29
+
30
+ import logging as _logging
31
+
32
+ from datacoolie.core.registry import PluginRegistry
33
+ from datacoolie.core.secret_resolver import BaseSecretResolver
34
+ from datacoolie.destinations.base import BaseDestinationWriter
35
+ from datacoolie.engines.base import BaseEngine
36
+ from datacoolie.platforms.base import BasePlatform
37
+ from datacoolie.sources.base import BaseSourceReader
38
+ from datacoolie.transformers.base import BaseTransformer
39
+
40
+ _logger = _logging.getLogger(__name__)
41
+
42
+ # ---------------------------------------------------------------------------
43
+ # Global plugin registries
44
+ # ---------------------------------------------------------------------------
45
+
46
+ engine_registry: PluginRegistry[BaseEngine] = PluginRegistry("datacoolie.engines", BaseEngine)
47
+ platform_registry: PluginRegistry[BasePlatform] = PluginRegistry("datacoolie.platforms", BasePlatform)
48
+ source_registry: PluginRegistry[BaseSourceReader] = PluginRegistry("datacoolie.sources", BaseSourceReader)
49
+ destination_registry: PluginRegistry[BaseDestinationWriter] = PluginRegistry("datacoolie.destinations", BaseDestinationWriter)
50
+ transformer_registry: PluginRegistry[BaseTransformer] = PluginRegistry("datacoolie.transformers", BaseTransformer)
51
+ resolver_registry: PluginRegistry[BaseSecretResolver] = PluginRegistry("datacoolie.resolvers", BaseSecretResolver)
52
+
53
+
54
+ # ---------------------------------------------------------------------------
55
+ # Factory functions
56
+ # ---------------------------------------------------------------------------
57
+
58
+ def create_engine(name: str, **kwargs: object) -> BaseEngine:
59
+ """Create an engine instance by name (e.g. ``"spark"``, ``"polars"``)."""
60
+ return engine_registry.get(name, **kwargs)
61
+
62
+
63
+ def create_platform(name: str, **kwargs: object) -> BasePlatform:
64
+ """Create a platform instance by name (e.g. ``"local"``, ``"fabric"``)."""
65
+ return platform_registry.get(name, **kwargs)
66
+
67
+
68
+ def create_source(name: str, **kwargs: object) -> BaseSourceReader:
69
+ """Create a source reader by name (e.g. ``"delta"``, ``"csv"``)."""
70
+ return source_registry.get(name, **kwargs)
71
+
72
+
73
+ def create_destination(name: str, **kwargs: object) -> BaseDestinationWriter:
74
+ """Create a destination writer by name (e.g. ``"delta"``, ``"parquet"``)."""
75
+ return destination_registry.get(name, **kwargs)
76
+
77
+
78
+ def create_transformer(name: str, **kwargs: object) -> BaseTransformer:
79
+ """Create a transformer by name (e.g. ``"schema_converter"``)."""
80
+ return transformer_registry.get(name, **kwargs)
81
+
82
+
83
+ def create_resolver(name: str, **kwargs: object) -> BaseSecretResolver:
84
+ """Create a secret resolver by name (e.g. ``"env"``)."""
85
+ return resolver_registry.get(name, **kwargs)
86
+
87
+
88
+ # ---------------------------------------------------------------------------
89
+ # Built-in registrations (after all packages are initialised)
90
+ # ---------------------------------------------------------------------------
91
+
92
+ def _register_builtins() -> None:
93
+ """Register all built-in plugins.
94
+
95
+ Uses try/except for each registration so that missing optional
96
+ dependencies (e.g. PySpark, Polars) don't break the import.
97
+ """
98
+ # -- Engines --
99
+ try:
100
+ from datacoolie.engines.spark_engine import SparkEngine
101
+ engine_registry.register("spark", SparkEngine)
102
+ except Exception as exc:
103
+ _logger.debug("Skipping built-in 'spark' engine: %s", exc)
104
+
105
+ try:
106
+ from datacoolie.engines.polars_engine import PolarsEngine
107
+ engine_registry.register("polars", PolarsEngine)
108
+ except Exception as exc:
109
+ _logger.debug("Skipping built-in 'polars' engine: %s", exc)
110
+
111
+ # -- Platforms --
112
+ try:
113
+ from datacoolie.platforms.local_platform import LocalPlatform
114
+ platform_registry.register("local", LocalPlatform)
115
+ except Exception as exc:
116
+ _logger.debug("Skipping built-in 'local' platform: %s", exc)
117
+
118
+ try:
119
+ from datacoolie.platforms.fabric_platform import FabricPlatform
120
+ platform_registry.register("fabric", FabricPlatform)
121
+ except Exception as exc:
122
+ _logger.debug("Skipping built-in 'fabric' platform: %s", exc)
123
+
124
+ try:
125
+ from datacoolie.platforms.databricks_platform import DatabricksPlatform
126
+ platform_registry.register("databricks", DatabricksPlatform)
127
+ except Exception as exc:
128
+ _logger.debug("Skipping built-in 'databricks' platform: %s", exc)
129
+
130
+ try:
131
+ from datacoolie.platforms.aws_platform import AWSPlatform
132
+ platform_registry.register("aws", AWSPlatform)
133
+ except Exception as exc:
134
+ _logger.debug("Skipping built-in 'aws' platform: %s", exc)
135
+
136
+ # -- Sources --
137
+ try:
138
+ from datacoolie.sources.delta_reader import DeltaReader
139
+ source_registry.register("delta", DeltaReader)
140
+ except Exception as exc:
141
+ _logger.debug("Skipping built-in 'delta' source: %s", exc)
142
+
143
+ try:
144
+ from datacoolie.sources.file_reader import FileReader
145
+ source_registry.register("parquet", FileReader)
146
+ source_registry.register("csv", FileReader)
147
+ source_registry.register("json", FileReader)
148
+ source_registry.register("jsonl", FileReader)
149
+ source_registry.register("avro", FileReader)
150
+ source_registry.register("excel", FileReader)
151
+ except Exception as exc:
152
+ _logger.debug("Skipping built-in file source readers: %s", exc)
153
+
154
+ try:
155
+ from datacoolie.sources.python_function_reader import PythonFunctionReader
156
+ source_registry.register("function", PythonFunctionReader)
157
+ except Exception as exc:
158
+ _logger.debug("Skipping built-in 'function' source: %s", exc)
159
+
160
+ try:
161
+ from datacoolie.sources.database_reader import DatabaseReader
162
+ source_registry.register("sql", DatabaseReader)
163
+ except Exception as exc:
164
+ _logger.debug("Skipping built-in 'sql' source: %s", exc)
165
+
166
+ try:
167
+ from datacoolie.sources.iceberg_reader import IcebergReader
168
+ source_registry.register("iceberg", IcebergReader)
169
+ except Exception as exc:
170
+ _logger.debug("Skipping built-in 'iceberg' source: %s", exc)
171
+
172
+ try:
173
+ from datacoolie.sources.api_reader import APIReader
174
+ source_registry.register("api", APIReader)
175
+ except Exception as exc:
176
+ _logger.debug("Skipping built-in 'api' source: %s", exc)
177
+
178
+ # -- Destinations --
179
+ try:
180
+ from datacoolie.destinations.delta_writer import DeltaWriter
181
+ destination_registry.register("delta", DeltaWriter)
182
+ except Exception as exc:
183
+ _logger.debug("Skipping built-in 'delta' destination: %s", exc)
184
+
185
+ try:
186
+ from datacoolie.destinations.file_writer import FileWriter
187
+ destination_registry.register("parquet", FileWriter)
188
+ destination_registry.register("csv", FileWriter)
189
+ destination_registry.register("json", FileWriter)
190
+ destination_registry.register("jsonl", FileWriter)
191
+ destination_registry.register("avro", FileWriter)
192
+ except Exception as exc:
193
+ _logger.debug("Skipping built-in file destination writers: %s", exc)
194
+
195
+ try:
196
+ from datacoolie.destinations.iceberg_writer import IcebergWriter
197
+ destination_registry.register("iceberg", IcebergWriter)
198
+ except Exception as exc:
199
+ _logger.debug("Skipping built-in 'iceberg' destination: %s", exc)
200
+
201
+ # -- Transformers --
202
+ try:
203
+ from datacoolie.transformers.schema_converter import SchemaConverter
204
+ transformer_registry.register("schema_converter", SchemaConverter)
205
+ except Exception as exc:
206
+ _logger.debug("Skipping built-in 'schema_converter' transformer: %s", exc)
207
+
208
+ try:
209
+ from datacoolie.transformers.deduplicator import Deduplicator
210
+ transformer_registry.register("deduplicator", Deduplicator)
211
+ except Exception as exc:
212
+ _logger.debug("Skipping built-in 'deduplicator' transformer: %s", exc)
213
+
214
+ try:
215
+ from datacoolie.transformers.column_adder import ColumnAdder, SCD2ColumnAdder, SystemColumnAdder
216
+ transformer_registry.register("column_adder", ColumnAdder)
217
+ transformer_registry.register("scd2_column_adder", SCD2ColumnAdder)
218
+ transformer_registry.register("system_column_adder", SystemColumnAdder)
219
+ except Exception as exc:
220
+ _logger.debug("Skipping built-in column adder transformers: %s", exc)
221
+
222
+ try:
223
+ from datacoolie.transformers.partition_handler import PartitionHandler
224
+ transformer_registry.register("partition_handler", PartitionHandler)
225
+ except Exception as exc:
226
+ _logger.debug("Skipping built-in 'partition_handler' transformer: %s", exc)
227
+
228
+ try:
229
+ from datacoolie.transformers.column_name_sanitizer import ColumnNameSanitizer
230
+ transformer_registry.register("column_name_sanitizer", ColumnNameSanitizer)
231
+ except Exception as exc:
232
+ _logger.debug("Skipping built-in 'column_name_sanitizer' transformer: %s", exc)
233
+
234
+ # -- Secret Resolvers --
235
+ try:
236
+ from datacoolie.core.secret_resolver import EnvResolver
237
+ resolver_registry.register("env", EnvResolver)
238
+ except Exception as exc:
239
+ _logger.debug("Skipping built-in 'env' resolver: %s", exc)
240
+
241
+
242
+ _register_builtins()
@@ -0,0 +1,81 @@
1
+ """DataCoolie core — domain models, constants, exceptions, and plugin registry."""
2
+
3
+ from datacoolie.core.constants import (
4
+ ConnectionType,
5
+ DataFlowStatus,
6
+ ExecutionType,
7
+ DatabaseType,
8
+ Format,
9
+ LoadType,
10
+ LogPurpose,
11
+ LogType,
12
+ MaintenanceType,
13
+ ProcessingMode,
14
+ ColumnCaseMode,
15
+ SystemColumn,
16
+ FileInfoColumn,
17
+ )
18
+ from datacoolie.core.exceptions import (
19
+ ConfigurationError,
20
+ DataCoolieError,
21
+ DataFlowError,
22
+ DestinationError,
23
+ EngineError,
24
+ MetadataError,
25
+ PlatformError,
26
+ SourceError,
27
+ TransformError,
28
+ WatermarkError,
29
+ )
30
+ from datacoolie.core.models import (
31
+ SchemaHint,
32
+ PartitionColumn,
33
+ AdditionalColumn,
34
+ Connection,
35
+ Source,
36
+ Destination,
37
+ Transform,
38
+ DataFlow,
39
+ DataCoolieRunConfig,
40
+ )
41
+ from datacoolie.core.registry import PluginRegistry
42
+
43
+ __all__ = [
44
+ # Enums
45
+ "ConnectionType",
46
+ "DataFlowStatus",
47
+ "ExecutionType",
48
+ "DatabaseType",
49
+ "Format",
50
+ "LoadType",
51
+ "LogPurpose",
52
+ "LogType",
53
+ "MaintenanceType",
54
+ "ProcessingMode",
55
+ "ColumnCaseMode",
56
+ "SystemColumn",
57
+ "FileInfoColumn",
58
+ # Exceptions
59
+ "ConfigurationError",
60
+ "DataCoolieError",
61
+ "DataFlowError",
62
+ "DestinationError",
63
+ "EngineError",
64
+ "MetadataError",
65
+ "PlatformError",
66
+ "SourceError",
67
+ "TransformError",
68
+ "WatermarkError",
69
+ # Models
70
+ "SchemaHint",
71
+ "PartitionColumn",
72
+ "AdditionalColumn",
73
+ "Connection",
74
+ "Source",
75
+ "Destination",
76
+ "Transform",
77
+ "DataFlow",
78
+ "DataCoolieRunConfig",
79
+ # Registry
80
+ "PluginRegistry",
81
+ ]
@@ -0,0 +1,223 @@
1
+ """Framework-wide enums and constants for the DataCoolie framework.
2
+
3
+ Every enum inherits from ``str`` so its ``.value`` is a plain string
4
+ that serialises naturally to JSON/YAML and matches database values.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import os
10
+ from enum import Enum
11
+ from typing import Dict, List
12
+
13
+
14
+ # ---------------------------------------------------------------------------
15
+ # Enums
16
+ # ---------------------------------------------------------------------------
17
+
18
+ class LoadType(str, Enum):
19
+ """Supported load (write) strategies."""
20
+
21
+ FULL_LOAD = "full_load"
22
+ OVERWRITE = "overwrite"
23
+ APPEND = "append"
24
+ MERGE_UPSERT = "merge_upsert"
25
+ MERGE_OVERWRITE = "merge_overwrite"
26
+ SCD2 = "scd2"
27
+
28
+
29
+ class Format(str, Enum):
30
+ """Supported data formats."""
31
+
32
+ DELTA = "delta"
33
+ ICEBERG = "iceberg"
34
+ PARQUET = "parquet"
35
+ CSV = "csv"
36
+ JSON = "json"
37
+ JSONL = "jsonl"
38
+ AVRO = "avro"
39
+ EXCEL = "excel"
40
+ SQL = "sql"
41
+ API = "api"
42
+ FUNCTION = "function"
43
+
44
+
45
+ class ConnectionType(str, Enum):
46
+ """Connection endpoint categories."""
47
+
48
+ LAKEHOUSE = "lakehouse"
49
+ FILE = "file"
50
+ DATABASE = "database"
51
+ API = "api"
52
+ STREAMING = "streaming"
53
+ FUNCTION = "function"
54
+
55
+
56
+ # ---------------------------------------------------------------------------
57
+ # Connection type ↔ format mapping
58
+ # ---------------------------------------------------------------------------
59
+
60
+ CONNECTION_TYPE_FORMATS: Dict[str, frozenset] = {
61
+ ConnectionType.FILE.value: frozenset({
62
+ Format.PARQUET.value,
63
+ Format.CSV.value,
64
+ Format.JSON.value,
65
+ Format.JSONL.value,
66
+ Format.AVRO.value,
67
+ Format.EXCEL.value,
68
+ }),
69
+ ConnectionType.LAKEHOUSE.value: frozenset({
70
+ Format.DELTA.value,
71
+ Format.ICEBERG.value,
72
+ }),
73
+ ConnectionType.DATABASE.value: frozenset({
74
+ Format.SQL.value,
75
+ }),
76
+ ConnectionType.API.value: frozenset({
77
+ Format.API.value,
78
+ }),
79
+ ConnectionType.FUNCTION.value: frozenset({
80
+ Format.FUNCTION.value,
81
+ }),
82
+ ConnectionType.STREAMING.value: frozenset(
83
+
84
+ ),
85
+ }
86
+
87
+
88
+ class ProcessingMode(str, Enum):
89
+ """ETL processing modes."""
90
+
91
+ BATCH = "batch"
92
+ MICROBATCH = "microbatch"
93
+ STREAMING = "streaming"
94
+
95
+
96
+ class DataFlowStatus(str, Enum):
97
+ """Dataflow execution statuses."""
98
+
99
+ PENDING = "pending"
100
+ RUNNING = "running"
101
+ SUCCEEDED = "succeeded"
102
+ FAILED = "failed"
103
+ SKIPPED = "skipped"
104
+
105
+
106
+ class ExecutionType(str, Enum):
107
+ """ETL execution types."""
108
+
109
+ ETL = "etl"
110
+ MAINTENANCE = "maintenance"
111
+
112
+
113
+ class DatabaseType(str, Enum):
114
+ """Supported database types for connection URL generation."""
115
+
116
+ POSTGRESQL = "postgresql"
117
+ MYSQL = "mysql"
118
+ MSSQL = "mssql"
119
+ ORACLE = "oracle"
120
+ SQLITE = "sqlite"
121
+
122
+
123
+ class MaintenanceType(str, Enum):
124
+ """Maintenance operation types."""
125
+
126
+ COMPACT = "compact"
127
+ CLEANUP = "cleanup"
128
+
129
+
130
+ class ColumnCaseMode(str, Enum):
131
+ """Column name case-conversion modes used by :class:`ColumnNameSanitizer`."""
132
+
133
+ LOWER = "lower"
134
+ SNAKE = "snake"
135
+
136
+
137
+ class LogType(str, Enum):
138
+ """Log entry types for structured logging."""
139
+
140
+ JOB_RUN_LOG = "job_run_log"
141
+ DATAFLOW_RUN_LOG = "dataflow_run_log"
142
+
143
+
144
+ class LogPurpose(str, Enum):
145
+ """Log output purposes / formats."""
146
+
147
+ DEBUG = "debug_json"
148
+ ANALYST = "analyst"
149
+
150
+
151
+ # ---------------------------------------------------------------------------
152
+ # System columns (auto-added to destination data)
153
+ # ---------------------------------------------------------------------------
154
+
155
+ class SystemColumn(str, Enum):
156
+ """Column names automatically added to every destination write."""
157
+
158
+ CREATED_AT = "__created_at"
159
+ UPDATED_AT = "__updated_at"
160
+ UPDATED_BY = "__updated_by"
161
+
162
+
163
+ class FileInfoColumn(str, Enum):
164
+ """Column names injected for file-based sources."""
165
+
166
+ FILE_NAME = "__file_name"
167
+ FILE_PATH = "__file_path"
168
+ FILE_MODIFICATION_TIME = "__file_modification_time"
169
+
170
+
171
+ class SCD2Column(str, Enum):
172
+ """Column names for Slowly Changing Dimension Type 2."""
173
+
174
+ VALID_FROM = "__valid_from"
175
+ VALID_TO = "__valid_to"
176
+ IS_CURRENT = "__is_current"
177
+
178
+
179
+ # Canonical order for columns that should always appear at the end of a
180
+ # DataFrame / table schema. Shared by the transformer pipeline
181
+ # (``ColumnNameSanitizer``) and the Iceberg adapter
182
+ # (``_reorder_iceberg_trailing_columns``).
183
+ TRAILING_COLUMNS: tuple[str, ...] = (
184
+ *(c.value for c in SCD2Column),
185
+ *(c.value for c in FileInfoColumn),
186
+ *(c.value for c in SystemColumn),
187
+ )
188
+
189
+ # ---------------------------------------------------------------------------
190
+ # Watermark
191
+ # ---------------------------------------------------------------------------
192
+
193
+ WATERMARK_FILE_NAME: str = "watermark_value.json"
194
+ DATETIME_PATTERN: str = "__datetime__"
195
+ DATE_PATTERN: str = "__date__"
196
+ TIME_PATTERN: str = "__time__"
197
+ DATE_FOLDER_PARTITION_KEY: str = "__date_folder_partition__"
198
+
199
+ # ---------------------------------------------------------------------------
200
+ # Date folder partition placeholders
201
+ # ---------------------------------------------------------------------------
202
+
203
+ DATE_PLACEHOLDERS: Dict[str, tuple[str, str]] = {
204
+ "{year}": (r"(\d{4})", "year"),
205
+ "{month}": (r"(\d{2})", "month"),
206
+ "{day}": (r"(\d{2})", "day"),
207
+ "{hour}": (r"(\d{2})", "hour"),
208
+ }
209
+
210
+ # ---------------------------------------------------------------------------
211
+ # Defaults
212
+ # ---------------------------------------------------------------------------
213
+
214
+ # Log partition pattern default is date-based, but can be overridden by config
215
+ DEFAULT_PARTITION_PATTERN = "__run_date={year}-{month}-{day}"
216
+
217
+ # Default values for various parameters across the framework
218
+ DEFAULT_AUTHOR: str = "DataCoolie"
219
+ DEFAULT_MAX_WORKERS: int = 8
220
+ DEFAULT_RETRY_COUNT: int = 0
221
+ DEFAULT_RETRY_DELAY: float = 5.0
222
+ DEFAULT_RETENTION_HOURS: int = 168 # 7 days
223
+
@@ -0,0 +1,114 @@
1
+ """Exception hierarchy for the DataCoolie framework.
2
+
3
+ All framework exceptions inherit from :class:`DataCoolieError` which carries
4
+ a human-readable ``message`` and an optional ``details`` dict for structured
5
+ error context.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ from typing import Any
11
+
12
+
13
+ class DataCoolieError(Exception):
14
+ """Base exception for all DataCoolie errors.
15
+
16
+ Attributes:
17
+ message: Human-readable error description.
18
+ details: Optional structured context for logging / debugging.
19
+ """
20
+
21
+ def __init__(self, message: str, details: dict[str, Any] | None = None) -> None:
22
+ self.message = message
23
+ self.details = details or {}
24
+ super().__init__(self.message)
25
+
26
+ def __str__(self) -> str:
27
+ if self.details:
28
+ return f"{self.message} | Details: {self.details}"
29
+ return self.message
30
+
31
+
32
+ # ---------------------------------------------------------------------------
33
+ # Component-specific errors
34
+ # ---------------------------------------------------------------------------
35
+
36
+
37
+ class ConfigurationError(DataCoolieError):
38
+ """Raised for invalid or missing configuration."""
39
+
40
+
41
+ class MetadataError(DataCoolieError):
42
+ """Raised for metadata loading / parsing failures."""
43
+
44
+
45
+ class SourceError(DataCoolieError):
46
+ """Raised when reading from a data source fails."""
47
+
48
+
49
+ class TransformError(DataCoolieError):
50
+ """Raised when a transformation step fails."""
51
+
52
+
53
+ class DestinationError(DataCoolieError):
54
+ """Raised when writing to a destination fails."""
55
+
56
+
57
+ class WatermarkError(DataCoolieError):
58
+ """Raised for watermark read / write / parse failures."""
59
+
60
+
61
+ class EngineError(DataCoolieError):
62
+ """Raised for engine-level failures (Spark, Polars)."""
63
+
64
+
65
+ class PlatformError(DataCoolieError):
66
+ """Raised for platform-specific failures (Fabric, Databricks, AWS)."""
67
+
68
+
69
+ class PipelineError(DataCoolieError):
70
+ """Raised when a pipeline step fails, carrying partial runtime results.
71
+
72
+ Attributes:
73
+ partial_result: The incomplete result tuple collected before the failure.
74
+ """
75
+
76
+ def __init__(
77
+ self,
78
+ message: str,
79
+ partial_result: Any = None,
80
+ details: dict[str, Any] | None = None,
81
+ ) -> None:
82
+ super().__init__(message, details)
83
+ self.partial_result = partial_result
84
+
85
+
86
+ class DataFlowError(DataCoolieError):
87
+ """Raised for dataflow-level execution errors.
88
+
89
+ Attributes:
90
+ dataflow_id: Identifier of the failed dataflow.
91
+ stage: Pipeline stage where the error occurred.
92
+ """
93
+
94
+ def __init__(
95
+ self,
96
+ message: str,
97
+ dataflow_id: str | None = None,
98
+ stage: str | None = None,
99
+ details: dict[str, Any] | None = None,
100
+ ) -> None:
101
+ self.dataflow_id = dataflow_id
102
+ self.stage = stage
103
+ super().__init__(message, details)
104
+
105
+ def __str__(self) -> str:
106
+ parts = [self.message]
107
+ if self.dataflow_id:
108
+ parts.append(f"DataFlow: {self.dataflow_id}")
109
+ if self.stage:
110
+ parts.append(f"Stage: {self.stage}")
111
+ if self.details:
112
+ parts.append(f"Details: {self.details}")
113
+ return " | ".join(parts)
114
+