dbt-common 1.3.0__py3-none-any.whl → 1.5.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.
- dbt_common/__about__.py +1 -1
- dbt_common/clients/system.py +3 -1
- dbt_common/record.py +136 -34
- {dbt_common-1.3.0.dist-info → dbt_common-1.5.0.dist-info}/METADATA +2 -1
- {dbt_common-1.3.0.dist-info → dbt_common-1.5.0.dist-info}/RECORD +7 -7
- {dbt_common-1.3.0.dist-info → dbt_common-1.5.0.dist-info}/WHEEL +1 -1
- {dbt_common-1.3.0.dist-info → dbt_common-1.5.0.dist-info}/licenses/LICENSE +0 -0
dbt_common/__about__.py
CHANGED
@@ -1 +1 @@
|
|
1
|
-
version = "1.
|
1
|
+
version = "1.5.0"
|
dbt_common/clients/system.py
CHANGED
@@ -62,7 +62,9 @@ class FindMatchingParams:
|
|
62
62
|
# Do not record or replay filesystem searches that were performed against
|
63
63
|
# files which are actually part of dbt's implementation.
|
64
64
|
return (
|
65
|
-
"dbt/include
|
65
|
+
"dbt/include"
|
66
|
+
not in self.root_path # TODO: This actually obviates the next two checks but is probably too coarse?
|
67
|
+
and "dbt/include/global_project" not in self.root_path
|
66
68
|
and "/plugins/postgres/dbt/include/" not in self.root_path
|
67
69
|
)
|
68
70
|
|
dbt_common/record.py
CHANGED
@@ -2,15 +2,17 @@
|
|
2
2
|
external systems during a command invocation, so that the command can be re-run
|
3
3
|
later with the recording 'replayed' to dbt.
|
4
4
|
|
5
|
-
The rationale for and architecture of this module
|
5
|
+
The rationale for and architecture of this module are described in detail in the
|
6
6
|
docs/guides/record_replay.md document in this repository.
|
7
7
|
"""
|
8
8
|
import functools
|
9
9
|
import dataclasses
|
10
10
|
import json
|
11
11
|
import os
|
12
|
+
|
13
|
+
from deepdiff import DeepDiff # type: ignore
|
12
14
|
from enum import Enum
|
13
|
-
from typing import Any, Dict, List, Mapping, Optional, Type
|
15
|
+
from typing import Any, Callable, Dict, List, Mapping, Optional, Type
|
14
16
|
|
15
17
|
from dbt_common.context import get_invocation_context
|
16
18
|
|
@@ -51,15 +53,68 @@ class Record:
|
|
51
53
|
|
52
54
|
|
53
55
|
class Diff:
|
54
|
-
|
56
|
+
def __init__(self, current_recording_path: str, previous_recording_path: str) -> None:
|
57
|
+
self.current_recording_path = current_recording_path
|
58
|
+
self.previous_recording_path = previous_recording_path
|
59
|
+
|
60
|
+
def diff_query_records(self, current: List, previous: List) -> Dict[str, Any]:
|
61
|
+
# some of the table results are returned as a stringified list of dicts that don't
|
62
|
+
# diff because order isn't consistent. convert it into a list of dicts so it can
|
63
|
+
# be diffed ignoring order
|
64
|
+
|
65
|
+
for i in range(len(current)):
|
66
|
+
if current[i].get("result").get("table") is not None:
|
67
|
+
current[i]["result"]["table"] = json.loads(current[i]["result"]["table"])
|
68
|
+
for i in range(len(previous)):
|
69
|
+
if previous[i].get("result").get("table") is not None:
|
70
|
+
previous[i]["result"]["table"] = json.loads(previous[i]["result"]["table"])
|
71
|
+
|
72
|
+
return DeepDiff(previous, current, ignore_order=True, verbose_level=2)
|
73
|
+
|
74
|
+
def diff_env_records(self, current: List, previous: List) -> Dict[str, Any]:
|
75
|
+
# The mode and filepath may change. Ignore them.
|
76
|
+
|
77
|
+
exclude_paths = [
|
78
|
+
"root[0]['result']['env']['DBT_RECORDER_FILE_PATH']",
|
79
|
+
"root[0]['result']['env']['DBT_RECORDER_MODE']",
|
80
|
+
]
|
81
|
+
|
82
|
+
return DeepDiff(
|
83
|
+
previous, current, ignore_order=True, verbose_level=2, exclude_paths=exclude_paths
|
84
|
+
)
|
85
|
+
|
86
|
+
def diff_default(self, current: List, previous: List) -> Dict[str, Any]:
|
87
|
+
return DeepDiff(previous, current, ignore_order=True, verbose_level=2)
|
88
|
+
|
89
|
+
def calculate_diff(self) -> Dict[str, Any]:
|
90
|
+
with open(self.current_recording_path) as current_recording:
|
91
|
+
current_dct = json.load(current_recording)
|
55
92
|
|
56
|
-
|
93
|
+
with open(self.previous_recording_path) as previous_recording:
|
94
|
+
previous_dct = json.load(previous_recording)
|
95
|
+
|
96
|
+
diff = {}
|
97
|
+
for record_type in current_dct:
|
98
|
+
if record_type == "QueryRecord":
|
99
|
+
diff[record_type] = self.diff_query_records(
|
100
|
+
current_dct[record_type], previous_dct[record_type]
|
101
|
+
)
|
102
|
+
elif record_type == "GetEnvRecord":
|
103
|
+
diff[record_type] = self.diff_env_records(
|
104
|
+
current_dct[record_type], previous_dct[record_type]
|
105
|
+
)
|
106
|
+
else:
|
107
|
+
diff[record_type] = self.diff_default(
|
108
|
+
current_dct[record_type], previous_dct[record_type]
|
109
|
+
)
|
110
|
+
|
111
|
+
return diff
|
57
112
|
|
58
113
|
|
59
114
|
class RecorderMode(Enum):
|
60
115
|
RECORD = 1
|
61
116
|
REPLAY = 2
|
62
|
-
|
117
|
+
DIFF = 3 # records and does diffing
|
63
118
|
|
64
119
|
|
65
120
|
class Recorder:
|
@@ -67,15 +122,32 @@ class Recorder:
|
|
67
122
|
_record_name_by_params_name: Dict[str, str] = {}
|
68
123
|
|
69
124
|
def __init__(
|
70
|
-
self,
|
125
|
+
self,
|
126
|
+
mode: RecorderMode,
|
127
|
+
types: Optional[List],
|
128
|
+
current_recording_path: str = "recording.json",
|
129
|
+
previous_recording_path: Optional[str] = None,
|
71
130
|
) -> None:
|
72
131
|
self.mode = mode
|
73
|
-
self.
|
132
|
+
self.recorded_types = types
|
74
133
|
self._records_by_type: Dict[str, List[Record]] = {}
|
134
|
+
self._unprocessed_records_by_type: Dict[str, List[Dict[str, Any]]] = {}
|
75
135
|
self._replay_diffs: List["Diff"] = []
|
136
|
+
self.diff: Optional[Diff] = None
|
137
|
+
self.previous_recording_path = previous_recording_path
|
138
|
+
self.current_recording_path = current_recording_path
|
139
|
+
|
140
|
+
if self.previous_recording_path is not None and self.mode in (
|
141
|
+
RecorderMode.REPLAY,
|
142
|
+
RecorderMode.DIFF,
|
143
|
+
):
|
144
|
+
self.diff = Diff(
|
145
|
+
current_recording_path=self.current_recording_path,
|
146
|
+
previous_recording_path=self.previous_recording_path,
|
147
|
+
)
|
76
148
|
|
77
|
-
|
78
|
-
|
149
|
+
if self.mode == RecorderMode.REPLAY:
|
150
|
+
self._unprocessed_records_by_type = self.load(self.previous_recording_path)
|
79
151
|
|
80
152
|
@classmethod
|
81
153
|
def register_record_type(cls, rec_type) -> Any:
|
@@ -90,7 +162,14 @@ class Recorder:
|
|
90
162
|
self._records_by_type[rec_cls_name].append(record)
|
91
163
|
|
92
164
|
def pop_matching_record(self, params: Any) -> Optional[Record]:
|
93
|
-
rec_type_name = self._record_name_by_params_name
|
165
|
+
rec_type_name = self._record_name_by_params_name.get(type(params).__name__)
|
166
|
+
|
167
|
+
if rec_type_name is None:
|
168
|
+
raise Exception(
|
169
|
+
f"A record of type {type(params).__name__} was requested, but no such type has been registered."
|
170
|
+
)
|
171
|
+
|
172
|
+
self._ensure_records_processed(rec_type_name)
|
94
173
|
records = self._records_by_type[rec_type_name]
|
95
174
|
match: Optional[Record] = None
|
96
175
|
for rec in records:
|
@@ -101,8 +180,8 @@ class Recorder:
|
|
101
180
|
|
102
181
|
return match
|
103
182
|
|
104
|
-
def write(self
|
105
|
-
with open(
|
183
|
+
def write(self) -> None:
|
184
|
+
with open(self.current_recording_path, "w") as file:
|
106
185
|
json.dump(self._to_dict(), file)
|
107
186
|
|
108
187
|
def _to_dict(self) -> Dict:
|
@@ -115,22 +194,20 @@ class Recorder:
|
|
115
194
|
return dct
|
116
195
|
|
117
196
|
@classmethod
|
118
|
-
def load(cls, file_name: str) -> Dict[str, List[
|
197
|
+
def load(cls, file_name: str) -> Dict[str, List[Dict[str, Any]]]:
|
119
198
|
with open(file_name) as file:
|
120
|
-
|
199
|
+
return json.load(file)
|
121
200
|
|
122
|
-
|
201
|
+
def _ensure_records_processed(self, record_type_name: str) -> None:
|
202
|
+
if record_type_name in self._records_by_type:
|
203
|
+
return
|
123
204
|
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
rec_list
|
129
|
-
|
130
|
-
rec = record_cls.from_dict(record_dct)
|
131
|
-
rec_list.append(rec) # type: ignore
|
132
|
-
records_by_type[record_type_name] = rec_list
|
133
|
-
return records_by_type
|
205
|
+
rec_list = []
|
206
|
+
record_cls = self._record_cls_by_name[record_type_name]
|
207
|
+
for record_dct in self._unprocessed_records_by_type[record_type_name]:
|
208
|
+
rec = record_cls.from_dict(record_dct)
|
209
|
+
rec_list.append(rec) # type: ignore
|
210
|
+
self._records_by_type[record_type_name] = rec_list
|
134
211
|
|
135
212
|
def expect_record(self, params: Any) -> Any:
|
136
213
|
record = self.pop_matching_record(params)
|
@@ -138,24 +215,27 @@ class Recorder:
|
|
138
215
|
if record is None:
|
139
216
|
raise Exception()
|
140
217
|
|
218
|
+
if record.result is None:
|
219
|
+
return None
|
220
|
+
|
141
221
|
result_tuple = dataclasses.astuple(record.result)
|
142
222
|
return result_tuple[0] if len(result_tuple) == 1 else result_tuple
|
143
223
|
|
144
224
|
def write_diffs(self, diff_file_name) -> None:
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
)
|
225
|
+
assert self.diff is not None
|
226
|
+
with open(diff_file_name, "w") as f:
|
227
|
+
json.dump(self.diff.calculate_diff(), f)
|
149
228
|
|
150
229
|
def print_diffs(self) -> None:
|
151
|
-
|
230
|
+
assert self.diff is not None
|
231
|
+
print(repr(self.diff.calculate_diff()))
|
152
232
|
|
153
233
|
|
154
234
|
def get_record_mode_from_env() -> Optional[RecorderMode]:
|
155
235
|
"""
|
156
236
|
Get the record mode from the environment variables.
|
157
237
|
|
158
|
-
If the mode is not set to 'RECORD' or 'REPLAY', return None.
|
238
|
+
If the mode is not set to 'RECORD', 'DIFF' or 'REPLAY', return None.
|
159
239
|
Expected format: 'DBT_RECORDER_MODE=RECORD'
|
160
240
|
"""
|
161
241
|
record_mode = os.environ.get("DBT_RECORDER_MODE")
|
@@ -165,6 +245,9 @@ def get_record_mode_from_env() -> Optional[RecorderMode]:
|
|
165
245
|
|
166
246
|
if record_mode.lower() == "record":
|
167
247
|
return RecorderMode.RECORD
|
248
|
+
# diffing requires a file path, otherwise treat as noop
|
249
|
+
elif record_mode.lower() == "diff" and os.environ.get("DBT_RECORDER_FILE_PATH") is not None:
|
250
|
+
return RecorderMode.DIFF
|
168
251
|
# replaying requires a file path, otherwise treat as noop
|
169
252
|
elif record_mode.lower() == "replay" and os.environ.get("DBT_RECORDER_FILE_PATH") is not None:
|
170
253
|
return RecorderMode.REPLAY
|
@@ -190,7 +273,21 @@ def get_record_types_from_env() -> Optional[List]:
|
|
190
273
|
return record_types_str.split(",")
|
191
274
|
|
192
275
|
|
193
|
-
def
|
276
|
+
def get_record_types_from_dict(fp: str) -> List:
|
277
|
+
"""
|
278
|
+
Get the record subset from the dict.
|
279
|
+
"""
|
280
|
+
with open(fp) as file:
|
281
|
+
loaded_dct = json.load(file)
|
282
|
+
return list(loaded_dct.keys())
|
283
|
+
|
284
|
+
|
285
|
+
def record_function(
|
286
|
+
record_type,
|
287
|
+
method: bool = False,
|
288
|
+
tuple_result: bool = False,
|
289
|
+
id_field_name: Optional[str] = None,
|
290
|
+
) -> Callable:
|
194
291
|
def record_function_inner(func_to_record):
|
195
292
|
# To avoid runtime overhead and other unpleasantness, we only apply the
|
196
293
|
# record/replay decorator if a relevant env var is set.
|
@@ -208,12 +305,17 @@ def record_function(record_type, method=False, tuple_result=False):
|
|
208
305
|
if recorder is None:
|
209
306
|
return func_to_record(*args, **kwargs)
|
210
307
|
|
211
|
-
if
|
308
|
+
if (
|
309
|
+
recorder.recorded_types is not None
|
310
|
+
and record_type.__name__ not in recorder.recorded_types
|
311
|
+
):
|
212
312
|
return func_to_record(*args, **kwargs)
|
213
313
|
|
214
314
|
# For methods, peel off the 'self' argument before calling the
|
215
315
|
# params constructor.
|
216
316
|
param_args = args[1:] if method else args
|
317
|
+
if method and id_field_name is not None:
|
318
|
+
param_args = (getattr(args[0], id_field_name),) + param_args
|
217
319
|
|
218
320
|
params = record_type.params_cls(*param_args, **kwargs)
|
219
321
|
|
@@ -230,7 +332,7 @@ def record_function(record_type, method=False, tuple_result=False):
|
|
230
332
|
r = func_to_record(*args, **kwargs)
|
231
333
|
result = (
|
232
334
|
None
|
233
|
-
if
|
335
|
+
if record_type.result_cls is None
|
234
336
|
else record_type.result_cls(*r)
|
235
337
|
if tuple_result
|
236
338
|
else record_type.result_cls(r)
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.3
|
2
2
|
Name: dbt-common
|
3
|
-
Version: 1.
|
3
|
+
Version: 1.5.0
|
4
4
|
Summary: The shared common utilities that dbt-core and adapter implementations use
|
5
5
|
Project-URL: Homepage, https://github.com/dbt-labs/dbt-common
|
6
6
|
Project-URL: Repository, https://github.com/dbt-labs/dbt-common.git
|
@@ -26,6 +26,7 @@ Classifier: Programming Language :: Python :: Implementation :: PyPy
|
|
26
26
|
Requires-Python: >=3.8
|
27
27
|
Requires-Dist: agate<1.10,>=1.7.0
|
28
28
|
Requires-Dist: colorama<0.5,>=0.3.9
|
29
|
+
Requires-Dist: deepdiff<8.0,>=7.0
|
29
30
|
Requires-Dist: isodate<0.7,>=0.6
|
30
31
|
Requires-Dist: jinja2<4,>=3.1.3
|
31
32
|
Requires-Dist: jsonschema<5.0,>=4.0
|
@@ -1,4 +1,4 @@
|
|
1
|
-
dbt_common/__about__.py,sha256=
|
1
|
+
dbt_common/__about__.py,sha256=Wgohrh7g_peUNIz0Cro4w7u88rZU94fNypIcm9oMWgQ,18
|
2
2
|
dbt_common/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
3
3
|
dbt_common/constants.py,sha256=-Y5DIL1SDPQWtlCNizXRYxFgbx1D7LaLs1ysamvGMRk,278
|
4
4
|
dbt_common/context.py,sha256=BhgT7IgyvpZHEtIdFVVuBBBX5LuU7obXT7NvIPeuD2g,1760
|
@@ -6,7 +6,7 @@ dbt_common/dataclass_schema.py,sha256=t3HGD0oXTSjitctuCVHv3iyq5BT3jxoSxv_VGkrJlE
|
|
6
6
|
dbt_common/helper_types.py,sha256=NoxqGFAq9bOjh7rqtz_eepXAxk20n3mmW_gUVpnMyYU,3901
|
7
7
|
dbt_common/invocation.py,sha256=Zw8jRPn75oi2VrUD6qGvaCDtSyIfqm5pJlPpRjs3s1E,202
|
8
8
|
dbt_common/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
9
|
-
dbt_common/record.py,sha256=
|
9
|
+
dbt_common/record.py,sha256=F-FbFt6Js_U4r5b5hXGp8DY-MTEKNb-dLMaBT80UWHw,12415
|
10
10
|
dbt_common/semver.py,sha256=2zoZYCQ7PfswqslT2NHuMGgPGMuMuX-yRThVoqfDWQU,13954
|
11
11
|
dbt_common/tests.py,sha256=6lC_JuRtoYO6cbAF8-R5aTM4HtQiM_EH8X5m_97duGY,315
|
12
12
|
dbt_common/ui.py,sha256=rc2TEM29raBFc_LXcg901pMDD07C2ohwp9qzkE-7pBY,2567
|
@@ -14,7 +14,7 @@ dbt_common/clients/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuF
|
|
14
14
|
dbt_common/clients/_jinja_blocks.py,sha256=xoJK9Y0F93U2PKfT_3SJbBopCGYCtl7LiwKuylXnrEE,12947
|
15
15
|
dbt_common/clients/agate_helper.py,sha256=n5Q0_gJPbBhFvjd286NGYGlcTtdEExYmIT3968lppyg,9124
|
16
16
|
dbt_common/clients/jinja.py,sha256=i6VQ94FU4F6ZCQLHTxNSeGHmvyYSIe34nDhNkH6wO08,18502
|
17
|
-
dbt_common/clients/system.py,sha256=
|
17
|
+
dbt_common/clients/system.py,sha256=K-b9Lx8ZhgR5tKq1vnqbX8sEVaVRQHqkEkLYQIjFc64,23158
|
18
18
|
dbt_common/contracts/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
19
19
|
dbt_common/contracts/constraints.py,sha256=hyqTW2oPB1dfXWW388LWnL-EFdqTpQciKISH3CeLkro,1267
|
20
20
|
dbt_common/contracts/metadata.py,sha256=K_M06Rue0wmrQhFP_mq3uvQszq10CIt93oGiAVgbRfE,1293
|
@@ -56,7 +56,7 @@ dbt_common/utils/encoding.py,sha256=6_kSY2FvGNYMg7oX7PrbvVioieydih3Kl7Ii802LaHI,
|
|
56
56
|
dbt_common/utils/executor.py,sha256=Zyzd1wML3aN-iYn9ZG2Gc_jj5vknmvQNyH-c0RaPIpo,2446
|
57
57
|
dbt_common/utils/formatting.py,sha256=JUn5rzJ-uajs9wPCN0-f2iRFY1pOJF5YjTD9dERuLoc,165
|
58
58
|
dbt_common/utils/jinja.py,sha256=XNfZHuZhLM_R_yPmzYojPm6bF7QOoxIjSWrkJRw6wks,965
|
59
|
-
dbt_common-1.
|
60
|
-
dbt_common-1.
|
61
|
-
dbt_common-1.
|
62
|
-
dbt_common-1.
|
59
|
+
dbt_common-1.5.0.dist-info/METADATA,sha256=phlsK2cVVw1AWNpJa2_uNaM2KG5HroA0RZw4gUsMCFQ,5298
|
60
|
+
dbt_common-1.5.0.dist-info/WHEEL,sha256=1yFddiXMmvYK7QYTqtRNtX66WJ0Mz8PYEiEUoOUUxRY,87
|
61
|
+
dbt_common-1.5.0.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
|
62
|
+
dbt_common-1.5.0.dist-info/RECORD,,
|
File without changes
|