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 CHANGED
@@ -1 +1 @@
1
- version = "1.14.0"
1
+ version = "1.16.0"
@@ -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 interaction with
2
- external systems during a command invocation, so that the command can be re-run
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 typing import Any, Callable, Dict, List, Mapping, Optional, Type
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
- return cls(params=p, result=r)
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
- json.dump(self._to_dict(), file)
221
+ self.write_json(file)
195
222
 
196
- def _to_dict(self) -> Dict:
197
- dct: Dict[str, Any] = {}
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 = [r.to_dict() for r in self._records_by_type[record_type]]
201
- dct[record_type] = record_list
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 dct
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 json.load(file)
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=QueryRecord,FileLoadRecord,OtherRecord'
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
- def record_function_inner(func_to_record):
301
- # To avoid runtime overhead and other unpleasantness, we only apply the
302
- # record/replay decorator if a relevant env var is set.
303
- if get_record_mode_from_env() is None:
304
- return func_to_record
305
-
306
- @functools.wraps(func_to_record)
307
- def record_replay_wrapper(*args, **kwargs) -> Any:
308
- recorder: Optional[Recorder] = None
309
- try:
310
- from dbt_common.context import get_invocation_context
311
-
312
- recorder = get_invocation_context().recorder
313
- except LookupError:
314
- pass
315
-
316
- if recorder is None:
317
- return func_to_record(*args, **kwargs)
318
-
319
- if recorder.recorded_types is not None and not (
320
- record_type.__name__ in recorder.recorded_types
321
- or record_type.group in recorder.recorded_types
322
- ):
323
- return func_to_record(*args, **kwargs)
324
-
325
- # For methods, peel off the 'self' argument before calling the
326
- # params constructor.
327
- param_args = args[1:] if method else args
328
- if method and id_field_name is not None:
329
- param_args = (getattr(args[0], id_field_name),) + param_args
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
- params = record_type.params_cls(*param_args, **kwargs)
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
- include = True
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
- if recorder.mode == RecorderMode.REPLAY:
341
- return recorder.expect_record(params)
411
+ @dataclasses.dataclass
412
+ class AutoValues(DataClassJSONMixin):
413
+ def _to_dict(self):
414
+ return self.to_dict()
342
415
 
343
- r = func_to_record(*args, **kwargs)
344
- result = (
345
- None
346
- if record_type.result_cls is None
347
- else record_type.result_cls(*r)
348
- if tuple_result
349
- else record_type.result_cls(r)
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
- recorder.add_record(record_type(params=params, result=result))
352
- return r
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
- return record_replay_wrapper
587
+ cls.__init_subclass__ = wrapping_init_subclass
355
588
 
356
- return record_function_inner
589
+ return cls
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.3
1
+ Metadata-Version: 2.4
2
2
  Name: dbt-common
3
- Version: 1.14.0
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. Bump the `version` in [dbt_common_/__about__.py](https://github.com/dbt-labs/dbt-common/blob/main/dbt_common/__about__.py)
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=jgA5fU_XZ5Gv-NyYGvOeyOvgy8rP58A_XiIlWHtvbZo,19
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=rk4EYBU4SpDXRhqbSvDTJwojilxPSoaiEdOxkXow_BU,2549
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=2ZchIr4Wq7NwtAjjB-mxHP9ITD6-r45jEq4Zooso0gc,210
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=vItvHEF2yl_NbvuaIXYHo-q6v0LUpCxR2twcTMVCeQ0,12698
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=Cc-6YWCXoyftzNhG-L3N4W8WiKBZZIWB6wV7-JPrQkg,20054
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.14.0.dist-info/METADATA,sha256=VgvfWv6LGaTre2VQJqnIiJAhSYhG4AAPWiCjrKQd0Tk,5211
61
- dbt_common-1.14.0.dist-info/WHEEL,sha256=C2FUgwZgiLbznR-k0b_5k3Ai_1aASOXDss3lzCUsUug,87
62
- dbt_common-1.14.0.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
63
- dbt_common-1.14.0.dist-info/RECORD,,
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,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: hatchling 1.26.3
2
+ Generator: hatchling 1.27.0
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any