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
dbt/task/retract.py ADDED
@@ -0,0 +1,438 @@
1
+ # =============================================================================
2
+ # DVT Retract Task
3
+ # =============================================================================
4
+ # Drops all materialized models from target databases.
5
+ #
6
+ # Usage:
7
+ # dvt retract # Drop all materialized models
8
+ # dvt retract --dry-run # Preview what would be dropped
9
+ # dvt retract --select "model*" # Drop matching models only
10
+ # dvt retract --exclude "dim_*" # Exclude matching models
11
+ #
12
+ # DVT v0.58.1: Added DROP CASCADE and reverse DAG order support
13
+ # =============================================================================
14
+
15
+ from __future__ import annotations
16
+
17
+ import json
18
+ import fnmatch
19
+ import time
20
+ from pathlib import Path
21
+ from typing import Any, Dict, List, Optional, Set, Tuple
22
+
23
+ from dbt.cli.flags import Flags
24
+ from dbt.config import RuntimeConfig
25
+ from dbt.contracts.graph.manifest import Manifest
26
+ from dbt.task.base import BaseTask
27
+
28
+ # Try to import Rich for beautiful output
29
+ try:
30
+ from rich.console import Console
31
+ from rich.table import Table
32
+ from rich.progress import Progress, SpinnerColumn, TextColumn, BarColumn, MofNCompleteColumn, TimeElapsedColumn
33
+ from rich.panel import Panel
34
+ from rich import box
35
+ HAS_RICH = True
36
+ except ImportError:
37
+ HAS_RICH = False
38
+
39
+
40
+ class RetractTask(BaseTask):
41
+ """
42
+ Task to drop materialized models from target databases.
43
+
44
+ This task:
45
+ 1. Reads the manifest to find all materialized models
46
+ 2. Builds dependency graph and orders models in REVERSE DAG order
47
+ 3. Groups models by their target adapter
48
+ 4. Drops each model's relation (table/view) using CASCADE
49
+ 5. Supports --dry-run, --select, and --exclude flags
50
+
51
+ DVT v0.58.1 Changes:
52
+ - Uses DROP ... CASCADE to handle dependent objects
53
+ - Follows reverse DAG order (drop dependents first, then dependencies)
54
+ """
55
+
56
+ def __init__(self, args: Flags, config: RuntimeConfig, manifest: Manifest):
57
+ super().__init__(args)
58
+ self.config = config
59
+ self.manifest = manifest
60
+ self.dry_run = getattr(args, 'DRY_RUN', False)
61
+ self._console = None
62
+ self._use_rich = HAS_RICH
63
+ if self._use_rich:
64
+ self._console = Console()
65
+
66
+ def _print(self, message: str, style: str = None):
67
+ """Print with optional Rich styling."""
68
+ if self._use_rich and style:
69
+ self._console.print(f"[{style}]{message}[/{style}]")
70
+ elif self._use_rich:
71
+ self._console.print(message)
72
+ else:
73
+ print(message)
74
+
75
+ def run(self) -> Tuple[bool, Dict[str, Any]]:
76
+ """Execute the retract task."""
77
+ start_time = time.time()
78
+
79
+ # Header
80
+ self._print("")
81
+ if self.dry_run:
82
+ if self._use_rich:
83
+ self._console.print(Panel(
84
+ "[bold cyan]Preview of models that would be dropped[/bold cyan]\n"
85
+ "[dim]Using reverse DAG order (dependents first)[/dim]",
86
+ title="[bold cyan]DVT RETRACT (DRY RUN)[/bold cyan]",
87
+ border_style="cyan",
88
+ box=box.DOUBLE,
89
+ ))
90
+ else:
91
+ self._print("=" * 60)
92
+ self._print(" DVT RETRACT (DRY RUN)")
93
+ self._print(" Preview of models that would be dropped")
94
+ self._print("=" * 60)
95
+ else:
96
+ if self._use_rich:
97
+ self._console.print(Panel(
98
+ "[bold red]Dropping materialized models from databases[/bold red]\n"
99
+ "[dim]Using DROP ... CASCADE in reverse DAG order[/dim]",
100
+ title="[bold red]DVT RETRACT[/bold red]",
101
+ border_style="red",
102
+ box=box.DOUBLE,
103
+ ))
104
+ else:
105
+ self._print("=" * 60)
106
+ self._print(" DVT RETRACT")
107
+ self._print(" Dropping materialized models from databases")
108
+ self._print("=" * 60)
109
+ self._print("")
110
+
111
+ # Get models to retract
112
+ models = self._get_models_to_retract()
113
+
114
+ if not models:
115
+ self._print("No materialized models found to retract.", "yellow")
116
+ return True, {"dropped": [], "failed": [], "skipped": []}
117
+
118
+ # Filter by --select and --exclude
119
+ models = self._filter_models(models)
120
+
121
+ if not models:
122
+ self._print("No models match the selection criteria.", "yellow")
123
+ return True, {"dropped": [], "failed": [], "skipped": []}
124
+
125
+ # Sort in REVERSE DAG order (dependents first, then dependencies)
126
+ models = self._sort_reverse_dag_order(models)
127
+
128
+ # Group by target
129
+ models_by_target = self._group_models_by_target(models)
130
+
131
+ dropped = []
132
+ failed = []
133
+ skipped = []
134
+
135
+ # Calculate total for progress bar
136
+ total_models = sum(len(m) for m in models_by_target.values())
137
+
138
+ if self._use_rich and not self.dry_run:
139
+ # Use progress bar for actual drops
140
+ with Progress(
141
+ SpinnerColumn(),
142
+ TextColumn("[bold blue]{task.description}"),
143
+ BarColumn(bar_width=40),
144
+ MofNCompleteColumn(),
145
+ TimeElapsedColumn(),
146
+ console=self._console,
147
+ ) as progress:
148
+ task = progress.add_task("[cyan]Dropping models...", total=total_models)
149
+
150
+ for target_name, target_models in models_by_target.items():
151
+ for model in target_models:
152
+ model_name = model.get("name")
153
+ relation_type = model.get("relation_type", "table")
154
+ schema = model.get("schema")
155
+ database = model.get("database")
156
+
157
+ # Build relation identifier
158
+ if database:
159
+ relation_id = f"{database}.{schema}.{model_name}"
160
+ else:
161
+ relation_id = f"{schema}.{model_name}"
162
+
163
+ progress.update(task, description=f"[cyan]Dropping[/cyan] [bold]{relation_id}[/bold]")
164
+
165
+ success = self._drop_relation(target_name, model, use_cascade=True)
166
+ if success:
167
+ dropped.append({"name": model_name, "target": target_name, "type": relation_type})
168
+ else:
169
+ failed.append({"name": model_name, "target": target_name, "error": "Drop failed"})
170
+
171
+ progress.advance(task)
172
+ else:
173
+ # Process each target (dry run or non-Rich)
174
+ for target_name, target_models in models_by_target.items():
175
+ self._print(f"\n Target: {target_name}", "bold")
176
+ self._print(" " + "-" * 40)
177
+
178
+ for model in target_models:
179
+ model_name = model.get("name")
180
+ relation_type = model.get("relation_type", "table")
181
+ schema = model.get("schema")
182
+ database = model.get("database")
183
+
184
+ # Build relation identifier
185
+ if database:
186
+ relation_id = f"{database}.{schema}.{model_name}"
187
+ else:
188
+ relation_id = f"{schema}.{model_name}"
189
+
190
+ if self.dry_run:
191
+ self._print(f" [would drop] {relation_id} ({relation_type}) CASCADE", "dim cyan")
192
+ dropped.append({"name": model_name, "target": target_name, "type": relation_type})
193
+ else:
194
+ success = self._drop_relation(target_name, model, use_cascade=True)
195
+ if success:
196
+ self._print(f" [green]OK[/green] Dropped {relation_id} ({relation_type}) CASCADE", "green")
197
+ dropped.append({"name": model_name, "target": target_name, "type": relation_type})
198
+ else:
199
+ self._print(f" [red]FAIL[/red] Failed to drop {relation_id}", "red")
200
+ failed.append({"name": model_name, "target": target_name, "error": "Drop failed"})
201
+
202
+ # Summary
203
+ elapsed = time.time() - start_time
204
+ self._print("")
205
+
206
+ if self._use_rich:
207
+ # Rich summary panel
208
+ if self.dry_run:
209
+ summary_text = f"[bold cyan]{len(dropped)} models would be dropped[/bold cyan]"
210
+ border_color = "cyan"
211
+ elif failed:
212
+ summary_text = f"[bold green]Dropped: {len(dropped)}[/bold green] | [bold red]Failed: {len(failed)}[/bold red]"
213
+ border_color = "yellow"
214
+ else:
215
+ summary_text = f"[bold green]Successfully dropped {len(dropped)} models[/bold green]"
216
+ border_color = "green"
217
+
218
+ self._console.print(Panel(
219
+ f"{summary_text}\n[dim]Time: {elapsed:.2f}s[/dim]",
220
+ title="[bold]Summary[/bold]",
221
+ border_style=border_color,
222
+ box=box.ROUNDED,
223
+ ))
224
+ else:
225
+ self._print("=" * 60)
226
+ if self.dry_run:
227
+ self._print(f" DRY RUN: {len(dropped)} models would be dropped")
228
+ else:
229
+ if failed:
230
+ self._print(f" Dropped: {len(dropped)} | Failed: {len(failed)}")
231
+ else:
232
+ self._print(f" Successfully dropped {len(dropped)} models")
233
+ self._print(f" Time: {elapsed:.2f}s")
234
+ self._print("=" * 60)
235
+
236
+ self._print("")
237
+
238
+ success = len(failed) == 0
239
+ return success, {"dropped": dropped, "failed": failed, "skipped": skipped}
240
+
241
+ def _get_models_to_retract(self) -> List[Dict[str, Any]]:
242
+ """Get list of materialized models from manifest."""
243
+ models = []
244
+
245
+ for node_id, node in self.manifest.nodes.items():
246
+ # Only process model nodes
247
+ if not node_id.startswith("model."):
248
+ continue
249
+
250
+ # Skip ephemeral models (not materialized)
251
+ materialization = getattr(node.config, 'materialized', 'view')
252
+ if materialization == 'ephemeral':
253
+ continue
254
+
255
+ # Determine relation type
256
+ if materialization == 'table':
257
+ relation_type = 'table'
258
+ elif materialization == 'incremental':
259
+ relation_type = 'table'
260
+ elif materialization == 'view':
261
+ relation_type = 'view'
262
+ else:
263
+ relation_type = 'table' # Default to table for custom materializations
264
+
265
+ # Get target - use model config target override or default
266
+ target = getattr(node.config, 'target', None) or self.config.target_name
267
+
268
+ # Get dependencies (models this model depends on)
269
+ depends_on = []
270
+ if hasattr(node, 'depends_on') and hasattr(node.depends_on, 'nodes'):
271
+ depends_on = [
272
+ dep for dep in node.depends_on.nodes
273
+ if dep.startswith('model.')
274
+ ]
275
+
276
+ # Get model info
277
+ model_info = {
278
+ "name": node.name,
279
+ "unique_id": node_id,
280
+ "schema": node.schema,
281
+ "database": getattr(node, 'database', None),
282
+ "relation_type": relation_type,
283
+ "target": target,
284
+ "materialization": materialization,
285
+ "depends_on": depends_on,
286
+ }
287
+ models.append(model_info)
288
+
289
+ return models
290
+
291
+ def _sort_reverse_dag_order(self, models: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
292
+ """
293
+ Sort models in REVERSE DAG order.
294
+
295
+ Models that depend on others should be dropped FIRST,
296
+ then their dependencies. This ensures we can drop without
297
+ foreign key or view dependency errors.
298
+
299
+ Uses topological sort reversed (Kahn's algorithm).
300
+ """
301
+ # Build lookup and dependency graph
302
+ model_lookup = {m["unique_id"]: m for m in models}
303
+ model_ids = set(model_lookup.keys())
304
+
305
+ # Build reverse dependency graph (who depends on me?)
306
+ # For reverse DAG order, we want to drop dependents before dependencies
307
+ dependents: Dict[str, Set[str]] = {uid: set() for uid in model_ids}
308
+ in_degree: Dict[str, int] = {uid: 0 for uid in model_ids}
309
+
310
+ for model in models:
311
+ uid = model["unique_id"]
312
+ for dep in model.get("depends_on", []):
313
+ if dep in model_ids:
314
+ # dep is a dependency of uid
315
+ # In reverse order, uid should come BEFORE dep
316
+ dependents[dep].add(uid)
317
+ in_degree[uid] += 1
318
+
319
+ # Kahn's algorithm for topological sort (reversed)
320
+ # Start with models that have no dependencies (in_degree = 0)
321
+ # These are the "leaf" nodes in the normal DAG, which should be dropped first
322
+ queue = [uid for uid, deg in in_degree.items() if deg == 0]
323
+ result = []
324
+
325
+ while queue:
326
+ # Get next model with no remaining dependencies
327
+ current = queue.pop(0)
328
+ result.append(model_lookup[current])
329
+
330
+ # For each model that depends on current
331
+ for dependent in dependents[current]:
332
+ in_degree[dependent] -= 1
333
+ if in_degree[dependent] == 0:
334
+ queue.append(dependent)
335
+
336
+ # Handle cycles (shouldn't happen in well-formed DAGs)
337
+ remaining = [m for m in models if m["unique_id"] not in {r["unique_id"] for r in result}]
338
+ result.extend(remaining)
339
+
340
+ # Reverse the result so dependents come first
341
+ # (models at the "top" of the DAG - those with many dependents - should be dropped last)
342
+ result.reverse()
343
+
344
+ return result
345
+
346
+ def _filter_models(self, models: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
347
+ """Filter models by --select and --exclude patterns."""
348
+ select_patterns = getattr(self.args, 'SELECT', None) or []
349
+ exclude_patterns = getattr(self.args, 'EXCLUDE', None) or []
350
+
351
+ # Flatten if nested
352
+ if select_patterns:
353
+ select_patterns = [p for ps in select_patterns for p in (ps if isinstance(ps, (list, tuple)) else [ps])]
354
+ if exclude_patterns:
355
+ exclude_patterns = [p for ps in exclude_patterns for p in (ps if isinstance(ps, (list, tuple)) else [ps])]
356
+
357
+ filtered = []
358
+ for model in models:
359
+ model_name = model.get("name", "")
360
+
361
+ # Check if matches any exclude pattern
362
+ if exclude_patterns:
363
+ excluded = any(fnmatch.fnmatch(model_name, p) for p in exclude_patterns)
364
+ if excluded:
365
+ continue
366
+
367
+ # Check if matches any select pattern (if provided)
368
+ if select_patterns:
369
+ selected = any(fnmatch.fnmatch(model_name, p) for p in select_patterns)
370
+ if not selected:
371
+ continue
372
+
373
+ filtered.append(model)
374
+
375
+ return filtered
376
+
377
+ def _group_models_by_target(self, models: List[Dict[str, Any]]) -> Dict[str, List[Dict[str, Any]]]:
378
+ """Group models by their target adapter, preserving order."""
379
+ grouped = {}
380
+ for model in models:
381
+ target = model.get("target", self.config.target_name)
382
+ if target not in grouped:
383
+ grouped[target] = []
384
+ grouped[target].append(model)
385
+ return grouped
386
+
387
+ def _drop_relation(self, target_name: str, model: Dict[str, Any], use_cascade: bool = True) -> bool:
388
+ """
389
+ Drop a relation from the database.
390
+
391
+ Args:
392
+ target_name: Name of the target adapter
393
+ model: Model info dictionary
394
+ use_cascade: Whether to use DROP ... CASCADE (default: True)
395
+
396
+ Returns:
397
+ True if successful, False otherwise
398
+ """
399
+ try:
400
+ from dbt.adapters.factory import get_adapter
401
+
402
+ # Get adapter for target
403
+ adapter = get_adapter(self.config)
404
+
405
+ # Build DROP statement
406
+ model_name = model.get("name")
407
+ schema = model.get("schema")
408
+ database = model.get("database")
409
+ relation_type = model.get("relation_type", "table").upper()
410
+
411
+ # Build qualified name with proper quoting
412
+ if database:
413
+ qualified_name = f'"{database}"."{schema}"."{model_name}"'
414
+ else:
415
+ qualified_name = f'"{schema}"."{model_name}"'
416
+
417
+ # Execute DROP with CASCADE
418
+ cascade_clause = " CASCADE" if use_cascade else ""
419
+ drop_sql = f"DROP {relation_type} IF EXISTS {qualified_name}{cascade_clause}"
420
+
421
+ with adapter.connection_named("retract"):
422
+ adapter.execute(drop_sql, auto_begin=True, fetch=False)
423
+ adapter.commit_if_has_connection()
424
+
425
+ return True
426
+
427
+ except Exception as e:
428
+ # Log error but don't fail the entire task
429
+ if self._use_rich:
430
+ self._console.print(f" [dim red]Error: {str(e)[:80]}[/dim red]")
431
+ return False
432
+
433
+ def interpret_results(self, results: Tuple[bool, Dict[str, Any]]) -> bool:
434
+ """Interpret task results."""
435
+ if isinstance(results, tuple):
436
+ success, data = results
437
+ return success
438
+ return bool(results)
dbt/task/retry.py ADDED
@@ -0,0 +1,175 @@
1
+ from pathlib import Path
2
+
3
+ from click import get_current_context
4
+ from click.core import ParameterSource
5
+
6
+ from dbt.artifacts.schemas.results import NodeStatus
7
+ from dbt.cli.flags import Flags
8
+ from dbt.cli.types import Command as CliCommand
9
+ from dbt.config import RuntimeConfig
10
+ from dbt.constants import RUN_RESULTS_FILE_NAME
11
+ from dbt.contracts.state import load_result_state
12
+ from dbt.flags import get_flags, set_flags
13
+ from dbt.graph import GraphQueue
14
+ from dbt.parser.manifest import parse_manifest
15
+ from dbt.task.base import ConfiguredTask
16
+ from dbt.task.build import BuildTask
17
+ from dbt.task.clone import CloneTask
18
+ from dbt.task.compile import CompileTask
19
+ from dbt.task.docs.generate import GenerateTask
20
+ from dbt.task.run import RunTask
21
+ from dbt.task.run_operation import RunOperationTask
22
+ from dbt.task.seed import SeedTask
23
+ from dbt.task.snapshot import SnapshotTask
24
+ from dbt.task.test import TestTask
25
+ from dbt_common.exceptions import DbtRuntimeError
26
+
27
+ RETRYABLE_STATUSES = {
28
+ NodeStatus.Error,
29
+ NodeStatus.Fail,
30
+ NodeStatus.Skipped,
31
+ NodeStatus.RuntimeErr,
32
+ NodeStatus.PartialSuccess,
33
+ }
34
+ IGNORE_PARENT_FLAGS = {
35
+ "log_path",
36
+ "output_path",
37
+ "profiles_dir",
38
+ "profiles_dir_exists_false",
39
+ "project_dir",
40
+ "defer_state",
41
+ "deprecated_state",
42
+ "target_path",
43
+ "warn_error",
44
+ }
45
+
46
+ ALLOW_CLI_OVERRIDE_FLAGS = {"vars", "threads"}
47
+
48
+ TASK_DICT = {
49
+ "build": BuildTask,
50
+ "compile": CompileTask,
51
+ "clone": CloneTask,
52
+ "generate": GenerateTask,
53
+ "seed": SeedTask,
54
+ "snapshot": SnapshotTask,
55
+ "test": TestTask,
56
+ "run": RunTask,
57
+ "run-operation": RunOperationTask,
58
+ }
59
+
60
+ CMD_DICT = {
61
+ "build": CliCommand.BUILD,
62
+ "compile": CliCommand.COMPILE,
63
+ "clone": CliCommand.CLONE,
64
+ "generate": CliCommand.DOCS_GENERATE,
65
+ "seed": CliCommand.SEED,
66
+ "snapshot": CliCommand.SNAPSHOT,
67
+ "test": CliCommand.TEST,
68
+ "run": CliCommand.RUN,
69
+ "run-operation": CliCommand.RUN_OPERATION,
70
+ }
71
+
72
+
73
+ class RetryTask(ConfiguredTask):
74
+ def __init__(self, args: Flags, config: RuntimeConfig) -> None:
75
+ # load previous run results
76
+ state_path = args.state or config.target_path
77
+ self.previous_results = load_result_state(
78
+ Path(config.project_root) / Path(state_path) / RUN_RESULTS_FILE_NAME
79
+ )
80
+ if not self.previous_results:
81
+ raise DbtRuntimeError(
82
+ f"Could not find previous run in '{state_path}' target directory"
83
+ )
84
+ self.previous_args = self.previous_results.args
85
+ self.previous_command_name = self.previous_args.get("which")
86
+
87
+ # Reslove flags and config
88
+ if args.warn_error:
89
+ RETRYABLE_STATUSES.add(NodeStatus.Warn)
90
+
91
+ cli_command = CMD_DICT.get(self.previous_command_name) # type: ignore
92
+ # Remove these args when their default values are present, otherwise they'll raise an exception
93
+ args_to_remove = {
94
+ "show": lambda x: True,
95
+ "resource_types": lambda x: x == [],
96
+ "warn_error_options": lambda x: x == {"warn": [], "error": [], "silence": []},
97
+ }
98
+ for k, v in args_to_remove.items():
99
+ if k in self.previous_args and v(self.previous_args[k]):
100
+ del self.previous_args[k]
101
+ previous_args = {
102
+ k: v for k, v in self.previous_args.items() if k not in IGNORE_PARENT_FLAGS
103
+ }
104
+ click_context = get_current_context()
105
+ current_args = {
106
+ k: v
107
+ for k, v in args.__dict__.items()
108
+ if k in IGNORE_PARENT_FLAGS
109
+ or (
110
+ click_context.get_parameter_source(k) == ParameterSource.COMMANDLINE
111
+ and k in ALLOW_CLI_OVERRIDE_FLAGS
112
+ )
113
+ }
114
+ combined_args = {**previous_args, **current_args}
115
+ retry_flags = Flags.from_dict(cli_command, combined_args) # type: ignore
116
+ set_flags(retry_flags)
117
+ retry_config = RuntimeConfig.from_args(args=retry_flags)
118
+
119
+ # Parse manifest using resolved config/flags
120
+ manifest = parse_manifest(retry_config, False, True, retry_flags.write_json, []) # type: ignore
121
+ super().__init__(args, retry_config, manifest)
122
+ self.task_class = TASK_DICT.get(self.previous_command_name) # type: ignore
123
+
124
+ def run(self):
125
+ unique_ids = {
126
+ result.unique_id
127
+ for result in self.previous_results.results
128
+ if result.status in RETRYABLE_STATUSES
129
+ and not (
130
+ self.previous_command_name != "run-operation"
131
+ and result.unique_id.startswith("operation.")
132
+ )
133
+ }
134
+
135
+ # We need this so that re-running of a microbatch model will only rerun
136
+ # batches that previously failed. Note _explicitly_ do no pass the
137
+ # batch info if there were _no_ successful batches previously. This is
138
+ # because passing the batch info _forces_ the microbatch process into
139
+ # _incremental_ model, and it may be that we need to be in full refresh
140
+ # mode which is only handled if previous_batch_results _isn't_ passed for a node
141
+ batch_map = {
142
+ result.unique_id: result.batch_results
143
+ for result in self.previous_results.results
144
+ if result.batch_results is not None
145
+ and len(result.batch_results.successful) != 0
146
+ and len(result.batch_results.failed) > 0
147
+ and not (
148
+ self.previous_command_name != "run-operation"
149
+ and result.unique_id.startswith("operation.")
150
+ )
151
+ }
152
+
153
+ class TaskWrapper(self.task_class):
154
+ def get_graph_queue(self):
155
+ new_graph = self.graph.get_subset_graph(unique_ids)
156
+ return GraphQueue(
157
+ new_graph.graph,
158
+ self.manifest,
159
+ unique_ids,
160
+ )
161
+
162
+ task = TaskWrapper(
163
+ get_flags(),
164
+ self.config,
165
+ self.manifest,
166
+ )
167
+
168
+ if self.task_class == RunTask:
169
+ task.batch_map = batch_map
170
+
171
+ return_value = task.run()
172
+ return return_value
173
+
174
+ def interpret_results(self, *args, **kwargs):
175
+ return self.task_class.interpret_results(*args, **kwargs)