dbt-common 1.2.0__py3-none-any.whl → 1.4.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 CHANGED
@@ -1 +1 @@
1
- version = "1.2.0"
1
+ version = "1.4.0"
@@ -1,7 +1,8 @@
1
1
  from dataclasses import dataclass
2
- from typing import Dict, Optional, Union
2
+ from typing import Dict, Optional, Union, NamedTuple
3
3
 
4
4
  from dbt_common.dataclass_schema import dbtClassMixin
5
+ from dbt_common.utils.formatting import lowercase
5
6
 
6
7
 
7
8
  @dataclass
@@ -24,3 +25,35 @@ class TableMetadata(dbtClassMixin):
24
25
  database: Optional[str] = None
25
26
  comment: Optional[str] = None
26
27
  owner: Optional[str] = None
28
+
29
+
30
+ CatalogKey = NamedTuple(
31
+ "CatalogKey", [("database", Optional[str]), ("schema", str), ("name", str)]
32
+ )
33
+
34
+
35
+ @dataclass
36
+ class ColumnMetadata(dbtClassMixin):
37
+ type: str
38
+ index: int
39
+ name: str
40
+ comment: Optional[str] = None
41
+
42
+
43
+ ColumnMap = Dict[str, ColumnMetadata]
44
+
45
+
46
+ @dataclass
47
+ class CatalogTable(dbtClassMixin):
48
+ metadata: TableMetadata
49
+ columns: ColumnMap
50
+ stats: StatsDict
51
+ # the same table with two unique IDs will just be listed two times
52
+ unique_id: Optional[str] = None
53
+
54
+ def key(self) -> CatalogKey:
55
+ return CatalogKey(
56
+ lowercase(self.metadata.database),
57
+ self.metadata.schema.lower(),
58
+ self.metadata.name.lower(),
59
+ )
dbt_common/record.py CHANGED
@@ -9,6 +9,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
15
  from typing import Any, Dict, List, Mapping, Optional, Type
14
16
 
@@ -51,15 +53,68 @@ class Record:
51
53
 
52
54
 
53
55
  class Diff:
54
- """Marker class for diffs?"""
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)
92
+
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
+ )
55
110
 
56
- pass
111
+ return diff
57
112
 
58
113
 
59
114
  class RecorderMode(Enum):
60
115
  RECORD = 1
61
116
  REPLAY = 2
62
- RECORD_QUERIES = 3
117
+ DIFF = 3 # records and does diffing
63
118
 
64
119
 
65
120
  class Recorder:
@@ -67,15 +122,31 @@ class Recorder:
67
122
  _record_name_by_params_name: Dict[str, str] = {}
68
123
 
