dbt-common 1.14.0__py3-none-any.whl → 1.16.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/jinja.py +66 -0
- dbt_common/context.py +14 -0
- dbt_common/invocation.py +8 -1
- dbt_common/record.py +299 -66
- {dbt_common-1.14.0.dist-info → dbt_common-1.16.0.dist-info}/METADATA +5 -6
- {dbt_common-1.14.0.dist-info → dbt_common-1.16.0.dist-info}/RECORD +9 -9
- {dbt_common-1.14.0.dist-info → dbt_common-1.16.0.dist-info}/WHEEL +1 -1
- {dbt_common-1.14.0.dist-info → dbt_common-1.16.0.dist-info}/licenses/LICENSE +0 -0
dbt_common/__about__.py
CHANGED
@@ -1 +1 @@
|
|
1
|
-
version = "1.
|
1
|
+
version = "1.16.0"
|
dbt_common/clients/jinja.py
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
import codecs
|
2
|
+
import dataclasses
|
2
3
|
import linecache
|
3
4
|
import os
|
4
5
|
import tempfile
|
@@ -90,6 +91,12 @@ def _linecache_inject(source: str, write: bool) -> str:
|
|
90
91
|
return filename
|
91
92
|
|
92
93
|
|
94
|
+
@dataclasses.dataclass
|
95
|
+
class MacroType:
|
96
|
+
name: str
|
97
|
+
type_params: List["MacroType"] = dataclasses.field(default_factory=list)
|
98
|
+
|
99
|
+
|
93
100
|
class MacroFuzzParser(jinja2.parser.Parser):
|
94
101
|
def parse_macro(self) -> jinja2.nodes.Macro:
|
95
102
|
node = jinja2.nodes.Macro(lineno=next(self.stream).lineno)
|
@@ -103,6 +110,65 @@ class MacroFuzzParser(jinja2.parser.Parser):
|
|
103
110
|
node.body = self.parse_statements(("name:endmacro",), drop_needle=True)
|
104
111
|
return node
|
105
112
|
|
113
|
+
def parse_signature(self, node: Union[jinja2.nodes.Macro, jinja2.nodes.CallBlock]) -> None:
|
114
|
+
"""Overrides the default jinja Parser.parse_signature method, modifying
|
115
|
+
the original implementation to allow macros to have typed parameters."""
|
116
|
+
|
117
|
+
# Jinja does not support extending its node types, such as Macro, so
|
118
|
+
# at least while typed macros are experimental, we will patch the
|
119
|
+
# information onto the existing types.
|
120
|
+
setattr(node, "arg_types", [])
|
121
|
+
setattr(node, "has_type_annotations", False)
|
122
|
+
|
123
|
+
args = node.args = [] # type: ignore
|
124
|
+
defaults = node.defaults = [] # type: ignore
|
125
|
+
|
126
|
+
self.stream.expect("lparen")
|
127
|
+
while self.stream.current.type != "rparen":
|
128
|
+
if args:
|
129
|
+
self.stream.expect("comma")
|
130
|
+
|
131
|
+
arg = self.parse_assign_target(name_only=True)
|
132
|
+
arg.set_ctx("param")
|
133
|
+
|
134
|
+
type_name: Optional[str]
|
135
|
+
if self.stream.skip_if("colon"):
|
136
|
+
node.has_type_annotations = True # type: ignore
|
137
|
+
type_name = self.parse_type_name()
|
138
|
+
else:
|
139
|
+
type_name = ""
|
140
|
+
|
141
|
+
node.arg_types.append(type_name) # type: ignore
|
142
|
+
|
143
|
+
if self.stream.skip_if("assign"):
|
144
|
+
defaults.append(self.parse_expression())
|
145
|
+
elif defaults:
|
146
|
+
self.fail("non-default argument follows default argument")
|
147
|
+
|
148
|
+
args.append(arg)
|
149
|
+
self.stream.expect("rparen")
|
150
|
+
|
151
|
+
def parse_type_name(self) -> MacroType:
|
152
|
+
# NOTE: Types syntax is validated here, but not whether type names
|
153
|
+
# are valid or have correct parameters.
|
154
|
+
|
155
|
+
# A type name should consist of a name (i.e. 'Dict')...
|
156
|
+
type_name = self.stream.expect("name").value
|
157
|
+
type = MacroType(type_name)
|
158
|
+
|
159
|
+
# ..and an optional comma-delimited list of type parameters
|
160
|
+
# as in the type declaration 'Dict[str, str]'
|
161
|
+
if self.stream.skip_if("lbracket"):
|
162
|
+
while self.stream.current.type != "rbracket":
|
163
|
+
if type.type_params:
|
164
|
+
self.stream.expect("comma")
|
165
|
+
param_type = self.parse_type_name()
|
166
|
+
type.type_params.append(param_type)
|
167
|
+
|
168
|
+
self.stream.expect("rbracket")
|
169
|
+
|
170
|
+
return type
|
171
|
+
|
106
172
|
|
107
173
|
class MacroFuzzEnvironment(jinja2.sandbox.SandboxedEnvironment):
|
108
174
|
def _parse(
|
dbt_common/context.py
CHANGED
@@ -39,9 +39,16 @@ class InvocationContext:
|
|
39
39
|
else:
|
40
40
|
self._env = env_public
|
41
41
|
|
42
|
+
self.name = "unset"
|
42
43
|
self._env_secrets: Optional[List[str]] = None
|
43
44
|
self._env_private = env_private
|
44
45
|
self.recorder: Optional[Recorder] = None
|
46
|
+
|
47
|
+
# If set to True later, this flag will prevent dbt from creating a new
|
48
|
+
# invocation context for every invocation, which is useful for testing
|
49
|
+
# scenarios.
|
50
|
+
self.do_not_reset = False
|
51
|
+
|
45
52
|
# This class will also eventually manage the invocation_id, flags, event manager, etc.
|
46
53
|
|
47
54
|
@property
|
@@ -84,3 +91,10 @@ def get_invocation_context() -> InvocationContext:
|
|
84
91
|
invocation_var = reliably_get_invocation_var()
|
85
92
|
ctx = invocation_var.get()
|
86
93
|
return ctx
|
94
|
+
|
95
|
+
|
96
|
+
def try_get_invocation_context() -> Optional[InvocationContext]:
|
97
|
+
try:
|
98
|
+
return get_invocation_context()
|
99
|
+
except Exception:
|
100
|
+
return None
|
dbt_common/invocation.py
CHANGED
@@ -1,12 +1,19 @@
|
|
1
1
|
import uuid
|
2
|
+
from datetime import datetime
|
2
3
|
|
3
4
|
_INVOCATION_ID = str(uuid.uuid4())
|
5
|
+
_INVOCATION_STARTED_AT = datetime.utcnow()
|
4
6
|
|
5
7
|
|
6
8
|
def get_invocation_id() -> str:
|
7
9
|
return _INVOCATION_ID
|
8
10
|
|
9
11
|
|
12
|
+
def get_invocation_started_at() -> datetime:
|
13
|
+
return _INVOCATION_STARTED_AT
|
14
|
+
|
15
|
+
|
10
16
|
def reset_invocation_id() -> None:
|
11
|
-
global _INVOCATION_ID
|
17
|
+
global _INVOCATION_ID, _INVOCATION_STARTED_AT
|
12
18
|
_INVOCATION_ID = str(uuid.uuid4())
|
19
|
+
_INVOCATION_STARTED_AT = datetime.utcnow()
|
dbt_common/record.py
CHANGED
@@ -1,17 +1,28 @@
|
|
1
|
-
"""The record module provides a mechanism for recording dbt's
|
2
|
-
external systems during a command invocation, so that the
|
3
|
-
later with the recording 'replayed' to dbt.
|
1
|
+
"""The record module provides a record/replay mechanism for recording dbt's
|
2
|
+
interactions with external systems during a command invocation, so that the
|
3
|
+
command can be re-run later with the recording 'replayed' to dbt.
|
4
4
|
|
5
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
|
+
import inspect
|
10
11
|
import json
|
11
12
|
import os
|
12
13
|
|
13
14
|
from enum import Enum
|
14
|
-
from
|
15
|
+
from threading import Lock
|
16
|
+
from typing import Any, Callable, Dict, List, Mapping, Optional, TextIO, Tuple, Type
|
17
|
+
|
18
|
+
from mashumaro import field_options
|
19
|
+
from mashumaro.mixins.json import DataClassJSONMixin
|
20
|
+
from mashumaro.types import SerializationStrategy
|
21
|
+
|
22
|
+
import contextvars
|
23
|
+
|
24
|
+
|
25
|
+
RECORDED_BY_HIGHER_FUNCTION = contextvars.ContextVar("RECORDED_BY_HIGHER_FUNCTION", default=False)
|
15
26
|
|
16
27
|
|
17
28
|
class Record:
|
@@ -23,9 +34,10 @@ class Record:
|
|
23
34
|
result_cls: Optional[type] = None
|
24
35
|
group: Optional[str] = None
|
25
36
|
|
26
|
-
def __init__(self, params, result) -> None:
|
37
|
+
def __init__(self, params, result, seq=None) -> None:
|
27
38
|
self.params = params
|
28
39
|
self.result = result
|
40
|
+
self.seq = seq
|
29
41
|
|
30
42
|
def to_dict(self) -> Dict[str, Any]:
|
31
43
|
return {
|
@@ -37,6 +49,7 @@ class Record:
|
|
37
49
|
else dataclasses.asdict(self.result)
|
38
50
|
if self.result is not None
|
39
51
|
else None,
|
52
|
+
"seq": self.seq,
|
40
53
|
}
|
41
54
|
|
42
55
|
@classmethod
|
@@ -53,7 +66,8 @@ class Record:
|
|
53
66
|
if cls.result_cls is not None
|
54
67
|
else None
|
55
68
|
)
|
56
|
-
|
69
|
+
s = dct.get("seq", None)
|
70
|
+
return cls(params=p, result=r, seq=s)
|
57
71
|
|
58
72
|
|
59
73
|
class Diff:
|
@@ -129,6 +143,7 @@ class RecorderMode(Enum):
|
|
129
143
|
class Recorder:
|
130
144
|
_record_cls_by_name: Dict[str, Type] = {}
|
131
145
|
_record_name_by_params_name: Dict[str, str] = {}
|
146
|
+
_auto_serialization_strategies: Dict[Type, SerializationStrategy] = {}
|
132
147
|
|
133
148
|
def __init__(
|
134
149
|
self,
|
@@ -158,6 +173,9 @@ class Recorder:
|
|
158
173
|
if self.mode == RecorderMode.REPLAY:
|
159
174
|
self._unprocessed_records_by_type = self.load(self.previous_recording_path)
|
160
175
|
|
176
|
+
self._counter = 0
|
177
|
+
self._counter_lock = Lock()
|
178
|
+
|
161
179
|
@classmethod
|
162
180
|
def register_record_type(cls, rec_type) -> Any:
|
163
181
|
cls._record_cls_by_name[rec_type.__name__] = rec_type
|
@@ -168,6 +186,11 @@ class Recorder:
|
|
168
186
|
rec_cls_name = record.__class__.__name__ # type: ignore
|
169
187
|
if rec_cls_name not in self._records_by_type:
|
170
188
|
self._records_by_type[rec_cls_name] = []
|
189
|
+
|
190
|
+
with self._counter_lock:
|
191
|
+
record.seq = self._counter
|
192
|
+
self._counter += 1
|
193
|
+
|
171
194
|
self._records_by_type[rec_cls_name].append(record)
|
172
195
|
|
173
196
|
def pop_matching_record(self, params: Any) -> Optional[Record]:
|
@@ -189,23 +212,38 @@ class Recorder:
|
|
189
212
|
|
190
213
|
return match
|
191
214
|
|
215
|
+
def write_json(self, out_stream: TextIO):
|
216
|
+
d = self._to_list()
|
217
|
+
json.dump(d, out_stream)
|
218
|
+
|
192
219
|
def write(self) -> None:
|
193
220
|
with open(self.current_recording_path, "w") as file:
|
194
|
-
|
221
|
+
self.write_json(file)
|
195
222
|
|
196
|
-
def
|
197
|
-
|
223
|
+
def _to_list(self) -> List[Dict]:
|
224
|
+
def get_tagged_dict(record: Record, record_type: str) -> Dict:
|
225
|
+
d = record.to_dict()
|
226
|
+
d["type"] = record_type
|
227
|
+
return d
|
198
228
|
|
229
|
+
record_list: List[Dict] = []
|
199
230
|
for record_type in self._records_by_type:
|
200
|
-
record_list
|
201
|
-
|
231
|
+
record_list.extend(
|
232
|
+
get_tagged_dict(r, record_type) for r in self._records_by_type[record_type]
|
233
|
+
)
|
234
|
+
|
235
|
+
record_list.sort(key=lambda r: r["seq"])
|
202
236
|
|
203
|
-
return
|
237
|
+
return record_list
|
204
238
|
|
205
239
|
@classmethod
|
206
240
|
def load(cls, file_name: str) -> Dict[str, List[Dict[str, Any]]]:
|
207
241
|
with open(file_name) as file:
|
208
|
-
return
|
242
|
+
return cls.load_json(file)
|
243
|
+
|
244
|
+
@classmethod
|
245
|
+
def load_json(cls, in_stream: TextIO) -> Dict[str, List[Dict[str, Any]]]:
|
246
|
+
return json.load(in_stream)
|
209
247
|
|
210
248
|
def _ensure_records_processed(self, record_type_name: str) -> None:
|
211
249
|
if record_type_name in self._records_by_type:
|
@@ -239,6 +277,12 @@ class Recorder:
|
|
239
277
|
assert self.diff is not None
|
240
278
|
print(repr(self.diff.calculate_diff()))
|
241
279
|
|
280
|
+
@classmethod
|
281
|
+
def register_serialization_strategy(
|
282
|
+
cls, t: Type, serialization_strategy: SerializationStrategy
|
283
|
+
) -> None:
|
284
|
+
cls._auto_serialization_strategies[t] = serialization_strategy
|
285
|
+
|
242
286
|
|
243
287
|
def get_record_mode_from_env() -> Optional[RecorderMode]:
|
244
288
|
"""
|
@@ -271,7 +315,7 @@ def get_record_types_from_env() -> Optional[List]:
|
|
271
315
|
|
272
316
|
If no types are provided, there will be no filtering.
|
273
317
|
Invalid types will be ignored.
|
274
|
-
Expected format: 'DBT_RECORDER_TYPES=
|
318
|
+
Expected format: 'DBT_RECORDER_TYPES=Database,FileLoadRecord'
|
275
319
|
"""
|
276
320
|
record_types_str = os.environ.get("DBT_RECORDER_TYPES")
|
277
321
|
|
@@ -283,74 +327,263 @@ def get_record_types_from_env() -> Optional[List]:
|
|
283
327
|
|
284
328
|
|
285
329
|
def get_record_types_from_dict(fp: str) -> List:
|
286
|
-
"""
|
287
|
-
Get the record subset from the dict.
|
288
|
-
"""
|
330
|
+
"""Get the record subset from the dict."""
|
289
331
|
with open(fp) as file:
|
290
332
|
loaded_dct = json.load(file)
|
291
333
|
return list(loaded_dct.keys())
|
292
334
|
|
293
335
|
|
336
|
+
def auto_record_function(
|
337
|
+
record_name: str,
|
338
|
+
method: bool = True,
|
339
|
+
group: Optional[str] = None,
|
340
|
+
index_on_thread_name: bool = True,
|
341
|
+
) -> Callable:
|
342
|
+
"""This is the @auto_record_function decorator. It works in a similar way to
|
343
|
+
the @record_function decorator, except automatically generates boilerplate
|
344
|
+
classes for the Record, Params, and Result classes which would otherwise be
|
345
|
+
needed. That makes it suitable for quickly adding record support to simple
|
346
|
+
functions with simple parameters."""
|
347
|
+
return functools.partial(
|
348
|
+
_record_function_inner,
|
349
|
+
record_name,
|
350
|
+
method,
|
351
|
+
False,
|
352
|
+
None,
|
353
|
+
group,
|
354
|
+
index_on_thread_name,
|
355
|
+
False,
|
356
|
+
)
|
357
|
+
|
358
|
+
|
294
359
|
def record_function(
|
295
360
|
record_type,
|
296
361
|
method: bool = False,
|
297
362
|
tuple_result: bool = False,
|
298
363
|
id_field_name: Optional[str] = None,
|
364
|
+
index_on_thread_id: bool = False,
|
299
365
|
) -> Callable:
|
300
|
-
|
301
|
-
|
302
|
-
|
303
|
-
|
304
|
-
|
305
|
-
|
306
|
-
|
307
|
-
|
308
|
-
|
309
|
-
|
310
|
-
|
311
|
-
|
312
|
-
|
313
|
-
|
314
|
-
|
315
|
-
|
316
|
-
|
317
|
-
|
318
|
-
|
319
|
-
|
320
|
-
|
321
|
-
|
322
|
-
|
323
|
-
|
324
|
-
|
325
|
-
|
326
|
-
|
327
|
-
|
328
|
-
|
329
|
-
|
366
|
+
"""This is the @record_function decorator, which marks functions which will
|
367
|
+
have their function calls recorded during record mode, and mocked out with
|
368
|
+
previously recorded replay data during replay."""
|
369
|
+
return functools.partial(
|
370
|
+
_record_function_inner,
|
371
|
+
record_type,
|
372
|
+
method,
|
373
|
+
tuple_result,
|
374
|
+
id_field_name,
|
375
|
+
None,
|
376
|
+
index_on_thread_id,
|
377
|
+
False,
|
378
|
+
)
|
379
|
+
|
380
|
+
|
381
|
+
def _get_arg_fields(
|
382
|
+
spec: inspect.FullArgSpec,
|
383
|
+
skip_first: bool = False,
|
384
|
+
) -> List[Tuple[str, Optional[Type], dataclasses.Field]]:
|
385
|
+
arg_fields = []
|
386
|
+
defaults = len(spec.defaults) if spec.defaults else 0
|
387
|
+
for i, arg_name in enumerate(spec.args):
|
388
|
+
if skip_first and i == 0:
|
389
|
+
continue
|
390
|
+
annotation = spec.annotations.get(arg_name)
|
391
|
+
if annotation is None:
|
392
|
+
raise Exception("Recorded functions must have type annotations.")
|
393
|
+
field = _get_field(arg_name, annotation)
|
394
|
+
if i >= len(spec.args) - defaults:
|
395
|
+
field[2].default = (
|
396
|
+
spec.defaults[i - len(spec.args) + defaults] if spec.defaults else None
|
397
|
+
)
|
398
|
+
arg_fields.append(field)
|
399
|
+
return arg_fields
|
400
|
+
|
330
401
|
|
331
|
-
|
402
|
+
def _get_field(field_name: str, t: Type) -> Tuple[str, Optional[Type], dataclasses.Field]:
|
403
|
+
dc_field: dataclasses.Field = dataclasses.field()
|
404
|
+
strat = Recorder._auto_serialization_strategies.get(t)
|
405
|
+
if strat is not None:
|
406
|
+
dc_field.metadata = field_options(serialization_strategy=Recorder._auto_serialization_strategies[t]) # type: ignore
|
332
407
|
|
333
|
-
|
334
|
-
if hasattr(params, "_include"):
|
335
|
-
include = params._include()
|
408
|
+
return field_name, t, dc_field
|
336
409
|
|
337
|
-
if not include:
|
338
|
-
return func_to_record(*args, **kwargs)
|
339
410
|
|
340
|
-
|
341
|
-
|
411
|
+
@dataclasses.dataclass
|
412
|
+
class AutoValues(DataClassJSONMixin):
|
413
|
+
def _to_dict(self):
|
414
|
+
return self.to_dict()
|
342
415
|
|
343
|
-
|
344
|
-
|
345
|
-
|
346
|
-
|
347
|
-
|
348
|
-
|
349
|
-
|
416
|
+
def _from_dict(self, data):
|
417
|
+
return self.from_dict(data)
|
418
|
+
|
419
|
+
|
420
|
+
def _record_function_inner(
|
421
|
+
record_type,
|
422
|
+
method,
|
423
|
+
tuple_result,
|
424
|
+
id_field_name,
|
425
|
+
group,
|
426
|
+
index_on_thread_id,
|
427
|
+
is_classmethod,
|
428
|
+
func_to_record,
|
429
|
+
):
|
430
|
+
if isinstance(record_type, str):
|
431
|
+
return_type = inspect.signature(func_to_record).return_annotation
|
432
|
+
fields = _get_arg_fields(inspect.getfullargspec(func_to_record), method)
|
433
|
+
if index_on_thread_id:
|
434
|
+
id_field_name = "thread_id"
|
435
|
+
fields.insert(0, _get_field("thread_id", str))
|
436
|
+
params_cls = dataclasses.make_dataclass(
|
437
|
+
f"{record_type}Params", fields, bases=(AutoValues,)
|
438
|
+
)
|
439
|
+
result_cls = (
|
440
|
+
None
|
441
|
+
if return_type is None or return_type == inspect._empty
|
442
|
+
else dataclasses.make_dataclass(
|
443
|
+
f"{record_type}Result",
|
444
|
+
[_get_field("return_val", return_type)],
|
445
|
+
bases=(AutoValues,),
|
350
446
|
)
|
351
|
-
|
352
|
-
|
447
|
+
)
|
448
|
+
|
449
|
+
record_type = type(
|
450
|
+
f"{record_type}Record",
|
451
|
+
(Record,),
|
452
|
+
{"params_cls": params_cls, "result_cls": result_cls, "group": group},
|
453
|
+
)
|
454
|
+
|
455
|
+
Recorder.register_record_type(record_type)
|
456
|
+
|
457
|
+
@functools.wraps(func_to_record)
|
458
|
+
def record_replay_wrapper(*args, **kwargs) -> Any:
|
459
|
+
recorder: Optional[Recorder] = None
|
460
|
+
try:
|
461
|
+
from dbt_common.context import get_invocation_context
|
462
|
+
|
463
|
+
recorder = get_invocation_context().recorder
|
464
|
+
except LookupError:
|
465
|
+
pass
|
466
|
+
|
467
|
+
call_args = args[1:] if is_classmethod else args
|
468
|
+
|
469
|
+
if recorder is None:
|
470
|
+
return func_to_record(*call_args, **kwargs)
|
471
|
+
|
472
|
+
if recorder.recorded_types is not None and not (
|
473
|
+
record_type.__name__ in recorder.recorded_types
|
474
|
+
or record_type.group in recorder.recorded_types
|
475
|
+
):
|
476
|
+
return func_to_record(*call_args, **kwargs)
|
477
|
+
|
478
|
+
# For methods, peel off the 'self' argument before calling the
|
479
|
+
# params constructor.
|
480
|
+
param_args = args[1:] if method else args
|
481
|
+
if method and id_field_name is not None:
|
482
|
+
if index_on_thread_id:
|
483
|
+
from dbt_common.events.contextvars import get_node_info
|
484
|
+
|
485
|
+
node_info = get_node_info()
|
486
|
+
if node_info and "unique_id" in node_info:
|
487
|
+
thread_name = node_info["unique_id"]
|
488
|
+
else:
|
489
|
+
from dbt_common.context import get_invocation_context
|
490
|
+
|
491
|
+
thread_name = get_invocation_context().name
|
492
|
+
param_args = (thread_name,) + param_args
|
493
|
+
else:
|
494
|
+
param_args = (getattr(args[0], id_field_name),) + param_args
|
495
|
+
|
496
|
+
params = record_type.params_cls(*param_args, **kwargs)
|
497
|
+
|
498
|
+
include = True
|
499
|
+
if hasattr(params, "_include"):
|
500
|
+
include = params._include()
|
501
|
+
|
502
|
+
if not include:
|
503
|
+
return func_to_record(*call_args, **kwargs)
|
504
|
+
|
505
|
+
if recorder.mode == RecorderMode.REPLAY:
|
506
|
+
return recorder.expect_record(params)
|
507
|
+
if RECORDED_BY_HIGHER_FUNCTION.get():
|
508
|
+
return func_to_record(*call_args, **kwargs)
|
509
|
+
|
510
|
+
RECORDED_BY_HIGHER_FUNCTION.set(True)
|
511
|
+
r = func_to_record(*call_args, **kwargs)
|
512
|
+
result = (
|
513
|
+
None
|
514
|
+
if record_type.result_cls is None
|
515
|
+
else record_type.result_cls(*r)
|
516
|
+
if tuple_result
|
517
|
+
else record_type.result_cls(r)
|
518
|
+
)
|
519
|
+
RECORDED_BY_HIGHER_FUNCTION.set(False)
|
520
|
+
recorder.add_record(record_type(params=params, result=result))
|
521
|
+
return r
|
522
|
+
|
523
|
+
setattr(
|
524
|
+
record_replay_wrapper,
|
525
|
+
"_record_metadata",
|
526
|
+
{
|
527
|
+
"record_type": record_type,
|
528
|
+
"method": method,
|
529
|
+
"tuple_result": tuple_result,
|
530
|
+
"id_field_name": id_field_name,
|
531
|
+
"group": group,
|
532
|
+
"index_on_thread_id": index_on_thread_id,
|
533
|
+
},
|
534
|
+
)
|
535
|
+
|
536
|
+
return record_replay_wrapper
|
537
|
+
|
538
|
+
|
539
|
+
def _is_classmethod(method):
|
540
|
+
b = inspect.ismethod(method) and isinstance(method.__self__, type)
|
541
|
+
return b
|
542
|
+
|
543
|
+
|
544
|
+
def supports_replay(cls):
|
545
|
+
"""Class decorator which adds record/replay support for a class. In particular,
|
546
|
+
this decorator ensures that calls to overriden functions are still recorded."""
|
547
|
+
|
548
|
+
# When record/replay is inactive, do nothing.
|
549
|
+
if get_record_mode_from_env() is None:
|
550
|
+
return cls
|
551
|
+
|
552
|
+
# Replace the __init_subclass__ method of this class so that when it
|
553
|
+
# is subclassed, methods on the new subclass which override recorded
|
554
|
+
# functions are modified to be recorded as well.
|
555
|
+
original_init_subclass = cls.__init_subclass__
|
556
|
+
|
557
|
+
@classmethod
|
558
|
+
def wrapping_init_subclass(sub_cls):
|
559
|
+
for method_name in dir(cls):
|
560
|
+
method = getattr(cls, method_name)
|
561
|
+
metadata = getattr(method, "_record_metadata", None)
|
562
|
+
if method and getattr(method, "_record_metadata", None):
|
563
|
+
sub_method = getattr(sub_cls, method_name, None)
|
564
|
+
recorded_sub_method = _record_function_inner(
|
565
|
+
metadata["record_type"],
|
566
|
+
metadata["method"],
|
567
|
+
metadata["tuple_result"],
|
568
|
+
metadata["id_field_name"],
|
569
|
+
metadata["group"],
|
570
|
+
metadata["index_on_thread_id"],
|
571
|
+
_is_classmethod(method),
|
572
|
+
sub_method,
|
573
|
+
)
|
574
|
+
|
575
|
+
if _is_classmethod(method):
|
576
|
+
recorded_sub_method = classmethod(recorded_sub_method)
|
577
|
+
|
578
|
+
if sub_method is not None:
|
579
|
+
setattr(
|
580
|
+
sub_cls,
|
581
|
+
method_name,
|
582
|
+
recorded_sub_method,
|
583
|
+
)
|
584
|
+
|
585
|
+
original_init_subclass()
|
353
586
|
|
354
|
-
|
587
|
+
cls.__init_subclass__ = wrapping_init_subclass
|
355
588
|
|
356
|
-
return
|
589
|
+
return cls
|
@@ -1,6 +1,6 @@
|
|
1
|
-
Metadata-Version: 2.
|
1
|
+
Metadata-Version: 2.4
|
2
2
|
Name: dbt-common
|
3
|
-
Version: 1.
|
3
|
+
Version: 1.16.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
|
@@ -8,7 +8,8 @@ Project-URL: Issues, https://github.com/dbt-labs/dbt-common/issues
|
|
8
8
|
Project-URL: Changelog, https://github.com/dbt-labs/dbt-common/blob/main/CHANGELOG.md
|
9
9
|
Author-email: dbt Labs <info@dbtlabs.com>
|
10
10
|
Maintainer-email: dbt Labs <info@dbtlabs.com>
|
11
|
-
License: Apache-2.0
|
11
|
+
License-Expression: Apache-2.0
|
12
|
+
License-File: LICENSE
|
12
13
|
Classifier: Development Status :: 2 - Pre-Alpha
|
13
14
|
Classifier: License :: OSI Approved :: Apache Software License
|
14
15
|
Classifier: Operating System :: MacOS :: MacOS X
|
@@ -65,9 +66,7 @@ The shared common utilities for dbt-core and adapter implementations use
|
|
65
66
|
|
66
67
|
### Releasing dbt-common
|
67
68
|
To release a new version of dbt-common to pypi, you'll need to:
|
68
|
-
1.
|
69
|
-
2. Run the [release workflow](https://github.com/dbt-labs/dbt-common/actions/workflows/release.yml) to test pypi and confirm a successful test release in: https://test.pypi.org/project/dbt-common/
|
70
|
-
3. Run the [release workflow](https://github.com/dbt-labs/dbt-common/actions/workflows/release.yml) to prod pypi and confirm a successful release in: https://pypi.org/project/dbt-common/
|
69
|
+
1. Run the [release workflow](https://github.com/dbt-labs/dbt-common/actions/workflows/release.yml) to bump the version, generate changelogs and release to pypi
|
71
70
|
4. Bump the version of `dbt-common` in `dbt-core` and `dbt-adapters` if you're releasing a new major version or a pre-release:
|
72
71
|
* `dbt-core`: [setup.py](https://github.com/dbt-labs/dbt-core/blob/main/core/setup.py)
|
73
72
|
* `dbt-adapters`: [pyproject.toml](https://github.com/dbt-labs/dbt-adapters/blob/main/pyproject.toml)
|
@@ -1,20 +1,20 @@
|
|
1
|
-
dbt_common/__about__.py,sha256=
|
1
|
+
dbt_common/__about__.py,sha256=6EsnLejJWxuMqRt_s2lK0ter3lZGaIm5OK1MPeLZC5M,19
|
2
2
|
dbt_common/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
3
3
|
dbt_common/behavior_flags.py,sha256=hQzxCqQSweJbRp_xoQqNnlUF77PBuOdCdLOSdcBlkxk,4885
|
4
4
|
dbt_common/constants.py,sha256=-Y5DIL1SDPQWtlCNizXRYxFgbx1D7LaLs1ysamvGMRk,278
|
5
|
-
dbt_common/context.py,sha256
|
5
|
+
dbt_common/context.py,sha256=-ErtKG4xfOh1-Y569fwu6u2O381nRan18HhATrYDoZE,2950
|
6
6
|
dbt_common/dataclass_schema.py,sha256=u2S0dxwxIghv8RMqC91HlWZJVxmsC_844yZQaGyOwdY,5563
|
7
7
|
dbt_common/helper_types.py,sha256=FWJGPmp7Qp2iToHyI4uvhkBbu_d1tl2_oF-obi98_N4,3917
|
8
|
-
dbt_common/invocation.py,sha256=
|
8
|
+
dbt_common/invocation.py,sha256=xw0NBIE-6LHd135cx4non-MkGGsia0mYN0lMkmNEucE,435
|
9
9
|
dbt_common/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
10
|
-
dbt_common/record.py,sha256=
|
10
|
+
dbt_common/record.py,sha256=B5Ybpf-yzPRPyhxp4JNZyDAOGoSd2dr6VjLykH7VKN4,20247
|
11
11
|
dbt_common/semver.py,sha256=Znewz6tc_NBpXr4mZf20bK_RayPL4ODrnxDbkUZrrRo,15034
|
12
12
|
dbt_common/tests.py,sha256=6lC_JuRtoYO6cbAF8-R5aTM4HtQiM_EH8X5m_97duGY,315
|
13
13
|
dbt_common/ui.py,sha256=rc2TEM29raBFc_LXcg901pMDD07C2ohwp9qzkE-7pBY,2567
|
14
14
|
dbt_common/clients/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
15
15
|
dbt_common/clients/_jinja_blocks.py,sha256=5I_VEWkkW_54uK09ErP_8ey7wj-OOXvt1OHqr73HLOk,14879
|
16
16
|
dbt_common/clients/agate_helper.py,sha256=anKKgKV5PSnFRuFeBwRMdHK3JCaQoUqB2ZXHD0su0Wo,9123
|
17
|
-
dbt_common/clients/jinja.py,sha256=
|
17
|
+
dbt_common/clients/jinja.py,sha256=VpCYe_0seh6K4667-4TmQ2PP-T48UIcvDRT3miRyn4o,22488
|
18
18
|
dbt_common/clients/system.py,sha256=aoUBtOuXVmkOyj6IhhJ3Y4a7JFzPO2F_zKyOtz3xy44,23932
|
19
19
|
dbt_common/contracts/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
20
20
|
dbt_common/contracts/constraints.py,sha256=_f1q3Rkcg2UwA7zI5XBbUMXnPUg6pw17UC7l1HyhyQM,1352
|
@@ -57,7 +57,7 @@ dbt_common/utils/encoding.py,sha256=6_kSY2FvGNYMg7oX7PrbvVioieydih3Kl7Ii802LaHI,
|
|
57
57
|
dbt_common/utils/executor.py,sha256=pNY0UbPlwQmTE69Vt_Rj91YGCIOEaqeYU3CjAds0T70,2454
|
58
58
|
dbt_common/utils/formatting.py,sha256=JUn5rzJ-uajs9wPCN0-f2iRFY1pOJF5YjTD9dERuLoc,165
|
59
59
|
dbt_common/utils/jinja.py,sha256=JXgNmJArGGy0h7qkbNLA3zaEQmoF1CxsNBYTlIwFXDw,1101
|
60
|
-
dbt_common-1.
|
61
|
-
dbt_common-1.
|
62
|
-
dbt_common-1.
|
63
|
-
dbt_common-1.
|
60
|
+
dbt_common-1.16.0.dist-info/METADATA,sha256=k7yyMmWSYXvX-1bT5gzMUphCMcqntoMMloji1mnszvg,4895
|
61
|
+
dbt_common-1.16.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
62
|
+
dbt_common-1.16.0.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
|
63
|
+
dbt_common-1.16.0.dist-info/RECORD,,
|
File without changes
|