dvt-core 1.11.0b4__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.

Potentially problematic release.


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

Files changed (261) hide show
  1. dvt/__init__.py +7 -0
  2. dvt/_pydantic_shim.py +26 -0
  3. dvt/adapters/__init__.py +16 -0
  4. dvt/adapters/multi_adapter_manager.py +268 -0
  5. dvt/artifacts/__init__.py +0 -0
  6. dvt/artifacts/exceptions/__init__.py +1 -0
  7. dvt/artifacts/exceptions/schemas.py +31 -0
  8. dvt/artifacts/resources/__init__.py +116 -0
  9. dvt/artifacts/resources/base.py +68 -0
  10. dvt/artifacts/resources/types.py +93 -0
  11. dvt/artifacts/resources/v1/analysis.py +10 -0
  12. dvt/artifacts/resources/v1/catalog.py +23 -0
  13. dvt/artifacts/resources/v1/components.py +275 -0
  14. dvt/artifacts/resources/v1/config.py +282 -0
  15. dvt/artifacts/resources/v1/documentation.py +11 -0
  16. dvt/artifacts/resources/v1/exposure.py +52 -0
  17. dvt/artifacts/resources/v1/function.py +53 -0
  18. dvt/artifacts/resources/v1/generic_test.py +32 -0
  19. dvt/artifacts/resources/v1/group.py +22 -0
  20. dvt/artifacts/resources/v1/hook.py +11 -0
  21. dvt/artifacts/resources/v1/macro.py +30 -0
  22. dvt/artifacts/resources/v1/metric.py +173 -0
  23. dvt/artifacts/resources/v1/model.py +146 -0
  24. dvt/artifacts/resources/v1/owner.py +10 -0
  25. dvt/artifacts/resources/v1/saved_query.py +112 -0
  26. dvt/artifacts/resources/v1/seed.py +42 -0
  27. dvt/artifacts/resources/v1/semantic_layer_components.py +72 -0
  28. dvt/artifacts/resources/v1/semantic_model.py +315 -0
  29. dvt/artifacts/resources/v1/singular_test.py +14 -0
  30. dvt/artifacts/resources/v1/snapshot.py +92 -0
  31. dvt/artifacts/resources/v1/source_definition.py +85 -0
  32. dvt/artifacts/resources/v1/sql_operation.py +10 -0
  33. dvt/artifacts/resources/v1/unit_test_definition.py +78 -0
  34. dvt/artifacts/schemas/__init__.py +0 -0
  35. dvt/artifacts/schemas/base.py +191 -0
  36. dvt/artifacts/schemas/batch_results.py +24 -0
  37. dvt/artifacts/schemas/catalog/__init__.py +12 -0
  38. dvt/artifacts/schemas/catalog/v1/__init__.py +0 -0
  39. dvt/artifacts/schemas/catalog/v1/catalog.py +60 -0
  40. dvt/artifacts/schemas/freshness/__init__.py +1 -0
  41. dvt/artifacts/schemas/freshness/v3/__init__.py +0 -0
  42. dvt/artifacts/schemas/freshness/v3/freshness.py +159 -0
  43. dvt/artifacts/schemas/manifest/__init__.py +2 -0
  44. dvt/artifacts/schemas/manifest/v12/__init__.py +0 -0
  45. dvt/artifacts/schemas/manifest/v12/manifest.py +212 -0
  46. dvt/artifacts/schemas/results.py +148 -0
  47. dvt/artifacts/schemas/run/__init__.py +2 -0
  48. dvt/artifacts/schemas/run/v5/__init__.py +0 -0
  49. dvt/artifacts/schemas/run/v5/run.py +184 -0
  50. dvt/artifacts/schemas/upgrades/__init__.py +4 -0
  51. dvt/artifacts/schemas/upgrades/upgrade_manifest.py +174 -0
  52. dvt/artifacts/schemas/upgrades/upgrade_manifest_dbt_version.py +2 -0
  53. dvt/artifacts/utils/validation.py +153 -0
  54. dvt/cli/__init__.py +1 -0
  55. dvt/cli/context.py +16 -0
  56. dvt/cli/exceptions.py +56 -0
  57. dvt/cli/flags.py +558 -0
  58. dvt/cli/main.py +971 -0
  59. dvt/cli/option_types.py +121 -0
  60. dvt/cli/options.py +79 -0
  61. dvt/cli/params.py +803 -0
  62. dvt/cli/requires.py +478 -0
  63. dvt/cli/resolvers.py +32 -0
  64. dvt/cli/types.py +40 -0
  65. dvt/clients/__init__.py +0 -0
  66. dvt/clients/checked_load.py +82 -0
  67. dvt/clients/git.py +164 -0
  68. dvt/clients/jinja.py +206 -0
  69. dvt/clients/jinja_static.py +245 -0
  70. dvt/clients/registry.py +192 -0
  71. dvt/clients/yaml_helper.py +68 -0
  72. dvt/compilation.py +833 -0
  73. dvt/compute/__init__.py +26 -0
  74. dvt/compute/base.py +288 -0
  75. dvt/compute/engines/__init__.py +13 -0
  76. dvt/compute/engines/duckdb_engine.py +368 -0
  77. dvt/compute/engines/spark_engine.py +273 -0
  78. dvt/compute/query_analyzer.py +212 -0
  79. dvt/compute/router.py +483 -0
  80. dvt/config/__init__.py +4 -0
  81. dvt/config/catalogs.py +95 -0
  82. dvt/config/compute_config.py +406 -0
  83. dvt/config/profile.py +411 -0
  84. dvt/config/profiles_v2.py +464 -0
  85. dvt/config/project.py +893 -0
  86. dvt/config/renderer.py +232 -0
  87. dvt/config/runtime.py +491 -0
  88. dvt/config/selectors.py +209 -0
  89. dvt/config/utils.py +78 -0
  90. dvt/connectors/.gitignore +6 -0
  91. dvt/connectors/README.md +306 -0
  92. dvt/connectors/catalog.yml +217 -0
  93. dvt/connectors/download_connectors.py +300 -0
  94. dvt/constants.py +29 -0
  95. dvt/context/__init__.py +0 -0
  96. dvt/context/base.py +746 -0
  97. dvt/context/configured.py +136 -0
  98. dvt/context/context_config.py +350 -0
  99. dvt/context/docs.py +82 -0
  100. dvt/context/exceptions_jinja.py +179 -0
  101. dvt/context/macro_resolver.py +195 -0
  102. dvt/context/macros.py +171 -0
  103. dvt/context/manifest.py +73 -0
  104. dvt/context/providers.py +2198 -0
  105. dvt/context/query_header.py +14 -0
  106. dvt/context/secret.py +59 -0
  107. dvt/context/target.py +74 -0
  108. dvt/contracts/__init__.py +0 -0
  109. dvt/contracts/files.py +413 -0
  110. dvt/contracts/graph/__init__.py +0 -0
  111. dvt/contracts/graph/manifest.py +1904 -0
  112. dvt/contracts/graph/metrics.py +98 -0
  113. dvt/contracts/graph/model_config.py +71 -0
  114. dvt/contracts/graph/node_args.py +42 -0
  115. dvt/contracts/graph/nodes.py +1806 -0
  116. dvt/contracts/graph/semantic_manifest.py +233 -0
  117. dvt/contracts/graph/unparsed.py +812 -0
  118. dvt/contracts/project.py +417 -0
  119. dvt/contracts/results.py +53 -0
  120. dvt/contracts/selection.py +23 -0
  121. dvt/contracts/sql.py +86 -0
  122. dvt/contracts/state.py +69 -0
  123. dvt/contracts/util.py +46 -0
  124. dvt/deprecations.py +347 -0
  125. dvt/deps/__init__.py +0 -0
  126. dvt/deps/base.py +153 -0
  127. dvt/deps/git.py +196 -0
  128. dvt/deps/local.py +80 -0
  129. dvt/deps/registry.py +131 -0
  130. dvt/deps/resolver.py +149 -0
  131. dvt/deps/tarball.py +121 -0
  132. dvt/docs/source/_ext/dbt_click.py +118 -0
  133. dvt/docs/source/conf.py +32 -0
  134. dvt/env_vars.py +64 -0
  135. dvt/event_time/event_time.py +40 -0
  136. dvt/event_time/sample_window.py +60 -0
  137. dvt/events/__init__.py +16 -0
  138. dvt/events/base_types.py +37 -0
  139. dvt/events/core_types_pb2.py +2 -0
  140. dvt/events/logging.py +109 -0
  141. dvt/events/types.py +2534 -0
  142. dvt/exceptions.py +1487 -0
  143. dvt/flags.py +89 -0
  144. dvt/graph/__init__.py +11 -0
  145. dvt/graph/cli.py +248 -0
  146. dvt/graph/graph.py +172 -0
  147. dvt/graph/queue.py +213 -0
  148. dvt/graph/selector.py +375 -0
  149. dvt/graph/selector_methods.py +976 -0
  150. dvt/graph/selector_spec.py +223 -0
  151. dvt/graph/thread_pool.py +18 -0
  152. dvt/hooks.py +21 -0
  153. dvt/include/README.md +49 -0
  154. dvt/include/__init__.py +3 -0
  155. dvt/include/global_project.py +4 -0
  156. dvt/include/starter_project/.gitignore +4 -0
  157. dvt/include/starter_project/README.md +15 -0
  158. dvt/include/starter_project/__init__.py +3 -0
  159. dvt/include/starter_project/analyses/.gitkeep +0 -0
  160. dvt/include/starter_project/dvt_project.yml +36 -0
  161. dvt/include/starter_project/macros/.gitkeep +0 -0
  162. dvt/include/starter_project/models/example/my_first_dbt_model.sql +27 -0
  163. dvt/include/starter_project/models/example/my_second_dbt_model.sql +6 -0
  164. dvt/include/starter_project/models/example/schema.yml +21 -0
  165. dvt/include/starter_project/seeds/.gitkeep +0 -0
  166. dvt/include/starter_project/snapshots/.gitkeep +0 -0
  167. dvt/include/starter_project/tests/.gitkeep +0 -0
  168. dvt/internal_deprecations.py +27 -0
  169. dvt/jsonschemas/__init__.py +3 -0
  170. dvt/jsonschemas/jsonschemas.py +309 -0
  171. dvt/jsonschemas/project/0.0.110.json +4717 -0
  172. dvt/jsonschemas/project/0.0.85.json +2015 -0
  173. dvt/jsonschemas/resources/0.0.110.json +2636 -0
  174. dvt/jsonschemas/resources/0.0.85.json +2536 -0
  175. dvt/jsonschemas/resources/latest.json +6773 -0
  176. dvt/links.py +4 -0
  177. dvt/materializations/__init__.py +0 -0
  178. dvt/materializations/incremental/__init__.py +0 -0
  179. dvt/materializations/incremental/microbatch.py +235 -0
  180. dvt/mp_context.py +8 -0
  181. dvt/node_types.py +37 -0
  182. dvt/parser/__init__.py +23 -0
  183. dvt/parser/analysis.py +21 -0
  184. dvt/parser/base.py +549 -0
  185. dvt/parser/common.py +267 -0
  186. dvt/parser/docs.py +52 -0
  187. dvt/parser/fixtures.py +51 -0
  188. dvt/parser/functions.py +30 -0
  189. dvt/parser/generic_test.py +100 -0
  190. dvt/parser/generic_test_builders.py +334 -0
  191. dvt/parser/hooks.py +119 -0
  192. dvt/parser/macros.py +137 -0
  193. dvt/parser/manifest.py +2204 -0
  194. dvt/parser/models.py +574 -0
  195. dvt/parser/partial.py +1179 -0
  196. dvt/parser/read_files.py +445 -0
  197. dvt/parser/schema_generic_tests.py +423 -0
  198. dvt/parser/schema_renderer.py +111 -0
  199. dvt/parser/schema_yaml_readers.py +936 -0
  200. dvt/parser/schemas.py +1467 -0
  201. dvt/parser/search.py +149 -0
  202. dvt/parser/seeds.py +28 -0
  203. dvt/parser/singular_test.py +20 -0
  204. dvt/parser/snapshots.py +44 -0
  205. dvt/parser/sources.py +557 -0
  206. dvt/parser/sql.py +63 -0
  207. dvt/parser/unit_tests.py +622 -0
  208. dvt/plugins/__init__.py +20 -0
  209. dvt/plugins/contracts.py +10 -0
  210. dvt/plugins/exceptions.py +2 -0
  211. dvt/plugins/manager.py +164 -0
  212. dvt/plugins/manifest.py +21 -0
  213. dvt/profiler.py +20 -0
  214. dvt/py.typed +1 -0
  215. dvt/runners/__init__.py +2 -0
  216. dvt/runners/exposure_runner.py +7 -0
  217. dvt/runners/no_op_runner.py +46 -0
  218. dvt/runners/saved_query_runner.py +7 -0
  219. dvt/selected_resources.py +8 -0
  220. dvt/task/__init__.py +0 -0
  221. dvt/task/base.py +504 -0
  222. dvt/task/build.py +197 -0
  223. dvt/task/clean.py +57 -0
  224. dvt/task/clone.py +162 -0
  225. dvt/task/compile.py +151 -0
  226. dvt/task/compute.py +366 -0
  227. dvt/task/debug.py +650 -0
  228. dvt/task/deps.py +280 -0
  229. dvt/task/docs/__init__.py +3 -0
  230. dvt/task/docs/generate.py +408 -0
  231. dvt/task/docs/index.html +250 -0
  232. dvt/task/docs/serve.py +28 -0
  233. dvt/task/freshness.py +323 -0
  234. dvt/task/function.py +122 -0
  235. dvt/task/group_lookup.py +46 -0
  236. dvt/task/init.py +374 -0
  237. dvt/task/list.py +237 -0
  238. dvt/task/printer.py +176 -0
  239. dvt/task/profiles.py +256 -0
  240. dvt/task/retry.py +175 -0
  241. dvt/task/run.py +1146 -0
  242. dvt/task/run_operation.py +142 -0
  243. dvt/task/runnable.py +802 -0
  244. dvt/task/seed.py +104 -0
  245. dvt/task/show.py +150 -0
  246. dvt/task/snapshot.py +57 -0
  247. dvt/task/sql.py +111 -0
  248. dvt/task/test.py +464 -0
  249. dvt/tests/fixtures/__init__.py +1 -0
  250. dvt/tests/fixtures/project.py +620 -0
  251. dvt/tests/util.py +651 -0
  252. dvt/tracking.py +529 -0
  253. dvt/utils/__init__.py +3 -0
  254. dvt/utils/artifact_upload.py +151 -0
  255. dvt/utils/utils.py +408 -0
  256. dvt/version.py +249 -0
  257. dvt_core-1.11.0b4.dist-info/METADATA +252 -0
  258. dvt_core-1.11.0b4.dist-info/RECORD +261 -0
  259. dvt_core-1.11.0b4.dist-info/WHEEL +5 -0
  260. dvt_core-1.11.0b4.dist-info/entry_points.txt +2 -0
  261. dvt_core-1.11.0b4.dist-info/top_level.txt +1 -0
