dbt-common 1.8.0__tar.gz → 1.10.0__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.
Files changed (107) hide show
  1. {dbt_common-1.8.0 → dbt_common-1.10.0}/PKG-INFO +1 -1
  2. dbt_common-1.10.0/dbt_common/__about__.py +1 -0
  3. {dbt_common-1.8.0 → dbt_common-1.10.0}/dbt_common/behavior_flags.py +37 -20
  4. {dbt_common-1.8.0 → dbt_common-1.10.0}/dbt_common/clients/jinja.py +75 -49
  5. {dbt_common-1.8.0 → dbt_common-1.10.0}/dbt_common/contracts/config/base.py +83 -7
  6. {dbt_common-1.8.0 → dbt_common-1.10.0}/dbt_common/events/types.proto +5 -6
  7. {dbt_common-1.8.0 → dbt_common-1.10.0}/dbt_common/events/types.py +6 -25
  8. dbt_common-1.10.0/dbt_common/events/types_pb2.py +77 -0
  9. {dbt_common-1.8.0 → dbt_common-1.10.0}/dbt_common/exceptions/base.py +3 -3
  10. {dbt_common-1.8.0 → dbt_common-1.10.0}/dbt_common/exceptions/jinja.py +9 -4
  11. {dbt_common-1.8.0 → dbt_common-1.10.0}/dbt_common/record.py +8 -2
  12. {dbt_common-1.8.0 → dbt_common-1.10.0}/dbt_common/semver.py +39 -27
  13. dbt_common-1.8.0/dbt_common/__about__.py +0 -1
  14. dbt_common-1.8.0/dbt_common/events/types_pb2.py +0 -77
  15. {dbt_common-1.8.0 → dbt_common-1.10.0}/.gitignore +0 -0
  16. {dbt_common-1.8.0 → dbt_common-1.10.0}/CHANGELOG.md +0 -0
  17. {dbt_common-1.8.0 → dbt_common-1.10.0}/LICENSE +0 -0
  18. {dbt_common-1.8.0 → dbt_common-1.10.0}/README.md +0 -0
  19. {dbt_common-1.8.0 → dbt_common-1.10.0}/codecov.yml +0 -0
  20. {dbt_common-1.8.0 → dbt_common-1.10.0}/dbt_common/__init__.py +0 -0
  21. {dbt_common-1.8.0 → dbt_common-1.10.0}/dbt_common/clients/__init__.py +0 -0
  22. {dbt_common-1.8.0 → dbt_common-1.10.0}/dbt_common/clients/_jinja_blocks.py +0 -0
  23. {dbt_common-1.8.0 → dbt_common-1.10.0}/dbt_common/clients/agate_helper.py +0 -0
  24. {dbt_common-1.8.0 → dbt_common-1.10.0}/dbt_common/clients/system.py +0 -0
  25. {dbt_common-1.8.0 → dbt_common-1.10.0}/dbt_common/constants.py +0 -0
  26. {dbt_common-1.8.0 → dbt_common-1.10.0}/dbt_common/context.py +0 -0
  27. {dbt_common-1.8.0 → dbt_common-1.10.0}/dbt_common/contracts/__init__.py +0 -0
  28. {dbt_common-1.8.0 → dbt_common-1.10.0}/dbt_common/contracts/config/__init__.py +0 -0
  29. {dbt_common-1.8.0 → dbt_common-1.10.0}/dbt_common/contracts/config/materialization.py +0 -0
  30. {dbt_common-1.8.0 → dbt_common-1.10.0}/dbt_common/contracts/config/metadata.py +0 -0
  31. {dbt_common-1.8.0 → dbt_common-1.10.0}/dbt_common/contracts/config/properties.py +0 -0
  32. {dbt_common-1.8.0 → dbt_common-1.10.0}/dbt_common/contracts/constraints.py +0 -0
  33. {dbt_common-1.8.0 → dbt_common-1.10.0}/dbt_common/contracts/metadata.py +0 -0
  34. {dbt_common-1.8.0 → dbt_common-1.10.0}/dbt_common/contracts/util.py +0 -0
  35. {dbt_common-1.8.0 → dbt_common-1.10.0}/dbt_common/dataclass_schema.py +0 -0
  36. {dbt_common-1.8.0 → dbt_common-1.10.0}/dbt_common/events/README.md +0 -0
  37. {dbt_common-1.8.0 → dbt_common-1.10.0}/dbt_common/events/__init__.py +0 -0
  38. {dbt_common-1.8.0 → dbt_common-1.10.0}/dbt_common/events/base_types.py +0 -0
  39. {dbt_common-1.8.0 → dbt_common-1.10.0}/dbt_common/events/contextvars.py +0 -0
  40. {dbt_common-1.8.0 → dbt_common-1.10.0}/dbt_common/events/event_handler.py +0 -0
  41. {dbt_common-1.8.0 → dbt_common-1.10.0}/dbt_common/events/event_manager.py +0 -0
  42. {dbt_common-1.8.0 → dbt_common-1.10.0}/dbt_common/events/event_manager_client.py +0 -0
  43. {dbt_common-1.8.0 → dbt_common-1.10.0}/dbt_common/events/format.py +0 -0
  44. {dbt_common-1.8.0 → dbt_common-1.10.0}/dbt_common/events/functions.py +0 -0
  45. {dbt_common-1.8.0 → dbt_common-1.10.0}/dbt_common/events/helpers.py +0 -0
  46. {dbt_common-1.8.0 → dbt_common-1.10.0}/dbt_common/events/interfaces.py +0 -0
  47. {dbt_common-1.8.0 → dbt_common-1.10.0}/dbt_common/events/logger.py +0 -0
  48. {dbt_common-1.8.0 → dbt_common-1.10.0}/dbt_common/exceptions/__init__.py +0 -0
  49. {dbt_common-1.8.0 → dbt_common-1.10.0}/dbt_common/exceptions/cache.py +0 -0
  50. {dbt_common-1.8.0 → dbt_common-1.10.0}/dbt_common/exceptions/connection.py +0 -0
  51. {dbt_common-1.8.0 → dbt_common-1.10.0}/dbt_common/exceptions/contracts.py +0 -0
  52. {dbt_common-1.8.0 → dbt_common-1.10.0}/dbt_common/exceptions/events.py +0 -0
  53. {dbt_common-1.8.0 → dbt_common-1.10.0}/dbt_common/exceptions/macros.py +0 -0
  54. {dbt_common-1.8.0 → dbt_common-1.10.0}/dbt_common/exceptions/system.py +0 -0
  55. {dbt_common-1.8.0 → dbt_common-1.10.0}/dbt_common/helper_types.py +0 -0
  56. {dbt_common-1.8.0 → dbt_common-1.10.0}/dbt_common/invocation.py +0 -0
  57. {dbt_common-1.8.0 → dbt_common-1.10.0}/dbt_common/py.typed +0 -0
  58. {dbt_common-1.8.0 → dbt_common-1.10.0}/dbt_common/tests.py +0 -0
  59. {dbt_common-1.8.0 → dbt_common-1.10.0}/dbt_common/ui.py +0 -0
  60. {dbt_common-1.8.0 → dbt_common-1.10.0}/dbt_common/utils/__init__.py +0 -0
  61. {dbt_common-1.8.0 → dbt_common-1.10.0}/dbt_common/utils/casting.py +0 -0
  62. {dbt_common-1.8.0 → dbt_common-1.10.0}/dbt_common/utils/connection.py +0 -0
  63. {dbt_common-1.8.0 → dbt_common-1.10.0}/dbt_common/utils/dict.py +0 -0
  64. {dbt_common-1.8.0 → dbt_common-1.10.0}/dbt_common/utils/encoding.py +0 -0
  65. {dbt_common-1.8.0 → dbt_common-1.10.0}/dbt_common/utils/executor.py +0 -0
  66. {dbt_common-1.8.0 → dbt_common-1.10.0}/dbt_common/utils/formatting.py +0 -0
  67. {dbt_common-1.8.0 → dbt_common-1.10.0}/dbt_common/utils/jinja.py +0 -0
  68. {dbt_common-1.8.0 → dbt_common-1.10.0}/docs/README.md +0 -0
  69. {dbt_common-1.8.0 → dbt_common-1.10.0}/docs/arch/adr-0001-build-tooling.md +0 -0
  70. {dbt_common-1.8.0 → dbt_common-1.10.0}/docs/guides/record_replay.md +0 -0
  71. {dbt_common-1.8.0 → dbt_common-1.10.0}/pyproject.toml +0 -0
  72. {dbt_common-1.8.0 → dbt_common-1.10.0}/third-party-stubs/agate/__init__.pyi +0 -0
  73. {dbt_common-1.8.0 → dbt_common-1.10.0}/third-party-stubs/agate/data_types.pyi +0 -0
  74. {dbt_common-1.8.0 → dbt_common-1.10.0}/third-party-stubs/colorama/__init__.pyi +0 -0
  75. {dbt_common-1.8.0 → dbt_common-1.10.0}/third-party-stubs/isodate/__init__.pyi +0 -0
  76. {dbt_common-1.8.0 → dbt_common-1.10.0}/third-party-stubs/mashumaro/__init__.pyi +0 -0
  77. {dbt_common-1.8.0 → dbt_common-1.10.0}/third-party-stubs/mashumaro/config.pyi +0 -0
  78. {dbt_common-1.8.0 → dbt_common-1.10.0}/third-party-stubs/mashumaro/core/__init__.pyi +0 -0
  79. {dbt_common-1.8.0 → dbt_common-1.10.0}/third-party-stubs/mashumaro/core/const.pyi +0 -0
  80. {dbt_common-1.8.0 → dbt_common-1.10.0}/third-party-stubs/mashumaro/core/helpers.pyi +0 -0
  81. {dbt_common-1.8.0 → dbt_common-1.10.0}/third-party-stubs/mashumaro/core/meta/__init__.pyi +0 -0
  82. {dbt_common-1.8.0 → dbt_common-1.10.0}/third-party-stubs/mashumaro/core/meta/code/__init__.pyi +0 -0
  83. {dbt_common-1.8.0 → dbt_common-1.10.0}/third-party-stubs/mashumaro/core/meta/code/builder.pyi +0 -0
  84. {dbt_common-1.8.0 → dbt_common-1.10.0}/third-party-stubs/mashumaro/core/meta/code/lines.pyi +0 -0
  85. {dbt_common-1.8.0 → dbt_common-1.10.0}/third-party-stubs/mashumaro/core/meta/helpers.pyi +0 -0
  86. {dbt_common-1.8.0 → dbt_common-1.10.0}/third-party-stubs/mashumaro/core/meta/mixin.pyi +0 -0
  87. {dbt_common-1.8.0 → dbt_common-1.10.0}/third-party-stubs/mashumaro/core/meta/types/__init__.pyi +0 -0
  88. {dbt_common-1.8.0 → dbt_common-1.10.0}/third-party-stubs/mashumaro/core/meta/types/common.pyi +0 -0
  89. {dbt_common-1.8.0 → dbt_common-1.10.0}/third-party-stubs/mashumaro/core/meta/types/pack.pyi +0 -0
  90. {dbt_common-1.8.0 → dbt_common-1.10.0}/third-party-stubs/mashumaro/core/meta/types/unpack.pyi +0 -0
  91. {dbt_common-1.8.0 → dbt_common-1.10.0}/third-party-stubs/mashumaro/dialect.pyi +0 -0
  92. {dbt_common-1.8.0 → dbt_common-1.10.0}/third-party-stubs/mashumaro/exceptions.pyi +0 -0
  93. {dbt_common-1.8.0 → dbt_common-1.10.0}/third-party-stubs/mashumaro/helper.pyi +0 -0
  94. {dbt_common-1.8.0 → dbt_common-1.10.0}/third-party-stubs/mashumaro/jsonschema/__init__.pyi +0 -0
  95. {dbt_common-1.8.0 → dbt_common-1.10.0}/third-party-stubs/mashumaro/jsonschema/annotations.pyi +0 -0
  96. {dbt_common-1.8.0 → dbt_common-1.10.0}/third-party-stubs/mashumaro/jsonschema/builder.pyi +0 -0
  97. {dbt_common-1.8.0 → dbt_common-1.10.0}/third-party-stubs/mashumaro/jsonschema/dialects.pyi +0 -0
  98. {dbt_common-1.8.0 → dbt_common-1.10.0}/third-party-stubs/mashumaro/jsonschema/models.pyi +0 -0
  99. {dbt_common-1.8.0 → dbt_common-1.10.0}/third-party-stubs/mashumaro/jsonschema/schema.pyi +0 -0
  100. {dbt_common-1.8.0 → dbt_common-1.10.0}/third-party-stubs/mashumaro/mixins/__init__.pyi +0 -0
  101. {dbt_common-1.8.0 → dbt_common-1.10.0}/third-party-stubs/mashumaro/mixins/dict.pyi +0 -0
  102. {dbt_common-1.8.0 → dbt_common-1.10.0}/third-party-stubs/mashumaro/mixins/json.pyi +0 -0
  103. {dbt_common-1.8.0 → dbt_common-1.10.0}/third-party-stubs/mashumaro/mixins/msgpack.pyi +0 -0
  104. {dbt_common-1.8.0 → dbt_common-1.10.0}/third-party-stubs/mashumaro/mixins/orjson.pyi +0 -0
  105. {dbt_common-1.8.0 → dbt_common-1.10.0}/third-party-stubs/mashumaro/mixins/toml.pyi +0 -0
  106. {dbt_common-1.8.0 → dbt_common-1.10.0}/third-party-stubs/mashumaro/mixins/yaml.pyi +0 -0
  107. {dbt_common-1.8.0 → dbt_common-1.10.0}/third-party-stubs/mashumaro/types.pyi +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: dbt-common
