dlt-utils-lib 1.2.4__tar.gz → 1.2.5__tar.gz

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 (19) hide show
  1. {dlt_utils_lib-1.2.4 → dlt_utils_lib-1.2.5}/PKG-INFO +1 -1
  2. {dlt_utils_lib-1.2.4 → dlt_utils_lib-1.2.5}/dlt_utils/main_cdc_utils.py +33 -6
  3. {dlt_utils_lib-1.2.4 → dlt_utils_lib-1.2.5}/dlt_utils_lib.egg-info/PKG-INFO +1 -1
  4. {dlt_utils_lib-1.2.4 → dlt_utils_lib-1.2.5}/tests/test_main_cdc_utils.py +81 -10
  5. {dlt_utils_lib-1.2.4 → dlt_utils_lib-1.2.5}/dlt_utils/__init__.py +0 -0
  6. {dlt_utils_lib-1.2.4 → dlt_utils_lib-1.2.5}/dlt_utils/dlt_autoloader_recovery_configuration.py +0 -0
  7. {dlt_utils_lib-1.2.4 → dlt_utils_lib-1.2.5}/dlt_utils/dlt_metadata_receiver.py +0 -0
  8. {dlt_utils_lib-1.2.4 → dlt_utils_lib-1.2.5}/dlt_utils/dlt_transformations.py +0 -0
  9. {dlt_utils_lib-1.2.4 → dlt_utils_lib-1.2.5}/dlt_utils/main_json_utils.py +0 -0
  10. {dlt_utils_lib-1.2.4 → dlt_utils_lib-1.2.5}/dlt_utils_lib.egg-info/SOURCES.txt +0 -0
  11. {dlt_utils_lib-1.2.4 → dlt_utils_lib-1.2.5}/dlt_utils_lib.egg-info/dependency_links.txt +0 -0
  12. {dlt_utils_lib-1.2.4 → dlt_utils_lib-1.2.5}/dlt_utils_lib.egg-info/requires.txt +0 -0
  13. {dlt_utils_lib-1.2.4 → dlt_utils_lib-1.2.5}/dlt_utils_lib.egg-info/top_level.txt +0 -0
  14. {dlt_utils_lib-1.2.4 → dlt_utils_lib-1.2.5}/setup.cfg +0 -0
  15. {dlt_utils_lib-1.2.4 → dlt_utils_lib-1.2.5}/setup.py +0 -0
  16. {dlt_utils_lib-1.2.4 → dlt_utils_lib-1.2.5}/tests/__init__.py +0 -0
  17. {dlt_utils_lib-1.2.4 → dlt_utils_lib-1.2.5}/tests/conftest.py +0 -0
  18. {dlt_utils_lib-1.2.4 → dlt_utils_lib-1.2.5}/tests/test_main_json_utils.py +0 -0
  19. {dlt_utils_lib-1.2.4 → dlt_utils_lib-1.2.5}/tests/test_transformation_module.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: dlt_utils_lib
3
- Version: 1.2.4
3
+ Version: 1.2.5
4
4
  Summary: UNKNOWN
5
5
  Home-page: UNKNOWN
6
6
  License: UNKNOWN
