dvt-core 0.59.0a51__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (299) 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 +2660 -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 +60 -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 +642 -0
  74. dbt/compute/federated_executor.py +1080 -0
  75. dbt/compute/filter_pushdown.py +273 -0
  76. dbt/compute/jar_provisioning.py +273 -0
  77. dbt/compute/java_compat.py +689 -0
  78. dbt/compute/jdbc_utils.py +1252 -0
  79. dbt/compute/metadata/__init__.py +63 -0
  80. dbt/compute/metadata/adapters_registry.py +370 -0
  81. dbt/compute/metadata/catalog_store.py +1036 -0
  82. dbt/compute/metadata/registry.py +674 -0
  83. dbt/compute/metadata/store.py +1020 -0
  84. dbt/compute/smart_selector.py +377 -0
  85. dbt/compute/spark_logger.py +272 -0
  86. dbt/compute/strategies/__init__.py +55 -0
  87. dbt/compute/strategies/base.py +165 -0
  88. dbt/compute/strategies/dataproc.py +207 -0
  89. dbt/compute/strategies/emr.py +203 -0
  90. dbt/compute/strategies/local.py +472 -0
  91. dbt/compute/strategies/standalone.py +262 -0
  92. dbt/config/__init__.py +4 -0
  93. dbt/config/catalogs.py +94 -0
  94. dbt/config/compute.py +513 -0
  95. dbt/config/dvt_profile.py +408 -0
  96. dbt/config/profile.py +422 -0
  97. dbt/config/project.py +888 -0
  98. dbt/config/project_utils.py +48 -0
  99. dbt/config/renderer.py +231 -0
  100. dbt/config/runtime.py +564 -0
  101. dbt/config/selectors.py +208 -0
  102. dbt/config/utils.py +77 -0
  103. dbt/constants.py +28 -0
  104. dbt/context/__init__.py +0 -0
  105. dbt/context/base.py +745 -0
  106. dbt/context/configured.py +135 -0
  107. dbt/context/context_config.py +382 -0
  108. dbt/context/docs.py +82 -0
  109. dbt/context/exceptions_jinja.py +178 -0
  110. dbt/context/macro_resolver.py +195 -0
  111. dbt/context/macros.py +171 -0
  112. dbt/context/manifest.py +72 -0
  113. dbt/context/providers.py +2249 -0
  114. dbt/context/query_header.py +13 -0
  115. dbt/context/secret.py +58 -0
  116. dbt/context/target.py +74 -0
  117. dbt/contracts/__init__.py +0 -0
  118. dbt/contracts/files.py +413 -0
  119. dbt/contracts/graph/__init__.py +0 -0
  120. dbt/contracts/graph/manifest.py +1904 -0
  121. dbt/contracts/graph/metrics.py +97 -0
  122. dbt/contracts/graph/model_config.py +70 -0
  123. dbt/contracts/graph/node_args.py +42 -0
  124. dbt/contracts/graph/nodes.py +1806 -0
  125. dbt/contracts/graph/semantic_manifest.py +232 -0
  126. dbt/contracts/graph/unparsed.py +811 -0
  127. dbt/contracts/project.py +419 -0
  128. dbt/contracts/results.py +53 -0
  129. dbt/contracts/selection.py +23 -0
  130. dbt/contracts/sql.py +85 -0
  131. dbt/contracts/state.py +68 -0
  132. dbt/contracts/util.py +46 -0
  133. dbt/deprecations.py +348 -0
  134. dbt/deps/__init__.py +0 -0
  135. dbt/deps/base.py +152 -0
  136. dbt/deps/git.py +195 -0
  137. dbt/deps/local.py +79 -0
  138. dbt/deps/registry.py +130 -0
  139. dbt/deps/resolver.py +149 -0
  140. dbt/deps/tarball.py +120 -0
  141. dbt/docs/source/_ext/dbt_click.py +119 -0
  142. dbt/docs/source/conf.py +32 -0
  143. dbt/env_vars.py +64 -0
  144. dbt/event_time/event_time.py +40 -0
  145. dbt/event_time/sample_window.py +60 -0
  146. dbt/events/__init__.py +15 -0
  147. dbt/events/base_types.py +36 -0
  148. dbt/events/core_types_pb2.py +2 -0
  149. dbt/events/logging.py +108 -0
  150. dbt/events/types.py +2516 -0
  151. dbt/exceptions.py +1486 -0
  152. dbt/flags.py +89 -0
  153. dbt/graph/__init__.py +11 -0
  154. dbt/graph/cli.py +249 -0
  155. dbt/graph/graph.py +172 -0
  156. dbt/graph/queue.py +214 -0
  157. dbt/graph/selector.py +374 -0
  158. dbt/graph/selector_methods.py +975 -0
  159. dbt/graph/selector_spec.py +222 -0
  160. dbt/graph/thread_pool.py +18 -0
  161. dbt/hooks.py +21 -0
  162. dbt/include/README.md +49 -0
  163. dbt/include/__init__.py +3 -0
  164. dbt/include/data/adapters_registry.duckdb +0 -0
  165. dbt/include/data/build_comprehensive_registry.py +1254 -0
  166. dbt/include/data/build_registry.py +242 -0
  167. dbt/include/data/csv/adapter_queries.csv +33 -0
  168. dbt/include/data/csv/syntax_rules.csv +9 -0
  169. dbt/include/data/csv/type_mappings_bigquery.csv +28 -0
  170. dbt/include/data/csv/type_mappings_databricks.csv +30 -0
  171. dbt/include/data/csv/type_mappings_mysql.csv +40 -0
  172. dbt/include/data/csv/type_mappings_oracle.csv +30 -0
  173. dbt/include/data/csv/type_mappings_postgres.csv +56 -0
  174. dbt/include/data/csv/type_mappings_redshift.csv +33 -0
  175. dbt/include/data/csv/type_mappings_snowflake.csv +38 -0
  176. dbt/include/data/csv/type_mappings_sqlserver.csv +35 -0
  177. dbt/include/dvt_starter_project/README.md +15 -0
  178. dbt/include/dvt_starter_project/__init__.py +3 -0
  179. dbt/include/dvt_starter_project/analyses/PLACEHOLDER +0 -0
  180. dbt/include/dvt_starter_project/dvt_project.yml +39 -0
  181. dbt/include/dvt_starter_project/logs/PLACEHOLDER +0 -0
  182. dbt/include/dvt_starter_project/macros/PLACEHOLDER +0 -0
  183. dbt/include/dvt_starter_project/models/example/my_first_dbt_model.sql +27 -0
  184. dbt/include/dvt_starter_project/models/example/my_second_dbt_model.sql +6 -0
  185. dbt/include/dvt_starter_project/models/example/schema.yml +21 -0
  186. dbt/include/dvt_starter_project/seeds/PLACEHOLDER +0 -0
  187. dbt/include/dvt_starter_project/snapshots/PLACEHOLDER +0 -0
  188. dbt/include/dvt_starter_project/tests/PLACEHOLDER +0 -0
  189. dbt/internal_deprecations.py +26 -0
  190. dbt/jsonschemas/__init__.py +3 -0
  191. dbt/jsonschemas/jsonschemas.py +309 -0
  192. dbt/jsonschemas/project/0.0.110.json +4717 -0
  193. dbt/jsonschemas/project/0.0.85.json +2015 -0
  194. dbt/jsonschemas/resources/0.0.110.json +2636 -0
  195. dbt/jsonschemas/resources/0.0.85.json +2536 -0
  196. dbt/jsonschemas/resources/latest.json +6773 -0
  197. dbt/links.py +4 -0
  198. dbt/materializations/__init__.py +0 -0
  199. dbt/materializations/incremental/__init__.py +0 -0
  200. dbt/materializations/incremental/microbatch.py +236 -0
  201. dbt/mp_context.py +8 -0
  202. dbt/node_types.py +37 -0
  203. dbt/parser/__init__.py +23 -0
  204. dbt/parser/analysis.py +21 -0
  205. dbt/parser/base.py +548 -0
  206. dbt/parser/common.py +266 -0
  207. dbt/parser/docs.py +52 -0
  208. dbt/parser/fixtures.py +51 -0
  209. dbt/parser/functions.py +30 -0
  210. dbt/parser/generic_test.py +100 -0
  211. dbt/parser/generic_test_builders.py +333 -0
  212. dbt/parser/hooks.py +122 -0
  213. dbt/parser/macros.py +137 -0
  214. dbt/parser/manifest.py +2208 -0
  215. dbt/parser/models.py +573 -0
  216. dbt/parser/partial.py +1178 -0
  217. dbt/parser/read_files.py +445 -0
  218. dbt/parser/schema_generic_tests.py +422 -0
  219. dbt/parser/schema_renderer.py +111 -0
  220. dbt/parser/schema_yaml_readers.py +935 -0
  221. dbt/parser/schemas.py +1466 -0
  222. dbt/parser/search.py +149 -0
  223. dbt/parser/seeds.py +28 -0
  224. dbt/parser/singular_test.py +20 -0
  225. dbt/parser/snapshots.py +44 -0
  226. dbt/parser/sources.py +558 -0
  227. dbt/parser/sql.py +62 -0
  228. dbt/parser/unit_tests.py +621 -0
  229. dbt/plugins/__init__.py +20 -0
  230. dbt/plugins/contracts.py +9 -0
  231. dbt/plugins/exceptions.py +2 -0
  232. dbt/plugins/manager.py +163 -0
  233. dbt/plugins/manifest.py +21 -0
  234. dbt/profiler.py +20 -0
  235. dbt/py.typed +1 -0
  236. dbt/query_analyzer.py +410 -0
  237. dbt/runners/__init__.py +2 -0
  238. dbt/runners/exposure_runner.py +7 -0
  239. dbt/runners/no_op_runner.py +45 -0
  240. dbt/runners/saved_query_runner.py +7 -0
  241. dbt/selected_resources.py +8 -0
  242. dbt/task/__init__.py +0 -0
  243. dbt/task/base.py +506 -0
  244. dbt/task/build.py +197 -0
  245. dbt/task/clean.py +56 -0
  246. dbt/task/clone.py +161 -0
  247. dbt/task/compile.py +150 -0
  248. dbt/task/compute.py +458 -0
  249. dbt/task/debug.py +513 -0
  250. dbt/task/deps.py +280 -0
  251. dbt/task/docs/__init__.py +3 -0
  252. dbt/task/docs/api/__init__.py +23 -0
  253. dbt/task/docs/api/catalog.py +204 -0
  254. dbt/task/docs/api/lineage.py +234 -0
  255. dbt/task/docs/api/profile.py +204 -0
  256. dbt/task/docs/api/spark.py +186 -0
  257. dbt/task/docs/generate.py +1002 -0
  258. dbt/task/docs/index.html +250 -0
  259. dbt/task/docs/serve.py +174 -0
  260. dbt/task/dvt_output.py +509 -0
  261. dbt/task/dvt_run.py +282 -0
  262. dbt/task/dvt_seed.py +806 -0
  263. dbt/task/freshness.py +322 -0
  264. dbt/task/function.py +121 -0
  265. dbt/task/group_lookup.py +46 -0
  266. dbt/task/init.py +1022 -0
  267. dbt/task/java.py +316 -0
  268. dbt/task/list.py +236 -0
  269. dbt/task/metadata.py +804 -0
  270. dbt/task/migrate.py +714 -0
  271. dbt/task/printer.py +175 -0
  272. dbt/task/profile.py +1489 -0
  273. dbt/task/profile_serve.py +662 -0
  274. dbt/task/retract.py +441 -0
  275. dbt/task/retry.py +175 -0
  276. dbt/task/run.py +1647 -0
  277. dbt/task/run_operation.py +141 -0
  278. dbt/task/runnable.py +758 -0
  279. dbt/task/seed.py +103 -0
  280. dbt/task/show.py +149 -0
  281. dbt/task/snapshot.py +56 -0
  282. dbt/task/spark.py +414 -0
  283. dbt/task/sql.py +110 -0
  284. dbt/task/target_sync.py +814 -0
  285. dbt/task/test.py +464 -0
  286. dbt/tests/fixtures/__init__.py +1 -0
  287. dbt/tests/fixtures/project.py +620 -0
  288. dbt/tests/util.py +651 -0
  289. dbt/tracking.py +529 -0
  290. dbt/utils/__init__.py +3 -0
  291. dbt/utils/artifact_upload.py +151 -0
  292. dbt/utils/utils.py +408 -0
  293. dbt/version.py +271 -0
  294. dvt_cli/__init__.py +158 -0
  295. dvt_core-0.59.0a51.dist-info/METADATA +288 -0
  296. dvt_core-0.59.0a51.dist-info/RECORD +299 -0
  297. dvt_core-0.59.0a51.dist-info/WHEEL +5 -0
  298. dvt_core-0.59.0a51.dist-info/entry_points.txt +2 -0
  299. dvt_core-0.59.0a51.dist-info/top_level.txt +2 -0
