dlt-utils-lib 1.2.5__tar.gz → 1.2.6__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.5 → dlt_utils_lib-1.2.6}/PKG-INFO +1 -1
  2. {dlt_utils_lib-1.2.5 → dlt_utils_lib-1.2.6}/dlt_utils/main_cdc_utils.py +21 -12
  3. {dlt_utils_lib-1.2.5 → dlt_utils_lib-1.2.6}/dlt_utils_lib.egg-info/PKG-INFO +1 -1
  4. {dlt_utils_lib-1.2.5 → dlt_utils_lib-1.2.6}/tests/test_main_cdc_utils.py +51 -83
  5. {dlt_utils_lib-1.2.5 → dlt_utils_lib-1.2.6}/dlt_utils/__init__.py +0 -0
  6. {dlt_utils_lib-1.2.5 → dlt_utils_lib-1.2.6}/dlt_utils/dlt_autoloader_recovery_configuration.py +0 -0
  7. {dlt_utils_lib-1.2.5 → dlt_utils_lib-1.2.6}/dlt_utils/dlt_metadata_receiver.py +0 -0
  8. {dlt_utils_lib-1.2.5 → dlt_utils_lib-1.2.6}/dlt_utils/dlt_transformations.py +0 -0
  9. {dlt_utils_lib-1.2.5 → dlt_utils_lib-1.2.6}/dlt_utils/main_json_utils.py +0 -0
  10. {dlt_utils_lib-1.2.5 → dlt_utils_lib-1.2.6}/dlt_utils_lib.egg-info/SOURCES.txt +0 -0
  11. {dlt_utils_lib-1.2.5 → dlt_utils_lib-1.2.6}/dlt_utils_lib.egg-info/dependency_links.txt +0 -0
  12. {dlt_utils_lib-1.2.5 → dlt_utils_lib-1.2.6}/dlt_utils_lib.egg-info/requires.txt +0 -0
  13. {dlt_utils_lib-1.2.5 → dlt_utils_lib-1.2.6}/dlt_utils_lib.egg-info/top_level.txt +0 -0
  14. {dlt_utils_lib-1.2.5 → dlt_utils_lib-1.2.6}/setup.cfg +0 -0
  15. {dlt_utils_lib-1.2.5 → dlt_utils_lib-1.2.6}/setup.py +0 -0
  16. {dlt_utils_lib-1.2.5 → dlt_utils_lib-1.2.6}/tests/__init__.py +0 -0
  17. {dlt_utils_lib-1.2.5 → dlt_utils_lib-1.2.6}/tests/conftest.py +0 -0
  18. {dlt_utils_lib-1.2.5 → dlt_utils_lib-1.2.6}/tests/test_main_json_utils.py +0 -0
  19. {dlt_utils_lib-1.2.5 → dlt_utils_lib-1.2.6}/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.5
3
+ Version: 1.2.6
4
4
  Summary: UNKNOWN
5
5
  Home-page: UNKNOWN
6
6
  License: UNKNOWN
@@ -139,6 +139,27 @@ def create_bronze_table_definition(spark,
139
139
  ):
140
140
  table_properties = _build_table_properties(extra_table_properties)
141
141
 
142
+ # Resolve the schemaHints outside the @dlt.table body so that the
143
+ # collect() inside _pick_latest_source_file runs once at pipeline
144
+ # setup — Lakeflow Declarative Pipelines forbids eager Spark ops
145
+ # like collect() inside a decorated function.
146
+ #
147
+ # We take the schema from the newest source file so hints track the
148
+ # current source-of-truth type after upstream drift (e.g. DMS
149
+ # widening decimal(19,2) -> decimal(19,3)). Directory reads — with
150
+ # or without mergeSchema — fail for scale-changed decimals because
151
+ # Spark cannot merge them (integer-digit counts differ). Older
152
+ # files with narrower types cast up losslessly under this hint, and
153
+ # delta.enableTypeWidening handles the existing bronze table.
154
+ latest_file = _pick_latest_source_file(spark, files_path)
155
+ schema_source_path = latest_file if latest_file else files_path
156
+ inferred_df = spark.read.format(file_format).load(schema_source_path).transform(rename_columns)
157
+ schema_string = ', '.join([
158
+ f"{field.name} {field.dataType.simpleString()}"
159
+ for field in inferred_df.schema.fields
160
+ if field.name not in schema_exclude_columns
161
+ ])
162
+
142
163
  @dlt.table(
143
164
  name=table_name,
144
165
  comment=f"This is the bronze table for table {source_table_name}.",
@@ -146,18 +167,6 @@ def create_bronze_table_definition(spark,
146
167
  table_properties=table_properties,
147
168
  )
148
169
  def transform_cdc_to_bronze():
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)
159
- fields = [field for field in df.schema.fields if field.name not in schema_exclude_columns]
160
- schema_string = ', '.join([f"{field.name} {field.dataType.simpleString()}" for field in fields])
161
170
  reader = spark \
