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
@@ -0,0 +1,975 @@
1
+ import abc
2
+ from fnmatch import fnmatch
3
+ from itertools import chain
4
+ from pathlib import Path
5
+ from typing import (
6
+ Any,
7
+ Callable,
8
+ Dict,
9
+ Iterator,
10
+ List,
11
+ Optional,
12
+ Set,
13
+ Tuple,
14
+ Type,
15
+ Union,
16
+ )
17
+
18
+ from dbt.contracts.graph.manifest import Manifest
19
+ from dbt.contracts.graph.nodes import (
20
+ Exposure,
21
+ FunctionNode,
22
+ GenericTestNode,
23
+ ManifestNode,
24
+ Metric,
25
+ ModelNode,
26
+ ResultNode,
27
+ SavedQuery,
28
+ SemanticModel,
29
+ SingularTestNode,
30
+ SourceDefinition,
31
+ UnitTestDefinition,
32
+ )
33
+ from dbt.contracts.graph.unparsed import UnparsedVersion
34
+ from dbt.contracts.state import PreviousState
35
+ from dbt.node_types import NodeType
36
+ from dbt_common.dataclass_schema import StrEnum
37
+ from dbt_common.events.contextvars import get_project_root
38
+ from dbt_common.exceptions import DbtInternalError, DbtRuntimeError
39
+
40
+ from .graph import UniqueId
41
+
42
+ SELECTOR_GLOB = "*"
43
+ SELECTOR_DELIMITER = ":"
44
+
45
+
46
+ class MethodName(StrEnum):
47
+ FQN = "fqn"
48
+ Tag = "tag"
49
+ Group = "group"
50
+ Access = "access"
51
+ Source = "source"
52
+ Path = "path"
53
+ File = "file"
54
+ Package = "package"
55
+ Config = "config"
56
+ TestName = "test_name"
57
+ TestType = "test_type"
58
+ ResourceType = "resource_type"
59
+ State = "state"
60
+ Exposure = "exposure"
61
+ Metric = "metric"
62
+ Result = "result"
63
+ SourceStatus = "source_status"
64
+ Version = "version"
65
+ SemanticModel = "semantic_model"
66
+ SavedQuery = "saved_query"
67
+ UnitTest = "unit_test"
68
+
69
+
70
+ def is_selected_node(fqn: List[str], node_selector: str, is_versioned: bool) -> bool:
71
+ # If qualified_name exactly matches model name (fqn's leaf), return True
72
+ if is_versioned:
73
+ flat_node_selector = node_selector.split(".")
74
+ if fqn[-2] == node_selector:
75
+ return True
76
+ # If this is a versioned model, then the last two segments should be allowed to exactly match on either the '.' or '_' delimiter
77
+ elif "_".join(fqn[-2:]) == "_".join(flat_node_selector[-2:]):
78
+ return True
79
+ else:
80
+ if fqn[-1] == node_selector:
81
+ return True
82
+ # Flatten node parts. Dots in model names act as namespace separators
83
+ flat_fqn = [item for segment in fqn for item in segment.split(".")]
84
+ # Selector components cannot be more than fqn's
85
+ if len(flat_fqn) < len(node_selector.split(".")):
86
+ return False
87
+
88
+ slurp_from_ix: Optional[int] = None
89
+ for i, selector_part in enumerate(node_selector.split(".")):
90
+ if any(wildcard in selector_part for wildcard in ("*", "?", "[", "]")):
91
+ slurp_from_ix = i
92
+ break
93
+ elif flat_fqn[i] == selector_part:
94
+ continue
95
+ else:
96
+ return False
97
+
98
+ if slurp_from_ix is not None:
99
+ # If we have a wildcard, we need to make sure that the selector matches the
100
+ # rest of the fqn, this is 100% backwards compatible with the old behavior of
101
+ # encountering a wildcard but more expressive in naturally allowing you to
102
+ # match the rest of the fqn with more advanced patterns
103
+ return fnmatch(
104
+ ".".join(flat_fqn[slurp_from_ix:]),
105
+ ".".join(node_selector.split(".")[slurp_from_ix:]),
106
+ )
107
+
108
+ # if we get all the way down here, then the node is a match
109
+ return True
110
+
111
+
112
+ SelectorTarget = Union[
113
+ SourceDefinition, ManifestNode, Exposure, Metric, SemanticModel, UnitTestDefinition, SavedQuery
114
+ ]
115
+
116
+
117
+ class SelectorMethod(metaclass=abc.ABCMeta):
118
+ def __init__(
119
+ self, manifest: Manifest, previous_state: Optional[PreviousState], arguments: List[str]
120
+ ) -> None:
121
+ self.manifest: Manifest = manifest
122
+ self.previous_state = previous_state
123
+ self.arguments: List[str] = arguments
124
+
125
+ def parsed_nodes(
126
+ self, included_nodes: Set[UniqueId]
127
+ ) -> Iterator[Tuple[UniqueId, ManifestNode]]:
128
+
129
+ for key, node in self.manifest.nodes.items():
130
+ unique_id = UniqueId(key)
131
+ if unique_id not in included_nodes:
132
+ continue
133
+ yield unique_id, node
134
+
135
+ def source_nodes(
136
+ self, included_nodes: Set[UniqueId]
137
+ ) -> Iterator[Tuple[UniqueId, SourceDefinition]]:
138
+
139
+ for key, source in self.manifest.sources.items():
140
+ unique_id = UniqueId(key)
141
+ if unique_id not in included_nodes:
142
+ continue
143
+ yield unique_id, source
144
+
145
+ def exposure_nodes(self, included_nodes: Set[UniqueId]) -> Iterator[Tuple[UniqueId, Exposure]]:
146
+
147
+ for key, exposure in self.manifest.exposures.items():
148
+ unique_id = UniqueId(key)
149
+ if unique_id not in included_nodes:
150
+ continue
151
+ yield unique_id, exposure
152
+
153
+ def metric_nodes(self, included_nodes: Set[UniqueId]) -> Iterator[Tuple[UniqueId, Metric]]:
154
+
155
+ for key, metric in self.manifest.metrics.items():
156
+ unique_id = UniqueId(key)
157
+ if unique_id not in included_nodes:
158
+ continue
159
+ yield unique_id, metric
160
+
161
+ def unit_tests(
162
+ self, included_nodes: Set[UniqueId]
163
+ ) -> Iterator[Tuple[UniqueId, UnitTestDefinition]]:
164
+ for unique_id, unit_test in self.manifest.unit_tests.items():
165
+ unique_id = UniqueId(unique_id)
166
+ if unique_id not in included_nodes:
167
+ continue
168
+ yield unique_id, unit_test
169
+
170
+ def parsed_and_unit_nodes(self, included_nodes: Set[UniqueId]):
171
+ yield from chain(
172
+ self.parsed_nodes(included_nodes),
173
+ self.unit_tests(included_nodes),
174
+ )
175
+
176
+ def semantic_model_nodes(
177
+ self, included_nodes: Set[UniqueId]
178
+ ) -> Iterator[Tuple[UniqueId, SemanticModel]]:
179
+
180
+ for key, semantic_model in self.manifest.semantic_models.items():
181
+ unique_id = UniqueId(key)
182
+ if unique_id not in included_nodes:
183
+ continue
184
+ yield unique_id, semantic_model
185
+
186
+ def saved_query_nodes(
187
+ self, included_nodes: Set[UniqueId]
188
+ ) -> Iterator[Tuple[UniqueId, SavedQuery]]:
189
+
190
+ for key, saved_query in self.manifest.saved_queries.items():
191
+ unique_id = UniqueId(key)
192
+ if unique_id not in included_nodes:
193
+ continue
194
+ yield unique_id, saved_query
195
+
196
+ def function_nodes(
197
+ self, included_nodes: Set[UniqueId]
198
+ ) -> Iterator[Tuple[UniqueId, FunctionNode]]:
199
+ for key, function in self.manifest.functions.items():
200
+ unique_id = UniqueId(key)
201
+ if unique_id not in included_nodes:
202
+ continue
203
+ yield unique_id, function
204
+
205
+ def all_nodes(
206
+ self, included_nodes: Set[UniqueId]
207
+ ) -> Iterator[Tuple[UniqueId, SelectorTarget]]:
208
+ yield from chain(
209
+ self.parsed_nodes(included_nodes),
210
+ self.source_nodes(included_nodes),
211
+ self.exposure_nodes(included_nodes),
212
+ self.metric_nodes(included_nodes),
213
+ self.unit_tests(included_nodes),
214
+ self.semantic_model_nodes(included_nodes),
215
+ self.saved_query_nodes(included_nodes),
216
+ self.function_nodes(included_nodes),
217
+ )
218
+
219
+ def configurable_nodes(
220
+ self, included_nodes: Set[UniqueId]
221
+ ) -> Iterator[Tuple[UniqueId, ResultNode]]:
222
+ yield from chain(self.parsed_nodes(included_nodes), self.source_nodes(included_nodes))
223
+
224
+ def non_source_nodes(
225
+ self,
226
+ included_nodes: Set[UniqueId],
227
+ ) -> Iterator[Tuple[UniqueId, Union[Exposure, ManifestNode, Metric]]]:
228
+ yield from chain(
229
+ self.parsed_nodes(included_nodes),
230
+ self.exposure_nodes(included_nodes),
231
+ self.metric_nodes(included_nodes),
232
+ self.unit_tests(included_nodes),
233
+ self.semantic_model_nodes(included_nodes),
234
+ self.saved_query_nodes(included_nodes),
235
+ self.function_nodes(included_nodes),
236
+ )
237
+
238
+ def groupable_nodes(
239
+ self,
240
+ included_nodes: Set[UniqueId],
241
+ ) -> Iterator[Tuple[UniqueId, Union[ManifestNode, Metric]]]:
242
+ yield from chain(
243
+ self.parsed_nodes(included_nodes),
244
+ self.metric_nodes(included_nodes),
245
+ )
246
+
247
+ @abc.abstractmethod
248
+ def search(
249
+ self,
250
+ included_nodes: Set[UniqueId],
251
+ selector: str,
252
+ ) -> Iterator[UniqueId]:
253
+ raise NotImplementedError("subclasses should implement this")
254
+
255
+
256
+ class QualifiedNameSelectorMethod(SelectorMethod):
257
+ def node_is_match(self, qualified_name: str, fqn: List[str], is_versioned: bool) -> bool:
258
+ """Determine if a qualified name matches an fqn for all package
259
+ names in the graph.
260
+
261
+ :param str qualified_name: The qualified name to match the nodes with
262
+ :param List[str] fqn: The node's fully qualified name in the graph.
263
+ """
264
+ unscoped_fqn = fqn[1:]
265
+
266
+ if is_selected_node(fqn, qualified_name, is_versioned):
267
+ return True
268
+ # Match nodes across different packages
269
+ elif is_selected_node(unscoped_fqn, qualified_name, is_versioned):
270
+ return True
271
+
272
+ return False
273
+
274
+ def search(self, included_nodes: Set[UniqueId], selector: str) -> Iterator[UniqueId]:
275
+ """Yield all nodes in the graph that match the selector.
276
+
277
+ :param str selector: The selector or node name
278
+ """
279
+ non_source_nodes = list(self.non_source_nodes(included_nodes))
280
+ for unique_id, node in non_source_nodes:
281
+ if self.node_is_match(selector, node.fqn, node.is_versioned):
282
+ yield unique_id
283
+
284
+
285
+ class TagSelectorMethod(SelectorMethod):
286
+ def search(self, included_nodes: Set[UniqueId], selector: str) -> Iterator[UniqueId]:
287
+ """yields nodes from included that have the specified tag"""
288
+ for unique_id, node in self.all_nodes(included_nodes):
289
+ if hasattr(node, "tags") and any(fnmatch(tag, selector) for tag in node.tags):
290
+ yield unique_id
291
+
292
+
293
+ class GroupSelectorMethod(SelectorMethod):
294
+ def search(self, included_nodes: Set[UniqueId], selector: str) -> Iterator[UniqueId]:
295
+ """yields nodes from included in the specified group"""
296
+ for unique_id, node in self.groupable_nodes(included_nodes):
297
+ node_group = node.config.get("group")
298
+ if node_group and fnmatch(node_group, selector):
299
+ yield unique_id
300
+
301
+
302
+ class AccessSelectorMethod(SelectorMethod):
303
+ def search(self, included_nodes: Set[UniqueId], selector: str) -> Iterator[UniqueId]:
304
+ """yields model nodes matching the specified access level"""
305
+ for unique_id, node in self.parsed_nodes(included_nodes):
306
+ if not isinstance(node, ModelNode):
307
+ continue
308
+ if selector == node.access:
309
+ yield unique_id
310
+
311
+
312
+ class SourceSelectorMethod(SelectorMethod):
313
+ def search(self, included_nodes: Set[UniqueId], selector: str) -> Iterator[UniqueId]:
314
+ """yields nodes from included are the specified source."""
315
+ parts = selector.split(".")
316
+ target_package = SELECTOR_GLOB
317
+ if len(parts) == 1:
318
+ target_source, target_table = parts[0], SELECTOR_GLOB
319
+ elif len(parts) == 2:
320
+ target_source, target_table = parts
321
+ elif len(parts) == 3:
322
+ target_package, target_source, target_table = parts
323
+ else: # len(parts) > 3 or len(parts) == 0
324
+ msg = (
325
+ 'Invalid source selector value "{}". Sources must be of the '
326
+ "form `${{source_name}}`, "
327
+ "`${{source_name}}.${{target_name}}`, or "
328
+ "`${{package_name}}.${{source_name}}.${{target_name}}"
329
+ ).format(selector)
330
+ raise DbtRuntimeError(msg)
331
+
332
+ for unique_id, node in self.source_nodes(included_nodes):
333
+ if not fnmatch(node.package_name, target_package):
334
+ continue
335
+ if not fnmatch(node.source_name, target_source):
336
+ continue
337
+ if not fnmatch(node.name, target_table):
338
+ continue
339
+ yield unique_id
340
+
341
+
342
+ class ExposureSelectorMethod(SelectorMethod):
343
+ def search(self, included_nodes: Set[UniqueId], selector: str) -> Iterator[UniqueId]:
344
+ parts = selector.split(".")
345
+ target_package = SELECTOR_GLOB
346
+ if len(parts) == 1:
347
+ target_name = parts[0]
348
+ elif len(parts) == 2:
349
+ target_package, target_name = parts
350
+ else:
351
+ msg = (
352
+ 'Invalid exposure selector value "{}". Exposures must be of '
353
+ "the form ${{exposure_name}} or "
354
+ "${{exposure_package.exposure_name}}"
355
+ ).format(selector)
356
+ raise DbtRuntimeError(msg)
357
+
358
+ for unique_id, node in self.exposure_nodes(included_nodes):
359
+ if not fnmatch(node.package_name, target_package):
360
+ continue
361
+ if not fnmatch(node.name, target_name):
362
+ continue
363
+
364
+ yield unique_id
365
+
366
+
367
+ class MetricSelectorMethod(SelectorMethod):
368
+ def search(self, included_nodes: Set[UniqueId], selector: str) -> Iterator[UniqueId]:
369
+ parts = selector.split(".")
370
+ target_package = SELECTOR_GLOB
371
+ if len(parts) == 1:
372
+ target_name = parts[0]
373
+ elif len(parts) == 2:
374
+ target_package, target_name = parts
375
+ else:
376
+ msg = (
377
+ 'Invalid metric selector value "{}". Metrics must be of '
378
+ "the form ${{metric_name}} or "
379
+ "${{metric_package.metric_name}}"
380
+ ).format(selector)
381
+ raise DbtRuntimeError(msg)
382
+
383
+ for unique_id, node in self.metric_nodes(included_nodes):
384
+ if not fnmatch(node.package_name, target_package):
385
+ continue
386
+ if not fnmatch(node.name, target_name):
387
+ continue
388
+
389
+ yield unique_id
390
+
391
+
392
+ class SemanticModelSelectorMethod(SelectorMethod):
393
+ def search(self, included_nodes: Set[UniqueId], selector: str) -> Iterator[UniqueId]:
394
+ parts = selector.split(".")
395
+ target_package = SELECTOR_GLOB
396
+ if len(parts) == 1:
397
+ target_name = parts[0]
398
+ elif len(parts) == 2:
399
+ target_package, target_name = parts
400
+ else:
401
+ msg = (
402
+ 'Invalid semantic model selector value "{}". Semantic models must be of '
403
+ "the form ${{semantic_model_name}} or "
404
+ "${{semantic_model_package.semantic_model_name}}"
405
+ ).format(selector)
406
+ raise DbtRuntimeError(msg)
407
+
408
+ for unique_id, node in self.semantic_model_nodes(included_nodes):
409
+ if not fnmatch(node.package_name, target_package):
410
+ continue
411
+ if not fnmatch(node.name, target_name):
412
+ continue
413
+
414
+ yield unique_id
415
+
416
+
417
+ class SavedQuerySelectorMethod(SelectorMethod):
418
+ def search(self, included_nodes: Set[UniqueId], selector: str) -> Iterator[UniqueId]:
419
+ parts = selector.split(".")
420
+ target_package = SELECTOR_GLOB
421
+ if len(parts) == 1:
422
+ target_name = parts[0]
423
+ elif len(parts) == 2:
424
+ target_package, target_name = parts
425
+ else:
426
+ msg = (
427
+ 'Invalid saved query selector value "{}". Saved queries must be of '
428
+ "the form ${{saved_query_name}} or "
429
+ "${{saved_query_package.saved_query_name}}"
430
+ ).format(selector)
431
+ raise DbtRuntimeError(msg)
432
+
433
+ for unique_id, node in self.saved_query_nodes(included_nodes):
434
+ if not fnmatch(node.package_name, target_package):
435
+ continue
436
+ if not fnmatch(node.name, target_name):
437
+ continue
438
+
439
+ yield unique_id
440
+
441
+
442
+ class UnitTestSelectorMethod(SelectorMethod):
443
+ def search(self, included_nodes: Set[UniqueId], selector: str) -> Iterator[UniqueId]:
444
+ parts = selector.split(".")
445
+ target_package = SELECTOR_GLOB
446
+ if len(parts) == 1:
447
+ target_name = parts[0]
448
+ elif len(parts) == 2:
449
+ target_package, target_name = parts
450
+ else:
451
+ msg = (
452
+ 'Invalid unit test selector value "{}". Saved queries must be of '
453
+ "the form ${{unit_test_name}} or "
454
+ "${{unit_test_package_name.unit_test_name}}"
455
+ ).format(selector)
456
+ raise DbtRuntimeError(msg)
457
+
458
+ for unique_id, node in self.unit_tests(included_nodes):
459
+ if not fnmatch(node.package_name, target_package):
460
+ continue
461
+ if not fnmatch(node.name, target_name):
462
+ continue
463
+
464
+ yield unique_id
465
+
466
+
467
+ class PathSelectorMethod(SelectorMethod):
468
+ def search(self, included_nodes: Set[UniqueId], selector: str) -> Iterator[UniqueId]:
469
+ """Yields nodes from included that match the given path."""
470
+ # get project root from contextvar
471
+ project_root = get_project_root()
472
+ if project_root:
473
+ root = Path(project_root)
474
+ else:
475
+ root = Path.cwd()
476
+ paths = set(p.relative_to(root) for p in root.glob(selector))
477
+ for unique_id, node in self.all_nodes(included_nodes):
478
+ ofp = Path(node.original_file_path)
479
+ if ofp in paths:
480
+ yield unique_id
481
+ if hasattr(node, "patch_path") and node.patch_path: # type: ignore
482
+ pfp = node.patch_path.split("://")[1] # type: ignore
483
+ ymlfp = Path(pfp)
484
+ if ymlfp in paths:
485
+ yield unique_id
486
+ if any(parent in paths for parent in ofp.parents):
487
+ yield unique_id
488
+
489
+
490
+ class FileSelectorMethod(SelectorMethod):
491
+ def search(self, included_nodes: Set[UniqueId], selector: str) -> Iterator[UniqueId]:
492
+ """Yields nodes from included that match the given file name."""
493
+ for unique_id, node in self.all_nodes(included_nodes):
494
+ if fnmatch(Path(node.original_file_path).name, selector):
495
+ yield unique_id
496
+ elif fnmatch(Path(node.original_file_path).stem, selector):
497
+ yield unique_id
498
+
499
+
500
+ class PackageSelectorMethod(SelectorMethod):
501
+ def search(self, included_nodes: Set[UniqueId], selector: str) -> Iterator[UniqueId]:
502
+ """Yields nodes from included that have the specified package"""
503
+ # `this` is an alias for the current dbt project name
504
+ if selector == "this" and self.manifest.metadata.project_name is not None:
505
+ selector = self.manifest.metadata.project_name
506
+
507
+ for unique_id, node in self.all_nodes(included_nodes):
508
+ if fnmatch(node.package_name, selector):
509
+ yield unique_id
510
+
511
+
512
+ def _getattr_descend(obj: Any, attrs: List[str]) -> Any:
513
+ value = obj
514
+ for attr in attrs:
515
+ try:
516
+ value = getattr(value, attr)
517
+ except AttributeError:
518
+ # if it implements getitem (dict, list, ...), use that. On failure,
519
+ # raise an attribute error instead of the KeyError, TypeError, etc.
520
+ # that arbitrary getitem calls might raise
521
+ try:
522
+ value = value[attr]
523
+ except Exception as exc:
524
+ raise AttributeError(f"'{type(value)}' object has no attribute '{attr}'") from exc
525
+ return value
526
+
527
+
528
+ class CaseInsensitive(str):
529
+ def __eq__(self, other):
530
+ if isinstance(other, str):
531
+ return self.upper() == other.upper()
532
+ else:
533
+ return self.upper() == other
534
+
535
+
536
+ class ConfigSelectorMethod(SelectorMethod):
537
+ def search(
538
+ self,
539
+ included_nodes: Set[UniqueId],
540
+ selector: Any,
541
+ ) -> Iterator[UniqueId]:
542
+ parts = self.arguments
543
+ # special case: if the user wanted to compare test severity,
544
+ # make the comparison case-insensitive
545
+ if parts == ["severity"]:
546
+ selector = CaseInsensitive(selector)
547
+
548
+ # search sources is kind of useless now source configs only have
549
+ # 'enabled', which you can't really filter on anyway, but maybe we'll
550
+ # add more someday, so search them anyway.
551
+ for unique_id, node in self.configurable_nodes(included_nodes):
552
+ try:
553
+ value = _getattr_descend(node.config, parts)
554
+ except AttributeError:
555
+ continue
556
+ else:
557
+ if isinstance(value, list):
558
+ if (
559
+ (selector in value)
560
+ or (CaseInsensitive(selector) == "true" and True in value)
561
+ or (CaseInsensitive(selector) == "false" and False in value)
562
+ ):
563
+ yield unique_id
564
+ else:
565
+ if (
566
+ (selector == value)
567
+ or (CaseInsensitive(selector) == "true" and value is True)
568
+ or (CaseInsensitive(selector) == "false")
569
+ and value is False
570
+ ):
571
+ yield unique_id
572
+
573
+
574
+ class ResourceTypeSelectorMethod(SelectorMethod):
575
+ def search(self, included_nodes: Set[UniqueId], selector: str) -> Iterator[UniqueId]:
576
+ try:
577
+ resource_type = NodeType(selector)
578
+ except ValueError as exc:
579
+ raise DbtRuntimeError(f'Invalid resource_type selector "{selector}"') from exc
580
+ for unique_id, node in self.all_nodes(included_nodes):
581
+ if node.resource_type == resource_type:
582
+ yield unique_id
583
+
584
+
585
+ class TestNameSelectorMethod(SelectorMethod):
586
+ __test__ = False
587
+
588
+ def search(self, included_nodes: Set[UniqueId], selector: str) -> Iterator[UniqueId]:
589
+ for unique_id, node in self.parsed_and_unit_nodes(included_nodes):
590
+ if node.resource_type == NodeType.Test and hasattr(node, "test_metadata"):
591
+ if fnmatch(node.test_metadata.name, selector): # type: ignore[union-attr]
592
+ yield unique_id
593
+ elif node.resource_type == NodeType.Unit:
594
+ if fnmatch(node.name, selector):
595
+ yield unique_id
596
+
597
+
598
+ class TestTypeSelectorMethod(SelectorMethod):
599
+ __test__ = False
600
+
601
+ def search(self, included_nodes: Set[UniqueId], selector: str) -> Iterator[UniqueId]:
602
+ search_types: List[Any]
603
+ # continue supporting 'schema' + 'data' for backwards compatibility
604
+ if selector in ("generic", "schema"):
605
+ search_types = [GenericTestNode]
606
+ elif selector in ("data"):
607
+ search_types = [GenericTestNode, SingularTestNode]
608
+ elif selector in ("singular"):
609
+ search_types = [SingularTestNode]
610
+ elif selector in ("unit"):
611
+ search_types = [UnitTestDefinition]
612
+ else:
613
+ raise DbtRuntimeError(
614
+ f'Invalid test type selector {selector}: expected "generic", "singular", "unit", or "data"'
615
+ )
616
+
617
+ for unique_id, node in self.parsed_and_unit_nodes(included_nodes):
618
+ if isinstance(node, tuple(search_types)):
619
+ yield unique_id
620
+
621
+
622
+ class StateSelectorMethod(SelectorMethod):
623
+ def __init__(self, *args, **kwargs) -> None:
624
+ super().__init__(*args, **kwargs)
625
+ self.modified_macros: Optional[List[str]] = None
626
+
627
+ def _macros_modified(self) -> List[str]:
628
+ # we checked in the caller!
629
+ if self.previous_state is None or self.previous_state.manifest is None:
630
+ raise DbtInternalError("No comparison manifest in _macros_modified")
631
+ old_macros = self.previous_state.manifest.macros
632
+ new_macros = self.manifest.macros
633
+
634
+ modified = []
635
+ for uid, macro in new_macros.items():
636
+ if uid in old_macros:
637
+ old_macro = old_macros[uid]
638
+ if macro.macro_sql != old_macro.macro_sql:
639
+ modified.append(uid)
640
+ else:
641
+ modified.append(uid)
642
+
643
+ for uid, _ in old_macros.items():
644
+ if uid not in new_macros:
645
+ modified.append(uid)
646
+
647
+ return modified
648
+
649
+ def recursively_check_macros_modified(self, node, visited_macros):
650
+ if not hasattr(node, "depends_on"):
651
+ return False
652
+
653
+ for macro_uid in node.depends_on.macros:
654
+ if macro_uid in visited_macros:
655
+ continue
656
+ visited_macros.append(macro_uid)
657
+
658
+ if macro_uid in self.modified_macros:
659
+ return True
660
+
661
+ # this macro hasn't been modified, but depends on other
662
+ # macros which each need to be tested for modification
663
+ macro_node = self.manifest.macros[macro_uid]
664
+ if len(macro_node.depends_on.macros) > 0:
665
+ upstream_macros_changed = self.recursively_check_macros_modified(
666
+ macro_node, visited_macros
667
+ )
668
+ if upstream_macros_changed:
669
+ return True
670
+ continue
671
+
672
+ # this macro hasn't been modified, but we haven't checked
673
+ # the other macros the node depends on, so keep looking
674
+ if len(node.depends_on.macros) > len(visited_macros):
675
+ continue
676
+
677
+ return False
678
+
679
+ def check_macros_modified(self, node):
680
+ # check if there are any changes in macros the first time
681
+ if self.modified_macros is None:
682
+ self.modified_macros = self._macros_modified()
683
+ # no macros have been modified, skip looping entirely
684
+ if not self.modified_macros:
685
+ return False
686
+ # recursively loop through upstream macros to see if any is modified
687
+ else:
688
+ visited_macros = []
689
+ return self.recursively_check_macros_modified(node, visited_macros)
690
+
691
+ # TODO check modifed_content and check_modified macro seems a bit redundent
692
+ def check_modified_content(
693
+ self, old: Optional[SelectorTarget], new: SelectorTarget, adapter_type: str
694
+ ) -> bool:
695
+ different_contents = False
696
+ if isinstance(
697
+ new,
698
+ (SourceDefinition, Exposure, Metric, SemanticModel, UnitTestDefinition, SavedQuery),
699
+ ):
700
+ # these all overwrite `same_contents`
701
+ different_contents = not new.same_contents(old) # type: ignore
702
+ elif new: # because we also pull in deleted/disabled nodes, this could be None
703
+ different_contents = not new.same_contents(old, adapter_type) # type: ignore
704
+
705
+ upstream_macro_change = self.check_macros_modified(new)
706
+
707
+ check_modified_contract = False
708
+ if isinstance(old, ModelNode):
709
+ func = self.check_modified_contract("same_contract", adapter_type)
710
+ check_modified_contract = func(old, new)
711
+
712
+ return different_contents or upstream_macro_change or check_modified_contract
713
+
714
+ def check_unmodified_content(
715
+ self, old: Optional[SelectorTarget], new: SelectorTarget, adapter_type: str
716
+ ) -> bool:
717
+ return not self.check_modified_content(old, new, adapter_type)
718
+
719
+ def check_modified_macros(self, old, new: SelectorTarget) -> bool:
720
+ return self.check_macros_modified(new)
721
+
722
+ @staticmethod
723
+ def check_modified_factory(
724
+ compare_method: str,
725
+ ) -> Callable[[Optional[SelectorTarget], SelectorTarget], bool]:
726
+ # get a function that compares two selector target based on compare method provided
727
+ def check_modified_things(old: Optional[SelectorTarget], new: SelectorTarget) -> bool:
728
+ if hasattr(new, compare_method):
729
+ # when old body does not exist or old and new are not the same
730
+ return not old or not getattr(new, compare_method)(old) # type: ignore
731
+ else:
732
+ return False
733
+
734
+ return check_modified_things
735
+
736
+ @staticmethod
737
+ def check_modified_contract(
738
+ compare_method: str,
739
+ adapter_type: Optional[str],
740
+ ) -> Callable[[Optional[SelectorTarget], SelectorTarget], bool]:
741
+ # get a function that compares two selector target based on compare method provided
742
+ def check_modified_contract(old: Optional[SelectorTarget], new: SelectorTarget) -> bool:
743
+ if new is None and hasattr(old, compare_method + "_removed"):
744
+ return getattr(old, compare_method + "_removed")()
745
+ elif hasattr(new, compare_method):
746
+ # when old body does not exist or old and new are not the same
747
+ return not old or not getattr(new, compare_method)(old, adapter_type) # type: ignore
748
+ else:
749
+ return False
750
+
751
+ return check_modified_contract
752
+
753
+ def search(self, included_nodes: Set[UniqueId], selector: str) -> Iterator[UniqueId]:
754
+ if self.previous_state is None or self.previous_state.manifest is None:
755
+ raise DbtRuntimeError("Got a state selector method, but no comparison manifest")
756
+
757
+ adapter_type = self.manifest.metadata.adapter_type
758
+
759
+ state_checks = {
760
+ # it's new if there is no old version
761
+ "new": lambda old, new: old is None,
762
+ "old": lambda old, new: old is not None,
763
+ # use methods defined above to compare properties of old + new
764
+ "modified": self.check_modified_content,
765
+ "unmodified": self.check_unmodified_content,
766
+ "modified.body": self.check_modified_factory("same_body"),
767
+ "modified.configs": self.check_modified_factory("same_config"),
768
+ "modified.persisted_descriptions": self.check_modified_factory(
769
+ "same_persisted_description"
770
+ ),
771
+ "modified.relation": self.check_modified_factory("same_database_representation"),
772
+ "modified.macros": self.check_modified_macros,
773
+ "modified.contract": self.check_modified_contract("same_contract", adapter_type),
774
+ }
775
+ if selector in state_checks:
776
+ checker = state_checks[selector]
777
+ else:
778
+ raise DbtRuntimeError(
779
+ f'Got an invalid selector "{selector}", expected one of ' f'"{list(state_checks)}"'
780
+ )
781
+
782
+ manifest: Manifest = self.previous_state.manifest
783
+
784
+ keyword_args = {} # initialize here to handle disabled node check below
785
+ for unique_id, node in self.all_nodes(included_nodes):
786
+ previous_node: Optional[SelectorTarget] = None
787
+
788
+ if unique_id in manifest.nodes:
789
+ previous_node = manifest.nodes[unique_id]
790
+ elif unique_id in manifest.sources:
791
+ previous_node = SourceDefinition.from_resource(manifest.sources[unique_id])
792
+ elif unique_id in manifest.exposures:
793
+ previous_node = Exposure.from_resource(manifest.exposures[unique_id])
794
+ elif unique_id in manifest.metrics:
795
+ previous_node = Metric.from_resource(manifest.metrics[unique_id])
796
+ elif unique_id in manifest.semantic_models:
797
+ previous_node = SemanticModel.from_resource(manifest.semantic_models[unique_id])
798
+ elif unique_id in manifest.unit_tests:
799
+ previous_node = UnitTestDefinition.from_resource(manifest.unit_tests[unique_id])
800
+ elif unique_id in manifest.saved_queries:
801
+ previous_node = SavedQuery.from_resource(manifest.saved_queries[unique_id])
802
+ elif unique_id in manifest.functions:
803
+ previous_node = FunctionNode.from_resource(manifest.functions[unique_id])
804
+
805
+ if checker.__name__ in [
806
+ "same_contract",
807
+ "check_modified_content",
808
+ "check_unmodified_content",
809
+ ]:
810
+ keyword_args["adapter_type"] = adapter_type # type: ignore
811
+
812
+ if checker(previous_node, node, **keyword_args): # type: ignore
813
+ yield unique_id
814
+
815
+ # checkers that can handle removed nodes
816
+ if checker.__name__ in [
817
+ "check_modified_contract",
818
+ "check_modified_content",
819
+ "check_unmodified_content",
820
+ ]:
821
+ # ignore included_nodes, since those cannot contain removed nodes
822
+ for previous_unique_id, previous_node in manifest.nodes.items():
823
+ # detect removed (deleted, renamed, or disabled) nodes
824
+ removed_node = None
825
+ if previous_unique_id in self.manifest.disabled.keys():
826
+ removed_node = self.manifest.disabled[previous_unique_id][0]
827
+ elif previous_unique_id not in self.manifest.nodes.keys():
828
+ removed_node = previous_node
829
+
830
+ if removed_node:
831
+ # do not yield -- removed nodes should never be selected for downstream execution
832
+ # as they are not part of the current project's manifest.nodes
833
+ checker(removed_node, None, **keyword_args) # type: ignore
834
+
835
+
836
+ class ResultSelectorMethod(SelectorMethod):
837
+ def search(self, included_nodes: Set[UniqueId], selector: str) -> Iterator[UniqueId]:
838
+ if self.previous_state is None or self.previous_state.results is None:
839
+ raise DbtInternalError("No comparison run_results")
840
+ matches = set(
841
+ result.unique_id for result in self.previous_state.results if result.status == selector
842
+ )
843
+ for unique_id, node in self.all_nodes(included_nodes):
844
+ if unique_id in matches:
845
+ yield unique_id
846
+
847
+
848
+ class SourceStatusSelectorMethod(SelectorMethod):
849
+ def search(self, included_nodes: Set[UniqueId], selector: str) -> Iterator[UniqueId]:
850
+
851
+ if self.previous_state is None or self.previous_state.sources is None:
852
+ raise DbtInternalError(
853
+ "No previous state comparison freshness results in sources.json"
854
+ )
855
+ elif self.previous_state.sources_current is None:
856
+ raise DbtInternalError("No current state comparison freshness results in sources.json")
857
+
858
+ current_state_sources = {
859
+ result.unique_id: getattr(result, "max_loaded_at", 0)
860
+ for result in self.previous_state.sources_current.results
861
+ if hasattr(result, "max_loaded_at")
862
+ }
863
+
864
+ current_state_sources_runtime_error = {
865
+ result.unique_id
866
+ for result in self.previous_state.sources_current.results
867
+ if not hasattr(result, "max_loaded_at")
868
+ }
869
+
870
+ previous_state_sources = {
871
+ result.unique_id: getattr(result, "max_loaded_at", 0)
872
+ for result in self.previous_state.sources.results
873
+ if hasattr(result, "max_loaded_at")
874
+ }
875
+
876
+ previous_state_sources_runtime_error = {
877
+ result.unique_id
878
+ for result in self.previous_state.sources_current.results
879
+ if not hasattr(result, "max_loaded_at")
880
+ }
881
+
882
+ matches = set()
883
+ if selector == "fresher":
884
+ for unique_id in current_state_sources:
885
+ if unique_id not in previous_state_sources:
886
+ matches.add(unique_id)
887
+ elif current_state_sources[unique_id] > previous_state_sources[unique_id]:
888
+ matches.add(unique_id)
889
+
890
+ for unique_id in matches:
891
+ if (
892
+ unique_id in previous_state_sources_runtime_error
893
+ or unique_id in current_state_sources_runtime_error
894
+ ):
895
+ matches.remove(unique_id)
896
+
897
+ for unique_id, node in self.all_nodes(included_nodes):
898
+ if unique_id in matches:
899
+ yield unique_id
900
+
901
+
902
+ class VersionSelectorMethod(SelectorMethod):
903
+ def search(self, included_nodes: Set[UniqueId], selector: str) -> Iterator[UniqueId]:
904
+ for unique_id, node in self.parsed_nodes(included_nodes):
905
+ if isinstance(node, ModelNode):
906
+ if selector == "latest":
907
+ if node.is_latest_version:
908
+ yield unique_id
909
+ elif selector == "prerelease":
910
+ if (
911
+ node.version
912
+ and node.latest_version
913
+ and UnparsedVersion(v=node.version)
914
+ > UnparsedVersion(v=node.latest_version)
915
+ ):
916
+ yield unique_id
917
+ elif selector == "old":
918
+ if (
919
+ node.version
920
+ and node.latest_version
921
+ and UnparsedVersion(v=node.version)
922
+ < UnparsedVersion(v=node.latest_version)
923
+ ):
924
+ yield unique_id
925
+ elif selector == "none":
926
+ if node.version is None:
927
+ yield unique_id
928
+ else:
929
+ raise DbtRuntimeError(
930
+ f'Invalid version type selector {selector}: expected one of: "latest", "prerelease", "old", or "none"'
931
+ )
932
+
933
+
934
+ class MethodManager:
935
+ SELECTOR_METHODS: Dict[MethodName, Type[SelectorMethod]] = {
936
+ MethodName.FQN: QualifiedNameSelectorMethod,
937
+ MethodName.Tag: TagSelectorMethod,
938
+ MethodName.Group: GroupSelectorMethod,
939
+ MethodName.Access: AccessSelectorMethod,
940
+ MethodName.Source: SourceSelectorMethod,
941
+ MethodName.Path: PathSelectorMethod,
942
+ MethodName.File: FileSelectorMethod,
943
+ MethodName.Package: PackageSelectorMethod,
944
+ MethodName.Config: ConfigSelectorMethod,
945
+ MethodName.TestName: TestNameSelectorMethod,
946
+ MethodName.TestType: TestTypeSelectorMethod,
947
+ MethodName.ResourceType: ResourceTypeSelectorMethod,
948
+ MethodName.State: StateSelectorMethod,
949
+ MethodName.Exposure: ExposureSelectorMethod,
950
+ MethodName.Metric: MetricSelectorMethod,
951
+ MethodName.Result: ResultSelectorMethod,
952
+ MethodName.SourceStatus: SourceStatusSelectorMethod,
953
+ MethodName.Version: VersionSelectorMethod,
954
+ MethodName.SemanticModel: SemanticModelSelectorMethod,
955
+ MethodName.SavedQuery: SavedQuerySelectorMethod,
956
+ MethodName.UnitTest: UnitTestSelectorMethod,
957
+ }
958
+
959
+ def __init__(
960
+ self,
961
+ manifest: Manifest,
962
+ previous_state: Optional[PreviousState],
963
+ ) -> None:
964
+ self.manifest = manifest
965
+ self.previous_state = previous_state
966
+
967
+ def get_method(self, method: MethodName, method_arguments: List[str]) -> SelectorMethod:
968
+
969
+ if method not in self.SELECTOR_METHODS:
970
+ raise DbtInternalError(
971
+ f'Method name "{method}" is a valid node selection '
972
+ f"method name, but it is not handled"
973
+ )
974
+ cls: Type[SelectorMethod] = self.SELECTOR_METHODS[method]
975
+ return cls(self.manifest, self.previous_state, method_arguments)