dbt/parser/sources.py ADDED
@@ -0,0 +1,558 @@
1
+ import itertools
2
+ from dataclasses import replace
3
+ from pathlib import Path
4
+ from typing import Any, Dict, Iterable, List, Optional, Set, Tuple
5
+
6
+ from dbt.adapters.capability import Capability
7
+ from dbt.adapters.factory import get_adapter
8
+ from dbt.artifacts.resources import FreshnessThreshold, SourceConfig, Time
9
+ from dbt.config import RuntimeConfig
10
+ from dbt.context.context_config import (
11
+ BaseContextConfigGenerator,
12
+ ContextConfigGenerator,
13
+ UnrenderedConfigGenerator,
14
+ )
15
+ from dbt.contracts.graph.manifest import Manifest, SourceKey
16
+ from dbt.contracts.graph.nodes import (
17
+ GenericTestNode,
18
+ SourceDefinition,
19
+ UnpatchedSourceDefinition,
20
+ )
21
+ from dbt.contracts.graph.unparsed import (
22
+ SourcePatch,
23
+ SourceTablePatch,
24
+ UnparsedColumn,
25
+ UnparsedSourceDefinition,
26
+ UnparsedSourceTableDefinition,
27
+ )
28
+ from dbt.events.types import FreshnessConfigProblem, UnusedTables, ValidationWarning
29
+ from dbt.exceptions import ParsingError
30
+ from dbt.node_types import NodeType
31
+ from dbt.parser.common import ParserRef
32
+ from dbt.parser.schema_generic_tests import SchemaGenericTestParser
33
+ from dbt_common.events.functions import fire_event, warn_or_error
34
+ from dbt_common.exceptions import DbtInternalError
35
+
36
+
37
+ # An UnparsedSourceDefinition is taken directly from the yaml
38
+ # file. It can affect multiple tables, all of which will eventually
39
+ # have their own source node. An UnparsedSourceDefinition will
40
+ # generate multiple UnpatchedSourceDefinition nodes (one per
41
+ # table) in the SourceParser.add_source_definitions. The
42
+ # SourcePatcher takes an UnparsedSourceDefinition and the
43
+ # SourcePatch and produces a SourceDefinition. Each
44
+ # SourcePatch can be applied to multiple UnpatchedSourceDefinitions.
45
+ class SourcePatcher:
46
+ def __init__(
47
+ self,
48
+ root_project: RuntimeConfig,
49
+ manifest: Manifest,
50
+ ) -> None:
51
+ self.root_project = root_project
52
+ self.manifest = manifest
53
+ self.generic_test_parsers: Dict[str, SchemaGenericTestParser] = {}
54
+ self.patches_used: Dict[SourceKey, Set[str]] = {}
55
+ self.sources: Dict[str, SourceDefinition] = {}
56
+ self._deprecations: Set[Any] = set()
57
+
58
+ # This method calls the 'parse_source' method which takes
59
+ # the UnpatchedSourceDefinitions in the manifest and combines them
60
+ # with SourcePatches to produce SourceDefinitions.
61
+ def construct_sources(self) -> None:
62
+ for unique_id, unpatched in self.manifest.sources.items():
63
+ schema_file = self.manifest.files[unpatched.file_id]
64
+ if isinstance(unpatched, SourceDefinition):
65
+ # In partial parsing, there will be SourceDefinitions
66
+ # which must be retained.
67
+ self.sources[unpatched.unique_id] = unpatched
68
+ continue
69
+ # returns None if there is no patch
70
+ patch = self.get_patch_for(unpatched)
71
+
72
+ # returns unpatched if there is no patch
73
+ patched = self.patch_source(unpatched, patch)
74
+
75
+ # now use the patched UnpatchedSourceDefinition to extract test data.
76
+ for test in self.get_source_tests(patched):
77
+ if test.config.enabled:
78
+ self.manifest.add_node_nofile(test)
79
+ else:
80
+ self.manifest.add_disabled_nofile(test)
81
+ # save the test unique_id in the schema_file, so we can
82
+ # process in partial parsing
83
+ test_from = {"key": "sources", "name": patched.source.name}
84
+ schema_file.add_test(test.unique_id, test_from)
85
+
86
+ # Convert UnpatchedSourceDefinition to a SourceDefinition
87
+ parsed = self.parse_source(patched)
88
+ if parsed.config.enabled:
89
+ self.sources[unique_id] = parsed
90
+ else:
91
+ self.manifest.add_disabled_nofile(parsed)
92
+
93
+ self.warn_unused()
94
+
95
+ def patch_source(
96
+ self,
97
+ unpatched: UnpatchedSourceDefinition,
98
+ patch: Optional[SourcePatch],
99
+ ) -> UnpatchedSourceDefinition:
100
+
101
+ # This skips patching if no patch exists because of the
102
+ # performance overhead of converting to and from dicts
103
+ if patch is None:
104
+ return unpatched
105
+
106
+ source_dct = unpatched.source.to_dict(omit_none=True)
107
+ table_dct = unpatched.table.to_dict(omit_none=True)
108
+ patch_path: Optional[Path] = None
109
+
110
+ source_table_patch: Optional[SourceTablePatch] = None
111
+
112
+ if patch is not None:
113
+ source_table_patch = patch.get_table_named(unpatched.table.name)
114
+ source_dct.update(patch.to_patch_dict())
115
+ patch_path = patch.path
116
+
117
+ if source_table_patch is not None:
118
+ table_dct.update(source_table_patch.to_patch_dict())
119
+
120
+ source = UnparsedSourceDefinition.from_dict(source_dct)
121
+ table = UnparsedSourceTableDefinition.from_dict(table_dct)
122
+ return replace(unpatched, source=source, table=table, patch_path=patch_path)
123
+
124
+ # This converts an UnpatchedSourceDefinition to a SourceDefinition
125
+ def parse_source(self, target: UnpatchedSourceDefinition) -> SourceDefinition:
126
+ source = target.source
127
+ table = target.table
128
+ refs = ParserRef.from_target(table)
129
+ unique_id = target.unique_id
130
+ description = table.description or ""
131
+ source_description = source.description or ""
132
+
133
+ quoting = source.quoting.merged(table.quoting)
134
+ # Retain original source meta prior to merge with table meta
135
+ source_meta = {**source.meta, **source.config.get("meta", {})}
136
+
137
+ config = self._generate_source_config(
138
+ target=target,
139
+ rendered=True,
140
+ )
141
+
142
+ config = config.finalize_and_validate()
143
+
144
+ unrendered_config = self._generate_source_config(
145
+ target=target,
146
+ rendered=False,
147
+ )
148
+
149
+ if not isinstance(config, SourceConfig):
150
+ raise DbtInternalError(
151
+ f"Calculated a {type(config)} for a source, but expected a SourceConfig"
152
+ )
153
+
154
+ # DVT: Get connection from source, fallback to default connection
155
+ connection_name = source.connection
156
+ default_database = self.root_project.credentials.database
157
+
158
+ parsed_source = SourceDefinition(
159
+ package_name=target.package_name,
160
+ database=(source.database or default_database),
161
+ unrendered_database=source.unrendered_database,
162
+ schema=(source.schema or source.name),
163
+ unrendered_schema=source.unrendered_schema,
164
+ identifier=(table.identifier or table.name),
165
+ path=target.path,
166
+ original_file_path=target.original_file_path,
167
+ columns=refs.column_info,
168
+ unique_id=unique_id,
169
+ name=table.name,
170
+ description=description,
171
+ external=table.external,
172
+ source_name=source.name,
173
+ source_description=source_description,
174
+ source_meta=source_meta,
175
+ meta=config.meta,
176
+ loader=source.loader,
177
+ loaded_at_field=config.loaded_at_field,
178
+ loaded_at_query=config.loaded_at_query,
179
+ freshness=config.freshness,
180
+ quoting=quoting,
181
+ resource_type=NodeType.Source,
182
+ fqn=target.fqn,
183
+ tags=config.tags,
184
+ config=config,
185
+ unrendered_config=unrendered_config,
186
+ connection=connection_name, # DVT: Store connection name in parsed source
187
+ )
188
+
189
+ if (
190
+ parsed_source.freshness
191
+ and not parsed_source.loaded_at_field
192
+ and not get_adapter(self.root_project).supports(Capability.TableLastModifiedMetadata)
193
+ ):
194
+ # Metadata-based freshness is being used by default for this node,
195
+ # but is not available through the configured adapter, so warn the
196
+ # user that freshness info will not be collected for this node at
197
+ # runtime.
198
+ fire_event(
199
+ FreshnessConfigProblem(
200
+ msg=f"The configured adapter does not support metadata-based freshness. A loaded_at_field must be specified for source '{source.name}.{table.name}'."
201
+ )
202
+ )
203
+
204
+ # relation name is added after instantiation because the adapter does
205
+ # not provide the relation name for a UnpatchedSourceDefinition object
206
+ parsed_source.relation_name = self._get_relation_name(parsed_source)
207
+ return parsed_source
208
+
209
+ # Use the SchemaGenericTestParser to parse the source tests
210
+ def get_generic_test_parser_for(self, package_name: str) -> "SchemaGenericTestParser":
211
+ if package_name in self.generic_test_parsers:
212
+ generic_test_parser = self.generic_test_parsers[package_name]
213
+ else:
214
+ all_projects = self.root_project.load_dependencies()
215
+ project = all_projects[package_name]
216
+ generic_test_parser = SchemaGenericTestParser(
217
+ project, self.manifest, self.root_project
218
+ )
219
+ self.generic_test_parsers[package_name] = generic_test_parser
220
+ return generic_test_parser
221
+
222
+ def get_source_tests(self, target: UnpatchedSourceDefinition) -> Iterable[GenericTestNode]:
223
+ is_root_project = True if self.root_project.project_name == target.package_name else False
224
+ target.validate_data_tests(is_root_project)
225
+ for data_test, column in target.get_tests():
226
+ yield self.parse_source_test(
227
+ target=target,
228
+ data_test=data_test,
229
+ column=column,
230
+ )
231
+
232
+ def get_patch_for(
233
+ self,
234
+ unpatched: UnpatchedSourceDefinition,
235
+ ) -> Optional[SourcePatch]:
236
+ if isinstance(unpatched, SourceDefinition):
237
+ return None
238
+ key = (unpatched.package_name, unpatched.source.name)
239
+ patch: Optional[SourcePatch] = self.manifest.source_patches.get(key)
240
+ if patch is None:
241
+ return None
242
+ if key not in self.patches_used:
243
+ # mark the key as used
244
+ self.patches_used[key] = set()
245
+ if patch.get_table_named(unpatched.table.name) is not None:
246
+ self.patches_used[key].add(unpatched.table.name)
247
+ return patch
248
+
249
+ # This calls parse_generic_test in the SchemaGenericTestParser
250
+ def parse_source_test(
251
+ self,
252
+ target: UnpatchedSourceDefinition,
253
+ data_test: Dict[str, Any],
254
+ column: Optional[UnparsedColumn],
255
+ ) -> GenericTestNode:
256
+ column_name: Optional[str]
257
+ if column is None:
258
+ column_name = None
259
+ else:
260
+ column_name = column.name
261
+ should_quote = column.quote or (column.quote is None and target.quote_columns)
262
+ if should_quote:
263
+ column_name = get_adapter(self.root_project).quote(column_name)
264
+
265
+ tags_sources = [target.source.tags, target.table.tags]
266
+ if column is not None:
267
+ tags_sources.append(column.tags)
268
+ if column_config_tags := column.config.get("tags", []):
269
+ if isinstance(column_config_tags, list):
270
+ tags_sources.append(column_config_tags)
271
+ elif isinstance(column_config_tags, str):
272
+ tags_sources.append([column_config_tags])
273
+ tags = list(itertools.chain.from_iterable(tags_sources))
274
+
275
+ generic_test_parser = self.get_generic_test_parser_for(target.package_name)
276
+ node = generic_test_parser.parse_generic_test(
277
+ target=target,
278
+ data_test=data_test,
279
+ tags=tags,
280
+ column_name=column_name,
281
+ schema_file_id=target.file_id,
282
+ version=None,
283
+ )
284
+ return node
285
+
286
+ def _generate_source_config(self, target: UnpatchedSourceDefinition, rendered: bool):
287
+ generator: BaseContextConfigGenerator
288
+ if rendered:
289
+ generator = ContextConfigGenerator(self.root_project)
290
+ else:
291
+ generator = UnrenderedConfigGenerator(self.root_project)
292
+
293
+ # configs with precendence set
294
+ precedence_configs = dict()
295
+ # first apply source configs
296
+ precedence_configs.update(target.source.config)
297
+ # then overrite anything that is defined on source tables
298
+ # this is not quite complex enough for configs that can be set as top-level node keys, but
299
+ # it works while source configs can only include `enabled`.
300
+ precedence_configs.update(target.table.config)
301
+
302
+ precedence_freshness = self.calculate_freshness_from_raw_target(target)
303
+ if precedence_freshness:
304
+ precedence_configs["freshness"] = precedence_freshness.to_dict()
305
+ elif precedence_freshness is None:
306
+ precedence_configs["freshness"] = None
307
+ else:
308
+ # this means that the user did not set a freshness threshold in the source schema file, as such
309
+ # there should be no freshness precedence
310
+ precedence_configs.pop("freshness", None)
311
+
312
+ precedence_loaded_at_field, precedence_loaded_at_query = (
313
+ self.calculate_loaded_at_field_query_from_raw_target(target)
314
+ )
315
+ precedence_configs["loaded_at_field"] = precedence_loaded_at_field
316
+ precedence_configs["loaded_at_query"] = precedence_loaded_at_query
317
+
318
+ # Handle merges across source, table, and config for meta and tags
319
+ precedence_meta = self.calculate_meta_from_raw_target(target)
320
+ precedence_configs["meta"] = precedence_meta
321
+
322
+ precedence_tags = self.calculate_tags_from_raw_target(target)
323
+ precedence_configs["tags"] = precedence_tags
324
+
325
+ # Because freshness is a "object" config, the freshness from the dbt_project.yml and the freshness
326
+ # from the schema file _won't_ get merged by this process. The result will be that the freshness will
327
+ # come from the schema file if provided, and if not, it'll fall back to the dbt_project.yml freshness.
328
+ return generator.calculate_node_config(
329
+ config_call_dict={},
330
+ fqn=target.fqn,
331
+ resource_type=NodeType.Source,
332
+ project_name=target.package_name,
333
+ base=False,
334
+ patch_config_dict=precedence_configs,
335
+ )
336
+
337
+ def _get_relation_name(self, node: SourceDefinition):
338
+ adapter = get_adapter(self.root_project)
339
+ relation_cls = adapter.Relation
340
+ return str(relation_cls.create_from(self.root_project, node))
341
+
342
+ def warn_unused(self) -> None:
343
+ unused_tables: Dict[SourceKey, Optional[Set[str]]] = {}
344
+ for patch in self.manifest.source_patches.values():
345
+ key = (patch.overrides, patch.name)
346
+ if key not in self.patches_used:
347
+ unused_tables[key] = None
348
+ elif patch.tables is not None:
349
+ table_patches = {t.name for t in patch.tables}
350
+ unused = table_patches - self.patches_used[key]
351
+ # don't add unused tables, the
352
+ if unused:
353
+ # because patches are required to be unique, we can safely
354
+ # write without looking
355
+ unused_tables[key] = unused
356
+
357
+ if unused_tables:
358
+ unused_tables_formatted = self.get_unused_msg(unused_tables)
359
+ warn_or_error(UnusedTables(unused_tables=unused_tables_formatted))
360
+
361
+ self.manifest.source_patches = {}
362
+
363
+ def get_unused_msg(
364
+ self,
365
+ unused_tables: Dict[SourceKey, Optional[Set[str]]],
366
+ ) -> List:
367
+ unused_tables_formatted = []
368
+ for key, table_names in unused_tables.items():
369
+ patch = self.manifest.source_patches[key]
370
+ patch_name = f"{patch.overrides}.{patch.name}"
371
+ if table_names is None:
372
+ unused_tables_formatted.append(f" - Source {patch_name} (in {patch.path})")
373
+ else:
374
+ for table_name in sorted(table_names):
375
+ unused_tables_formatted.append(
376
+ f" - Source table {patch_name}.{table_name} " f"(in {patch.path})"
377
+ )
378
+ return unused_tables_formatted
379
+
380
+ def calculate_freshness_from_raw_target(
381
+ self,
382
+ target: UnpatchedSourceDefinition,
383
+ ) -> Optional[FreshnessThreshold]:
384
+ source: UnparsedSourceDefinition = target.source
385
+
386
+ source_freshness = source.freshness
387
+
388
+ source_config_freshness_raw: Optional[Dict] = source.config.get(
389
+ "freshness", {}
390
+ ) # Will only be None if the user explicitly set it to null
391
+ source_config_freshness: Optional[FreshnessThreshold] = (
392
+ FreshnessThreshold.from_dict(source_config_freshness_raw)
393
+ if source_config_freshness_raw is not None
394
+ else None
395
+ )
396
+
397
+ table: UnparsedSourceTableDefinition = target.table
398
+ table_freshness = table.freshness
399
+
400
+ table_config_freshness_raw: Optional[Dict] = table.config.get(
401
+ "freshness", {}
402
+ ) # Will only be None if the user explicitly set it to null
403
+ table_config_freshness: Optional[FreshnessThreshold] = (
404
+ FreshnessThreshold.from_dict(table_config_freshness_raw)
405
+ if table_config_freshness_raw is not None
406
+ else None
407
+ )
408
+
409
+ return merge_source_freshness(
410
+ source_freshness,
411
+ source_config_freshness,
412
+ table_freshness,
413
+ table_config_freshness,
414
+ )
415
+
416
+ def calculate_loaded_at_field_query_from_raw_target(
417
+ self, target: UnpatchedSourceDefinition
418
+ ) -> Tuple[Optional[str], Optional[str]]:
419
+ # We need to be able to tell the difference between explicitly setting the loaded_at_field to None/null
420
+ # and when it's simply not set. This allows a user to override the source level loaded_at_field so that
421
+ # specific table can default to metadata-based freshness.
422
+
423
+ # loaded_at_field and loaded_at_query are supported both at top-level (deprecated) and config-level (preferred) on sources and tables.
424
+ if target.table.loaded_at_field_present and (
425
+ target.table.loaded_at_query or target.table.config.get("loaded_at_query")
426
+ ):
427
+ raise ParsingError(
428
+ "Cannot specify both loaded_at_field and loaded_at_query at table level."
429
+ )
430
+ if (target.source.loaded_at_field or target.source.config.get("loaded_at_field")) and (
431
+ target.source.loaded_at_query or target.source.config.get("loaded_at_query")
432
+ ):
433
+ raise ParsingError(
434
+ "Cannot specify both loaded_at_field and loaded_at_query at source level."
435
+ )
436
+
437
+ if (
438
+ target.table.loaded_at_field_present
439
+ or target.table.loaded_at_field is not None
440
+ or target.table.config.get("loaded_at_field") is not None
441
+ ):
442
+ loaded_at_field = target.table.loaded_at_field or target.table.config.get(
443
+ "loaded_at_field"
444
+ )
445
+ else:
446
+ loaded_at_field = target.source.loaded_at_field or target.source.config.get(
447
+ "loaded_at_field"
448
+ ) # may be None, that's okay
449
+
450
+ loaded_at_query: Optional[str]
451
+ if (
452
+ target.table.loaded_at_query is not None
453
+ or target.table.config.get("loaded_at_query") is not None
454
+ ):
455
+ loaded_at_query = target.table.loaded_at_query or target.table.config.get(
456
+ "loaded_at_query"
457
+ )
458
+ else:
459
+ if target.table.loaded_at_field_present:
460
+ loaded_at_query = None
461
+ else:
462
+ loaded_at_query = target.source.loaded_at_query or target.source.config.get(
463
+ "loaded_at_query"
464
+ )
465
+
466
+ return loaded_at_field, loaded_at_query
467
+
468
+ def calculate_meta_from_raw_target(self, target: UnpatchedSourceDefinition) -> Dict[str, Any]:
469
+ source_meta = target.source.meta or {}
470
+ source_config_meta = target.source.config.get("meta", {})
471
+ source_config_meta = source_config_meta if isinstance(source_config_meta, dict) else {}
472
+
473
+ table_meta = target.table.meta or {}
474
+ table_config_meta = target.table.config.get("meta", {})
475
+ table_config_meta = table_config_meta if isinstance(table_config_meta, dict) else {}
476
+
477
+ return {**source_meta, **source_config_meta, **table_meta, **table_config_meta}
478
+
479
+ def calculate_tags_from_raw_target(self, target: UnpatchedSourceDefinition) -> List[str]:
480
+ source_tags = target.source.tags or []
481
+ source_config_tags = self._get_config_tags(
482
+ target.source.config.get("tags", []), target.source.name
483
+ )
484
+
485
+ table_tags = target.table.tags or []
486
+ table_config_tags = self._get_config_tags(
487
+ target.table.config.get("tags", []), target.table.name
488
+ )
489
+
490
+ return sorted(
491
+ set(itertools.chain(source_tags, source_config_tags, table_tags, table_config_tags))
492
+ )
493
+
494
+ def _get_config_tags(self, tags: Any, source_name: str) -> List[str]:
495
+ config_tags = tags if isinstance(tags, list) else [tags]
496
+
497
+ config_tags_valid: List[str] = []
498
+ for tag in config_tags:
499
+ if not isinstance(tag, str):
500
+ warn_or_error(
501
+ ValidationWarning(
502
+ field_name=f"`config.tags`: {tags}",
503
+ resource_type=NodeType.Source.value,
504
+ node_name=source_name,
505
+ )
506
+ )
507
+ else:
508
+ config_tags_valid.append(tag)
509
+
510
+ return config_tags_valid
511
+
512
+
513
+ def merge_freshness_time_thresholds(
514
+ base: Optional[Time], update: Optional[Time]
515
+ ) -> Optional[Time]:
516
+ if base and update:
517
+ return base.merged(update)
518
+ elif update is None:
519
+ return None
520
+ else:
521
+ return update or base
522
+
523
+
524
+ def merge_source_freshness(
525
+ *thresholds: Optional[FreshnessThreshold],
526
+ ) -> Optional[FreshnessThreshold]:
527
+ if not thresholds:
528
+ return None
529
+
530
+ # Initialize with the first threshold.
531
+ # If the first threshold is None, current_merged_value will be None,
532
+ # and subsequent merges will correctly follow the original logic.
533
+ current_merged_value: Optional[FreshnessThreshold] = thresholds[0]
534
+
535
+ # Iterate through the rest of the thresholds, applying the original pairwise logic
536
+ for i in range(1, len(thresholds)):
537
+ base = current_merged_value
538
+ update = thresholds[i]
539
+
540
+ if base is not None and update is not None:
541
+ merged_freshness_obj = base.merged(update)
542
+ # merge one level deeper the error_after and warn_after thresholds
543
+ merged_error_after = merge_freshness_time_thresholds(
544
+ base.error_after, update.error_after
545
+ )
546
+ merged_warn_after = merge_freshness_time_thresholds(base.warn_after, update.warn_after)
547
+
548
+ merged_freshness_obj.error_after = merged_error_after
549
+ merged_freshness_obj.warn_after = merged_warn_after
550
+ current_merged_value = merged_freshness_obj
551
+ elif base is None and bool(update):
552
+ # If current_merged_value (base) is None, the update becomes the new value
553
+ current_merged_value = update
554
+ else: # This covers cases where 'update' is None, or both 'base' and 'update' are None.
555
+ # Following original logic, if 'update' is None, the result of the pair-merge is None.
556
+ current_merged_value = None
557
+
558
+ return current_merged_value
dbt/parser/sql.py ADDED
@@ -0,0 +1,62 @@
1
+ import os
2
+ from dataclasses import dataclass
3
+ from typing import Iterable
4
+
5
+ from dbt.contracts.graph.manifest import SourceFile
6
+ from dbt.contracts.graph.nodes import Macro, SqlNode
7
+ from dbt.contracts.graph.unparsed import UnparsedMacro
8
+ from dbt.node_types import NodeType
9
+ from dbt.parser.base import SimpleSQLParser
10
+ from dbt.parser.macros import MacroParser
11
+ from dbt.parser.search import FileBlock
12
+ from dbt_common.exceptions import DbtInternalError
13
+
14
+
15
+ @dataclass
16
+ class SqlBlock(FileBlock):
17
+ block_name: str
18
+
19
+ @property
20
+ def name(self):
21
+ return self.block_name
22
+
23
+
24
+ class SqlBlockParser(SimpleSQLParser[SqlNode]):
25
+ def parse_from_dict(self, dct, validate=True) -> SqlNode:
26
+ if validate:
27
+ SqlNode.validate(dct)
28
+ return SqlNode.from_dict(dct)
29
+
30
+ @property
31
+ def resource_type(self) -> NodeType:
32
+ return NodeType.SqlOperation
33
+
34
+ @staticmethod
35
+ def get_compiled_path(block: FileBlock):
36
+ # we do it this way to make mypy happy
37
+ if not isinstance(block, SqlBlock):
38
+ raise DbtInternalError(
39
+ "While parsing SQL operation, got an actual file block instead of "
40
+ "an SQL block: {}".format(block)
41
+ )
42
+
43
+ return os.path.join("sql", block.name)
44
+
45
+ def parse_remote(self, sql: str, name: str) -> SqlNode:
46
+ source_file = SourceFile.remote(sql, self.project.project_name, "sql")
47
+ contents = SqlBlock(block_name=name, file=source_file)
48
+ return self.parse_node(contents)
49
+
50
+
51
+ class SqlMacroParser(MacroParser):
52
+ def parse_remote(self, contents) -> Iterable[Macro]:
53
+ base = UnparsedMacro(
54
+ path="from remote system",
55
+ original_file_path="from remote system",
56
+ package_name=self.project.project_name,
57
+ raw_code=contents,
58
+ language="sql",
59
+ resource_type=NodeType.Macro,
60
+ )
61
+ for node in self.parse_unparsed_macros(base):
62
+ yield node