@@ -33,6 +33,29 @@ def _build_table_properties(extra: Optional[dict] = None,
33
33
  return props
34
34
 
35
35
 
36
+ def _pick_latest_source_file(spark, files_path: str) -> Optional[str]:
37
+ """Return the path of the most recently modified source file, or None.
38
+
39
+ Spark's parquet reader cannot mergeSchema across decimal scales
40
+ (e.g. decimal(19,2) vs decimal(19,3) — their integer-digit counts
41
+ differ). Grabbing the newest file's schema instead gives us the
42
+ current source-of-truth type; older files with narrower types are
43
+ read losslessly under that hint by Auto Loader, and Delta type
44
+ widening handles the existing bronze table.
45
+ """
46
+ listing = (
47
+ spark.read.format('binaryFile')
48
+ .load(files_path)
49
+ .select('path', 'modificationTime')
50
+ .orderBy('modificationTime', ascending=False)
51
+ .limit(1)
52
+ .collect()
53
+ )
54
+ if not listing:
55
+ return None
56
+ return listing[0]['path']
57
+
58
+
36
59
  # Base code pipeline: streaming process
37
60
  def base_cdc_replication_process(dlt,
38
61
  spark,
@@ -123,12 +146,16 @@ def create_bronze_table_definition(spark,
123
146
  table_properties=table_properties,
124
147
  )
125
148
  def transform_cdc_to_bronze():
126
- # mergeSchema=true tells the parquet reader to union footers across every
127
- # bronze file, so schemaHints below always reflect the widest observed
128
- # type for each column (e.g. picks decimal(19,3) once the source starts
129
- # writing at that scale, instead of pinning to the first-seen (19,2)
130
- # and rescuing new values to NULL).
131
- df = spark.read.format(file_format).option("mergeSchema", "true").load(files_path).transform(rename_columns)
149
+ # Read the schema from the newest source file so schemaHints tracks
150
+ # the current source-of-truth type after upstream drift (e.g. DMS
151
+ # widening decimal(19,2) -> decimal(19,3)). Directory reads —
152
+ # with or without mergeSchema — fail for scale-changed decimals
153
+ # because Spark can't merge them (integer-digit counts differ).
154
+ # Older files with narrower types cast up losslessly under this
155
+ # hint, and delta.enableTypeWidening handles the bronze table.
156
+ latest_file = _pick_latest_source_file(spark, files_path)
157
+ schema_source_path = latest_file if latest_file else files_path
158
+ df = spark.read.format(file_format).load(schema_source_path).transform(rename_columns)
132
159
  fields = [field for field in df.schema.fields if field.name not in schema_exclude_columns]
133
160
  schema_string = ', '.join([f"{field.name} {field.dataType.simpleString()}" for field in fields])
134
161
  reader = spark \
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: dlt-utils-lib
3
- Version: 1.2.4
3
+ Version: 1.2.5
4
4
  Summary: UNKNOWN
5
5
  Home-page: UNKNOWN
6
6
  License: UNKNOWN
@@ -140,7 +140,7 @@ def test_silver_streaming_process_merges_extra_table_properties(monkeypatch):
140
140
  assert table_properties["my.custom.prop"] == "42"
141
141
 
142
142
 
143
- def test_create_bronze_table_definition_reads_schema_with_merge_and_enables_type_widening():
143
+ def test_create_bronze_table_definition_reads_schema_from_latest_file_and_enables_type_widening(monkeypatch):
144
144
  class _DT:
145
145
  def __init__(self, t):
146
146
  self._t = t
@@ -164,25 +164,28 @@ def test_create_bronze_table_definition_reads_schema_with_merge_and_enables_type
164
164
  def transform(self, _fn):
165
165
  return self
166
166
 
167
- calls = {"options": {}, "format": None}
167
+ calls = {"load_paths": [], "format": None}
168
168
 
169
169
  class _Reader:
170
170
  def format(self, fmt):
171
171
  calls["format"] = fmt
172
172
  return self
173
173
 
174
- def option(self, key, value):
175
- calls["options"][key] = value
176
- return self
177
-
178
- def load(self, _path):
179
- # After a mergeSchema=true read the parquet reader would union
180
- # decimal(19,2) and decimal(19,3) footers into decimal(19,3).
174
+ def load(self, path):
175
+ calls["load_paths"].append(path)
176
+ # The picked "latest" file reports decimal(19,3), the current
177
+ # source-of-truth after upstream drift.
181
178
  return _DF([_Field("pnl", "decimal(19,3)"), _Field("id", "bigint")])
182
179
 
183
180
  spark = type("_Spark", (), {})()
184
181
  spark.read = _Reader()
185
182
 
183
+ monkeypatch.setattr(
184
+ main_cdc_utils,
185
+ "_pick_latest_source_file",
186
+ lambda _spark, _files_path: "s3://bucket/dms_bronze/artemis/settled_bet/20260701-090901614.parquet",
187
+ )
188
+
186
189
  captured = {}
187
190
 
188
191
  class _Dlt:
@@ -218,5 +221,73 @@ def test_create_bronze_table_definition_reads_schema_with_merge_and_enables_type
218
221
  pass
219
222
 
220
223
  assert calls["format"] == "parquet"
221
- assert calls["options"].get("mergeSchema") == "true"
224
+ assert calls["load_paths"] == [
225
+ "s3://bucket/dms_bronze/artemis/settled_bet/20260701-090901614.parquet"
226
+ ]
222
227
  assert captured["kwargs"]["table_properties"]["delta.enableTypeWidening"] == "true"
228
+
229
+
230
+ def test_create_bronze_table_definition_falls_back_to_directory_read_when_listing_empty(monkeypatch):
231
+ class _DT:
232
+ def simpleString(self):
233
+ return "bigint"
234
+
235
+ class _Field:
236
+ def __init__(self, name):
237
+ self.name = name
238
+ self.dataType = _DT()
239
+
240
+ class _DF:
241
+ def __init__(self):
242
+ class _Schema:
243
+ pass
244
+
245
+ self.schema = _Schema()
246
+ self.schema.fields = [_Field("id")]
247
+
248
+ def transform(self, _fn):
249
+ return self
250
+
251
+ load_paths = []
252
+
253
+ class _Reader:
254
+ def format(self, _fmt):
255
+ return self
256
+
257
+ def load(self, path):
258
+ load_paths.append(path)
259
+ return _DF()
260
+
261
+ spark = type("_Spark", (), {})()
262
+ spark.read = _Reader()
263
+
264
+ monkeypatch.setattr(main_cdc_utils, "_pick_latest_source_file", lambda *_: None)
265
+
266
+ class _Dlt:
267
+ def table(self, **_kwargs):
268
+ def _decorator(fn):
269
+ _Dlt.fn = staticmethod(fn)
270
+ return fn
271
+
272
+ return _decorator
273
+
274
+ main_cdc_utils.create_bronze_table_definition(
275
+ spark=spark,
276
+ dlt=_Dlt(),
277
+ source_table_name="settled_bet",
278
+ table_name="apollo.artemis.bronze_settled_bet",
279
+ files_path="s3://bucket/dms_bronze/artemis/settled_bet/",
280
+ file_format="parquet",
281
+ partitions=[],
282
+ schema_exclude_columns=[],
283
+ time_diff_history_cdc_timestamp=30,
284
+ default_value_for_removed_col={},
285
+ use_managed_file_events=False,
286
+ )
287
+
288
+ try:
289
+ _Dlt.fn()
290
+ except AttributeError:
291
+ pass
292
+
293
+ assert load_paths == ["s3://bucket/dms_bronze/artemis/settled_bet/"]
File without changes
File without changes