dvt-core 0.52.2__cp310-cp310-macosx_10_9_x86_64.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.

Potentially problematic release.


This version of dvt-core might be problematic. Click here for more details.

Files changed (275) hide show
  1. dbt/__init__.py +7 -0
  2. dbt/_pydantic_shim.py +26 -0
  3. dbt/artifacts/__init__.py +0 -0
  4. dbt/artifacts/exceptions/__init__.py +1 -0
  5. dbt/artifacts/exceptions/schemas.py +31 -0
  6. dbt/artifacts/resources/__init__.py +116 -0
  7. dbt/artifacts/resources/base.py +67 -0
  8. dbt/artifacts/resources/types.py +93 -0
  9. dbt/artifacts/resources/v1/analysis.py +10 -0
  10. dbt/artifacts/resources/v1/catalog.py +23 -0
  11. dbt/artifacts/resources/v1/components.py +274 -0
  12. dbt/artifacts/resources/v1/config.py +277 -0
  13. dbt/artifacts/resources/v1/documentation.py +11 -0
  14. dbt/artifacts/resources/v1/exposure.py +51 -0
  15. dbt/artifacts/resources/v1/function.py +52 -0
  16. dbt/artifacts/resources/v1/generic_test.py +31 -0
  17. dbt/artifacts/resources/v1/group.py +21 -0
  18. dbt/artifacts/resources/v1/hook.py +11 -0
  19. dbt/artifacts/resources/v1/macro.py +29 -0
  20. dbt/artifacts/resources/v1/metric.py +172 -0
  21. dbt/artifacts/resources/v1/model.py +145 -0
  22. dbt/artifacts/resources/v1/owner.py +10 -0
  23. dbt/artifacts/resources/v1/saved_query.py +111 -0
  24. dbt/artifacts/resources/v1/seed.py +41 -0
  25. dbt/artifacts/resources/v1/semantic_layer_components.py +72 -0
  26. dbt/artifacts/resources/v1/semantic_model.py +314 -0
  27. dbt/artifacts/resources/v1/singular_test.py +14 -0
  28. dbt/artifacts/resources/v1/snapshot.py +91 -0
  29. dbt/artifacts/resources/v1/source_definition.py +84 -0
  30. dbt/artifacts/resources/v1/sql_operation.py +10 -0
  31. dbt/artifacts/resources/v1/unit_test_definition.py +77 -0
  32. dbt/artifacts/schemas/__init__.py +0 -0
  33. dbt/artifacts/schemas/base.py +191 -0
  34. dbt/artifacts/schemas/batch_results.py +24 -0
  35. dbt/artifacts/schemas/catalog/__init__.py +11 -0
  36. dbt/artifacts/schemas/catalog/v1/__init__.py +0 -0
  37. dbt/artifacts/schemas/catalog/v1/catalog.py +59 -0
  38. dbt/artifacts/schemas/freshness/__init__.py +1 -0
  39. dbt/artifacts/schemas/freshness/v3/__init__.py +0 -0
  40. dbt/artifacts/schemas/freshness/v3/freshness.py +158 -0
  41. dbt/artifacts/schemas/manifest/__init__.py +2 -0
  42. dbt/artifacts/schemas/manifest/v12/__init__.py +0 -0
  43. dbt/artifacts/schemas/manifest/v12/manifest.py +211 -0
  44. dbt/artifacts/schemas/results.py +147 -0
  45. dbt/artifacts/schemas/run/__init__.py +2 -0
  46. dbt/artifacts/schemas/run/v5/__init__.py +0 -0
  47. dbt/artifacts/schemas/run/v5/run.py +184 -0
  48. dbt/artifacts/schemas/upgrades/__init__.py +4 -0
  49. dbt/artifacts/schemas/upgrades/upgrade_manifest.py +174 -0
  50. dbt/artifacts/schemas/upgrades/upgrade_manifest_dbt_version.py +2 -0
  51. dbt/artifacts/utils/validation.py +153 -0
  52. dbt/cli/__init__.py +1 -0
  53. dbt/cli/context.py +17 -0
  54. dbt/cli/exceptions.py +57 -0
  55. dbt/cli/flags.py +560 -0
  56. dbt/cli/main.py +2039 -0
  57. dbt/cli/option_types.py +121 -0
  58. dbt/cli/options.py +80 -0
  59. dbt/cli/params.py +804 -0
  60. dbt/cli/requires.py +490 -0
  61. dbt/cli/resolvers.py +50 -0
  62. dbt/cli/types.py +40 -0
  63. dbt/clients/__init__.py +0 -0
  64. dbt/clients/checked_load.py +83 -0
  65. dbt/clients/git.py +164 -0
  66. dbt/clients/jinja.py +206 -0
  67. dbt/clients/jinja_static.py +245 -0
  68. dbt/clients/registry.py +192 -0
  69. dbt/clients/yaml_helper.py +68 -0
  70. dbt/compilation.py +876 -0
  71. dbt/compute/__init__.py +14 -0
  72. dbt/compute/engines/__init__.py +12 -0
  73. dbt/compute/engines/spark_engine.py +624 -0
  74. dbt/compute/federated_executor.py +837 -0
  75. dbt/compute/filter_pushdown.cpython-310-darwin.so +0 -0
  76. dbt/compute/filter_pushdown.py +273 -0
  77. dbt/compute/jar_provisioning.cpython-310-darwin.so +0 -0
  78. dbt/compute/jar_provisioning.py +255 -0
  79. dbt/compute/java_compat.cpython-310-darwin.so +0 -0
  80. dbt/compute/java_compat.py +689 -0
  81. dbt/compute/jdbc_utils.cpython-310-darwin.so +0 -0
  82. dbt/compute/jdbc_utils.py +678 -0
  83. dbt/compute/smart_selector.cpython-310-darwin.so +0 -0
  84. dbt/compute/smart_selector.py +311 -0
  85. dbt/compute/strategies/__init__.py +54 -0
  86. dbt/compute/strategies/base.py +165 -0
  87. dbt/compute/strategies/dataproc.py +207 -0
  88. dbt/compute/strategies/emr.py +203 -0
  89. dbt/compute/strategies/local.py +364 -0
  90. dbt/compute/strategies/standalone.py +262 -0
  91. dbt/config/__init__.py +4 -0
  92. dbt/config/catalogs.py +94 -0
  93. dbt/config/compute.cpython-310-darwin.so +0 -0
  94. dbt/config/compute.py +547 -0
  95. dbt/config/dvt_profile.cpython-310-darwin.so +0 -0
  96. dbt/config/dvt_profile.py +342 -0
  97. dbt/config/profile.py +422 -0
  98. dbt/config/project.py +873 -0
  99. dbt/config/project_utils.py +28 -0
  100. dbt/config/renderer.py +231 -0
  101. dbt/config/runtime.py +553 -0
  102. dbt/config/selectors.py +208 -0
  103. dbt/config/utils.py +77 -0
  104. dbt/constants.py +28 -0
  105. dbt/context/__init__.py +0 -0
  106. dbt/context/base.py +745 -0
  107. dbt/context/configured.py +135 -0
  108. dbt/context/context_config.py +382 -0
  109. dbt/context/docs.py +82 -0
  110. dbt/context/exceptions_jinja.py +178 -0
  111. dbt/context/macro_resolver.py +195 -0
  112. dbt/context/macros.py +171 -0
  113. dbt/context/manifest.py +72 -0
  114. dbt/context/providers.py +2249 -0
  115. dbt/context/query_header.py +13 -0
  116. dbt/context/secret.py +58 -0
  117. dbt/context/target.py +74 -0
  118. dbt/contracts/__init__.py +0 -0
  119. dbt/contracts/files.py +413 -0
  120. dbt/contracts/graph/__init__.py +0 -0
  121. dbt/contracts/graph/manifest.py +1904 -0
  122. dbt/contracts/graph/metrics.py +97 -0
  123. dbt/contracts/graph/model_config.py +70 -0
  124. dbt/contracts/graph/node_args.py +42 -0
  125. dbt/contracts/graph/nodes.py +1806 -0
  126. dbt/contracts/graph/semantic_manifest.py +232 -0
  127. dbt/contracts/graph/unparsed.py +811 -0
  128. dbt/contracts/project.py +417 -0
  129. dbt/contracts/results.py +53 -0
  130. dbt/contracts/selection.py +23 -0
  131. dbt/contracts/sql.py +85 -0
  132. dbt/contracts/state.py +68 -0
  133. dbt/contracts/util.py +46 -0
  134. dbt/deprecations.py +346 -0
  135. dbt/deps/__init__.py +0 -0
  136. dbt/deps/base.py +152 -0
  137. dbt/deps/git.py +195 -0
  138. dbt/deps/local.py +79 -0
  139. dbt/deps/registry.py +130 -0
  140. dbt/deps/resolver.py +149 -0
  141. dbt/deps/tarball.py +120 -0
  142. dbt/docs/source/_ext/dbt_click.py +119 -0
  143. dbt/docs/source/conf.py +32 -0
  144. dbt/env_vars.py +64 -0
  145. dbt/event_time/event_time.py +40 -0
  146. dbt/event_time/sample_window.py +60 -0
  147. dbt/events/__init__.py +15 -0
  148. dbt/events/base_types.py +36 -0
  149. dbt/events/core_types_pb2.py +2 -0
  150. dbt/events/logging.py +108 -0
  151. dbt/events/types.py +2516 -0
  152. dbt/exceptions.py +1486 -0
  153. dbt/flags.py +89 -0
  154. dbt/graph/__init__.py +11 -0
  155. dbt/graph/cli.py +247 -0
  156. dbt/graph/graph.py +172 -0
  157. dbt/graph/queue.py +214 -0
  158. dbt/graph/selector.py +374 -0
  159. dbt/graph/selector_methods.py +975 -0
  160. dbt/graph/selector_spec.py +222 -0
  161. dbt/graph/thread_pool.py +18 -0
  162. dbt/hooks.py +21 -0
  163. dbt/include/README.md +49 -0
  164. dbt/include/__init__.py +3 -0
  165. dbt/include/starter_project/.gitignore +4 -0
  166. dbt/include/starter_project/README.md +15 -0
  167. dbt/include/starter_project/__init__.py +3 -0
  168. dbt/include/starter_project/analyses/.gitkeep +0 -0
  169. dbt/include/starter_project/dbt_project.yml +36 -0
  170. dbt/include/starter_project/macros/.gitkeep +0 -0
  171. dbt/include/starter_project/models/example/my_first_dbt_model.sql +27 -0
  172. dbt/include/starter_project/models/example/my_second_dbt_model.sql +6 -0
  173. dbt/include/starter_project/models/example/schema.yml +21 -0
  174. dbt/include/starter_project/seeds/.gitkeep +0 -0
  175. dbt/include/starter_project/snapshots/.gitkeep +0 -0
  176. dbt/include/starter_project/tests/.gitkeep +0 -0
  177. dbt/internal_deprecations.py +26 -0
  178. dbt/jsonschemas/__init__.py +3 -0
  179. dbt/jsonschemas/jsonschemas.py +309 -0
  180. dbt/jsonschemas/project/0.0.110.json +4717 -0
  181. dbt/jsonschemas/project/0.0.85.json +2015 -0
  182. dbt/jsonschemas/resources/0.0.110.json +2636 -0
  183. dbt/jsonschemas/resources/0.0.85.json +2536 -0
  184. dbt/jsonschemas/resources/latest.json +6773 -0
  185. dbt/links.py +4 -0
  186. dbt/materializations/__init__.py +0 -0
  187. dbt/materializations/incremental/__init__.py +0 -0
  188. dbt/materializations/incremental/microbatch.py +236 -0
  189. dbt/mp_context.py +8 -0
  190. dbt/node_types.py +37 -0
  191. dbt/parser/__init__.py +23 -0
  192. dbt/parser/analysis.py +21 -0
  193. dbt/parser/base.py +548 -0
  194. dbt/parser/common.py +266 -0
  195. dbt/parser/docs.py +52 -0
  196. dbt/parser/fixtures.py +51 -0
  197. dbt/parser/functions.py +30 -0
  198. dbt/parser/generic_test.py +100 -0
  199. dbt/parser/generic_test_builders.py +333 -0
  200. dbt/parser/hooks.py +118 -0
  201. dbt/parser/macros.py +137 -0
  202. dbt/parser/manifest.py +2204 -0
  203. dbt/parser/models.py +573 -0
  204. dbt/parser/partial.py +1178 -0
  205. dbt/parser/read_files.py +445 -0
  206. dbt/parser/schema_generic_tests.py +422 -0
  207. dbt/parser/schema_renderer.py +111 -0
  208. dbt/parser/schema_yaml_readers.py +935 -0
  209. dbt/parser/schemas.py +1466 -0
  210. dbt/parser/search.py +149 -0
  211. dbt/parser/seeds.py +28 -0
  212. dbt/parser/singular_test.py +20 -0
  213. dbt/parser/snapshots.py +44 -0
  214. dbt/parser/sources.py +558 -0
  215. dbt/parser/sql.py +62 -0
  216. dbt/parser/unit_tests.py +621 -0
  217. dbt/plugins/__init__.py +20 -0
  218. dbt/plugins/contracts.py +9 -0
  219. dbt/plugins/exceptions.py +2 -0
  220. dbt/plugins/manager.py +163 -0
  221. dbt/plugins/manifest.py +21 -0
  222. dbt/profiler.py +20 -0
  223. dbt/py.typed +1 -0
  224. dbt/query_analyzer.cpython-310-darwin.so +0 -0
  225. dbt/query_analyzer.py +410 -0
  226. dbt/runners/__init__.py +2 -0
  227. dbt/runners/exposure_runner.py +7 -0
  228. dbt/runners/no_op_runner.py +45 -0
  229. dbt/runners/saved_query_runner.py +7 -0
  230. dbt/selected_resources.py +8 -0
  231. dbt/task/__init__.py +0 -0
  232. dbt/task/base.py +503 -0
  233. dbt/task/build.py +197 -0
  234. dbt/task/clean.py +56 -0
  235. dbt/task/clone.py +161 -0
  236. dbt/task/compile.py +150 -0
  237. dbt/task/compute.py +454 -0
  238. dbt/task/debug.py +505 -0
  239. dbt/task/deps.py +280 -0
  240. dbt/task/docs/__init__.py +3 -0
  241. dbt/task/docs/generate.py +660 -0
  242. dbt/task/docs/index.html +250 -0
  243. dbt/task/docs/serve.py +29 -0
  244. dbt/task/freshness.py +322 -0
  245. dbt/task/function.py +121 -0
  246. dbt/task/group_lookup.py +46 -0
  247. dbt/task/init.py +553 -0
  248. dbt/task/java.py +316 -0
  249. dbt/task/list.py +236 -0
  250. dbt/task/printer.py +175 -0
  251. dbt/task/retry.py +175 -0
  252. dbt/task/run.py +1306 -0
  253. dbt/task/run_operation.py +141 -0
  254. dbt/task/runnable.py +758 -0
  255. dbt/task/seed.py +103 -0
  256. dbt/task/show.py +149 -0
  257. dbt/task/snapshot.py +56 -0
  258. dbt/task/spark.py +414 -0
  259. dbt/task/sql.py +110 -0
  260. dbt/task/target_sync.py +759 -0
  261. dbt/task/test.py +464 -0
  262. dbt/tests/fixtures/__init__.py +1 -0
  263. dbt/tests/fixtures/project.py +620 -0
  264. dbt/tests/util.py +651 -0
  265. dbt/tracking.py +529 -0
  266. dbt/utils/__init__.py +3 -0
  267. dbt/utils/artifact_upload.py +151 -0
  268. dbt/utils/utils.py +408 -0
  269. dbt/version.py +268 -0
  270. dvt_cli/__init__.py +72 -0
  271. dvt_core-0.52.2.dist-info/METADATA +286 -0
  272. dvt_core-0.52.2.dist-info/RECORD +275 -0
  273. dvt_core-0.52.2.dist-info/WHEEL +5 -0
  274. dvt_core-0.52.2.dist-info/entry_points.txt +2 -0
  275. dvt_core-0.52.2.dist-info/top_level.txt +2 -0
