dvt-core 0.58.6__cp311-cp311-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.
Files changed (324) 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 +2403 -0
  57. dbt/cli/option_types.py +121 -0
  58. dbt/cli/options.py +80 -0
  59. dbt/cli/params.py +844 -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.cpython-311-darwin.so +0 -0
  74. dbt/compute/engines/spark_engine.py +642 -0
  75. dbt/compute/federated_executor.cpython-311-darwin.so +0 -0
  76. dbt/compute/federated_executor.py +1080 -0
  77. dbt/compute/filter_pushdown.cpython-311-darwin.so +0 -0
  78. dbt/compute/filter_pushdown.py +273 -0
  79. dbt/compute/jar_provisioning.cpython-311-darwin.so +0 -0
  80. dbt/compute/jar_provisioning.py +255 -0
  81. dbt/compute/java_compat.cpython-311-darwin.so +0 -0
  82. dbt/compute/java_compat.py +689 -0
  83. dbt/compute/jdbc_utils.cpython-311-darwin.so +0 -0
  84. dbt/compute/jdbc_utils.py +678 -0
  85. dbt/compute/metadata/__init__.py +40 -0
  86. dbt/compute/metadata/adapters_registry.cpython-311-darwin.so +0 -0
  87. dbt/compute/metadata/adapters_registry.py +370 -0
  88. dbt/compute/metadata/registry.cpython-311-darwin.so +0 -0
  89. dbt/compute/metadata/registry.py +674 -0
  90. dbt/compute/metadata/store.cpython-311-darwin.so +0 -0
  91. dbt/compute/metadata/store.py +1499 -0
  92. dbt/compute/smart_selector.cpython-311-darwin.so +0 -0
  93. dbt/compute/smart_selector.py +377 -0
  94. dbt/compute/strategies/__init__.py +55 -0
  95. dbt/compute/strategies/base.cpython-311-darwin.so +0 -0
  96. dbt/compute/strategies/base.py +165 -0
  97. dbt/compute/strategies/dataproc.cpython-311-darwin.so +0 -0
  98. dbt/compute/strategies/dataproc.py +207 -0
  99. dbt/compute/strategies/emr.cpython-311-darwin.so +0 -0
  100. dbt/compute/strategies/emr.py +203 -0
  101. dbt/compute/strategies/local.cpython-311-darwin.so +0 -0
  102. dbt/compute/strategies/local.py +443 -0
  103. dbt/compute/strategies/standalone.cpython-311-darwin.so +0 -0
  104. dbt/compute/strategies/standalone.py +262 -0
  105. dbt/config/__init__.py +4 -0
  106. dbt/config/catalogs.py +94 -0
  107. dbt/config/compute.cpython-311-darwin.so +0 -0
  108. dbt/config/compute.py +513 -0
  109. dbt/config/dvt_profile.cpython-311-darwin.so +0 -0
  110. dbt/config/dvt_profile.py +342 -0
  111. dbt/config/profile.py +422 -0
  112. dbt/config/project.py +873 -0
  113. dbt/config/project_utils.py +28 -0
  114. dbt/config/renderer.py +231 -0
  115. dbt/config/runtime.py +553 -0
  116. dbt/config/selectors.py +208 -0
  117. dbt/config/utils.py +77 -0
  118. dbt/constants.py +28 -0
  119. dbt/context/__init__.py +0 -0
  120. dbt/context/base.py +745 -0
  121. dbt/context/configured.py +135 -0
  122. dbt/context/context_config.py +382 -0
  123. dbt/context/docs.py +82 -0
  124. dbt/context/exceptions_jinja.py +178 -0
  125. dbt/context/macro_resolver.py +195 -0
  126. dbt/context/macros.py +171 -0
  127. dbt/context/manifest.py +72 -0
  128. dbt/context/providers.py +2249 -0
  129. dbt/context/query_header.py +13 -0
  130. dbt/context/secret.py +58 -0
  131. dbt/context/target.py +74 -0
  132. dbt/contracts/__init__.py +0 -0
  133. dbt/contracts/files.py +413 -0
  134. dbt/contracts/graph/__init__.py +0 -0
  135. dbt/contracts/graph/manifest.py +1904 -0
  136. dbt/contracts/graph/metrics.py +97 -0
  137. dbt/contracts/graph/model_config.py +70 -0
  138. dbt/contracts/graph/node_args.py +42 -0
  139. dbt/contracts/graph/nodes.py +1806 -0
  140. dbt/contracts/graph/semantic_manifest.py +232 -0
  141. dbt/contracts/graph/unparsed.py +811 -0
  142. dbt/contracts/project.py +417 -0
  143. dbt/contracts/results.py +53 -0
  144. dbt/contracts/selection.py +23 -0
  145. dbt/contracts/sql.py +85 -0
  146. dbt/contracts/state.py +68 -0
  147. dbt/contracts/util.py +46 -0
  148. dbt/deprecations.py +348 -0
  149. dbt/deps/__init__.py +0 -0
  150. dbt/deps/base.py +152 -0
  151. dbt/deps/git.py +195 -0
  152. dbt/deps/local.py +79 -0
  153. dbt/deps/registry.py +130 -0
  154. dbt/deps/resolver.py +149 -0
  155. dbt/deps/tarball.py +120 -0
  156. dbt/docs/source/_ext/dbt_click.py +119 -0
  157. dbt/docs/source/conf.py +32 -0
  158. dbt/env_vars.py +64 -0
  159. dbt/event_time/event_time.py +40 -0
  160. dbt/event_time/sample_window.py +60 -0
  161. dbt/events/__init__.py +15 -0
  162. dbt/events/base_types.py +36 -0
  163. dbt/events/core_types_pb2.py +2 -0
  164. dbt/events/logging.py +108 -0
  165. dbt/events/types.py +2516 -0
  166. dbt/exceptions.py +1486 -0
  167. dbt/flags.py +89 -0
  168. dbt/graph/__init__.py +11 -0
  169. dbt/graph/cli.py +249 -0
  170. dbt/graph/graph.py +172 -0
  171. dbt/graph/queue.py +214 -0
  172. dbt/graph/selector.py +374 -0
  173. dbt/graph/selector_methods.py +975 -0
  174. dbt/graph/selector_spec.py +222 -0
  175. dbt/graph/thread_pool.py +18 -0
  176. dbt/hooks.py +21 -0
  177. dbt/include/README.md +49 -0
  178. dbt/include/__init__.py +3 -0
  179. dbt/include/data/adapters_registry.duckdb +0 -0
  180. dbt/include/data/build_registry.py +242 -0
  181. dbt/include/data/csv/adapter_queries.csv +33 -0
  182. dbt/include/data/csv/syntax_rules.csv +9 -0
  183. dbt/include/data/csv/type_mappings_bigquery.csv +28 -0
  184. dbt/include/data/csv/type_mappings_databricks.csv +30 -0
  185. dbt/include/data/csv/type_mappings_mysql.csv +40 -0
  186. dbt/include/data/csv/type_mappings_oracle.csv +30 -0
  187. dbt/include/data/csv/type_mappings_postgres.csv +56 -0
  188. dbt/include/data/csv/type_mappings_redshift.csv +33 -0
  189. dbt/include/data/csv/type_mappings_snowflake.csv +38 -0
  190. dbt/include/data/csv/type_mappings_sqlserver.csv +35 -0
  191. dbt/include/starter_project/.gitignore +4 -0
  192. dbt/include/starter_project/README.md +15 -0
  193. dbt/include/starter_project/__init__.py +3 -0
  194. dbt/include/starter_project/analyses/.gitkeep +0 -0
  195. dbt/include/starter_project/dbt_project.yml +36 -0
  196. dbt/include/starter_project/macros/.gitkeep +0 -0
  197. dbt/include/starter_project/models/example/my_first_dbt_model.sql +27 -0
  198. dbt/include/starter_project/models/example/my_second_dbt_model.sql +6 -0
  199. dbt/include/starter_project/models/example/schema.yml +21 -0
  200. dbt/include/starter_project/seeds/.gitkeep +0 -0
  201. dbt/include/starter_project/snapshots/.gitkeep +0 -0
  202. dbt/include/starter_project/tests/.gitkeep +0 -0
  203. dbt/internal_deprecations.py +26 -0
  204. dbt/jsonschemas/__init__.py +3 -0
  205. dbt/jsonschemas/jsonschemas.py +309 -0
  206. dbt/jsonschemas/project/0.0.110.json +4717 -0
  207. dbt/jsonschemas/project/0.0.85.json +2015 -0
  208. dbt/jsonschemas/resources/0.0.110.json +2636 -0
  209. dbt/jsonschemas/resources/0.0.85.json +2536 -0
  210. dbt/jsonschemas/resources/latest.json +6773 -0
  211. dbt/links.py +4 -0
  212. dbt/materializations/__init__.py +0 -0
  213. dbt/materializations/incremental/__init__.py +0 -0
  214. dbt/materializations/incremental/microbatch.py +236 -0
  215. dbt/mp_context.py +8 -0
  216. dbt/node_types.py +37 -0
  217. dbt/parser/__init__.py +23 -0
  218. dbt/parser/analysis.py +21 -0
  219. dbt/parser/base.py +548 -0
  220. dbt/parser/common.py +266 -0
  221. dbt/parser/docs.py +52 -0
  222. dbt/parser/fixtures.py +51 -0
  223. dbt/parser/functions.py +30 -0
  224. dbt/parser/generic_test.py +100 -0
  225. dbt/parser/generic_test_builders.py +333 -0
  226. dbt/parser/hooks.py +118 -0
  227. dbt/parser/macros.py +137 -0
  228. dbt/parser/manifest.py +2204 -0
  229. dbt/parser/models.py +573 -0
  230. dbt/parser/partial.py +1178 -0
  231. dbt/parser/read_files.py +445 -0
  232. dbt/parser/schema_generic_tests.py +422 -0
  233. dbt/parser/schema_renderer.py +111 -0
  234. dbt/parser/schema_yaml_readers.py +935 -0
  235. dbt/parser/schemas.py +1466 -0
  236. dbt/parser/search.py +149 -0
  237. dbt/parser/seeds.py +28 -0
  238. dbt/parser/singular_test.py +20 -0
  239. dbt/parser/snapshots.py +44 -0
  240. dbt/parser/sources.py +558 -0
  241. dbt/parser/sql.py +62 -0
  242. dbt/parser/unit_tests.py +621 -0
  243. dbt/plugins/__init__.py +20 -0
  244. dbt/plugins/contracts.py +9 -0
  245. dbt/plugins/exceptions.py +2 -0
  246. dbt/plugins/manager.py +163 -0
  247. dbt/plugins/manifest.py +21 -0
  248. dbt/profiler.py +20 -0
  249. dbt/py.typed +1 -0
  250. dbt/query_analyzer.cpython-311-darwin.so +0 -0
  251. dbt/query_analyzer.py +410 -0
  252. dbt/runners/__init__.py +2 -0
  253. dbt/runners/exposure_runner.py +7 -0
  254. dbt/runners/no_op_runner.py +45 -0
  255. dbt/runners/saved_query_runner.py +7 -0
  256. dbt/selected_resources.py +8 -0
  257. dbt/task/__init__.py +0 -0
  258. dbt/task/base.py +503 -0
  259. dbt/task/build.py +197 -0
  260. dbt/task/clean.py +56 -0
  261. dbt/task/clone.py +161 -0
  262. dbt/task/compile.py +150 -0
  263. dbt/task/compute.cpython-311-darwin.so +0 -0
  264. dbt/task/compute.py +458 -0
  265. dbt/task/debug.py +505 -0
  266. dbt/task/deps.py +280 -0
  267. dbt/task/docs/__init__.py +3 -0
  268. dbt/task/docs/api/__init__.py +23 -0
  269. dbt/task/docs/api/catalog.cpython-311-darwin.so +0 -0
  270. dbt/task/docs/api/catalog.py +204 -0
  271. dbt/task/docs/api/lineage.cpython-311-darwin.so +0 -0
  272. dbt/task/docs/api/lineage.py +234 -0
  273. dbt/task/docs/api/profile.cpython-311-darwin.so +0 -0
  274. dbt/task/docs/api/profile.py +204 -0
  275. dbt/task/docs/api/spark.cpython-311-darwin.so +0 -0
  276. dbt/task/docs/api/spark.py +186 -0
  277. dbt/task/docs/generate.py +947 -0
  278. dbt/task/docs/index.html +250 -0
  279. dbt/task/docs/serve.cpython-311-darwin.so +0 -0
  280. dbt/task/docs/serve.py +174 -0
  281. dbt/task/dvt_output.py +362 -0
  282. dbt/task/dvt_run.py +204 -0
  283. dbt/task/freshness.py +322 -0
  284. dbt/task/function.py +121 -0
  285. dbt/task/group_lookup.py +46 -0
  286. dbt/task/init.cpython-311-darwin.so +0 -0
  287. dbt/task/init.py +604 -0
  288. dbt/task/java.cpython-311-darwin.so +0 -0
  289. dbt/task/java.py +316 -0
  290. dbt/task/list.py +236 -0
  291. dbt/task/metadata.cpython-311-darwin.so +0 -0
  292. dbt/task/metadata.py +804 -0
  293. dbt/task/printer.py +175 -0
  294. dbt/task/profile.cpython-311-darwin.so +0 -0
  295. dbt/task/profile.py +1307 -0
  296. dbt/task/profile_serve.py +615 -0
  297. dbt/task/retract.py +438 -0
  298. dbt/task/retry.py +175 -0
  299. dbt/task/run.py +1387 -0
  300. dbt/task/run_operation.py +141 -0
  301. dbt/task/runnable.py +758 -0
  302. dbt/task/seed.py +103 -0
  303. dbt/task/show.py +149 -0
  304. dbt/task/snapshot.py +56 -0
  305. dbt/task/spark.cpython-311-darwin.so +0 -0
  306. dbt/task/spark.py +414 -0
  307. dbt/task/sql.py +110 -0
  308. dbt/task/target_sync.cpython-311-darwin.so +0 -0
  309. dbt/task/target_sync.py +766 -0
  310. dbt/task/test.py +464 -0
  311. dbt/tests/fixtures/__init__.py +1 -0
  312. dbt/tests/fixtures/project.py +620 -0
  313. dbt/tests/util.py +651 -0
  314. dbt/tracking.py +529 -0
  315. dbt/utils/__init__.py +3 -0
  316. dbt/utils/artifact_upload.py +151 -0
  317. dbt/utils/utils.py +408 -0
  318. dbt/version.py +270 -0
  319. dvt_cli/__init__.py +72 -0
  320. dvt_core-0.58.6.dist-info/METADATA +288 -0
  321. dvt_core-0.58.6.dist-info/RECORD +324 -0
  322. dvt_core-0.58.6.dist-info/WHEEL +5 -0
  323. dvt_core-0.58.6.dist-info/entry_points.txt +2 -0
  324. dvt_core-0.58.6.dist-info/top_level.txt +2 -0
