dlt-utils-lib 1.2.7__tar.gz → 1.3.1__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.
- {dlt_utils_lib-1.2.7 → dlt_utils_lib-1.3.1}/PKG-INFO +1 -1
- dlt_utils_lib-1.3.1/dlt_utils/dlt_table_properties.py +32 -0
- {dlt_utils_lib-1.2.7 → dlt_utils_lib-1.3.1}/dlt_utils/main_cdc_utils.py +16 -30
- {dlt_utils_lib-1.2.7 → dlt_utils_lib-1.3.1}/dlt_utils/main_json_utils.py +28 -12
- {dlt_utils_lib-1.2.7 → dlt_utils_lib-1.3.1}/dlt_utils_lib.egg-info/PKG-INFO +1 -1
- {dlt_utils_lib-1.2.7 → dlt_utils_lib-1.3.1}/dlt_utils_lib.egg-info/SOURCES.txt +1 -0
- {dlt_utils_lib-1.2.7 → dlt_utils_lib-1.3.1}/tests/test_main_cdc_utils.py +48 -0
- {dlt_utils_lib-1.2.7 → dlt_utils_lib-1.3.1}/tests/test_main_json_utils.py +60 -0
- {dlt_utils_lib-1.2.7 → dlt_utils_lib-1.3.1}/tests/test_transformation_module.py +10 -2
- {dlt_utils_lib-1.2.7 → dlt_utils_lib-1.3.1}/dlt_utils/__init__.py +0 -0
- {dlt_utils_lib-1.2.7 → dlt_utils_lib-1.3.1}/dlt_utils/dlt_autoloader_recovery_configuration.py +0 -0
- {dlt_utils_lib-1.2.7 → dlt_utils_lib-1.3.1}/dlt_utils/dlt_metadata_receiver.py +0 -0
- {dlt_utils_lib-1.2.7 → dlt_utils_lib-1.3.1}/dlt_utils/dlt_transformations.py +0 -0
- {dlt_utils_lib-1.2.7 → dlt_utils_lib-1.3.1}/dlt_utils_lib.egg-info/dependency_links.txt +0 -0
- {dlt_utils_lib-1.2.7 → dlt_utils_lib-1.3.1}/dlt_utils_lib.egg-info/requires.txt +0 -0
- {dlt_utils_lib-1.2.7 → dlt_utils_lib-1.3.1}/dlt_utils_lib.egg-info/top_level.txt +0 -0
- {dlt_utils_lib-1.2.7 → dlt_utils_lib-1.3.1}/setup.cfg +0 -0
- {dlt_utils_lib-1.2.7 → dlt_utils_lib-1.3.1}/setup.py +0 -0
- {dlt_utils_lib-1.2.7 → dlt_utils_lib-1.3.1}/tests/__init__.py +0 -0
- {dlt_utils_lib-1.2.7 → dlt_utils_lib-1.3.1}/tests/conftest.py +0 -0
- {dlt_utils_lib-1.2.7 → dlt_utils_lib-1.3.1}/tests/test_autoloader_recovery_configuration.py +0 -0
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import json
|
|
2
|
+
from typing import Optional
|
|
3
|
+
|
|
4
|
+
# delta.enableTypeWidening lets Delta accept widened column types (e.g. decimal
|
|
5
|
+
# scale bumps) from upstream schema drift without a manual ALTER + full refresh.
|
|
6
|
+
# Applied to every bronze and silver table so future source-side type changes
|
|
7
|
+
# flow through the pipeline dynamically.
|
|
8
|
+
#
|
|
9
|
+
# Shared by the CDC and JSON replication paths rather than defined in each. They
|
|
10
|
+
# held separate copies once and the copies disagreed: the JSON path never gained
|
|
11
|
+
# type widening, so Kafka-sourced tables needed a full refresh for a change the
|
|
12
|
+
# CDC tables absorbed.
|
|
13
|
+
DEFAULT_TABLE_PROPERTIES = {
|
|
14
|
+
"delta.autoOptimize.optimizeWrite": "true",
|
|
15
|
+
"delta.autoOptimize.autoCompact": "true",
|
|
16
|
+
"delta.enableTypeWidening": "true",
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def build_table_properties(extra: Optional[dict] = None,
|
|
21
|
+
column_comments: Optional[dict] = None) -> dict:
|
|
22
|
+
"""Defaults, plus column comments when the caller tracks them, plus overrides.
|
|
23
|
+
|
|
24
|
+
`column_comments=None` leaves the key out entirely, which is how bronze
|
|
25
|
+
tables are built; an empty dict still writes "{}" so the property exists.
|
|
26
|
+
"""
|
|
27
|
+
props = dict(DEFAULT_TABLE_PROPERTIES)
|
|
28
|
+
if column_comments is not None:
|
|
29
|
+
props["column_comments"] = json.dumps(column_comments) if column_comments else "{}"
|
|
30
|
+
if extra:
|
|
31
|
+
props.update(extra)
|
|
32
|
+
return props
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import json
|
|
2
1
|
from typing import Optional
|
|
3
2
|
|
|
4
3
|
from pyspark.sql.functions import col, expr, current_timestamp
|
|
5
4
|
|
|
6
5
|
from .dlt_autoloader_recovery_configuration import resolve_autoloader_recovery
|
|
6
|
+
from .dlt_table_properties import build_table_properties
|
|
7
7
|
from .dlt_transformations import (
|
|
8
8
|
add_default_value_for_removed_col,
|
|
9
9
|
apply_partitions,
|
|
@@ -12,27 +12,6 @@ from .dlt_transformations import (
|
|
|
12
12
|
)
|
|
13
13
|
|
|
14
14
|
|
|
15
|
-
# delta.enableTypeWidening lets Delta accept widened column types (e.g. decimal
|
|
16
|
-
# scale bumps) from upstream schema drift without a manual ALTER + full refresh.
|
|
17
|
-
# Applied to every bronze and silver table so future source-side type changes
|
|
18
|
-
# flow through the pipeline dynamically.
|
|
19
|
-
_DEFAULT_TABLE_PROPERTIES = {
|
|
20
|
-
"delta.autoOptimize.optimizeWrite": "true",
|
|
21
|
-
"delta.autoOptimize.autoCompact": "true",
|
|
22
|
-
"delta.enableTypeWidening": "true",
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
def _build_table_properties(extra: Optional[dict] = None,
|
|
27
|
-
column_comments: Optional[dict] = None) -> dict:
|
|
28
|
-
props = dict(_DEFAULT_TABLE_PROPERTIES)
|
|
29
|
-
if column_comments is not None:
|
|
30
|
-
props["column_comments"] = json.dumps(column_comments) if column_comments else "{}"
|
|
31
|
-
if extra:
|
|
32
|
-
props.update(extra)
|
|
33
|
-
return props
|
|
34
|
-
|
|
35
|
-
|
|
36
15
|
def _pick_latest_source_file(spark, files_path: str) -> Optional[str]:
|
|
37
16
|
"""Return the path of the most recently modified source file, or None.
|
|
38
17
|
|
|
@@ -60,22 +39,29 @@ def _pick_latest_source_file(spark, files_path: str) -> Optional[str]:
|
|
|
60
39
|
def base_cdc_replication_process(dlt,
|
|
61
40
|
spark,
|
|
62
41
|
cdc_tables_map: list,
|
|
63
|
-
bronze_directory: str,
|
|
64
|
-
bucket_name: str,
|
|
42
|
+
bronze_directory: Optional[str] = None,
|
|
43
|
+
bucket_name: Optional[str] = None,
|
|
65
44
|
time_diff_history_cdc_timestamp: int = 30,
|
|
66
45
|
bronze_schema: Optional[str] = None,
|
|
67
46
|
silver_schema: Optional[str] = None,
|
|
68
|
-
use_managed_file_events: bool = False
|
|
47
|
+
use_managed_file_events: bool = False,
|
|
48
|
+
volume_source_path: Optional[str] = None):
|
|
49
|
+
if volume_source_path is None and (not bronze_directory or not bucket_name):
|
|
50
|
+
raise ValueError("Provide volume_source_path or both bucket_name and bronze_directory")
|
|
51
|
+
|
|
69
52
|
# parse list with tables for replication
|
|
70
53
|
for table in cdc_tables_map:
|
|
71
54
|
source_table_name = table['source_table_name']
|
|
72
|
-
source_schema = table['source_schema']
|
|
73
55
|
file_format = table['format']
|
|
74
56
|
keys = table['keys']
|
|
75
57
|
exclude_columns = table['exclude_columns']
|
|
76
58
|
enable_truncate = table['enable_truncate']
|
|
77
|
-
|
|
78
|
-
|
|
59
|
+
# The volume path already includes the source schema.
|
|
60
|
+
if volume_source_path is not None:
|
|
61
|
+
bronze_table_path = f"{volume_source_path.rstrip('/')}/{source_table_name}/"
|
|
62
|
+
else:
|
|
63
|
+
source_schema = table['source_schema']
|
|
64
|
+
bronze_table_path = f"{bucket_name}/{bronze_directory}/{source_schema}/{source_table_name}/"
|
|
79
65
|
|
|
80
66
|
# Optional parameters
|
|
81
67
|
default_value_for_removed_col = table.get('default_value_for_removed_col', {})
|
|
@@ -137,7 +123,7 @@ def create_bronze_table_definition(spark,
|
|
|
137
123
|
use_managed_file_events: bool,
|
|
138
124
|
extra_table_properties: Optional[dict] = None,
|
|
139
125
|
):
|
|
140
|
-
table_properties =
|
|
126
|
+
table_properties = build_table_properties(extra_table_properties)
|
|
141
127
|
|
|
142
128
|
# Resolve the schemaHints outside the @dlt.table body so that the
|
|
143
129
|
# collect() inside _pick_latest_source_file runs once at pipeline
|
|
@@ -208,7 +194,7 @@ def silver_streaming_process(dlt,
|
|
|
208
194
|
cluster_by_value: list = None,
|
|
209
195
|
is_cluster_by_auto: bool = True,
|
|
210
196
|
extra_table_properties: Optional[dict] = None):
|
|
211
|
-
table_properties =
|
|
197
|
+
table_properties = build_table_properties(
|
|
212
198
|
extra_table_properties,
|
|
213
199
|
column_comments=column_comments,
|
|
214
200
|
)
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import json
|
|
2
1
|
from typing import Callable, Optional
|
|
3
2
|
|
|
4
3
|
from pyspark.sql import DataFrame
|
|
5
4
|
from pyspark.sql.functions import col, struct
|
|
6
5
|
|
|
7
6
|
from .dlt_autoloader_recovery_configuration import resolve_autoloader_recovery
|
|
7
|
+
from .dlt_table_properties import build_table_properties
|
|
8
8
|
|
|
9
9
|
|
|
10
10
|
def base_json_replication_process(
|
|
@@ -29,6 +29,7 @@ def base_json_replication_process(
|
|
|
29
29
|
is_cluster_by_auto = table.get("is_cluster_by_auto", False)
|
|
30
30
|
keys = table.get("keys")
|
|
31
31
|
sequence_by_columns = table.get("sequence_by_columns")
|
|
32
|
+
extra_table_properties = table.get("table_properties") or {}
|
|
32
33
|
|
|
33
34
|
if bronze_schema and silver_schema:
|
|
34
35
|
bronze_table_name = f"{bronze_schema}.{dataset_name}"
|
|
@@ -47,6 +48,7 @@ def base_json_replication_process(
|
|
|
47
48
|
schema_hints=schema_hints,
|
|
48
49
|
partitions=bronze_partitions,
|
|
49
50
|
use_managed_file_events=use_managed_file_events,
|
|
51
|
+
extra_table_properties=extra_table_properties,
|
|
50
52
|
)
|
|
51
53
|
|
|
52
54
|
if keys and sequence_by_columns:
|
|
@@ -64,6 +66,7 @@ def base_json_replication_process(
|
|
|
64
66
|
is_cluster_by_auto=is_cluster_by_auto,
|
|
65
67
|
keys=keys,
|
|
66
68
|
sequence_by_columns=sequence_by_columns,
|
|
69
|
+
extra_table_properties=extra_table_properties,
|
|
67
70
|
)
|
|
68
71
|
else:
|
|
69
72
|
create_silver_json_table_definition(
|
|
@@ -78,6 +81,7 @@ def base_json_replication_process(
|
|
|
78
81
|
column_comments=column_comments,
|
|
79
82
|
cluster_by_value=cluster_by_value,
|
|
80
83
|
is_cluster_by_auto=is_cluster_by_auto,
|
|
84
|
+
extra_table_properties=extra_table_properties,
|
|
81
85
|
)
|
|
82
86
|
|
|
83
87
|
|
|
@@ -87,6 +91,7 @@ def _build_autoloader_reader(
|
|
|
87
91
|
file_format: str,
|
|
88
92
|
schema_hints: Optional[str],
|
|
89
93
|
use_managed_file_events: bool,
|
|
94
|
+
include_source_file: bool = False,
|
|
90
95
|
):
|
|
91
96
|
reader = (
|
|
92
97
|
spark.readStream
|
|
@@ -108,7 +113,15 @@ def _build_autoloader_reader(
|
|
|
108
113
|
.option("cloudFiles.validateOptions", "false")
|
|
109
114
|
)
|
|
110
115
|
|
|
111
|
-
|
|
116
|
+
loaded = reader.load(source_path)
|
|
117
|
+
|
|
118
|
+
if include_source_file:
|
|
119
|
+
# Which file a row was read from. Off by default because it adds a
|
|
120
|
+
# column, and a column added to an existing bronze table is a schema
|
|
121
|
+
# change for everything already reading it.
|
|
122
|
+
loaded = loaded.withColumn("source_file", col("_metadata.file_path"))
|
|
123
|
+
|
|
124
|
+
return loaded
|
|
112
125
|
|
|
113
126
|
|
|
114
127
|
def create_bronze_json_table_definition(
|
|
@@ -121,11 +134,14 @@ def create_bronze_json_table_definition(
|
|
|
121
134
|
schema_hints: Optional[str],
|
|
122
135
|
partitions: Optional[list],
|
|
123
136
|
use_managed_file_events: bool,
|
|
137
|
+
extra_table_properties: Optional[dict] = None,
|
|
138
|
+
include_source_file: bool = False,
|
|
124
139
|
):
|
|
125
140
|
table_kwargs = {
|
|
126
141
|
"name": table_name,
|
|
127
142
|
"comment": f"Raw JSON stream data for {dataset_name}.",
|
|
128
143
|
"temporary": False,
|
|
144
|
+
"table_properties": build_table_properties(extra_table_properties),
|
|
129
145
|
}
|
|
130
146
|
|
|
131
147
|
if partitions:
|
|
@@ -139,6 +155,7 @@ def create_bronze_json_table_definition(
|
|
|
139
155
|
file_format=file_format,
|
|
140
156
|
schema_hints=schema_hints,
|
|
141
157
|
use_managed_file_events=use_managed_file_events,
|
|
158
|
+
include_source_file=include_source_file,
|
|
142
159
|
)
|
|
143
160
|
|
|
144
161
|
return transform_json_to_bronze
|
|
@@ -156,17 +173,18 @@ def create_silver_json_table_definition(
|
|
|
156
173
|
column_comments: Optional[dict] = None,
|
|
157
174
|
cluster_by_value: Optional[list] = None,
|
|
158
175
|
is_cluster_by_auto: bool = False,
|
|
176
|
+
extra_table_properties: Optional[dict] = None,
|
|
159
177
|
):
|
|
160
178
|
supports_clustering = not partitions
|
|
161
179
|
table_kwargs = {
|
|
162
180
|
"name": table_name,
|
|
163
181
|
"comment": table_description or f"Structured JSON stream data for {dataset_name}.",
|
|
164
182
|
"temporary": False,
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
},
|
|
183
|
+
# `or {}` rather than passing None through: the JSON path has always
|
|
184
|
+
# written a column_comments property even when it had none, and dropping
|
|
185
|
+
# the key would change it on every existing silver table. The CDC path
|
|
186
|
+
# omits it instead, which is why the two calls differ here.
|
|
187
|
+
"table_properties": build_table_properties(extra_table_properties, column_comments=column_comments or {}),
|
|
170
188
|
}
|
|
171
189
|
|
|
172
190
|
if partitions:
|
|
@@ -200,6 +218,7 @@ def create_silver_json_cdc_definition(
|
|
|
200
218
|
column_comments: Optional[dict] = None,
|
|
201
219
|
cluster_by_value: Optional[list] = None,
|
|
202
220
|
is_cluster_by_auto: bool = False,
|
|
221
|
+
extra_table_properties: Optional[dict] = None,
|
|
203
222
|
):
|
|
204
223
|
staging_view_name = f"{dataset_name}_cdc_source"
|
|
205
224
|
supports_clustering = not partitions
|
|
@@ -211,11 +230,8 @@ def create_silver_json_cdc_definition(
|
|
|
211
230
|
|
|
212
231
|
dlt.create_streaming_table(
|
|
213
232
|
name=table_name,
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
"delta.autoOptimize.autoCompact": "true",
|
|
217
|
-
"column_comments": json.dumps(column_comments) if column_comments else "{}",
|
|
218
|
-
},
|
|
233
|
+
# See the note in create_silver_json_table_definition on `or {}`.
|
|
234
|
+
table_properties=build_table_properties(extra_table_properties, column_comments=column_comments or {}),
|
|
219
235
|
comment=table_description or f"Structured JSON stream data for {dataset_name}.",
|
|
220
236
|
partition_cols=partitions if partitions else None,
|
|
221
237
|
cluster_by=cluster_by_value if supports_clustering else None,
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
|
|
1
3
|
import dlt_utils.main_cdc_utils as main_cdc_utils
|
|
2
4
|
|
|
3
5
|
|
|
@@ -259,3 +261,49 @@ def test_create_bronze_table_definition_falls_back_to_directory_when_listing_emp
|
|
|
259
261
|
)
|
|
260
262
|
|
|
261
263
|
assert load_paths == [directory]
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
@pytest.mark.parametrize("source_options, expected", [
|
|
267
|
+
({"bucket_name": "s3://bucket", "bronze_directory": "dms_bronze"},
|
|
268
|
+
"s3://bucket/dms_bronze/bifrost/customer/"),
|
|
269
|
+
({"volume_source_path": "/Volumes/artemis_prod/stage/dms_raw/"},
|
|
270
|
+
"/Volumes/artemis_prod/stage/dms_raw/customer/"),
|
|
271
|
+
({"volume_source_path": "/Volumes/artemis_prod/stage/dms_raw",
|
|
272
|
+
"bucket_name": "s3://bucket", "bronze_directory": "dms_bronze"},
|
|
273
|
+
"/Volumes/artemis_prod/stage/dms_raw/customer/"),
|
|
274
|
+
])
|
|
275
|
+
def test_base_cdc_source_paths(monkeypatch, source_options, expected):
|
|
276
|
+
captured = []
|
|
277
|
+
monkeypatch.setattr(main_cdc_utils, "create_bronze_table_definition", lambda **kw: captured.append(kw))
|
|
278
|
+
monkeypatch.setattr(main_cdc_utils, "silver_streaming_process", lambda **kw: None)
|
|
279
|
+
table = _build_cdc_table()
|
|
280
|
+
if "volume_source_path" in source_options:
|
|
281
|
+
del table["source_schema"]
|
|
282
|
+
main_cdc_utils.base_cdc_replication_process(
|
|
283
|
+
dlt=object(), spark=object(), cdc_tables_map=[table],
|
|
284
|
+
**source_options,
|
|
285
|
+
)
|
|
286
|
+
assert captured[0]["files_path"] == expected
|
|
287
|
+
|
|
288
|
+
|
|
289
|
+
@pytest.mark.parametrize("source_options", [
|
|
290
|
+
{},
|
|
291
|
+
{"bucket_name": "s3://bucket"},
|
|
292
|
+
{"bronze_directory": "dms_bronze"},
|
|
293
|
+
])
|
|
294
|
+
def test_base_cdc_requires_complete_source_configuration(source_options):
|
|
295
|
+
with pytest.raises(ValueError, match="Provide volume_source_path or both bucket_name and bronze_directory"):
|
|
296
|
+
main_cdc_utils.base_cdc_replication_process(
|
|
297
|
+
dlt=object(), spark=object(), cdc_tables_map=[_build_cdc_table()],
|
|
298
|
+
**source_options,
|
|
299
|
+
)
|
|
300
|
+
|
|
301
|
+
|
|
302
|
+
def test_base_cdc_s3_requires_source_schema():
|
|
303
|
+
table = _build_cdc_table()
|
|
304
|
+
del table["source_schema"]
|
|
305
|
+
with pytest.raises(KeyError, match="source_schema"):
|
|
306
|
+
main_cdc_utils.base_cdc_replication_process(
|
|
307
|
+
dlt=object(), spark=object(), cdc_tables_map=[table],
|
|
308
|
+
bucket_name="s3://bucket", bronze_directory="dms_bronze",
|
|
309
|
+
)
|
|
@@ -141,3 +141,63 @@ def test_base_json_replication_process_enables_managed_file_events(monkeypatch):
|
|
|
141
141
|
)
|
|
142
142
|
|
|
143
143
|
assert captured_bronze_kwargs["use_managed_file_events"] is True
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def test_bronze_and_silver_enable_type_widening_by_default():
|
|
147
|
+
dlt_spy = _DltSpy()
|
|
148
|
+
|
|
149
|
+
main_json_utils.base_json_replication_process(
|
|
150
|
+
dlt=dlt_spy,
|
|
151
|
+
spark=object(),
|
|
152
|
+
json_tables_map=[_build_json_table()],
|
|
153
|
+
bronze_schema="bifrost_bronze",
|
|
154
|
+
silver_schema="bifrost_silver",
|
|
155
|
+
)
|
|
156
|
+
|
|
157
|
+
# Bronze carried no table properties at all before this; a Kafka-sourced
|
|
158
|
+
# table needed a full refresh for a widening the CDC tables absorbed.
|
|
159
|
+
for table_kwargs in dlt_spy.decorated_tables:
|
|
160
|
+
properties = table_kwargs["table_properties"]
|
|
161
|
+
assert properties["delta.enableTypeWidening"] == "true"
|
|
162
|
+
assert properties["delta.autoOptimize.optimizeWrite"] == "true"
|
|
163
|
+
assert properties["delta.autoOptimize.autoCompact"] == "true"
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
def test_cdc_silver_enables_type_widening_by_default(monkeypatch):
|
|
167
|
+
monkeypatch.setattr(main_json_utils, "col", lambda value: value)
|
|
168
|
+
monkeypatch.setattr(main_json_utils, "struct", lambda *values: values)
|
|
169
|
+
|
|
170
|
+
dlt_spy = _DltSpy()
|
|
171
|
+
json_table = dict(_build_json_table(), keys=["id"], sequence_by_columns=["kafka_timestamp"])
|
|
172
|
+
|
|
173
|
+
main_json_utils.base_json_replication_process(
|
|
174
|
+
dlt=dlt_spy,
|
|
175
|
+
spark=object(),
|
|
176
|
+
json_tables_map=[json_table],
|
|
177
|
+
bronze_schema="bifrost_bronze",
|
|
178
|
+
silver_schema="bifrost_silver",
|
|
179
|
+
)
|
|
180
|
+
|
|
181
|
+
assert dlt_spy.streaming_tables[0]["table_properties"]["delta.enableTypeWidening"] == "true"
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
def test_table_properties_override_the_library_defaults():
|
|
185
|
+
dlt_spy = _DltSpy()
|
|
186
|
+
json_table = dict(
|
|
187
|
+
_build_json_table(),
|
|
188
|
+
table_properties={"delta.enableTypeWidening": "false", "my.custom.prop": "42"},
|
|
189
|
+
)
|
|
190
|
+
|
|
191
|
+
main_json_utils.base_json_replication_process(
|
|
192
|
+
dlt=dlt_spy,
|
|
193
|
+
spark=object(),
|
|
194
|
+
json_tables_map=[json_table],
|
|
195
|
+
bronze_schema="bifrost_bronze",
|
|
196
|
+
silver_schema="bifrost_silver",
|
|
197
|
+
)
|
|
198
|
+
|
|
199
|
+
for table_kwargs in dlt_spy.decorated_tables:
|
|
200
|
+
properties = table_kwargs["table_properties"]
|
|
201
|
+
# Caller override wins over the library default.
|
|
202
|
+
assert properties["delta.enableTypeWidening"] == "false"
|
|
203
|
+
assert properties["my.custom.prop"] == "42"
|
|
@@ -7,10 +7,18 @@ from pyspark.sql.functions import expr
|
|
|
7
7
|
from pyspark.sql.types import TimestampType, StringType
|
|
8
8
|
from datetime import datetime
|
|
9
9
|
from dlt_utils.dlt_transformations import rename_columns, apply_partitions, update_cdc_timestamp, add_default_value_for_removed_col, rename_columns
|
|
10
|
-
# Mock databricks.sdk.runtime if not in a Databricks environment
|
|
10
|
+
# Mock databricks.sdk.runtime if not in a Databricks environment.
|
|
11
|
+
#
|
|
12
|
+
# Catching Exception and not ImportError: importing this module builds a
|
|
13
|
+
# Config and tries to authenticate, so off a cluster it fails in whatever way
|
|
14
|
+
# the surrounding credentials happen to be wrong. In CI that is
|
|
15
|
+
# "more than one authorization method configured: oauth and pat", because the
|
|
16
|
+
# project still carries a PAT next to the OAuth variables. On a laptop it is
|
|
17
|
+
# usually several matching profiles in ~/.databrickscfg. Both mean the same
|
|
18
|
+
# thing here, which is that we are not on a cluster and should mock.
|
|
11
19
|
try:
|
|
12
20
|
from databricks.sdk.runtime import *
|
|
13
|
-
except
|
|
21
|
+
except Exception:
|
|
14
22
|
print("Databricks runtime not available. Mocking required modules.")
|
|
15
23
|
mock_dbutils = mock.Mock()
|
|
16
24
|
sys.modules['databricks.sdk.runtime'] = mock.Mock()
|
|
File without changes
|
{dlt_utils_lib-1.2.7 → dlt_utils_lib-1.3.1}/dlt_utils/dlt_autoloader_recovery_configuration.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|