dbt/parser/models.py ADDED
@@ -0,0 +1,573 @@
1
+ # New for Python models :p
2
+ import ast
3
+ import random
4
+ from copy import deepcopy
5
+ from functools import reduce
6
+ from itertools import chain
7
+ from typing import Any, Dict, Iterator, List, Optional, Tuple, Union
8
+
9
+ import dbt.tracking as tracking
10
+ from dbt import utils
11
+ from dbt.artifacts.resources import RefArgs
12
+ from dbt.clients.jinja import get_rendered
13
+ from dbt.context.context_config import ContextConfig
14
+ from dbt.contracts.graph.nodes import ModelNode
15
+ from dbt.exceptions import (
16
+ ModelConfigError,
17
+ ParsingError,
18
+ PythonLiteralEvalError,
19
+ PythonParsingError,
20
+ )
21
+ from dbt.flags import get_flags
22
+ from dbt.node_types import ModelLanguage, NodeType
23
+ from dbt.parser.base import SimpleSQLParser
24
+ from dbt.parser.search import FileBlock
25
+ from dbt_common.contracts.config.base import merge_config_dicts
26
+ from dbt_common.dataclass_schema import ValidationError
27
+ from dbt_common.exceptions.macros import UndefinedMacroError
28
+ from dbt_extractor import ExtractionError, py_extract_from_source # type: ignore
29
+
30
+ dbt_function_key_words = set(["ref", "source", "config", "get"])
31
+ dbt_function_full_names = set(["dbt.ref", "dbt.source", "dbt.config", "dbt.config.get"])
32
+
33
+
34
+ class PythonValidationVisitor(ast.NodeVisitor):
35
+ def __init__(self) -> None:
36
+ super().__init__()
37
+ self.dbt_errors: List[str] = []
38
+ self.num_model_def = 0
39
+
40
+ def visit_FunctionDef(self, node: ast.FunctionDef) -> None:
41
+ if node.name == "model":
42
+ self.num_model_def += 1
43
+ if node.args.args and not node.args.args[0].arg == "dbt":
44
+ self.dbt_errors.append("'dbt' not provided for model as the first argument")
45
+ if len(node.args.args) != 2:
46
+ self.dbt_errors.append(
47
+ "model function should have two args, `dbt` and a session to current warehouse"
48
+ )
49
+ # check we have a return and only one
50
+ if not isinstance(node.body[-1], ast.Return) or isinstance(
51
+ node.body[-1].value, ast.Tuple
52
+ ):
53
+ self.dbt_errors.append(
54
+ "In current version, model function should return only one dataframe object"
55
+ )
56
+
57
+ def check_error(self, node):
58
+ if self.num_model_def != 1:
59
+ raise ParsingError(
60
+ f"dbt allows exactly one model defined per python file, found {self.num_model_def}",
61
+ node=node,
62
+ )
63
+
64
+ if len(self.dbt_errors) != 0:
65
+ raise ParsingError("\n".join(self.dbt_errors), node=node)
66
+
67
+
68
+ class PythonParseVisitor(ast.NodeVisitor):
69
+ def __init__(self, dbt_node):
70
+ super().__init__()
71
+
72
+ self.dbt_node = dbt_node
73
+ self.dbt_function_calls = []
74
+ self.packages = []
75
+
76
+ @classmethod
77
+ def _flatten_attr(cls, node):
78
+ if isinstance(node, ast.Attribute):
79
+ return str(cls._flatten_attr(node.value)) + "." + node.attr
80
+ elif isinstance(node, ast.Name):
81
+ return str(node.id)
82
+ else:
83
+ pass
84
+
85
+ def _safe_eval(self, node):
86
+ try:
87
+ return ast.literal_eval(node)
88
+ except (SyntaxError, ValueError, TypeError, MemoryError, RecursionError) as exc:
89
+ raise PythonLiteralEvalError(exc, node=self.dbt_node) from exc
90
+
91
+ def _get_call_literals(self, node):
92
+ # List of literals
93
+ arg_literals = []
94
+ kwarg_literals = {}
95
+
96
+ # TODO : Make sure this throws (and that we catch it)
97
+ # for non-literal inputs
98
+ for arg in node.args:
99
+ rendered = self._safe_eval(arg)
100
+ arg_literals.append(rendered)
101
+
102
+ for keyword in node.keywords:
103
+ key = keyword.arg
104
+ rendered = self._safe_eval(keyword.value)
105
+ kwarg_literals[key] = rendered
106
+
107
+ return arg_literals, kwarg_literals
108
+
109
+ def visit_Call(self, node: ast.Call) -> None:
110
+ # check weather the current call could be a dbt function call
111
+ if isinstance(node.func, ast.Attribute) and node.func.attr in dbt_function_key_words:
112
+ func_name = self._flatten_attr(node.func)
113
+ # check weather the current call really is a dbt function call
114
+ if func_name in dbt_function_full_names:
115
+ # drop the dot-dbt prefix
116
+ func_name = func_name.split(".")[-1]
117
+ args, kwargs = self._get_call_literals(node)
118
+ self.dbt_function_calls.append((func_name, args, kwargs))
119
+
120
+ # no matter what happened above, we should keep visiting the rest of the tree
121
+ # visit args and kwargs to see if there's call in it
122
+ for obj in node.args + [kwarg.value for kwarg in node.keywords]:
123
+ if isinstance(obj, ast.Call):
124
+ self.visit_Call(obj)
125
+ # support dbt.ref in list args, kwargs
126
+ elif isinstance(obj, ast.List) or isinstance(obj, ast.Tuple):
127
+ for el in obj.elts:
128
+ if isinstance(el, ast.Call):
129
+ self.visit_Call(el)
130
+ # support dbt.ref in dict args, kwargs
131
+ elif isinstance(obj, ast.Dict):
132
+ for value in obj.values:
133
+ if isinstance(value, ast.Call):
134
+ self.visit_Call(value)
135
+ # support dbt function calls in f-strings
136
+ elif isinstance(obj, ast.JoinedStr):
137
+ for value in obj.values:
138
+ if isinstance(value, ast.FormattedValue) and isinstance(value.value, ast.Call):
139
+ self.visit_Call(value.value)
140
+
141
+ # visit node.func.value if we are at an call attr
142
+ if isinstance(node.func, ast.Attribute):
143
+ self.attribute_helper(node.func)
144
+
145
+ def attribute_helper(self, node: ast.Attribute) -> None:
146
+ while isinstance(node, ast.Attribute):
147
+ node = node.value # type: ignore
148
+ if isinstance(node, ast.Call):
149
+ self.visit_Call(node)
150
+
151
+ def visit_Import(self, node: ast.Import) -> None:
152
+ for n in node.names:
153
+ self.packages.append(n.name.split(".")[0])
154
+
155
+ def visit_ImportFrom(self, node: ast.ImportFrom) -> None:
156
+ if node.module:
157
+ self.packages.append(node.module.split(".")[0])
158
+
159
+
160
+ def verify_python_model_code(node):
161
+ # TODO: add a test for this
162
+ try:
163
+ rendered_python = get_rendered(
164
+ node.raw_code,
165
+ {},
166
+ node,
167
+ )
168
+ if rendered_python != node.raw_code:
169
+ raise ParsingError("")
170
+ except (UndefinedMacroError, ParsingError):
171
+ raise ParsingError("No jinja in python model code is allowed", node=node)
172
+
173
+
174
+ class ModelParser(SimpleSQLParser[ModelNode]):
175
+ def parse_from_dict(self, dct, validate=True) -> ModelNode:
176
+ if validate:
177
+ ModelNode.validate(dct)
178
+ return ModelNode.from_dict(dct)
179
+
180
+ @property
181
+ def resource_type(self) -> NodeType:
182
+ return NodeType.Model
183
+
184
+ @classmethod
185
+ def get_compiled_path(cls, block: FileBlock):
186
+ return block.path.relative_path
187
+
188
+ def parse_python_model(self, node, config, context):
189
+ config_keys_used = []
190
+ config_keys_defaults = []
191
+
192
+ try:
193
+ tree = ast.parse(node.raw_code, filename=node.original_file_path)
194
+ except SyntaxError as exc:
195
+ raise PythonParsingError(exc, node=node) from exc
196
+
197
+ # Only parse if AST tree has instructions in body
198
+ if tree.body:
199
+ # We are doing a validator and a parser because visit_FunctionDef in parser
200
+ # would actually make the parser not doing the visit_Calls any more
201
+ dbt_validator = PythonValidationVisitor()
202
+ dbt_validator.visit(tree)
203
+ dbt_validator.check_error(node)
204
+
205
+ dbt_parser = PythonParseVisitor(node)
206
+ dbt_parser.visit(tree)
207
+
208
+ for func, args, kwargs in dbt_parser.dbt_function_calls:
209
+ if func == "get":
210
+ num_args = len(args)
211
+ if num_args == 0:
212
+ raise ParsingError(
213
+ "dbt.config.get() requires at least one argument",
214
+ node=node,
215
+ )
216
+ if num_args > 2:
217
+ raise ParsingError(
218
+ f"dbt.config.get() takes at most 2 arguments ({num_args} given)",
219
+ node=node,
220
+ )
221
+ key = args[0]
222
+ default_value = args[1] if num_args == 2 else None
223
+ config_keys_used.append(key)
224
+ config_keys_defaults.append(default_value)
225
+ continue
226
+
227
+ context[func](*args, **kwargs)
228
+
229
+ if config_keys_used:
230
+ # this is being used in macro build_config_dict
231
+ context["config"](
232
+ config_keys_used=config_keys_used,
233
+ config_keys_defaults=config_keys_defaults,
234
+ )
235
+
236
+ def render_update(
237
+ self, node: ModelNode, config: ContextConfig, validate_config_call_dict: bool = False
238
+ ) -> None:
239
+ self.manifest._parsing_info.static_analysis_path_count += 1
240
+ flags = get_flags()
241
+ if node.language == ModelLanguage.python:
242
+ try:
243
+ verify_python_model_code(node)
244
+ context = self._context_for(node, config)
245
+ self.parse_python_model(node, config, context)
246
+ self.update_parsed_node_config(
247
+ node, config, context=context, validate_config_call_dict=True
248
+ )
249
+
250
+ except ValidationError as exc:
251
+ # we got a ValidationError - probably bad types in config()
252
+ raise ModelConfigError(exc, node=node) from exc
253
+ return
254
+
255
+ elif not flags.STATIC_PARSER:
256
+ # jinja rendering
257
+ super().render_update(node, config)
258
+ return
259
+
260
+ # only sample for experimental parser correctness on normal runs,
261
+ # not when the experimental parser flag is on.
262
+ exp_sample: bool = False
263
+ # sampling the stable static parser against jinja is significantly
264
+ # more expensive and therefore done far less frequently.
265
+ stable_sample: bool = False
266
+ # there are two samples above, and it is perfectly fine if both happen
267
+ # at the same time. If that happens, the experimental parser, stable
268
+ # parser, and jinja rendering will run on the same model file and
269
+ # send back codes for experimental v stable, and stable v jinja.
270
+ if not flags.USE_EXPERIMENTAL_PARSER:
271
+ # `True` roughly 1/5000 times this function is called
272
+ # sample = random.randint(1, 5001) == 5000
273
+ stable_sample = random.randint(1, 5001) == 5000
274
+ # sampling the experimental parser is explicitly disabled here, but use the following
275
+ # commented code to sample a fraction of the time when new
276
+ # experimental features are added.
277
+ # `True` roughly 1/100 times this function is called
278
+ # exp_sample = random.randint(1, 101) == 100
279
+
280
+ # top-level declaration of variables
281
+ statically_parsed: Optional[Union[str, Dict[str, List[Any]]]] = None
282
+ experimental_sample: Optional[Union[str, Dict[str, List[Any]]]] = None
283
+ exp_sample_node: Optional[ModelNode] = None
284
+ exp_sample_config: Optional[ContextConfig] = None
285
+ jinja_sample_node: Optional[ModelNode] = None
286
+ jinja_sample_config: Optional[ContextConfig] = None
287
+ result: List[str] = []
288
+
289
+ # sample the experimental parser only during a normal run
290
+ if exp_sample and not flags.USE_EXPERIMENTAL_PARSER:
291
+ experimental_sample = self.run_experimental_parser(node)
292
+ # if the experimental parser succeeded, make a full copy of model parser
293
+ # and populate _everything_ into it so it can be compared apples-to-apples
294
+ # with a fully jinja-rendered project. This is necessary because the experimental
295
+ # parser will likely add features that the existing static parser will fail on
296
+ # so comparing those directly would give us bad results. The comparison will be
297
+ # conducted after this model has been fully rendered either by the static parser
298
+ # or by full jinja rendering
299
+ if isinstance(experimental_sample, dict):
300
+ model_parser_copy = self.partial_deepcopy()
301
+ exp_sample_node = deepcopy(node)
302
+ exp_sample_config = deepcopy(config)
303
+ model_parser_copy.populate(exp_sample_node, exp_sample_config, experimental_sample)
304
+ # use the experimental parser exclusively if the flag is on
305
+ if flags.USE_EXPERIMENTAL_PARSER:
306
+ statically_parsed = self.run_experimental_parser(node)
307
+ # run the stable static parser unless it is explicitly turned off
308
+ else:
309
+ statically_parsed = self.run_static_parser(node)
310
+
311
+ # if the static parser succeeded, extract some data in easy-to-compare formats
312
+ if isinstance(statically_parsed, dict):
313
+ # only sample jinja for the purpose of comparing with the stable static parser
314
+ # if we know we don't need to fall back to jinja (i.e. - nothing to compare
315
+ # with jinja v jinja).
316
+ # This means we skip sampling for 40% of the 1/5000 samples. We could run the
317
+ # sampling rng here, but the effect would be the same since we would only roll
318
+ # it 40% of the time. So I've opted to keep all the rng code colocated above.
319
+ if stable_sample and not flags.USE_EXPERIMENTAL_PARSER:
320
+ # if this will _never_ mutate anything `self` we could avoid these deep copies,
321
+ # but we can't really guarantee that going forward.
322
+ model_parser_copy = self.partial_deepcopy()
323
+ jinja_sample_node = deepcopy(node)
324
+ jinja_sample_config = deepcopy(config)
325
+ # rendering mutates the node and the config
326
+ super(ModelParser, model_parser_copy).render_update(
327
+ jinja_sample_node, jinja_sample_config
328
+ )
329
+
330
+ # update the unrendered config with values from the static parser.
331
+ # values from yaml files are in there already
332
+ self.populate(node, config, statically_parsed)
333
+
334
+ # if we took a jinja sample, compare now that the base node has been populated
335
+ if jinja_sample_node is not None and jinja_sample_config is not None:
336
+ result = _get_stable_sample_result(
337
+ jinja_sample_node, jinja_sample_config, node, config
338
+ )
339
+
340
+ # if we took an experimental sample, compare now that the base node has been populated
341
+ if exp_sample_node is not None and exp_sample_config is not None:
342
+ result = _get_exp_sample_result(
343
+ exp_sample_node,
344
+ exp_sample_config,
345
+ node,
346
+ config,
347
+ )
348
+
349
+ self.manifest._parsing_info.static_analysis_parsed_path_count += 1
350
+ # if the static parser didn't succeed, fall back to jinja
351
+ else:
352
+ # jinja rendering
353
+ super().render_update(node, config, validate_config_call_dict=True)
354
+
355
+ # if sampling, add the correct messages for tracking
356
+ if exp_sample and isinstance(experimental_sample, str):
357
+ if experimental_sample == "cannot_parse":
358
+ result += ["01_experimental_parser_cannot_parse"]
359
+ elif experimental_sample == "has_banned_macro":
360
+ result += ["08_has_banned_macro"]
361
+ elif stable_sample and isinstance(statically_parsed, str):
362
+ if statically_parsed == "cannot_parse":
363
+ result += ["81_stable_parser_cannot_parse"]
364
+ elif statically_parsed == "has_banned_macro":
365
+ result += ["88_has_banned_macro"]
366
+
367
+ # only send the tracking event if there is at least one result code
368
+ if result:
369
+ # fire a tracking event. this fires one event for every sample
370
+ # so that we have data on a per file basis. Not only can we expect
371
+ # no false positives or misses, we can expect the number model
372
+ # files parseable by the experimental parser to match our internal
373
+ # testing.
374
+ if tracking.active_user is not None: # None in some tests
375
+ tracking.track_experimental_parser_sample(
376
+ {
377
+ "project_id": self.root_project.hashed_name(),
378
+ "file_id": utils.get_hash(node),
379
+ "status": result,
380
+ }
381
+ )
382
+
383
+ def run_static_parser(self, node: ModelNode) -> Optional[Union[str, Dict[str, List[Any]]]]:
384
+ # if any banned macros have been overridden by the user, we cannot use the static parser.
385
+ if self._has_banned_macro(node):
386
+ return "has_banned_macro"
387
+
388
+ # run the stable static parser and return the results
389
+ try:
390
+ statically_parsed = py_extract_from_source(node.raw_code)
391
+ return _shift_sources(statically_parsed)
392
+ # if we want information on what features are barring the static
393
+ # parser from reading model files, this is where we would add that
394
+ # since that information is stored in the `ExtractionError`.
395
+ except ExtractionError:
396
+ return "cannot_parse"
397
+
398
+ def run_experimental_parser(
399
+ self, node: ModelNode
400
+ ) -> Optional[Union[str, Dict[str, List[Any]]]]:
401
+ # if any banned macros have been overridden by the user, we cannot use the static parser.
402
+ if self._has_banned_macro(node):
403
+ return "has_banned_macro"
404
+
405
+ # run the experimental parser and return the results
406
+ try:
407
+ # for now, this line calls the stable static parser since there are no
408
+ # experimental features. Change `py_extract_from_source` to the new
409
+ # experimental call when we add additional features.
410
+ experimentally_parsed = py_extract_from_source(node.raw_code)
411
+ return _shift_sources(experimentally_parsed)
412
+ # if we want information on what features are barring the experimental
413
+ # parser from reading model files, this is where we would add that
414
+ # since that information is stored in the `ExtractionError`.
415
+ except ExtractionError:
416
+ return "cannot_parse"
417
+
418
+ # checks for banned macros
419
+ def _has_banned_macro(self, node: ModelNode) -> bool:
420
+ # first check if there is a banned macro defined in scope for this model file
421
+ root_project_name = self.root_project.project_name
422
+ project_name = node.package_name
423
+ banned_macros = ["ref", "source", "config"]
424
+
425
+ all_banned_macro_keys: Iterator[str] = chain.from_iterable(
426
+ map(
427
+ lambda name: [f"macro.{project_name}.{name}", f"macro.{root_project_name}.{name}"],
428
+ banned_macros,
429
+ )
430
+ )
431
+
432
+ return reduce(
433
+ lambda z, key: z or (key in self.manifest.macros), all_banned_macro_keys, False
434
+ )
435
+
436
+ # this method updates the model node rendered and unrendered config as well
437
+ # as the node object. Used to populate these values when circumventing jinja
438
+ # rendering like the static parser.
439
+ def populate(self, node: ModelNode, config: ContextConfig, statically_parsed: Dict[str, Any]):
440
+ # manually fit configs in
441
+ config._config_call_dict = _get_config_call_dict(statically_parsed)
442
+
443
+ # if there are hooks present this, it WILL render jinja. Will need to change
444
+ # when the experimental parser supports hooks
445
+ self.update_parsed_node_config(node, config, validate_config_call_dict=True)
446
+
447
+ # update the unrendered config with values from the file.
448
+ # values from yaml files are in there already
449
+ node.unrendered_config.update(dict(statically_parsed["configs"]))
450
+
451
+ # set refs and sources on the node object
452
+ refs: List[RefArgs] = []
453
+ for ref in statically_parsed["refs"]:
454
+ name = ref.get("name")
455
+ package = ref.get("package")
456
+ version = ref.get("version")
457
+ refs.append(RefArgs(name, package, version))
458
+
459
+ node.refs += refs
460
+ node.sources += statically_parsed["sources"]
461
+
462
+ # configs don't need to be merged into the node because they
463
+ # are read from config._config_call_dict
464
+
465
+ # the manifest is often huge so this method avoids deepcopying it
466
+ def partial_deepcopy(self):
467
+ return ModelParser(deepcopy(self.project), self.manifest, deepcopy(self.root_project))
468
+
469
+
470
+ # pure function. safe to use elsewhere, but unlikely to be useful outside this file.
471
+ def _get_config_call_dict(static_parser_result: Dict[str, Any]) -> Dict[str, Any]:
472
+ config_call_dict: Dict[str, Any] = {}
473
+
474
+ for c in static_parser_result["configs"]:
475
+ merge_config_dicts(config_call_dict, {c[0]: c[1]})
476
+
477
+ return config_call_dict
478
+
479
+
480
+ # TODO if we format sources in the extractor to match this type, we won't need this function.
481
+ def _shift_sources(static_parser_result: Dict[str, List[Any]]) -> Dict[str, List[Any]]:
482
+ shifted_result = deepcopy(static_parser_result)
483
+ source_calls = []
484
+
485
+ for s in static_parser_result["sources"]:
486
+ source_calls.append([s[0], s[1]])
487
+ shifted_result["sources"] = source_calls
488
+
489
+ return shifted_result
490
+
491
+
492
+ # returns a list of string codes to be sent as a tracking event
493
+ def _get_exp_sample_result(
494
+ sample_node: ModelNode,
495
+ sample_config: ContextConfig,
496
+ node: ModelNode,
497
+ config: ContextConfig,
498
+ ) -> List[str]:
499
+ result: List[Tuple[int, str]] = _get_sample_result(sample_node, sample_config, node, config)
500
+
501
+ def process(codemsg):
502
+ code, msg = codemsg
503
+ return f"0{code}_experimental_{msg}"
504
+
505
+ return list(map(process, result))
506
+
507
+
508
+ # returns a list of string codes to be sent as a tracking event
509
+ def _get_stable_sample_result(
510
+ sample_node: ModelNode,
511
+ sample_config: ContextConfig,
512
+ node: ModelNode,
513
+ config: ContextConfig,
514
+ ) -> List[str]:
515
+ result: List[Tuple[int, str]] = _get_sample_result(sample_node, sample_config, node, config)
516
+
517
+ def process(codemsg):
518
+ code, msg = codemsg
519
+ return f"8{code}_stable_{msg}"
520
+
521
+ return list(map(process, result))
522
+
523
+
524
+ # returns a list of string codes that need a single digit prefix to be prepended
525
+ # before being sent as a tracking event
526
+ def _get_sample_result(
527
+ sample_node: ModelNode,
528
+ sample_config: ContextConfig,
529
+ node: ModelNode,
530
+ config: ContextConfig,
531
+ ) -> List[Tuple[int, str]]:
532
+ result: List[Tuple[int, str]] = []
533
+ # look for false positive configs
534
+ for k in sample_config._config_call_dict.keys():
535
+ if k not in config._config_call_dict.keys():
536
+ result += [(2, "false_positive_config_value")]
537
+ break
538
+
539
+ # look for missed configs
540
+ for k in config._config_call_dict.keys():
541
+ if k not in sample_config._config_call_dict.keys():
542
+ result += [(3, "missed_config_value")]
543
+ break
544
+
545
+ # look for false positive sources
546
+ for s in sample_node.sources:
547
+ if s not in node.sources:
548
+ result += [(4, "false_positive_source_value")]
549
+ break
550
+
551
+ # look for missed sources
552
+ for s in node.sources:
553
+ if s not in sample_node.sources:
554
+ result += [(5, "missed_source_value")]
555
+ break
556
+
557
+ # look for false positive refs
558
+ for r in sample_node.refs:
559
+ if r not in node.refs:
560
+ result += [(6, "false_positive_ref_value")]
561
+ break
562
+
563
+ # look for missed refs
564
+ for r in node.refs:
565
+ if r not in sample_node.refs:
566
+ result += [(7, "missed_ref_value")]
567
+ break
568
+
569
+ # if there are no errors, return a success value
570
+ if not result:
571
+ result = [(0, "exact_match")]
572
+
573
+ return result