162
171
  .readStream \
163
172
  .format('cloudFiles') \
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: dlt-utils-lib
3
- Version: 1.2.5
3
+ Version: 1.2.6
4
4
  Summary: UNKNOWN
5
5
  Home-page: UNKNOWN
6
6
  License: UNKNOWN
@@ -33,9 +33,15 @@ def _build_cdc_table():
33
33
  }
34
34
 
35
35
 
36
+ def _stub_bronze_setup(monkeypatch):
37
+ """Bypass the setup-time parquet read used to infer schemaHints."""
38
+ monkeypatch.setattr(main_cdc_utils, "_pick_latest_source_file", lambda *_: None)
39
+
40
+
36
41
  def test_base_cdc_replication_process_uses_pipeline_schema_configuration(monkeypatch):
37
42
  dlt_spy = _DltSpy()
38
- spark = object()
43
+ spark = _make_spark_with_reader([], {})
44
+ _stub_bronze_setup(monkeypatch)
39
45
 
40
46
  monkeypatch.setattr(main_cdc_utils, "col", lambda value: value)
41
47
  monkeypatch.setattr(main_cdc_utils, "expr", lambda value: value)
@@ -58,7 +64,8 @@ def test_base_cdc_replication_process_uses_pipeline_schema_configuration(monkeyp
58
64
 
59
65
  def test_base_cdc_replication_process_uses_legacy_names_without_both_schemas(monkeypatch):
60
66
  dlt_spy = _DltSpy()
61
- spark = object()
67
+ spark = _make_spark_with_reader([], {})
68
+ _stub_bronze_setup(monkeypatch)
62
69
 
63
70
  monkeypatch.setattr(main_cdc_utils, "col", lambda value: value)
64
71
  monkeypatch.setattr(main_cdc_utils, "expr", lambda value: value)
@@ -140,52 +147,53 @@ def test_silver_streaming_process_merges_extra_table_properties(monkeypatch):
140
147
  assert table_properties["my.custom.prop"] == "42"
141
148
 
142
149
 
143
- def test_create_bronze_table_definition_reads_schema_from_latest_file_and_enables_type_widening(monkeypatch):
144
- class _DT:
145
- def __init__(self, t):
146
- self._t = t
150
+ class _FakeField:
151
+ def __init__(self, name, type_str):
152
+ self.name = name
147
153
 
148
- def simpleString(self):
149
- return self._t
154
+ class _DT:
155
+ def simpleString(self_inner):
156
+ return type_str
150
157
 
151
- class _Field:
152
- def __init__(self, name, type_str):
153
- self.name = name
154
- self.dataType = _DT(type_str)
158
+ self.dataType = _DT()
155
159
 
156
- class _DF:
157
- def __init__(self, fields):
158
- class _Schema:
159
- pass
160
160
 
161
- self.schema = _Schema()
162
- self.schema.fields = fields
161
+ class _FakeDF:
162
+ def __init__(self, fields):
163
+ class _Schema:
164
+ pass
163
165
 
164
- def transform(self, _fn):
165
- return self
166
+ self.schema = _Schema()
167
+ self.schema.fields = fields
166
168
 
167
- calls = {"load_paths": [], "format": None}
169
+ def transform(self, _fn):
170
+ return self
168
171
 
172
+
173
+ def _make_spark_with_reader(load_paths, fields_by_path):
169
174
  class _Reader:
170
- def format(self, fmt):
171
- calls["format"] = fmt
175
+ def format(self, _fmt):
172
176
  return self
173
177
 
174
178
  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.
178
- return _DF([_Field("pnl", "decimal(19,3)"), _Field("id", "bigint")])
179
+ load_paths.append(path)
180
+ return _FakeDF(fields_by_path.get(path, []))
179
181
 
180
182
  spark = type("_Spark", (), {})()
181
183
  spark.read = _Reader()
184
+ return spark
185
+
182
186
 
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
+ def test_create_bronze_table_definition_reads_schema_from_latest_file_at_setup_time(monkeypatch):
188
+ load_paths = []
189
+ latest_path = "s3://bucket/dms_bronze/artemis/settled_bet/20260701-090901614.parquet"
190
+ spark = _make_spark_with_reader(
191
+ load_paths,
192
+ {latest_path: [_FakeField("pnl", "decimal(19,3)"), _FakeField("id", "bigint")]},
187
193
  )
188
194
 
195
+ monkeypatch.setattr(main_cdc_utils, "_pick_latest_source_file", lambda *_: latest_path)
196
+
189
197
  captured = {}
190
198
 
191
199
  class _Dlt:
@@ -212,61 +220,26 @@ def test_create_bronze_table_definition_reads_schema_from_latest_file_and_enable
212
220
  use_managed_file_events=False,
213
221
  )