3
- Version: 1.8.0
3
+ Version: 1.10.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
@@ -0,0 +1 @@
1
+ version = "1.10.0"
@@ -9,8 +9,8 @@ except ImportError:
9
9
  from typing import Optional as NotRequired
10
10
 
11
11
  from dbt_common.events.functions import fire_event
12
- from dbt_common.events.types import BehaviorDeprecationEvent
13
- from dbt_common.exceptions import CompilationError
12
+ from dbt_common.events.types import BehaviorChangeEvent
13
+ from dbt_common.exceptions import CompilationError, DbtInternalError
14
14
 
15
15
 
16
16
  class BehaviorFlag(TypedDict):
@@ -20,16 +20,20 @@ class BehaviorFlag(TypedDict):
20
20
  Args:
21
21
  name: the name of the behavior flag
22
22
  default: default setting, starts as False, becomes True after a bake-in period
23
- deprecation_version: the version when the default will change to True
24
- deprecation_message: an additional message to send when the flag evaluates to False
23
+ description: an additional message to send when the flag evaluates to False
25
24
  docs_url: the url to the relevant docs on docs.getdbt.com
25
+
26
+ *Note*:
27
+ While `description` and `docs_url` are both listed as `NotRequired`, at least one of them is required.
28
+ This is validated when the flag is rendered in `BehaviorFlagRendered` below.
29
+ The goal of this restriction is to provide the end user with context so they can make an informed decision
30
+ about if, and when, to enable the behavior flag.
26
31
  """
27
32
 
28
33
  name: str
29
34
  default: bool
30
35
  source: NotRequired[str]
31
- deprecation_version: NotRequired[str]
32
- deprecation_message: NotRequired[str]
36
+ description: NotRequired[str]
33
37
  docs_url: NotRequired[str]
34
38
 
35
39
 
@@ -42,15 +46,37 @@ class BehaviorFlagRendered:
42
46
  user_overrides: a set of user settings, one of which may be an override on this behavior flag
43
47
  """