69
124
  def __init__(
70
- self, mode: RecorderMode, types: Optional[List], recording_path: Optional[str] = None
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
132
  self.types = types
74
133
  self._records_by_type: Dict[str, List[Record]] = {}
75
134
  self._replay_diffs: List["Diff"] = []
135
+ self.diff: Diff
136
+ self.previous_recording_path = previous_recording_path
137
+ self.current_recording_path = current_recording_path
138
+
139
+ if self.previous_recording_path is not None and self.mode in (
140
+ RecorderMode.REPLAY,
141
+ RecorderMode.DIFF,
142
+ ):
143
+ self.diff = Diff(
144
+ current_recording_path=self.current_recording_path,
145
+ previous_recording_path=self.previous_recording_path,
146
+ )
76
147
 
77
- if recording_path is not None:
78
- self._records_by_type = self.load(recording_path)
148
+ if self.mode == RecorderMode.REPLAY:
149
+ self._records_by_type = self.load(self.previous_recording_path)
79
150
 
80
151
  @classmethod
81
152
  def register_record_type(cls, rec_type) -> Any:
@@ -101,8 +172,8 @@ class Recorder:
101
172
 
102
173
  return match
103
174
 
104
- def write(self, file_name) -> None:
105
- with open(file_name, "w") as file:
175
+ def write(self) -> None:
176
+ with open(self.current_recording_path, "w") as file:
106
177
  json.dump(self._to_dict(), file)
107
178
 
108
179
  def _to_dict(self) -> Dict:
@@ -143,19 +214,19 @@ class Recorder:
143
214
 
144
215
  def write_diffs(self, diff_file_name) -> None:
145
216
  json.dump(
146
- self._replay_diffs,
217
+ self.diff.calculate_diff(),
147
218
  open(diff_file_name, "w"),
148
219
  )
149
220
 
150
221
  def print_diffs(self) -> None:
151
- print(repr(self._replay_diffs))
222
+ print(repr(self.diff.calculate_diff()))
152
223
 
153
224
 
154
225
  def get_record_mode_from_env() -> Optional[RecorderMode]:
155
226
  """
156
227
  Get the record mode from the environment variables.
157
228
 
158
- If the mode is not set to 'RECORD' or 'REPLAY', return None.
229
+ If the mode is not set to 'RECORD', 'DIFF' or 'REPLAY', return None.
159
230
  Expected format: 'DBT_RECORDER_MODE=RECORD'
160
231
  """
161
232
  record_mode = os.environ.get("DBT_RECORDER_MODE")
@@ -165,6 +236,9 @@ def get_record_mode_from_env() -> Optional[RecorderMode]:
165
236
 
166
237
  if record_mode.lower() == "record":
167
238
  return RecorderMode.RECORD
239
+ # diffing requires a file path, otherwise treat as noop
240
+ elif record_mode.lower() == "diff" and os.environ.get("DBT_RECORDER_FILE_PATH") is not None:
241
+ return RecorderMode.DIFF
168
242
  # replaying requires a file path, otherwise treat as noop
169
243
  elif record_mode.lower() == "replay" and os.environ.get("DBT_RECORDER_FILE_PATH") is not None:
170
244
  return RecorderMode.REPLAY
@@ -190,6 +264,15 @@ def get_record_types_from_env() -> Optional[List]:
190
264
  return record_types_str.split(",")
191
265
 
192
266
 
267
+ def get_record_types_from_dict(fp: str) -> List:
268
+ """
269
+ Get the record subset from the dict.
270
+ """
271
+ with open(fp) as file:
272
+ loaded_dct = json.load(file)
273
+ return list(loaded_dct.keys())
274
+
275
+
193
276
  def record_function(record_type, method=False, tuple_result=False):
194
277
  def record_function_inner(func_to_record):
195
278
  # To avoid runtime overhead and other unpleasantness, we only apply the
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: dbt-common
3
- Version: 1.2.0
3
+ Version: 1.4.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=-XvoSdwL-0go8ZX6VN74e-D6dwG0z-0p7vL5MO0Sz5s,18
1
+ dbt_common/__about__.py,sha256=0kccNYBMuNA3PIhlESWmh8xP1TWpNtIEzS0d-x80SC0,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=fwp8Q2x0UiB9fHbMFkmOlAgKLst1dhDxQpSSsd04aDw,8204
9
+ dbt_common/record.py,sha256=_P-OZWKQwGmq6nTC4r2oQDyh89gi-VGC2gdok6TywhI,11765
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
@@ -17,7 +17,7 @@ dbt_common/clients/jinja.py,sha256=i6VQ94FU4F6ZCQLHTxNSeGHmvyYSIe34nDhNkH6wO08,1
17
17
  dbt_common/clients/system.py,sha256=OOhRDWR5t0Ns3OhkqjPTNTtyl_RMRWPDHWCzDoFtgkA,23014
18
18
  dbt_common/contracts/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
19
19
  dbt_common/contracts/constraints.py,sha256=hyqTW2oPB1dfXWW388LWnL-EFdqTpQciKISH3CeLkro,1267
20
- dbt_common/contracts/metadata.py,sha256=d5zKvIR3s2j6_ZvafvlyNm5z6MVs8augT2jclbOSG-k,528
20
+ dbt_common/contracts/metadata.py,sha256=K_M06Rue0wmrQhFP_mq3uvQszq10CIt93oGiAVgbRfE,1293
21
21
  dbt_common/contracts/util.py,sha256=RZpeEExSKdyFwTq7MM3rd1ZkAf11C7I-bgppUJ6SXOg,741
22
22
  dbt_common/contracts/config/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
23
23
  dbt_common/contracts/config/base.py,sha256=jWLf6SBUy7wngYs0Z5Zmx1O1v86XRueYaABlZ0W2Bxc,8356
@@ -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.2.0.dist-info/METADATA,sha256=7rA7to-AQn0UQvVJVAy14wx7uhcyB8D_1wHmTTtEHDo,5264
60
- dbt_common-1.2.0.dist-info/WHEEL,sha256=zEMcRr9Kr03x1ozGwg5v9NQBKn3kndp6LSoSlVg-jhU,87
61
- dbt_common-1.2.0.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
62
- dbt_common-1.2.0.dist-info/RECORD,,
59
+ dbt_common-1.4.0.dist-info/METADATA,sha256=QH1OkqWWMGMTDUDSrqHmEnEvHs0akdlpDU-5asSCUD8,5298
60
+ dbt_common-1.4.0.dist-info/WHEEL,sha256=zEMcRr9Kr03x1ozGwg5v9NQBKn3kndp6LSoSlVg-jhU,87
61
+ dbt_common-1.4.0.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
62
+ dbt_common-1.4.0.dist-info/RECORD,,