214
222
 
215
- # dlt.table just captures the transform — invoke it to exercise the
216
- # initial spark.read path (the readStream chain that follows is not
217
- # exercised here; the AttributeError it raises is expected).
218
- try:
219
- captured["fn"]()
220
- except AttributeError:
221
- pass
222
-
223
- assert calls["format"] == "parquet"
224
- assert calls["load_paths"] == [
225
- "s3://bucket/dms_bronze/artemis/settled_bet/20260701-090901614.parquet"
226
- ]
223
+ # The schema-inference read must happen at setup time, BEFORE the
224
+ # @dlt.table body ever runs. That means load_paths already contains
225
+ # the latest file just from calling create_bronze_table_definition.
226
+ assert load_paths == [latest_path]
227
227
  assert captured["kwargs"]["table_properties"]["delta.enableTypeWidening"] == "true"
228
228
 
229
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
-
230
+ def test_create_bronze_table_definition_falls_back_to_directory_when_listing_empty(monkeypatch):
251
231
  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()
232
+ directory = "s3://bucket/dms_bronze/artemis/settled_bet/"
233
+ spark = _make_spark_with_reader(
234
+ load_paths,
235
+ {directory: [_FakeField("id", "bigint")]},
236
+ )
263
237
 
264
238
  monkeypatch.setattr(main_cdc_utils, "_pick_latest_source_file", lambda *_: None)
265
239
 
266
240
  class _Dlt:
267
241
  def table(self, **_kwargs):
268
242
  def _decorator(fn):
269
- _Dlt.fn = staticmethod(fn)
270
243
  return fn
271
244
 
272
245
  return _decorator
@@ -276,7 +249,7 @@ def test_create_bronze_table_definition_falls_back_to_directory_read_when_listin
276
249
  dlt=_Dlt(),
277
250
  source_table_name="settled_bet",
278
251
  table_name="apollo.artemis.bronze_settled_bet",
279
- files_path="s3://bucket/dms_bronze/artemis/settled_bet/",
252
+ files_path=directory,
280
253
  file_format="parquet",
281
254
  partitions=[],
282
255
  schema_exclude_columns=[],
@@ -285,9 +258,4 @@ def test_create_bronze_table_definition_falls_back_to_directory_read_when_listin
285
258
  use_managed_file_events=False,
286
259
  )
287
260
 
288
- try:
289
- _Dlt.fn()
290
- except AttributeError:
291
- pass
292
-
293
- assert load_paths == ["s3://bucket/dms_bronze/artemis/settled_bet/"]
261
+ assert load_paths == [directory]
File without changes
File without changes