44
48
 
49
+ fired: bool = False
50
+
45
51
  def __init__(self, flag: BehaviorFlag, user_overrides: Dict[str, Any]) -> None:
52
+ self._validate(flag)
53
+
46
54
  self.name = flag["name"]
47
55
  self.setting = user_overrides.get(flag["name"], flag["default"])
48
- self.deprecation_event = self._deprecation_event(flag)
56
+
57
+ default_description = (
58
+ f"""The behavior controlled by `{flag["name"]}` is currently turned off.\n"""
59
+ )
60
+ default_docs_url = "https://docs.getdbt.com/reference/global-configs/behavior-changes"
61
+ self._behavior_change_event = BehaviorChangeEvent(
62
+ flag_name=flag["name"],
63
+ flag_source=flag.get("source", self._default_source()),
64
+ description=flag.get("description", default_description),
65
+ docs_url=flag.get("docs_url", default_docs_url),
66
+ )
67
+
68
+ @staticmethod
69
+ def _validate(flag: BehaviorFlag) -> None:
70
+ if flag.get("description") is None and flag.get("docs_url") is None:
71
+ raise DbtInternalError(
72
+ "Behavior change flags require at least one of `description` and `docs_url`."
73
+ )
49
74
 
50
75
  @property
51
76
  def setting(self) -> bool:
52
- if self._setting is False:
53
- fire_event(self.deprecation_event)
77
+ if self._setting is False and not self.fired:
78
+ fire_event(self._behavior_change_event)
79
+ self.fired = True
54
80
  return self._setting
55
81
 
56
82
  @setting.setter
@@ -61,15 +87,6 @@ class BehaviorFlagRendered:
61
87
  def no_warn(self) -> bool:
62
88
  return self._setting
63
89
 
64
- def _deprecation_event(self, flag: BehaviorFlag) -> BehaviorDeprecationEvent:
65
- return BehaviorDeprecationEvent(
66
- flag_name=flag["name"],
67
- flag_source=flag.get("source", self._default_source()),
68
- deprecation_version=flag.get("deprecation_version"),
69
- deprecation_message=flag.get("deprecation_message"),
70
- docs_url=flag.get("docs_url"),
71
- )
72
-
73
90
  @staticmethod
74
91
  def _default_source() -> str:
75
92
  """
@@ -95,7 +112,7 @@ class Behavior:
95
112
  if adapter.behavior.my_flag:
96
113
  ...
97
114
 
98
- if adapter.behavior.my_flag.no_warn: # this will not fire the deprecation event
115
+ if adapter.behavior.my_flag.no_warn: # this will not fire the behavior change event
99
116
  ...
100
117
  ```
101
118
  ```jinja
@@ -103,7 +120,7 @@ class Behavior:
103
120
  ...
104
121
  {% endif %}
105
122
 
106
- {% if adapter.behavior.my_flag.no_warn %} {# this will not fire the deprecation event #}
123
+ {% if adapter.behavior.my_flag.no_warn %} {# this will not fire the behavior change event #}
107
124
  ...
108
125
  {% endif %}
109
126
  ```
@@ -6,7 +6,21 @@ from ast import literal_eval
6
6
  from collections import ChainMap
7
7
  from contextlib import contextmanager
8
8
  from itertools import chain, islice
9
- from typing import Any, Callable, Dict, Iterator, List, Mapping, Optional, Union, Set, Type
9
+ from types import CodeType
10
+ from typing import (
11
+ Any,
12
+ Callable,
13
+ Dict,
14
+ Iterator,
15
+ List,
16
+ Mapping,
17
+ Optional,
18
+ Union,
19
+ Set,
20
+ Type,
21
+ NoReturn,
22
+ )
23
+
10
24
  from typing_extensions import Protocol
11
25
 
12
26
  import jinja2
@@ -39,10 +53,17 @@ from dbt_common.exceptions.macros import MacroReturn, UndefinedMacroError, Caugh
39
53
  SUPPORTED_LANG_ARG = jinja2.nodes.Name("supported_languages", "param")
40
54
 
41
55
  # Global which can be set by dependents of dbt-common (e.g. core via flag parsing)
42
- MACRO_DEBUGGING = False
56
+ MACRO_DEBUGGING: Union[str, bool] = False
57
+
58
+ _ParseReturn = Union[jinja2.nodes.Node, List[jinja2.nodes.Node]]
59
+
60
+
61
+ # Temporary type capturing the concept the functions in this file expect for a "node"
62
+ class _NodeProtocol(Protocol):
63
+ pass
43
64
 
44
65
 
45
- def _linecache_inject(source, write):
66
+ def _linecache_inject(source: str, write: bool) -> str:
46
67
  if write:
47
68
  # this is the only reliable way to accomplish this. Obviously, it's
48
69
  # really darn noisy and will fill your temporary directory
@@ -58,18 +79,18 @@ def _linecache_inject(source, write):
58
79
  else:
59
80
  # `codecs.encode` actually takes a `bytes` as the first argument if
60
81
  # the second argument is 'hex' - mypy does not know this.
61
- rnd = codecs.encode(os.urandom(12), "hex") # type: ignore
82
+ rnd = codecs.encode(os.urandom(12), "hex")
62
83
  filename = rnd.decode("ascii")
63
84
 
64
85
  # put ourselves in the cache
65
86
  cache_entry = (len(source), None, [line + "\n" for line in source.splitlines()], filename)
66
87
  # linecache does in fact have an attribute `cache`, thanks
67
- linecache.cache[filename] = cache_entry # type: ignore
88
+ linecache.cache[filename] = cache_entry
68
89
  return filename
69
90
 
70
91
 
71
92
  class MacroFuzzParser(jinja2.parser.Parser):
72
- def parse_macro(self):
93
+ def parse_macro(self) -> jinja2.nodes.Macro:
73
94
  node = jinja2.nodes.Macro(lineno=next(self.stream).lineno)
74
95
 
75
96
  # modified to fuzz macros defined in the same file. this way
@@ -83,16 +104,13 @@ class MacroFuzzParser(jinja2.parser.Parser):
83
104
 
84
105
 
85
106
  class MacroFuzzEnvironment(jinja2.sandbox.SandboxedEnvironment):
86
- def _parse(self, source, name, filename):
107
+ def _parse(
108
+ self, source: str, name: Optional[str], filename: Optional[str]
109
+ ) -> jinja2.nodes.Template:
87
110
  return MacroFuzzParser(self, source, name, filename).parse()
88
111
 
89
- def _compile(self, source, filename):
112
+ def _compile(self, source: str, filename: str) -> CodeType:
90
113
  """