@@ -0,0 +1,333 @@
1
+ import re
2
+ from copy import deepcopy
3
+ from typing import Any, Dict, Generic, List, Optional, Tuple
4
+
5
+ from dbt import deprecations
6
+ from dbt.artifacts.resources import NodeVersion
7
+ from dbt.clients.jinja import GENERIC_TEST_KWARGS_NAME, get_rendered
8
+ from dbt.contracts.graph.nodes import UnpatchedSourceDefinition
9
+ from dbt.contracts.graph.unparsed import UnparsedModelUpdate, UnparsedNodeUpdate
10
+ from dbt.exceptions import (
11
+ CustomMacroPopulatingConfigValueError,
12
+ SameKeyNestedError,
13
+ TagNotStringError,
14
+ TagsNotListOfStringsError,
15
+ TestArgIncludesModelError,
16
+ TestArgsNotDictError,
17
+ TestDefinitionDictLengthError,
18
+ TestNameNotStringError,
19
+ TestTypeError,
20
+ UnexpectedTestNamePatternError,
21
+ )
22
+ from dbt.flags import get_flags
23
+ from dbt.parser.common import Testable
24
+ from dbt.utils import md5
25
+ from dbt_common.exceptions.macros import UndefinedMacroError
26
+
27
+
28
+ def synthesize_generic_test_names(
29
+ test_type: str, test_name: str, args: Dict[str, Any]
30
+ ) -> Tuple[str, str]:
31
+ # Using the type, name, and arguments to this generic test, synthesize a (hopefully) unique name
32
+ # Will not be unique if multiple tests have same name + arguments, and only configs differ
33
+ # Returns a shorter version (hashed/truncated, for the compiled file)
34
+ # as well as the full name (for the unique_id + FQN)
35
+ flat_args = []
36
+ for arg_name in sorted(args):
37
+ # the model is already embedded in the name, so skip it
38
+ if arg_name == "model":
39
+ continue
40
+ arg_val = args[arg_name]
41
+
42
+ if isinstance(arg_val, dict):
43
+ parts = list(arg_val.values())
44
+ elif isinstance(arg_val, (list, tuple)):
45
+ parts = list(arg_val)
46
+ else:
47
+ parts = [arg_val]
48
+
49
+ flat_args.extend([str(part) for part in parts])
50
+
51
+ clean_flat_args = [re.sub("[^0-9a-zA-Z_]+", "_", arg) for arg in flat_args]
52
+ unique = "__".join(clean_flat_args)
53
+
54
+ # for the file path + alias, the name must be <64 characters
55
+ # if the full name is too long, include the first 30 identifying chars plus
56
+ # a 32-character hash of the full contents
57
+
58
+ test_identifier = "{}_{}".format(test_type, test_name)
59
+ full_name = "{}_{}".format(test_identifier, unique)
60
+
61
+ if len(full_name) >= 64:
62
+ test_trunc_identifier = test_identifier[:30]
63
+ label = md5(full_name)
64
+ short_name = "{}_{}".format(test_trunc_identifier, label)
65
+ else:
66
+ short_name = full_name
67
+
68
+ return short_name, full_name
69
+
70
+
71
+ class TestBuilder(Generic[Testable]):
72
+ """An object to hold assorted test settings and perform basic parsing
73
+
74
+ Test names have the following pattern:
75
+ - the test name itself may be namespaced (package.test)
76
+ - or it may not be namespaced (test)
77
+
78
+ """
79
+
80
+ # The 'test_name' is used to find the 'macro' that implements the test
81
+ TEST_NAME_PATTERN = re.compile(
82
+ r"((?P<test_namespace>([a-zA-Z_][0-9a-zA-Z_]*))\.)?"
83
+ r"(?P<test_name>([a-zA-Z_][0-9a-zA-Z_]*))"
84
+ )
85
+ # args in the test entry representing test configs
86
+ CONFIG_ARGS = (
87
+ "severity",
88
+ "tags",
89
+ "enabled",
90
+ "where",
91
+ "limit",
92
+ "warn_if",
93
+ "error_if",
94
+ "fail_calc",
95
+ "store_failures",
96
+ "store_failures_as",
97
+ "meta",
98
+ "database",
99
+ "schema",
100
+ "alias",
101
+ )
102
+
103
+ def __init__(
104
+ self,
105
+ data_test: Dict[str, Any],
106
+ target: Testable,
107
+ package_name: str,
108
+ render_ctx: Dict[str, Any],
109
+ column_name: Optional[str] = None,
110
+ version: Optional[NodeVersion] = None,
111
+ ) -> None:
112
+ test_name, test_args = self.extract_test_args(
113
+ data_test, target.original_file_path, target.name, column_name, package_name
114
+ )
115
+ self.args: Dict[str, Any] = test_args
116
+ if "model" in self.args:
117
+ raise TestArgIncludesModelError()
118
+ self.package_name: str = package_name
119
+ self.target: Testable = target
120
+ self.version: Optional[NodeVersion] = version
121
+ self.render_ctx: Dict[str, Any] = render_ctx
122
+ self.column_name: Optional[str] = column_name
123
+ self.args["model"] = self.build_model_str()
124
+
125
+ match = self.TEST_NAME_PATTERN.match(test_name)
126
+ if match is None:
127
+ raise UnexpectedTestNamePatternError(test_name)
128
+
129
+ groups = match.groupdict()
130
+ self.name: str = groups["test_name"]
131
+ self.namespace: str = groups["test_namespace"]
132
+ self.config: Dict[str, Any] = {}
133
+ # Process legacy args
134
+ self.config.update(self._process_legacy_args())
135
+
136
+ # Process config args if present
137
+ if "config" in self.args:
138
+ self.config.update(self._render_values(self.args.pop("config", {})))
139
+
140
+ if self.namespace is not None:
141
+ self.package_name = self.namespace
142
+
143
+ # If the user has provided a description for this generic test, use it
144
+ # Then delete the "description" argument to:
145
+ # 1. Avoid passing it into the test macro
146
+ # 2. Avoid passing it into the test name synthesis
147
+ # Otherwise, use an empty string
148
+ self.description: str = ""
149
+
150
+ if "description" in self.args:
151
+ self.description = self.args["description"]
152
+ del self.args["description"]
153
+
154
+ # If the user has provided a custom name for this generic test, use it
155
+ # Then delete the "name" argument to avoid passing it into the test macro
156
+ # Otherwise, use an auto-generated name synthesized from test inputs
157
+ self.compiled_name: str = ""
158
+ self.fqn_name: str = ""
159
+
160
+ if "name" in self.args:
161
+ # Assign the user-defined name here, which will be checked for uniqueness later
162
+ # we will raise an error if two tests have same name for same model + column combo
163
+ self.compiled_name = self.args["name"]
164
+ self.fqn_name = self.args["name"]
165
+ del self.args["name"]
166
+ else:
167
+ short_name, full_name = self.get_synthetic_test_names()
168
+ self.compiled_name = short_name
169
+ self.fqn_name = full_name
170
+ # use hashed name as alias if full name is too long
171
+ if short_name != full_name and "alias" not in self.config:
172
+ self.config["alias"] = short_name
173
+
174
+ def _process_legacy_args(self):
175
+ config = {}
176
+ for key in self.CONFIG_ARGS:
177
+ value = self.args.pop(key, None)
178
+ if value and "config" in self.args and key in self.args["config"]:
179
+ raise SameKeyNestedError()
180
+ if not value and "config" in self.args:
181
+ value = self.args["config"].pop(key, None)
182
+ config[key] = value
183
+
184
+ return self._render_values(config)
185
+
186
+ def _render_values(self, config: Dict[str, Any]) -> Dict[str, Any]:
187
+ rendered_config = {}
188
+ for key, value in config.items():
189
+ if isinstance(value, str):
190
+ try:
191
+ value = get_rendered(value, self.render_ctx, native=True)
192
+ except UndefinedMacroError as e:
193
+ raise CustomMacroPopulatingConfigValueError(
194
+ target_name=self.target.name,
195
+ column_name=self.column_name,
196
+ name=self.name,
197
+ key=key,
198
+ err_msg=e.msg,
199
+ )
200
+ if value is not None:
201
+ rendered_config[key] = value
202
+ return rendered_config
203
+
204
+ def _bad_type(self) -> TypeError:
205
+ return TypeError('invalid target type "{}"'.format(type(self.target)))
206
+
207
+ @staticmethod
208
+ def extract_test_args(
209
+ data_test, file_path, resource_name=None, column_name=None, package_name=None
210
+ ) -> Tuple[str, Dict[str, Any]]:
211
+ if not isinstance(data_test, dict):
212
+ raise TestTypeError(data_test)
213
+
214
+ # If the test is a dictionary with top-level keys, the test name is "test_name"
215
+ # and the rest are arguments
216
+ # {'name': 'my_favorite_test', 'test_name': 'unique', 'config': {'where': '1=1'}}
217
+ if "test_name" in data_test.keys():
218
+ test_name = data_test.pop("test_name")
219
+ test_args = data_test
220
+ # If the test is a nested dictionary with one top-level key, the test name
221
+ # is the dict name, and nested keys are arguments
222
+ # {'unique': {'name': 'my_favorite_test', 'config': {'where': '1=1'}}}
223
+ else:
224
+ data_test = list(data_test.items())
225
+ if len(data_test) != 1:
226
+ raise TestDefinitionDictLengthError(data_test)
227
+ test_name, test_args = data_test[0]
228
+
229
+ if not isinstance(test_args, dict):
230
+ raise TestArgsNotDictError(test_args)
231
+ if not isinstance(test_name, str):
232
+ raise TestNameNotStringError(test_name)
233
+ test_args = deepcopy(test_args)
234
+ if column_name is not None:
235
+ test_args["column_name"] = column_name
236
+
237
+ # Extract kwargs when they are nested under new 'arguments' property separately from 'config' if require_generic_test_arguments_property is enabled
238
+ if get_flags().require_generic_test_arguments_property:
239
+ arguments = test_args.pop("arguments", {})
240
+ if not arguments and any(
241
+ k not in ("config", "column_name", "description", "name") for k in test_args.keys()
242
+ ):
243
+ resource = (
244
+ f"'{resource_name}' in package '{package_name}'"
245
+ if package_name
246
+ else f"'{resource_name}'"
247
+ )
248
+ deprecations.warn(
249
+ "missing-arguments-property-in-generic-test-deprecation",
250
+ test_name=f"`{test_name}` defined on {resource} ({file_path})",
251
+ )
252
+ if isinstance(arguments, dict):
253
+ test_args = {**test_args, **arguments}
254
+ elif "arguments" in test_args:
255
+ deprecations.warn(
256
+ "arguments-property-in-generic-test-deprecation",
257
+ test_name=f"`{test_name}` ({test_args['arguments']})",
258
+ )
259
+
260
+ return test_name, test_args
261
+
262
+ def tags(self) -> List[str]:
263
+ tags = self.config.get("tags", [])
264
+ if isinstance(tags, str):
265
+ tags = [tags]
266
+ if not isinstance(tags, list):
267
+ raise TagsNotListOfStringsError(tags)
268
+ for tag in tags:
269
+ if not isinstance(tag, str):
270
+ raise TagNotStringError(tag)
271
+ return tags[:]
272
+
273
+ def macro_name(self) -> str:
274
+ macro_name = "test_{}".format(self.name)
275
+ if self.namespace is not None:
276
+ macro_name = "{}.{}".format(self.namespace, macro_name)
277
+ return macro_name
278
+
279
+ def get_synthetic_test_names(self) -> Tuple[str, str]:
280
+ # Returns two names: shorter (for the compiled file), full (for the unique_id + FQN)
281
+ target_name = self.target.name
282
+ if isinstance(self.target, UnparsedModelUpdate):
283
+ name = self.name
284
+ if self.version:
285
+ target_name = f"{self.target.name}_v{self.version}"
286
+ elif isinstance(self.target, UnparsedNodeUpdate):
287
+ name = self.name
288
+ elif isinstance(self.target, UnpatchedSourceDefinition):
289
+ name = "source_" + self.name
290
+ else:
291
+ raise self._bad_type()
292
+ if self.namespace is not None:
293
+ name = "{}_{}".format(self.namespace, name)
294
+ return synthesize_generic_test_names(name, target_name, self.args)
295
+
296
+ def construct_config(self) -> str:
297
+ configs = ",".join(
298
+ [
299
+ f"{key}="
300
+ + (
301
+ ('"' + value.replace('"', '\\"') + '"')
302
+ if isinstance(value, str)
303
+ else str(value)
304
+ )
305
+ for key, value in self.config.items()
306
+ ]
307
+ )
308
+ if configs:
309
+ return f"{{{{ config({configs}) }}}}"
310
+ else:
311
+ return ""
312
+
313
+ # this is the 'raw_code' that's used in 'render_update' and execution
314
+ # of the test macro
315
+ def build_raw_code(self) -> str:
316
+ return ("{{{{ {macro}(**{kwargs_name}) }}}}{config}").format(
317
+ macro=self.macro_name(),
318
+ config=self.construct_config(),
319
+ kwargs_name=GENERIC_TEST_KWARGS_NAME,
320
+ )
321
+
322
+ def build_model_str(self):
323
+ targ = self.target
324
+ if isinstance(self.target, UnparsedModelUpdate):
325
+ if self.version:
326
+ target_str = f"ref('{targ.name}', version='{self.version}')"
327
+ else:
328
+ target_str = f"ref('{targ.name}')"
329
+ elif isinstance(self.target, UnparsedNodeUpdate):
330
+ target_str = f"ref('{targ.name}')"
331
+ elif isinstance(self.target, UnpatchedSourceDefinition):
332
+ target_str = f"source('{targ.source.name}', '{targ.table.name}')"
333
+ return f"{{{{ get_where_subquery({target_str}) }}}}"
dbt/parser/hooks.py ADDED
@@ -0,0 +1,118 @@
1
+ from dataclasses import dataclass
2
+ from typing import Iterable, Iterator, List, Tuple, Union
3
+
4
+ from dbt.context.context_config import ContextConfig
5
+ from dbt.contracts.files import FilePath
6
+ from dbt.contracts.graph.nodes import HookNode
7
+ from dbt.node_types import NodeType, RunHookType
8
+ from dbt.parser.base import SimpleParser
9
+ from dbt.parser.search import FileBlock
10
+ from dbt.utils import get_pseudo_hook_path
11
+ from dbt_common.exceptions import DbtInternalError
12
+
13
+
14
+ @dataclass
15
+ class HookBlock(FileBlock):
16
+ project: str
17
+ value: str
18
+ index: int
19
+ hook_type: RunHookType
20
+
21
+ @property
22
+ def contents(self):
23
+ return self.value
24
+
25
+ @property
26
+ def name(self):
27
+ return "{}-{!s}-{!s}".format(self.project, self.hook_type, self.index)
28
+
29
+
30
+ class HookSearcher(Iterable[HookBlock]):
31
+ def __init__(self, project, source_file, hook_type) -> None:
32
+ self.project = project
33
+ self.source_file = source_file
34
+ self.hook_type = hook_type
35
+
36
+ def _hook_list(self, hooks: Union[str, List[str], Tuple[str, ...]]) -> List[str]:
37
+ if isinstance(hooks, tuple):
38
+ hooks = list(hooks)
39
+ elif not isinstance(hooks, list):
40
+ hooks = [hooks]
41
+ return hooks
42
+
43
+ def get_hook_defs(self) -> List[str]:
44
+ if self.hook_type == RunHookType.Start:
45
+ hooks = self.project.on_run_start
46
+ elif self.hook_type == RunHookType.End:
47
+ hooks = self.project.on_run_end
48
+ else:
49
+ raise DbtInternalError(
50
+ 'hook_type must be one of "{}" or "{}" (got {})'.format(
51
+ RunHookType.Start, RunHookType.End, self.hook_type
52
+ )
53
+ )
54
+ return self._hook_list(hooks)
55
+
56
+ def __iter__(self) -> Iterator[HookBlock]:
57
+ hooks = self.get_hook_defs()
58
+ for index, hook in enumerate(hooks):
59
+ yield HookBlock(
60
+ file=self.source_file,
61
+ project=self.project.project_name,
62
+ value=hook,
63
+ index=index,
64
+ hook_type=self.hook_type,
65
+ )
66
+
67
+
68
+ class HookParser(SimpleParser[HookBlock, HookNode]):
69
+
70
+ # Hooks are only in the dbt_project.yml file for the project
71
+ def get_path(self) -> FilePath:
72
+ # There ought to be an existing file object for this, but
73
+ # until that is implemented use a dummy modification time
74
+ path = FilePath(
75
+ project_root=self.project.project_root,
76
+ searched_path=".",
77
+ relative_path="dbt_project.yml",
78
+ modification_time=0.0,
79
+ )
80
+ return path
81
+
82
+ def parse_from_dict(self, dct, validate=True) -> HookNode:
83
+ if validate:
84
+ HookNode.validate(dct)
85
+ return HookNode.from_dict(dct)
86
+
87
+ @classmethod
88
+ def get_compiled_path(cls, block: HookBlock):
89
+ return get_pseudo_hook_path(block.name)
90
+
91
+ def _create_parsetime_node(
92
+ self,
93
+ block: HookBlock,
94
+ path: str,
95
+ config: ContextConfig,
96
+ fqn: List[str],
97
+ name=None,
98
+ **kwargs,
99
+ ) -> HookNode:
100
+
101
+ return super()._create_parsetime_node(
102
+ block=block,
103
+ path=path,
104
+ config=config,
105
+ fqn=fqn,
106
+ index=block.index,
107
+ name=name,
108
+ tags=[str(block.hook_type)],
109
+ )
110
+
111
+ @property
112
+ def resource_type(self) -> NodeType:
113
+ return NodeType.Operation
114
+
115
+ def parse_file(self, block: FileBlock) -> None:
116
+ for hook_type in RunHookType:
117
+ for hook in HookSearcher(self.project, block.file, hook_type):
118
+ self.parse_node(hook)
dbt/parser/macros.py ADDED
@@ -0,0 +1,137 @@
1
+ from typing import Iterable, List
2
+
3
+ import jinja2
4
+
5
+ from dbt.artifacts.resources import MacroArgument
6
+ from dbt.clients.jinja import get_supported_languages
7
+ from dbt.contracts.files import FilePath, SourceFile
8
+ from dbt.contracts.graph.nodes import Macro
9
+ from dbt.contracts.graph.unparsed import UnparsedMacro
10
+ from dbt.exceptions import ParsingError
11
+ from dbt.flags import get_flags
12
+ from dbt.node_types import NodeType
13
+ from dbt.parser.base import BaseParser
14
+ from dbt.parser.search import FileBlock, filesystem_search
15
+ from dbt_common.clients import jinja
16
+ from dbt_common.clients._jinja_blocks import ExtractWarning
17
+ from dbt_common.utils import MACRO_PREFIX
18
+
19
+
20
+ class MacroParser(BaseParser[Macro]):
21
+ # This is only used when creating a MacroManifest separate
22
+ # from the normal parsing flow.
23
+ def get_paths(self) -> List[FilePath]:
24
+ return filesystem_search(
25
+ project=self.project, relative_dirs=self.project.macro_paths, extension=".sql"
26
+ )
27
+
28
+ @property
29
+ def resource_type(self) -> NodeType:
30
+ return NodeType.Macro
31
+
32
+ @classmethod
33
+ def get_compiled_path(cls, block: FileBlock):
34
+ return block.path.relative_path
35
+
36
+ def parse_macro(self, block: jinja.BlockTag, base_node: UnparsedMacro, name: str) -> Macro:
37
+ unique_id = self.generate_unique_id(name)
38
+ macro_sql = block.full_block or ""
39
+
40
+ return Macro(
41
+ path=base_node.path,
42
+ macro_sql=macro_sql,
43
+ original_file_path=base_node.original_file_path,
44
+ package_name=base_node.package_name,
45
+ resource_type=base_node.resource_type,
46
+ name=name,
47
+ unique_id=unique_id,
48
+ )
49
+
50
+ def parse_unparsed_macros(self, base_node: UnparsedMacro) -> Iterable[Macro]:
51
+ # This is a bit of a hack to get the file path to the deprecation
52
+ def wrap_handle_extract_warning(warning: ExtractWarning) -> None:
53
+ self._handle_extract_warning(warning=warning, file=base_node.original_file_path)
54
+
55
+ try:
56
+ blocks: List[jinja.BlockTag] = [
57
+ t
58
+ for t in jinja.extract_toplevel_blocks(
59
+ base_node.raw_code,
60
+ allowed_blocks={"macro", "materialization", "test", "data_test"},
61
+ collect_raw_data=False,
62
+ warning_callback=wrap_handle_extract_warning,
63
+ )
64
+ if isinstance(t, jinja.BlockTag)
65
+ ]
66
+ except ParsingError as exc:
67
+ exc.add_node(base_node)
68
+ raise
69
+
70
+ for block in blocks:
71
+ try:
72
+ ast = jinja.parse(block.full_block)
73
+ except ParsingError as e:
74
+ e.add_node(base_node)
75
+ raise
76
+
77
+ if (
78
+ isinstance(ast, jinja2.nodes.Template)
79
+ and hasattr(ast, "body")
80
+ and len(ast.body) == 1
81
+ and isinstance(ast.body[0], jinja2.nodes.Macro)
82
+ ):
83
+ # If the top level node in the Template is a Macro, things look
84
+ # good and this is much faster than traversing the full ast, as
85
+ # in the following else clause. It's not clear if that traversal
86
+ # is ever really needed.
87
+ macro = ast.body[0]
88
+ else:
89
+ macro_nodes = list(ast.find_all(jinja2.nodes.Macro))
90
+
91
+ if len(macro_nodes) != 1:
92
+ # things have gone disastrously wrong, we thought we only
93
+ # parsed one block!
94
+ raise ParsingError(
95
+ f"Found multiple macros in {block.full_block}, expected 1", node=base_node
96
+ )
97
+
98
+ macro = macro_nodes[0]
99
+
100
+ if not macro.name.startswith(MACRO_PREFIX):
101
+ continue
102
+
103
+ name: str = macro.name.replace(MACRO_PREFIX, "")
104
+ node = self.parse_macro(block, base_node, name)
105
+
106
+ if getattr(get_flags(), "validate_macro_args", False):
107
+ node.arguments = self._extract_args(macro)
108
+
109
+ # get supported_languages for materialization macro
110
+ if block.block_type_name == "materialization":
111
+ node.supported_languages = get_supported_languages(macro)
112
+ yield node
113
+
114
+ def _extract_args(self, macro) -> List[MacroArgument]:
115
+ try:
116
+ return list([MacroArgument(name=arg.name) for arg in macro.args])
117
+ except Exception:
118
+ return []
119
+
120
+ def parse_file(self, block: FileBlock):
121
+ assert isinstance(block.file, SourceFile)
122
+ source_file = block.file
123
+ assert isinstance(source_file.contents, str)
124
+ original_file_path = source_file.path.original_file_path
125
+
126
+ # this is really only used for error messages
127
+ base_node = UnparsedMacro(
128
+ path=original_file_path,
129
+ original_file_path=original_file_path,
130
+ package_name=self.project.project_name,
131
+ raw_code=source_file.contents,
132
+ resource_type=NodeType.Macro,
133
+ language="sql",
134
+ )
135
+
136
+ for node in self.parse_unparsed_macros(base_node):
137
+ self.manifest.add_macro(block.file, node)