@@ -0,0 +1,275 @@
1
+ import time
2
+ from dataclasses import dataclass, field
3
+ from datetime import timedelta
4
+ from typing import Any, Dict, List, Optional, Union
5
+
6
+ from dvt.artifacts.resources.base import Docs, FileHash, GraphResource
7
+ from dvt.artifacts.resources.types import NodeType, TimePeriod
8
+ from dvt.artifacts.resources.v1.config import NodeConfig
9
+
10
+ from dbt_common.contracts.config.base import BaseConfig, MergeBehavior
11
+ from dbt_common.contracts.config.properties import AdditionalPropertiesMixin
12
+ from dbt_common.contracts.constraints import ColumnLevelConstraint
13
+ from dbt_common.contracts.util import Mergeable
14
+ from dbt_common.dataclass_schema import ExtensibleDbtClassMixin, dbtClassMixin
15
+ from dbt_semantic_interfaces.type_enums import TimeGranularity
16
+
17
+ NodeVersion = Union[str, float]
18
+
19
+
20
+ def _backcompat_doc_blocks(doc_blocks: Any) -> List[str]:
21
+ """
22
+ Make doc_blocks backwards-compatible for scenarios where a user specifies `doc_blocks` on a model or column.
23
+ Mashumaro will raise a serialization error if the specified `doc_blocks` isn't a list of strings.
24
+ In such a scenario, this method returns an empty list to avoid a serialization error.
25
+ Further along, `_get_doc_blocks` in `manifest.py` populates the correct `doc_blocks` for the happy path.
26
+ """
27
+
28
+ if isinstance(doc_blocks, list) and all(isinstance(x, str) for x in doc_blocks):
29
+ return doc_blocks
30
+
31
+ return []
32
+
33
+
34
+ @dataclass
35
+ class MacroDependsOn(dbtClassMixin):
36
+ macros: List[str] = field(default_factory=list)
37
+
38
+ # 'in' on lists is O(n) so this is O(n^2) for # of macros
39
+ def add_macro(self, value: str):
40
+ if value not in self.macros:
41
+ self.macros.append(value)
42
+
43
+
44
+ @dataclass
45
+ class DependsOn(MacroDependsOn):
46
+ nodes: List[str] = field(default_factory=list)
47
+
48
+ def add_node(self, value: str):
49
+ if value not in self.nodes:
50
+ self.nodes.append(value)
51
+
52
+
53
+ @dataclass
54
+ class RefArgs(dbtClassMixin):
55
+ name: str
56
+ package: Optional[str] = None
57
+ version: Optional[NodeVersion] = None
58
+
59
+ @property
60
+ def positional_args(self) -> List[str]:
61
+ if self.package:
62
+ return [self.package, self.name]
63
+ else:
64
+ return [self.name]
65
+
66
+ @property
67
+ def keyword_args(self) -> Dict[str, Optional[NodeVersion]]:
68
+ if self.version:
69
+ return {"version": self.version}
70
+ else:
71
+ return {}
72
+
73
+
74
+ @dataclass
75
+ class ColumnConfig(BaseConfig):
76
+ meta: Dict[str, Any] = field(default_factory=dict, metadata=MergeBehavior.Update.meta())
77
+ tags: List[str] = field(default_factory=list)
78
+
79
+
80
+ @dataclass
81
+ class ColumnInfo(AdditionalPropertiesMixin, ExtensibleDbtClassMixin):
82
+ """Used in all ManifestNodes and SourceDefinition"""
83
+
84
+ name: str
85
+ description: str = ""
86
+ meta: Dict[str, Any] = field(default_factory=dict)
87
+ data_type: Optional[str] = None
88
+ constraints: List[ColumnLevelConstraint] = field(default_factory=list)
89
+ quote: Optional[bool] = None
90
+ config: ColumnConfig = field(default_factory=ColumnConfig)
91
+ tags: List[str] = field(default_factory=list)
92
+ _extra: Dict[str, Any] = field(default_factory=dict)
93
+ granularity: Optional[TimeGranularity] = None
94
+ doc_blocks: List[str] = field(default_factory=list)
95
+
96
+ def __post_serialize__(self, dct: Dict, context: Optional[Dict] = None) -> dict:
97
+ dct = super().__post_serialize__(dct, context)
98
+ dct["doc_blocks"] = _backcompat_doc_blocks(dct["doc_blocks"])
99
+ return dct
100
+
101
+
102
+ @dataclass
103
+ class InjectedCTE(dbtClassMixin):
104
+ """Used in CompiledNodes as part of ephemeral model processing"""
105
+
106
+ id: str
107
+ sql: str
108
+
109
+
110
+ @dataclass
111
+ class Contract(dbtClassMixin):
112
+ enforced: bool = False
113
+ alias_types: bool = True
114
+ checksum: Optional[str] = None
115
+
116
+
117
+ @dataclass
118
+ class Quoting(dbtClassMixin, Mergeable):
119
+ database: Optional[bool] = None
120
+ schema: Optional[bool] = None
121
+ identifier: Optional[bool] = None
122
+ column: Optional[bool] = None
123
+
124
+
125
+ @dataclass
126
+ class Time(dbtClassMixin, Mergeable):
127
+ count: Optional[int] = None
128
+ period: Optional[TimePeriod] = None
129
+
130
+ def exceeded(self, actual_age: float) -> bool:
131
+ if self.period is None or self.count is None:
132
+ return False
133
+ kwargs: Dict[str, int] = {self.period.plural(): self.count}
134
+ difference = timedelta(**kwargs).total_seconds()
135
+ return actual_age > difference
136
+
137
+ def __bool__(self):
138
+ return self.count is not None and self.period is not None
139
+
140
+
141
+ @dataclass
142
+ class FreshnessThreshold(dbtClassMixin, Mergeable):
143
+ warn_after: Optional[Time] = field(default_factory=Time)
144
+ error_after: Optional[Time] = field(default_factory=Time)
145
+ filter: Optional[str] = None
146
+
147
+ def status(self, age: float) -> "dbt.artifacts.schemas.results.FreshnessStatus": # type: ignore # noqa F821
148
+ from dvt.artifacts.schemas.results import FreshnessStatus
149
+
150
+ if self.error_after and self.error_after.exceeded(age):
151
+ return FreshnessStatus.Error
152
+ elif self.warn_after and self.warn_after.exceeded(age):
153
+ return FreshnessStatus.Warn
154
+ else:
155
+ return FreshnessStatus.Pass
156
+
157
+ def __bool__(self):
158
+ return bool(self.warn_after) or bool(self.error_after)
159
+
160
+
161
+ @dataclass
162
+ class HasRelationMetadata(dbtClassMixin):
163
+ database: Optional[str]
164
+ schema: str
165
+
166
+ # Can't set database to None like it ought to be
167
+ # because it messes up the subclasses and default parameters
168
+ # so hack it here
169
+ @classmethod
170
+ def __pre_deserialize__(cls, data):
171
+ data = super().__pre_deserialize__(data)
172
+ if "database" not in data:
173
+ data["database"] = None
174
+ return data
175
+
176
+ @property
177
+ def quoting_dict(self) -> Dict[str, bool]:
178
+ if hasattr(self, "quoting"):
179
+ return self.quoting.to_dict(omit_none=True)
180
+ else:
181
+ return {}
182
+
183
+
184
+ @dataclass
185
+ class DeferRelation(HasRelationMetadata):
186
+ alias: str
187
+ relation_name: Optional[str]
188
+ # The rest of these fields match RelationConfig protocol exactly
189
+ resource_type: NodeType
190
+ name: str
191
+ description: str
192
+ compiled_code: Optional[str]
193
+ meta: Dict[str, Any]
194
+ tags: List[str]
195
+ config: Optional[NodeConfig]
196
+
197
+ @property
198
+ def identifier(self):
199
+ return self.alias
200
+
201
+
202
+ @dataclass
203
+ class ParsedResourceMandatory(GraphResource, HasRelationMetadata):
204
+ alias: str
205
+ checksum: FileHash
206
+ config: NodeConfig = field(default_factory=NodeConfig)
207
+
208
+ @property
209
+ def identifier(self):
210
+ return self.alias
211
+
212
+
213
+ @dataclass
214
+ class ParsedResource(ParsedResourceMandatory):
215
+ tags: List[str] = field(default_factory=list)
216
+ description: str = field(default="")
217
+ columns: Dict[str, ColumnInfo] = field(default_factory=dict)
218
+ meta: Dict[str, Any] = field(default_factory=dict)
219
+ group: Optional[str] = None
220
+ docs: Docs = field(default_factory=Docs)
221
+ patch_path: Optional[str] = None
222
+ build_path: Optional[str] = None
223
+ unrendered_config: Dict[str, Any] = field(default_factory=dict)
224
+ created_at: float = field(default_factory=lambda: time.time())
225
+ config_call_dict: Dict[str, Any] = field(default_factory=dict)
226
+ unrendered_config_call_dict: Dict[str, Any] = field(default_factory=dict)
227
+ relation_name: Optional[str] = None
228
+ raw_code: str = ""
229
+ doc_blocks: List[str] = field(default_factory=list)
230
+
231
+ def __post_serialize__(self, dct: Dict, context: Optional[Dict] = None):
232
+ dct = super().__post_serialize__(dct, context)
233
+
234
+ if context and context.get("artifact") and "config_call_dict" in dct:
235
+ del dct["config_call_dict"]
236
+ if context and context.get("artifact") and "unrendered_config_call_dict" in dct:
237
+ del dct["unrendered_config_call_dict"]
238
+
239
+ dct["doc_blocks"] = _backcompat_doc_blocks(dct["doc_blocks"])
240
+
241
+ return dct
242
+
243
+
244
+ @dataclass
245
+ class CompiledResource(ParsedResource):
246
+ """Contains attributes necessary for SQL files and nodes with refs, sources, etc,
247
+ so all ManifestNodes except SeedNode."""
248
+
249
+ language: str = "sql"
250
+ refs: List[RefArgs] = field(default_factory=list)
251
+ sources: List[List[str]] = field(default_factory=list)
252
+ metrics: List[List[str]] = field(default_factory=list)
253
+ functions: List[List[str]] = field(default_factory=list)
254
+ depends_on: DependsOn = field(default_factory=DependsOn)
255
+ compiled_path: Optional[str] = None
256
+ compiled: bool = False
257
+ compiled_code: Optional[str] = None
258
+ extra_ctes_injected: bool = False
259
+ extra_ctes: List[InjectedCTE] = field(default_factory=list)
260
+ _pre_injected_sql: Optional[str] = None
261
+ contract: Contract = field(default_factory=Contract)
262
+
263
+ def __post_serialize__(self, dct: Dict, context: Optional[Dict] = None):
264
+ dct = super().__post_serialize__(dct, context)
265
+ if "_pre_injected_sql" in dct:
266
+ del dct["_pre_injected_sql"]
267
+ # Remove compiled attributes
268
+ if "compiled" in dct and dct["compiled"] is False:
269
+ del dct["compiled"]
270
+ del dct["extra_ctes_injected"]
271
+ del dct["extra_ctes"]
272
+ # "omit_none" means these might not be in the dictionary
273
+ if "compiled_code" in dct:
274
+ del dct["compiled_code"]
275
+ return dct
@@ -0,0 +1,282 @@
1
+ import re
2
+ from dataclasses import dataclass, field
3
+ from typing import Any, Dict, List, Optional, Union
4
+
5
+ from dvt import hooks
6
+ from dvt.artifacts.resources.base import Docs
7
+ from dvt.artifacts.resources.types import ModelHookType
8
+ from dvt.artifacts.utils.validation import validate_color
9
+ from mashumaro.jsonschema.annotations import Pattern
10
+ from typing_extensions import Annotated
11
+
12
+ from dbt_common.contracts.config.base import BaseConfig, CompareBehavior, MergeBehavior
13
+ from dbt_common.contracts.config.materialization import OnConfigurationChangeOption
14
+ from dbt_common.contracts.config.metadata import Metadata, ShowBehavior
15
+ from dbt_common.dataclass_schema import ValidationError, dbtClassMixin
16
+
17
+
18
+ def list_str() -> List[str]:
19
+ return []
20
+
21
+
22
+ class Severity(str):
23
+ pass
24
+
25
+
26
+ def metas(*metas: Metadata) -> Dict[str, Any]:
27
+ existing: Dict[str, Any] = {}
28
+ for m in metas:
29
+ existing = m.meta(existing)
30
+ return existing
31
+
32
+
33
+ @dataclass
34
+ class ContractConfig(dbtClassMixin):
35
+ enforced: bool = False
36
+ alias_types: bool = True
37
+
38
+
39
+ @dataclass
40
+ class Hook(dbtClassMixin):
41
+ sql: str
42
+ transaction: bool = True
43
+ index: Optional[int] = None
44
+
45
+
46
+ @dataclass
47
+ class NodeAndTestConfig(BaseConfig):
48
+ enabled: bool = True
49
+ # these fields are included in serialized output, but are not part of
50
+ # config comparison (they are part of database_representation)
51
+ alias: Optional[str] = field(
52
+ default=None,
53
+ metadata=CompareBehavior.Exclude.meta(),
54
+ )
55
+ schema: Optional[str] = field(
56
+ default=None,
57
+ metadata=CompareBehavior.Exclude.meta(),
58
+ )
59
+ database: Optional[str] = field(
60
+ default=None,
61
+ metadata=CompareBehavior.Exclude.meta(),
62
+ )
63
+ tags: Union[List[str], str] = field(
64
+ default_factory=list_str,
65
+ metadata=metas(ShowBehavior.Hide, MergeBehavior.Append, CompareBehavior.Exclude),
66
+ )
67
+ meta: Dict[str, Any] = field(
68
+ default_factory=dict,
69
+ metadata=MergeBehavior.Update.meta(),
70
+ )
71
+ group: Optional[str] = field(
72
+ default=None,
73
+ metadata=CompareBehavior.Exclude.meta(),
74
+ )
75
+
76
+
77
+ @dataclass
78
+ class NodeConfig(NodeAndTestConfig):
79
+ # Note: if any new fields are added with MergeBehavior, also update the
80
+ # 'mergebehavior' dictionary
81
+ materialized: str = "view"
82
+ incremental_strategy: Optional[str] = None
83
+ batch_size: Any = None
84
+ lookback: Any = 1
85
+ begin: Any = None
86
+ persist_docs: Dict[str, Any] = field(default_factory=dict)
87
+ post_hook: List[Hook] = field(
88
+ default_factory=list,
89
+ metadata={"merge": MergeBehavior.Append, "alias": "post-hook"},
90
+ )
91
+ pre_hook: List[Hook] = field(
92
+ default_factory=list,
93
+ metadata={"merge": MergeBehavior.Append, "alias": "pre-hook"},
94
+ )
95
+ quoting: Dict[str, Any] = field(
96
+ default_factory=dict,
97
+ metadata=MergeBehavior.Update.meta(),
98
+ )
99
+ # This is actually only used by seeds. Should it be available to others?
100
+ # That would be a breaking change!
101
+ column_types: Dict[str, Any] = field(
102
+ default_factory=dict,
103
+ metadata=MergeBehavior.Update.meta(),
104
+ )
105
+ full_refresh: Optional[bool] = None
106
+ # 'unique_key' doesn't use 'Optional' because typing.get_type_hints was
107
+ # sometimes getting the Union order wrong, causing serialization failures.
108
+ unique_key: Union[str, List[str], None] = None
109
+ on_schema_change: Optional[str] = "ignore"
110
+ on_configuration_change: OnConfigurationChangeOption = field(
111
+ default_factory=OnConfigurationChangeOption.default
112
+ )
113
+ grants: Dict[str, Any] = field(
114
+ default_factory=dict, metadata=MergeBehavior.DictKeyAppend.meta()
115
+ )
116
+ packages: List[str] = field(
117
+ default_factory=list,
118
+ metadata=MergeBehavior.Append.meta(),
119
+ )
120
+ docs: Docs = field(
121
+ default_factory=Docs,
122
+ metadata=MergeBehavior.Update.meta(),
123
+ )
124
+ contract: ContractConfig = field(
125
+ default_factory=ContractConfig,
126
+ metadata=MergeBehavior.Update.meta(),
127
+ )
128
+ event_time: Any = None
129
+ concurrent_batches: Any = None
130
+
131
+ # DVT-specific configuration fields
132
+ compute_engine: Optional[str] = field(
133
+ default=None,
134
+ metadata=MergeBehavior.Clobber.meta(),
135
+ )
136
+ pushdown_enabled: Optional[bool] = field(
137
+ default=None,
138
+ metadata=MergeBehavior.Clobber.meta(),
139
+ )
140
+ target_profile: Optional[str] = field(
141
+ default=None,
142
+ metadata=MergeBehavior.Clobber.meta(),
143
+ )
144
+
145
+ def __post_init__(self):
146
+ # we validate that node_color has a suitable value to prevent dbt-docs from crashing
147
+ if self.docs.node_color:
148
+ node_color = self.docs.node_color
149
+ if not validate_color(node_color):
150
+ raise ValidationError(
151
+ f"Invalid color name for docs.node_color: {node_color}. "
152
+ "It is neither a valid HTML color name nor a valid HEX code."
153
+ )
154
+
155
+ if (
156
+ self.contract.enforced
157
+ and self.materialized == "incremental"
158
+ and self.on_schema_change not in ("append_new_columns", "fail")
159
+ ):
160
+ raise ValidationError(
161
+ f"Invalid value for on_schema_change: {self.on_schema_change}. Models "
162
+ "materialized as incremental with contracts enabled must set "
163
+ "on_schema_change to 'append_new_columns' or 'fail'"
164
+ )
165
+
166
+ @classmethod
167
+ def __pre_deserialize__(cls, data):
168
+ data = super().__pre_deserialize__(data)
169
+ for key in ModelHookType:
170
+ if key in data:
171
+ data[key] = [hooks.get_hook_dict(h) for h in data[key]]
172
+ return data
173
+
174
+
175
+ SEVERITY_PATTERN = r"^([Ww][Aa][Rr][Nn]|[Ee][Rr][Rr][Oo][Rr])$"
176
+
177
+
178
+ @dataclass
179
+ class TestConfig(NodeAndTestConfig):
180
+ __test__ = False
181
+
182
+ # this is repeated because of a different default
183
+ schema: Optional[str] = field(
184
+ default="dbt_test__audit",
185
+ metadata=CompareBehavior.Exclude.meta(),
186
+ )
187
+ materialized: str = "test"
188
+ # Annotated is used by mashumaro for jsonschema generation
189
+ severity: Annotated[Severity, Pattern(SEVERITY_PATTERN)] = Severity("ERROR")
190
+ store_failures: Optional[bool] = None
191
+ store_failures_as: Optional[str] = None
192
+ where: Optional[str] = None
193
+ limit: Optional[int] = None
194
+ fail_calc: str = "count(*)"
195
+ warn_if: str = "!= 0"
196
+ error_if: str = "!= 0"
197
+
198
+ def finalize_and_validate(self):
199
+ """
200
+ The presence of a setting for `store_failures_as` overrides any existing setting for `store_failures`,
201
+ regardless of level of granularity. If `store_failures_as` is not set, then `store_failures` takes effect.
202
+ At the time of implementation, `store_failures = True` would always create a table; the user could not
203
+ configure this. Hence, if `store_failures = True` and `store_failures_as` is not specified, then it
204
+ should be set to "table" to mimic the existing functionality.
205
+
206
+ A side effect of this overriding functionality is that `store_failures_as="view"` at the project
207
+ level cannot be turned off at the model level without setting both `store_failures_as` and
208
+ `store_failures`. The former would cascade down and override `store_failures=False`. The proposal
209
+ is to include "ephemeral" as a value for `store_failures_as`, which effectively sets
210
+ `store_failures=False`.
211
+
212
+ The exception handling for this is tricky. If we raise an exception here, the entire run fails at
213
+ parse time. We would rather well-formed models run successfully, leaving only exceptions to be rerun
214
+ if necessary. Hence, the exception needs to be raised in the test materialization. In order to do so,
215
+ we need to make sure that we go down the `store_failures = True` route with the invalid setting for
216
+ `store_failures_as`. This results in the `.get()` defaulted to `True` below, instead of a normal
217
+ dictionary lookup as is done in the `if` block. Refer to the test materialization for the
218
+ exception that is raise as a result of an invalid value.
219
+
220
+ The intention of this block is to behave as if `store_failures_as` is the only setting,
221
+ but still allow for backwards compatibility for `store_failures`.
222
+ See https://github.com/dbt-labs/dbt-core/issues/6914 for more information.
223
+ """
224
+ super().finalize_and_validate()
225
+
226
+ # if `store_failures_as` is not set, it gets set by `store_failures`
227
+ # the settings below mimic existing behavior prior to `store_failures_as`
228
+ get_store_failures_as_map = {
229
+ True: "table",
230
+ False: "ephemeral",
231
+ None: None,
232
+ }
233
+
234
+ # if `store_failures_as` is set, it dictates what `store_failures` gets set to
235
+ # the settings below overrides whatever `store_failures` is set to by the user
236
+ get_store_failures_map = {
237
+ "ephemeral": False,
238
+ "table": True,
239
+ "view": True,
240
+ }
241
+
242
+ if self.store_failures_as is None:
243
+ self.store_failures_as = get_store_failures_as_map[self.store_failures]
244
+ else:
245
+ self.store_failures = get_store_failures_map.get(self.store_failures_as, True)
246
+
247
+ return self
248
+
249
+ @classmethod
250
+ def same_contents(cls, unrendered: Dict[str, Any], other: Dict[str, Any]) -> bool:
251
+ """This is like __eq__, except it explicitly checks certain fields."""
252
+ modifiers = [
253
+ "severity",
254
+ "where",
255
+ "limit",
256
+ "fail_calc",
257
+ "warn_if",
258
+ "error_if",
259
+ "store_failures",
260
+ "store_failures_as",
261
+ ]
262
+
263
+ seen = set()
264
+ for _, target_name in cls._get_fields():
265
+ key = target_name
266
+ seen.add(key)
267
+ if key in modifiers:
268
+ if not cls.compare_key(unrendered, other, key):
269
+ return False
270
+ return True
271
+
272
+ @classmethod
273
+ def validate(cls, data):
274
+ if data.get("severity") and not re.match(SEVERITY_PATTERN, data.get("severity")):
275
+ raise ValidationError(
276
+ f"Severity must be either 'warn' or 'error'. Got '{data.get('severity')}'"
277
+ )
278
+
279
+ super().validate(data)
280
+
281
+ if data.get("materialized") and data.get("materialized") != "test":
282
+ raise ValidationError("A test must have a materialized value of 'test'")
@@ -0,0 +1,11 @@
1
+ from dataclasses import dataclass
2
+ from typing import Literal
3
+
4
+ from dvt.artifacts.resources.base import BaseResource
5
+ from dvt.artifacts.resources.types import NodeType
6
+
7
+
8
+ @dataclass
9
+ class Documentation(BaseResource):
10
+ resource_type: Literal[NodeType.Documentation]
11
+ block_contents: str
@@ -0,0 +1,52 @@
1
+ import time
2
+ from dataclasses import dataclass, field
3
+ from typing import Any, Dict, List, Literal, Optional
4
+
5
+ from dvt.artifacts.resources.base import GraphResource
6
+ from dvt.artifacts.resources.types import NodeType
7
+ from dvt.artifacts.resources.v1.components import DependsOn, RefArgs
8
+ from dvt.artifacts.resources.v1.owner import Owner
9
+
10
+ from dbt_common.contracts.config.base import BaseConfig
11
+ from dbt_common.dataclass_schema import StrEnum
12
+
13
+
14
+ class ExposureType(StrEnum):
15
+ Dashboard = "dashboard"
16
+ Notebook = "notebook"
17
+ Analysis = "analysis"
18
+ ML = "ml"
19
+ Application = "application"
20
+
21
+
22
+ class MaturityType(StrEnum):
23
+ Low = "low"
24
+ Medium = "medium"
25
+ High = "high"
26
+
27
+
28
+ @dataclass
29
+ class ExposureConfig(BaseConfig):
30
+ enabled: bool = True
31
+ tags: List[str] = field(default_factory=list)
32
+ meta: Dict[str, Any] = field(default_factory=dict)
33
+
34
+
35
+ @dataclass
36
+ class Exposure(GraphResource):
37
+ type: ExposureType
38
+ owner: Owner
39
+ resource_type: Literal[NodeType.Exposure]
40
+ description: str = ""
41
+ label: Optional[str] = None
42
+ maturity: Optional[MaturityType] = None
43
+ meta: Dict[str, Any] = field(default_factory=dict)
44
+ tags: List[str] = field(default_factory=list)
45
+ config: ExposureConfig = field(default_factory=ExposureConfig)
46
+ unrendered_config: Dict[str, Any] = field(default_factory=dict)
47
+ url: Optional[str] = None
48
+ depends_on: DependsOn = field(default_factory=DependsOn)
49
+ refs: List[RefArgs] = field(default_factory=list)
50
+ sources: List[List[str]] = field(default_factory=list)
51
+ metrics: List[List[str]] = field(default_factory=list)
52
+ created_at: float = field(default_factory=lambda: time.time())
@@ -0,0 +1,53 @@
1
+ from dataclasses import dataclass, field
2
+ from typing import List, Literal, Optional
3
+
4
+ from dvt.artifacts.resources.types import FunctionType, FunctionVolatility, NodeType
5
+ from dvt.artifacts.resources.v1.components import CompiledResource
6
+ from dvt.artifacts.resources.v1.config import NodeConfig
7
+
8
+ from dbt_common.dataclass_schema import dbtClassMixin
9
+
10
+ # =============
11
+ # Function config, and supporting classes
12
+ # =============
13
+
14
+
15
+ @dataclass
16
+ class FunctionConfig(NodeConfig):
17
+ # The fact that this is a property, that can be changed, seems wrong.
18
+ # A function's materialization should never be changed, so why allow for it?
19
+ materialized: str = "function"
20
+ type: FunctionType = FunctionType.Scalar
21
+ volatility: Optional[FunctionVolatility] = None
22
+ runtime_version: Optional[str] = None
23
+ entry_point: Optional[str] = None
24
+
25
+
26
+ # =============
27
+ # Function resource, and supporting classes
28
+ # =============
29
+
30
+
31
+ @dataclass
32
+ class FunctionArgument(dbtClassMixin):
33
+ name: str
34
+ data_type: str
35
+ description: Optional[str] = None
36
+
37
+
38
+ @dataclass
39
+ class FunctionReturns(dbtClassMixin):
40
+ data_type: str
41
+ description: Optional[str] = None
42
+
43
+
44
+ @dataclass
45
+ class FunctionMandatory(dbtClassMixin):
46
+ returns: FunctionReturns
47
+
48
+
49
+ @dataclass
50
+ class Function(CompiledResource, FunctionMandatory):
51
+ resource_type: Literal[NodeType.Function]
52
+ config: FunctionConfig
53
+ arguments: List[FunctionArgument] = field(default_factory=list)