91
-
92
-
93
-
94
-
95
-
96
114
  Override jinja's compilation. Use to stash the rendered source inside
97
115
  the python linecache for debugging when the appropriate environment
98
116
  variable is set.
@@ -108,7 +126,7 @@ class MacroFuzzEnvironment(jinja2.sandbox.SandboxedEnvironment):
108
126
 
109
127
 
110
128
  class MacroFuzzTemplate(jinja2.nativetypes.NativeTemplate):
111
- environment_class = MacroFuzzEnvironment
129
+ environment_class = MacroFuzzEnvironment # type: ignore
112
130
 
113
131
  def new_context(
114
132
  self,
@@ -171,11 +189,11 @@ class NumberMarker(NativeMarker):
171
189
  pass
172
190
 
173
191
 
174
- def _is_number(value) -> bool:
192
+ def _is_number(value: Any) -> bool:
175
193
  return isinstance(value, (int, float)) and not isinstance(value, bool)
176
194
 
177
195
 
178
- def quoted_native_concat(nodes):
196
+ def quoted_native_concat(nodes: Iterator[str]) -> Any:
179
197
  """Handle special case for native_concat from the NativeTemplate.
180
198
 
181
199
  This is almost native_concat from the NativeTemplate, except in the
@@ -213,7 +231,7 @@ def quoted_native_concat(nodes):
213
231
  class NativeSandboxTemplate(jinja2.nativetypes.NativeTemplate): # mypy: ignore
214
232
  environment_class = NativeSandboxEnvironment # type: ignore
215
233
 
216
- def render(self, *args, **kwargs):
234
+ def render(self, *args: Any, **kwargs: Any) -> Any:
217
235
  """Render the template to produce a native Python type.
218
236
 
219
237
  If the result is a single node, its value is returned. Otherwise,
@@ -229,6 +247,11 @@ class NativeSandboxTemplate(jinja2.nativetypes.NativeTemplate): # mypy: ignore
229
247
  return self.environment.handle_exception()
230
248
 
231
249
 
250
+ class MacroProtocol(Protocol):
251
+ name: str
252
+ macro_sql: str
253
+
254
+
232
255
  NativeSandboxEnvironment.template_class = NativeSandboxTemplate # type: ignore
233
256
 
234
257
 
@@ -236,7 +259,7 @@ class TemplateCache:
236
259
  def __init__(self) -> None:
237
260
  self.file_cache: Dict[str, jinja2.Template] = {}
238
261
 
239
- def get_node_template(self, node) -> jinja2.Template:
262
+ def get_node_template(self, node: MacroProtocol) -> jinja2.Template:
240
263
  key = node.macro_sql
241
264
 
242
265
  if key in self.file_cache:
@@ -251,7 +274,7 @@ class TemplateCache:
251
274
  self.file_cache[key] = template
252
275
  return template
253
276
 
254
- def clear(self):
277
+ def clear(self) -> None:
255
278
  self.file_cache.clear()
256
279
 
257
280
 
@@ -262,13 +285,13 @@ class BaseMacroGenerator:
262
285
  def __init__(self, context: Optional[Dict[str, Any]] = None) -> None:
263
286
  self.context: Optional[Dict[str, Any]] = context
264
287
 
265
- def get_template(self):
288
+ def get_template(self) -> jinja2.Template:
266
289
  raise NotImplementedError("get_template not implemented!")
267
290
 
268
291
  def get_name(self) -> str:
269
292
  raise NotImplementedError("get_name not implemented!")
270
293
 
271
- def get_macro(self):
294
+ def get_macro(self) -> Callable:
272
295
  name = self.get_name()
273
296
  template = self.get_template()
274
297
  # make the module. previously we set both vars and local, but that's
@@ -286,7 +309,7 @@ class BaseMacroGenerator:
286
309
  except (TypeError, jinja2.exceptions.TemplateRuntimeError) as e:
287
310
  raise CaughtMacroError(e)
288
311
 
289
- def call_macro(self, *args, **kwargs):
312
+ def call_macro(self, *args: Any, **kwargs: Any) -> Any:
290
313
  # called from __call__ methods
291
314
  if self.context is None:
292
315
  raise DbtInternalError("Context is still None in call_macro!")
@@ -301,11 +324,6 @@ class BaseMacroGenerator:
301
324
  return e.value
302
325
 
303
326
 
304
- class MacroProtocol(Protocol):
305
- name: str
306
- macro_sql: str
307
-
308
-
309
327
  class CallableMacroGenerator(BaseMacroGenerator):
310
328
  def __init__(
311
329
  self,
@@ -315,7 +333,7 @@ class CallableMacroGenerator(BaseMacroGenerator):
315
333
  super().__init__(context)
316
334
  self.macro = macro
317
335
 
318
- def get_template(self):
336
+ def get_template(self) -> jinja2.Template:
319
337
  return template_cache.get_node_template(self.macro)
320
338
 
321
339
  def get_name(self) -> str:
@@ -332,14 +350,14 @@ class CallableMacroGenerator(BaseMacroGenerator):
332
350
  raise e
333
351
 
334
352
  # this makes MacroGenerator objects callable like functions
335
- def __call__(self, *args, **kwargs):
353
+ def __call__(self, *args: Any, **kwargs: Any) -> Any:
336
354
  return self.call_macro(*args, **kwargs)
337
355
 
338
356
 
339
357
  class MaterializationExtension(jinja2.ext.Extension):
340
358
  tags = ["materialization"]
341
359
 
342
- def parse(self, parser):
360
+ def parse(self, parser: jinja2.parser.Parser) -> _ParseReturn:
343
361
  node = jinja2.nodes.Macro(lineno=next(parser.stream).lineno)
344
362
  materialization_name = parser.parse_assign_target(name_only=True).name
345
363
 
@@ -382,7 +400,7 @@ class MaterializationExtension(jinja2.ext.Extension):
382
400
  class DocumentationExtension(jinja2.ext.Extension):
383
401
  tags = ["docs"]
384
402
 
385
- def parse(self, parser):
403
+ def parse(self, parser: jinja2.parser.Parser) -> _ParseReturn:
386
404
  node = jinja2.nodes.Macro(lineno=next(parser.stream).lineno)
387
405
  docs_name = parser.parse_assign_target(name_only=True).name
388
406
 
@@ -396,7 +414,7 @@ class DocumentationExtension(jinja2.ext.Extension):
396
414
  class TestExtension(jinja2.ext.Extension):
397
415
  tags = ["test"]
398
416
 
399
- def parse(self, parser):
417
+ def parse(self, parser: jinja2.parser.Parser) -> _ParseReturn:
400
418
  node = jinja2.nodes.Macro(lineno=next(parser.stream).lineno)
401
419
  test_name = parser.parse_assign_target(name_only=True).name
402
420
 
@@ -406,13 +424,19 @@ class TestExtension(jinja2.ext.Extension):
406
424
  return node
407
425
 
408
426
 
409
- def _is_dunder_name(name):
427
+ def _is_dunder_name(name: str) -> bool:
410
428
  return name.startswith("__") and name.endswith("__")
411
429
 
412
430
 
413
- def create_undefined(node=None):
431
+ def create_undefined(node: Optional[_NodeProtocol] = None) -> Type[jinja2.Undefined]:
414
432
  class Undefined(jinja2.Undefined):
415
- def __init__(self, hint=None, obj=None, name=None, exc=None):
433
+ def __init__(
434
+ self,
435
+ hint: Optional[str] = None,
436
+ obj: Any = None,
437
+ name: Optional[str] = None,
438
+ exc: Any = None,
439
+ ) -> None:
416
440
  super().__init__(hint=hint, name=name)
417
441
  self.node = node
418
442
  self.name = name
@@ -422,12 +446,12 @@ def create_undefined(node=None):
422
446
  self.unsafe_callable = False
423
447
  self.alters_data = False
424
448
 
425
- def __getitem__(self, name):
449
+ def __getitem__(self, name: Any) -> "Undefined":
426
450
  # Propagate the undefined value if a caller accesses this as if it
427
451
  # were a dictionary
428
452
  return self
429
453
 
430
- def __getattr__(self, name):
454
+ def __getattr__(self, name: str) -> "Undefined":
431
455
  if name == "name" or _is_dunder_name(name):
432
456
  raise AttributeError(
433
457
  "'{}' object has no attribute '{}'".format(type(self).__name__, name)
@@ -437,11 +461,11 @@ def create_undefined(node=None):
437
461
 
438
462
  return self.__class__(hint=self.hint, name=self.name)
439
463
 
440
- def __call__(self, *args, **kwargs):
464
+ def __call__(self, *args: Any, **kwargs: Any) -> "Undefined":
441
465
  return self
442
466
 
443
- def __reduce__(self):
444
- raise UndefinedCompilationError(name=self.name, node=node)
467
+ def __reduce__(self) -> NoReturn:
468
+ raise UndefinedCompilationError(name=self.name or "unknown", node=node)
445
469
 
446
470
  return Undefined
447
471
 
@@ -463,7 +487,7 @@ TEXT_FILTERS: Dict[str, Callable[[Any], Any]] = {
463
487
 
464
488
 
465
489
  def get_environment(
466
- node=None,
490
+ node: Optional[_NodeProtocol] = None,
467
491
  capture_macros: bool = False,
468
492
  native: bool = False,
469
493
  ) -> jinja2.Environment:
@@ -472,7 +496,7 @@ def get_environment(
472
496
  }
473
497
 
474
498
  if capture_macros:
475
- args["undefined"] = create_undefined(node)
499
+ args["undefined"] = create_undefined(node) # type: ignore
476
500
 
477
501
  args["extensions"].append(MaterializationExtension)
478
502
  args["extensions"].append(DocumentationExtension)
@@ -493,7 +517,7 @@ def get_environment(
493
517
 
494
518
 
495
519
  @contextmanager
496
- def catch_jinja(node=None) -> Iterator[None]:
520
+ def catch_jinja(node: Optional[_NodeProtocol] = None) -> Iterator[None]:
497
521
  try:
498
522
  yield
499
523
  except jinja2.exceptions.TemplateSyntaxError as e:
@@ -506,16 +530,16 @@ def catch_jinja(node=None) -> Iterator[None]:
506
530
  raise
507
531
 
508
532
 
509
- _TESTING_PARSE_CACHE: Dict[str, jinja2.Template] = {}
533
+ _TESTING_PARSE_CACHE: Dict[str, jinja2.nodes.Template] = {}
510
534
 
511
535
 
512
- def parse(string):
536
+ def parse(string: Any) -> jinja2.nodes.Template:
513
537
  str_string = str(string)
514
538
  if test_caching_enabled() and str_string in _TESTING_PARSE_CACHE:
515
539
  return _TESTING_PARSE_CACHE[str_string]
516
540
 
517
541
  with catch_jinja():
518
- parsed = get_environment().parse(str(string))
542
+ parsed: jinja2.nodes.Template = get_environment().parse(str(string))
519
543
  if test_caching_enabled():
520
544
  _TESTING_PARSE_CACHE[str_string] = parsed
521
545
  return parsed
@@ -524,10 +548,10 @@ def parse(string):
524
548
  def get_template(
525
549
  string: str,
526
550
  ctx: Dict[str, Any],
527
- node=None,
551
+ node: Optional[_NodeProtocol] = None,
528
552
  capture_macros: bool = False,
529
553
  native: bool = False,
530
- ):
554
+ ) -> jinja2.Template:
531
555
  with catch_jinja(node):
532
556
  env = get_environment(node, capture_macros, native=native)
533
557
 
@@ -535,7 +559,9 @@ def get_template(
535
559
  return env.from_string(template_source, globals=ctx)
536
560
 
537
561
 
538
- def render_template(template, ctx: Dict[str, Any], node=None) -> str:
562
+ def render_template(
563
+ template: jinja2.Template, ctx: Dict[str, Any], node: Optional[_NodeProtocol] = None
564
+ ) -> str:
539
565
  with catch_jinja(node):
540
566
  return template.render(ctx)
541
567
 
@@ -96,10 +96,14 @@ class BaseConfig(AdditionalPropertiesAllowed, Replaceable):
96
96
  return False
97
97
  return True
98
98
 
99
- # This is used in 'add_config_call' to create the combined config_call_dict.
100
- # 'meta' moved here from node
99
+ # This is used in 'merge_config_dicts' to create the combined orig_dict.
100
+ # Note: "clobber" fields aren't defined, because that's the default.
101
+ # "access" is currently the only Clobber field.
102
+ # This shouldn't really be defined here. It would be better to have it
103
+ # associated with the config definitions, but at the point we use it, we
104
+ # don't know which config we're dealing with.
101
105
  mergebehavior = {
102
- "append": ["pre-hook", "pre_hook", "post-hook", "post_hook", "tags"],
106
+ "append": ["pre-hook", "pre_hook", "post-hook", "post_hook", "tags", "packages"],
103
107
  "update": [
104
108
  "quoting",
105
109
  "column_types",
@@ -108,6 +112,7 @@ class BaseConfig(AdditionalPropertiesAllowed, Replaceable):
108
112
  "contract",
109
113
  ],
110
114
  "dict_key_append": ["grants"],
115
+ "object": ["snapshot_meta_column_names"],
111
116
  }
112
117
 
113
118
  @classmethod
@@ -180,6 +185,7 @@ class MergeBehavior(Metadata):
180
185
  Update = 2
181
186
  Clobber = 3
182
187
  DictKeyAppend = 4
188
+ Object = 5
183
189
 
184
190
  @classmethod
185
191
  def default_field(cls) -> "MergeBehavior":
@@ -215,8 +221,10 @@ def _listify(value: Any) -> List[Any]:
215
221
 
216
222
 
217
223
  # There are two versions of this code. The one here is for config
218
- # objects, the one in _add_config_call in core context_config.py is for
219
- # config_call_dict dictionaries.
224
+ # objects which can get the "MergeBehavior" from the field in the class,
225
+ # the one below in 'merge_config_dicts' (formerly in
226
+ # _add_config_call in core context_config.py) is for config_call dictionaries
227
+ # where we need to get the MergeBehavior from someplace else.
220
228
  def _merge_field_value(
221
229
  merge_behavior: MergeBehavior,
222
230
  self_value: Any,
@@ -225,7 +233,8 @@ def _merge_field_value(
225
233
  if merge_behavior == MergeBehavior.Clobber:
226
234
  return other_value
227
235
  elif merge_behavior == MergeBehavior.Append:
228
- return _listify(self_value) + _listify(other_value)
236
+ new_value = _listify(self_value) + _listify(other_value)
237
+ return new_value
229
238
  elif merge_behavior == MergeBehavior.Update:
230
239
  if not isinstance(self_value, dict):
231
240
  raise DbtInternalError(f"expected dict, got {self_value}")
@@ -258,6 +267,73 @@ def _merge_field_value(
258
267
  # clobber the list
259
268
  new_dict[new_key] = _listify(other_value[key])
260
269
  return new_dict
261
-
270
+ elif merge_behavior == MergeBehavior.Object:
271
+ # All fields in classes with MergeBehavior.Object should have a default of None
272
+ if not type(self_value).__name__ == type(other_value).__name__:
273
+ raise DbtInternalError(
274
+ f"got conflicting types: {type(self_value).__name__} and {type(other_value).__name__}"
275
+ )
276
+ new_value = self_value.copy()
277
+ new_value.update(other_value)
278
+ return new_value
262
279
  else:
263
280
  raise DbtInternalError(f"Got an invalid merge_behavior: {merge_behavior}")
281
+
282
+
283
+ # This is used in ContextConfig._add_config_call. It updates the orig_dict in place.
284
+ def merge_config_dicts(orig_dict: Dict[str, Any], new_dict: Dict[str, Any]) -> None:
285
+ # orig_dict is already encountered configs, new_dict is new
286
+ # This mirrors code in _merge_field_value in model_config.py which is similar but
287
+ # operates on config objects.
288
+ if orig_dict == {}:
289
+ orig_dict.update(new_dict)
290
+ return
291
+ for k, v in new_dict.items():
292
+ # MergeBehavior for post-hook and pre-hook is to collect all
293
+ # values, instead of overwriting
294
+ if k in BaseConfig.mergebehavior["append"]:
295
+ if k in orig_dict: # should always be a list here
296
+ orig_dict[k] = _listify(orig_dict[k]) + _listify(v)
297
+ else:
298
+ orig_dict[k] = _listify(v)
299
+ elif k in BaseConfig.mergebehavior["update"]:
300
+ if not isinstance(v, dict):
301
+ raise DbtInternalError(f"expected dict, got {v}")
302
+ if k in orig_dict and isinstance(orig_dict[k], dict):
303
+ orig_dict[k].update(v)
304
+ else:
305
+ orig_dict[k] = v
306
+ elif k in BaseConfig.mergebehavior["dict_key_append"]:
307
+ if not isinstance(v, dict):
308
+ raise DbtInternalError(f"expected dict, got {v}")
309
+ if k in orig_dict: # should always be a dict
310
+ for key in orig_dict[k].keys():
311
+ orig_dict[k][key] = _listify(orig_dict[k][key])
312
+ for key, value in v.items():
313
+ extend = False
314
+ # This might start with a +, to indicate we should extend the list
315
+ # instead of just clobbering it. We don't want to remove the + here
316
+ # (like in the other method) because we want it preserved
317
+ if key.startswith("+"):
318
+ extend = True
319
+ if key in orig_dict[k] and extend:
320
+ # extend the list
321
+ orig_dict[k][key].extend(_listify(value))
322
+ else:
323
+ # clobber the list
324
+ orig_dict[k][key] = _listify(value)
325
+ else:
326
+ # This is always a dictionary
327
+ orig_dict[k] = v
328
+ # listify everything
329
+ for key, value in orig_dict[k].items():
330
+ orig_dict[k][key] = _listify(value)
331
+ elif k in BaseConfig.mergebehavior["object"]:
332
+ if not isinstance(v, dict):
333
+ raise DbtInternalError(f"expected dict, got {v}")
334
+ if k not in orig_dict:
335
+ orig_dict[k] = {}
336
+ for obj_k, obj_v in v.items():
337
+ orig_dict[k][obj_k] = obj_v
338
+ else: # Clobber
339
+ orig_dict[k] = v
@@ -26,17 +26,16 @@ message GenericMessage {
26
26
  // D - Deprecations
27
27
 
28
28
  // D018
29
- message BehaviorDeprecationEvent {
29
+ message BehaviorChangeEvent {
30
30
  string flag_name = 1;
31
31
  string flag_source = 2;
32
- string deprecation_version = 3;
33
- string deprecation_message = 4;
34
- string docs_url = 5;
32
+ string description = 3;
33
+ string docs_url = 4;
35
34
  }
36
35
 
37
- message BehaviorDeprecationEventMsg {
36
+ message BehaviorChangeEventMsg {
38
37
  EventInfo info = 1;
39
- BehaviorDeprecationEvent data = 2;
38
+ BehaviorChangeEvent data = 2;
40
39
  }
41
40
 
42
41
  // M - Deps generation
@@ -1,5 +1,3 @@
1
- from typing import Optional
2
-
3
1
  from dbt_common.events.base_types import (
4
2
  DebugLevel,
5
3
  InfoLevel,
@@ -38,33 +36,16 @@ from dbt_common.ui import warning_tag
38
36
  # =======================================================
39
37
 
40
38
 
41
- class BehaviorDeprecationEvent(WarnLevel):
42
- flag_name: str
43
- flag_source: str
44
- deprecation_version: Optional[str]
45
- deprecation_message: Optional[str]
46
- docs_url: Optional[str]
47
-
39
+ class BehaviorChangeEvent(WarnLevel):
48
40
  def code(self) -> str:
49
41
  return "D018"
50
42
 
51
43
  def message(self) -> str:
52
- msg = f"The legacy behavior controlled by `{self.flag_name}` is deprecated.\n"
53
-
54
- if self.deprecation_version:
55
- msg = (
56
- f"The legacy behavior is expected to be retired in `{self.deprecation_version}`.\n"
57
- )
58
-
59
- msg += f"The new behavior can be turned on by setting `flags.{self.flag_name}` to `True` in `dbt_project.yml`.\n"
60
-
61
- if self.deprecation_message:
62
- msg += f"{self.deprecation_message}.\n"
63
-
64
- docs_url = self.docs_url or f"https://docs.getdbt.com/search?q={self.flag_name}"
65
- msg += f"Visit {docs_url} for more information."
66
-
67
- return warning_tag(msg)
44
+ return warning_tag(
45
+ f"{self.description}\n"
46
+ f"You may opt into the new behavior sooner by setting `flags.{self.flag_name}` to `True` in `dbt_project.yml`.\n"
47
+ f"Visit {self.docs_url} for more information."
48
+ )
68
49
 
69
50
 
70
51
  # =======================================================