dvt-core 0.52.2__cp310-cp310-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 (275) 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 +2039 -0
  57. dbt/cli/option_types.py +121 -0
  58. dbt/cli/options.py +80 -0
  59. dbt/cli/params.py +804 -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.py +624 -0
  74. dbt/compute/federated_executor.py +837 -0
  75. dbt/compute/filter_pushdown.cpython-310-darwin.so +0 -0
  76. dbt/compute/filter_pushdown.py +273 -0
  77. dbt/compute/jar_provisioning.cpython-310-darwin.so +0 -0
  78. dbt/compute/jar_provisioning.py +255 -0
  79. dbt/compute/java_compat.cpython-310-darwin.so +0 -0
  80. dbt/compute/java_compat.py +689 -0
  81. dbt/compute/jdbc_utils.cpython-310-darwin.so +0 -0
  82. dbt/compute/jdbc_utils.py +678 -0
  83. dbt/compute/smart_selector.cpython-310-darwin.so +0 -0
  84. dbt/compute/smart_selector.py +311 -0
  85. dbt/compute/strategies/__init__.py +54 -0
  86. dbt/compute/strategies/base.py +165 -0
  87. dbt/compute/strategies/dataproc.py +207 -0
  88. dbt/compute/strategies/emr.py +203 -0
  89. dbt/compute/strategies/local.py +364 -0
  90. dbt/compute/strategies/standalone.py +262 -0
  91. dbt/config/__init__.py +4 -0
  92. dbt/config/catalogs.py +94 -0
  93. dbt/config/compute.cpython-310-darwin.so +0 -0
  94. dbt/config/compute.py +547 -0
  95. dbt/config/dvt_profile.cpython-310-darwin.so +0 -0
  96. dbt/config/dvt_profile.py +342 -0
  97. dbt/config/profile.py +422 -0
  98. dbt/config/project.py +873 -0
  99. dbt/config/project_utils.py +28 -0
  100. dbt/config/renderer.py +231 -0
  101. dbt/config/runtime.py +553 -0
  102. dbt/config/selectors.py +208 -0
  103. dbt/config/utils.py +77 -0
  104. dbt/constants.py +28 -0
  105. dbt/context/__init__.py +0 -0
  106. dbt/context/base.py +745 -0
  107. dbt/context/configured.py +135 -0
  108. dbt/context/context_config.py +382 -0
  109. dbt/context/docs.py +82 -0
  110. dbt/context/exceptions_jinja.py +178 -0
  111. dbt/context/macro_resolver.py +195 -0
  112. dbt/context/macros.py +171 -0
  113. dbt/context/manifest.py +72 -0
  114. dbt/context/providers.py +2249 -0
  115. dbt/context/query_header.py +13 -0
  116. dbt/context/secret.py +58 -0
  117. dbt/context/target.py +74 -0
  118. dbt/contracts/__init__.py +0 -0
  119. dbt/contracts/files.py +413 -0
  120. dbt/contracts/graph/__init__.py +0 -0
  121. dbt/contracts/graph/manifest.py +1904 -0
  122. dbt/contracts/graph/metrics.py +97 -0
  123. dbt/contracts/graph/model_config.py +70 -0
  124. dbt/contracts/graph/node_args.py +42 -0
  125. dbt/contracts/graph/nodes.py +1806 -0
  126. dbt/contracts/graph/semantic_manifest.py +232 -0
  127. dbt/contracts/graph/unparsed.py +811 -0
  128. dbt/contracts/project.py +417 -0
  129. dbt/contracts/results.py +53 -0
  130. dbt/contracts/selection.py +23 -0
  131. dbt/contracts/sql.py +85 -0
  132. dbt/contracts/state.py +68 -0
  133. dbt/contracts/util.py +46 -0
  134. dbt/deprecations.py +346 -0
  135. dbt/deps/__init__.py +0 -0
  136. dbt/deps/base.py +152 -0
  137. dbt/deps/git.py +195 -0
  138. dbt/deps/local.py +79 -0
  139. dbt/deps/registry.py +130 -0
  140. dbt/deps/resolver.py +149 -0
  141. dbt/deps/tarball.py +120 -0
  142. dbt/docs/source/_ext/dbt_click.py +119 -0
  143. dbt/docs/source/conf.py +32 -0
  144. dbt/env_vars.py +64 -0
  145. dbt/event_time/event_time.py +40 -0
  146. dbt/event_time/sample_window.py +60 -0
  147. dbt/events/__init__.py +15 -0
  148. dbt/events/base_types.py +36 -0
  149. dbt/events/core_types_pb2.py +2 -0
  150. dbt/events/logging.py +108 -0
  151. dbt/events/types.py +2516 -0
  152. dbt/exceptions.py +1486 -0
  153. dbt/flags.py +89 -0
  154. dbt/graph/__init__.py +11 -0
  155. dbt/graph/cli.py +247 -0
  156. dbt/graph/graph.py +172 -0
  157. dbt/graph/queue.py +214 -0
  158. dbt/graph/selector.py +374 -0
  159. dbt/graph/selector_methods.py +975 -0
  160. dbt/graph/selector_spec.py +222 -0
  161. dbt/graph/thread_pool.py +18 -0
  162. dbt/hooks.py +21 -0
  163. dbt/include/README.md +49 -0
  164. dbt/include/__init__.py +3 -0
  165. dbt/include/starter_project/.gitignore +4 -0
  166. dbt/include/starter_project/README.md +15 -0
  167. dbt/include/starter_project/__init__.py +3 -0
  168. dbt/include/starter_project/analyses/.gitkeep +0 -0
  169. dbt/include/starter_project/dbt_project.yml +36 -0
  170. dbt/include/starter_project/macros/.gitkeep +0 -0
  171. dbt/include/starter_project/models/example/my_first_dbt_model.sql +27 -0
  172. dbt/include/starter_project/models/example/my_second_dbt_model.sql +6 -0
  173. dbt/include/starter_project/models/example/schema.yml +21 -0
  174. dbt/include/starter_project/seeds/.gitkeep +0 -0
  175. dbt/include/starter_project/snapshots/.gitkeep +0 -0
  176. dbt/include/starter_project/tests/.gitkeep +0 -0
  177. dbt/internal_deprecations.py +26 -0
  178. dbt/jsonschemas/__init__.py +3 -0
  179. dbt/jsonschemas/jsonschemas.py +309 -0
  180. dbt/jsonschemas/project/0.0.110.json +4717 -0
  181. dbt/jsonschemas/project/0.0.85.json +2015 -0
  182. dbt/jsonschemas/resources/0.0.110.json +2636 -0
  183. dbt/jsonschemas/resources/0.0.85.json +2536 -0
  184. dbt/jsonschemas/resources/latest.json +6773 -0
  185. dbt/links.py +4 -0
  186. dbt/materializations/__init__.py +0 -0
  187. dbt/materializations/incremental/__init__.py +0 -0
  188. dbt/materializations/incremental/microbatch.py +236 -0
  189. dbt/mp_context.py +8 -0
  190. dbt/node_types.py +37 -0
  191. dbt/parser/__init__.py +23 -0
  192. dbt/parser/analysis.py +21 -0
  193. dbt/parser/base.py +548 -0
  194. dbt/parser/common.py +266 -0
  195. dbt/parser/docs.py +52 -0
  196. dbt/parser/fixtures.py +51 -0
  197. dbt/parser/functions.py +30 -0
  198. dbt/parser/generic_test.py +100 -0
  199. dbt/parser/generic_test_builders.py +333 -0
  200. dbt/parser/hooks.py +118 -0
  201. dbt/parser/macros.py +137 -0
  202. dbt/parser/manifest.py +2204 -0
  203. dbt/parser/models.py +573 -0
  204. dbt/parser/partial.py +1178 -0
  205. dbt/parser/read_files.py +445 -0
  206. dbt/parser/schema_generic_tests.py +422 -0
  207. dbt/parser/schema_renderer.py +111 -0
  208. dbt/parser/schema_yaml_readers.py +935 -0
  209. dbt/parser/schemas.py +1466 -0
  210. dbt/parser/search.py +149 -0
  211. dbt/parser/seeds.py +28 -0
  212. dbt/parser/singular_test.py +20 -0
  213. dbt/parser/snapshots.py +44 -0
  214. dbt/parser/sources.py +558 -0
  215. dbt/parser/sql.py +62 -0
  216. dbt/parser/unit_tests.py +621 -0
  217. dbt/plugins/__init__.py +20 -0
  218. dbt/plugins/contracts.py +9 -0
  219. dbt/plugins/exceptions.py +2 -0
  220. dbt/plugins/manager.py +163 -0
  221. dbt/plugins/manifest.py +21 -0
  222. dbt/profiler.py +20 -0
  223. dbt/py.typed +1 -0
  224. dbt/query_analyzer.cpython-310-darwin.so +0 -0
  225. dbt/query_analyzer.py +410 -0
  226. dbt/runners/__init__.py +2 -0
  227. dbt/runners/exposure_runner.py +7 -0
  228. dbt/runners/no_op_runner.py +45 -0
  229. dbt/runners/saved_query_runner.py +7 -0
  230. dbt/selected_resources.py +8 -0
  231. dbt/task/__init__.py +0 -0
  232. dbt/task/base.py +503 -0
  233. dbt/task/build.py +197 -0
  234. dbt/task/clean.py +56 -0
  235. dbt/task/clone.py +161 -0
  236. dbt/task/compile.py +150 -0
  237. dbt/task/compute.py +454 -0
  238. dbt/task/debug.py +505 -0
  239. dbt/task/deps.py +280 -0
  240. dbt/task/docs/__init__.py +3 -0
  241. dbt/task/docs/generate.py +660 -0
  242. dbt/task/docs/index.html +250 -0
  243. dbt/task/docs/serve.py +29 -0
  244. dbt/task/freshness.py +322 -0
  245. dbt/task/function.py +121 -0
  246. dbt/task/group_lookup.py +46 -0
  247. dbt/task/init.py +553 -0
  248. dbt/task/java.py +316 -0
  249. dbt/task/list.py +236 -0
  250. dbt/task/printer.py +175 -0
  251. dbt/task/retry.py +175 -0
  252. dbt/task/run.py +1306 -0
  253. dbt/task/run_operation.py +141 -0
  254. dbt/task/runnable.py +758 -0
  255. dbt/task/seed.py +103 -0
  256. dbt/task/show.py +149 -0
  257. dbt/task/snapshot.py +56 -0
  258. dbt/task/spark.py +414 -0
  259. dbt/task/sql.py +110 -0
  260. dbt/task/target_sync.py +759 -0
  261. dbt/task/test.py +464 -0
  262. dbt/tests/fixtures/__init__.py +1 -0
  263. dbt/tests/fixtures/project.py +620 -0
  264. dbt/tests/util.py +651 -0
  265. dbt/tracking.py +529 -0
  266. dbt/utils/__init__.py +3 -0
  267. dbt/utils/artifact_upload.py +151 -0
  268. dbt/utils/utils.py +408 -0
  269. dbt/version.py +268 -0
  270. dvt_cli/__init__.py +72 -0
  271. dvt_core-0.52.2.dist-info/METADATA +286 -0
  272. dvt_core-0.52.2.dist-info/RECORD +275 -0
  273. dvt_core-0.52.2.dist-info/WHEEL +5 -0
  274. dvt_core-0.52.2.dist-info/entry_points.txt +2 -0
  275. dvt_core-0.52.2.dist-info/top_level.txt +2 -0
dbt/utils/utils.py ADDED
@@ -0,0 +1,408 @@
1
+ import collections
2
+ import decimal
3
+ import functools
4
+ import itertools
5
+ import json
6
+ import os
7
+ import sys
8
+ from datetime import date, datetime, time, timezone
9
+ from enum import Enum
10
+ from pathlib import PosixPath, WindowsPath
11
+ from typing import (
12
+ AbstractSet,
13
+ Any,
14
+ Dict,
15
+ Iterable,
16
+ Iterator,
17
+ List,
18
+ Mapping,
19
+ Optional,
20
+ Sequence,
21
+ Set,
22
+ Tuple,
23
+ Type,
24
+ )
25
+
26
+ import jinja2
27
+
28
+ from dbt import flags
29
+ from dbt.exceptions import DuplicateAliasError
30
+ from dbt_common.exceptions import RecursionError
31
+ from dbt_common.helper_types import WarnErrorOptionsV2
32
+ from dbt_common.utils import md5
33
+
34
+ DECIMALS: Tuple[Type[Any], ...]
35
+ try:
36
+ import cdecimal # typing: ignore
37
+ except ImportError:
38
+ DECIMALS = (decimal.Decimal,)
39
+ else:
40
+ DECIMALS = (decimal.Decimal, cdecimal.Decimal)
41
+
42
+
43
+ class ExitCodes(int, Enum):
44
+ Success = 0
45
+ ModelError = 1
46
+ UnhandledError = 2
47
+
48
+
49
+ def coalesce(*args):
50
+ for arg in args:
51
+ if arg is not None:
52
+ return arg
53
+ return None
54
+
55
+
56
+ def get_profile_from_project(project):
57
+ target_name = project.get("target", {})
58
+ profile = project.get("outputs", {}).get(target_name, {})
59
+ return profile
60
+
61
+
62
+ def get_model_name_or_none(model):
63
+ if model is None:
64
+ name = "<None>"
65
+
66
+ elif isinstance(model, str):
67
+ name = model
68
+ elif isinstance(model, dict):
69
+ name = model.get("alias", model.get("name"))
70
+ elif hasattr(model, "alias"):
71
+ name = model.alias
72
+ elif hasattr(model, "name"):
73
+ name = model.name
74
+ else:
75
+ name = str(model)
76
+ return name
77
+
78
+
79
+ def split_path(path):
80
+ return path.split(os.sep)
81
+
82
+
83
+ def get_pseudo_test_path(node_name, source_path):
84
+ "schema tests all come from schema.yml files. fake a source sql file"
85
+ source_path_parts = split_path(source_path)
86
+ source_path_parts.pop() # ignore filename
87
+ suffix = ["{}.sql".format(node_name)]
88
+ pseudo_path_parts = source_path_parts + suffix
89
+ return os.path.join(*pseudo_path_parts)
90
+
91
+
92
+ def get_pseudo_hook_path(hook_name):
93
+ path_parts = ["hooks", "{}.sql".format(hook_name)]
94
+ return os.path.join(*path_parts)
95
+
96
+
97
+ def get_hash(model):
98
+ return md5(model.unique_id)
99
+
100
+
101
+ def get_hashed_contents(model):
102
+ return md5(model.raw_code)
103
+
104
+
105
+ def flatten_nodes(dep_list):
106
+ return list(itertools.chain.from_iterable(dep_list))
107
+
108
+
109
+ class memoized:
110
+ """Decorator. Caches a function's return value each time it is called. If
111
+ called later with the same arguments, the cached value is returned (not
112
+ reevaluated).
113
+
114
+ Taken from https://wiki.python.org/moin/PythonDecoratorLibrary#Memoize"""
115
+
116
+ def __init__(self, func) -> None:
117
+ self.func = func
118
+ self.cache: Dict[Any, Any] = {}
119
+
120
+ def __call__(self, *args):
121
+ if not isinstance(args, collections.abc.Hashable):
122
+ # uncacheable. a list, for instance.
123
+ # better to not cache than blow up.
124
+ return self.func(*args)
125
+ if args in self.cache:
126
+ return self.cache[args]
127
+ value = self.func(*args)
128
+ self.cache[args] = value
129
+ return value
130
+
131
+ def __repr__(self):
132
+ """Return the function's docstring."""
133
+ return self.func.__doc__
134
+
135
+ def __get__(self, obj, objtype):
136
+ """Support instance methods."""
137
+ return functools.partial(self.__call__, obj)
138
+
139
+
140
+ def add_ephemeral_model_prefix(s: str) -> str:
141
+ return "__dbt__cte__{}".format(s)
142
+
143
+
144
+ def timestring() -> str:
145
+ """Get the current datetime as an RFC 3339-compliant string"""
146
+ # isoformat doesn't include the mandatory trailing 'Z' for UTC.
147
+ return datetime.now(timezone.utc).replace(tzinfo=None).isoformat() + "Z"
148
+
149
+
150
+ def humanize_execution_time(execution_time: int) -> str:
151
+ minutes, seconds = divmod(execution_time, 60)
152
+ hours, minutes = divmod(minutes, 60)
153
+
154
+ return f" in {int(hours)} hours {int(minutes)} minutes and {seconds:0.2f} seconds"
155
+
156
+
157
+ class JSONEncoder(json.JSONEncoder):
158
+ """A 'custom' json encoder that does normal json encoder things, but also
159
+ handles `Decimal`s and `Undefined`s. Decimals can lose precision because
160
+ they get converted to floats. Undefined's are serialized to an empty string
161
+ """
162
+
163
+ def default(self, obj):
164
+ if isinstance(obj, DECIMALS):
165
+ return float(obj)
166
+ elif isinstance(obj, (datetime, date, time)):
167
+ return obj.isoformat()
168
+ elif isinstance(obj, jinja2.Undefined):
169
+ return ""
170
+ elif isinstance(obj, Exception):
171
+ return repr(obj)
172
+ elif hasattr(obj, "to_dict"):
173
+ # if we have a to_dict we should try to serialize the result of
174
+ # that!
175
+ return obj.to_dict(omit_none=True)
176
+ else:
177
+ return super().default(obj)
178
+
179
+
180
+ class Translator:
181
+ def __init__(self, aliases: Mapping[str, str], recursive: bool = False) -> None:
182
+ self.aliases = aliases
183
+ self.recursive = recursive
184
+
185
+ def translate_mapping(self, kwargs: Mapping[str, Any]) -> Dict[str, Any]:
186
+ result: Dict[str, Any] = {}
187
+
188
+ for key, value in kwargs.items():
189
+ canonical_key = self.aliases.get(key, key)
190
+ if canonical_key in result:
191
+ raise DuplicateAliasError(kwargs, self.aliases, canonical_key)
192
+ result[canonical_key] = self.translate_value(value)
193
+ return result
194
+
195
+ def translate_sequence(self, value: Sequence[Any]) -> List[Any]:
196
+ return [self.translate_value(v) for v in value]
197
+
198
+ def translate_value(self, value: Any) -> Any:
199
+ if self.recursive:
200
+ if isinstance(value, Mapping):
201
+ return self.translate_mapping(value)
202
+ elif isinstance(value, (list, tuple)):
203
+ return self.translate_sequence(value)
204
+ return value
205
+
206
+ def translate(self, value: Mapping[str, Any]) -> Dict[str, Any]:
207
+ try:
208
+ return self.translate_mapping(value)
209
+ except RuntimeError as exc:
210
+ if "maximum recursion depth exceeded" in str(exc):
211
+ raise RecursionError("Cycle detected in a value passed to translate!")
212
+ raise
213
+
214
+
215
+ def translate_aliases(
216
+ kwargs: Dict[str, Any],
217
+ aliases: Dict[str, str],
218
+ recurse: bool = False,
219
+ ) -> Dict[str, Any]:
220
+ """Given a dict of keyword arguments and a dict mapping aliases to their
221
+ canonical values, canonicalize the keys in the kwargs dict.
222
+
223
+ If recurse is True, perform this operation recursively.
224
+
225
+ :returns: A dict containing all the values in kwargs referenced by their
226
+ canonical key.
227
+ :raises: `AliasError`, if a canonical key is defined more than once.
228
+ """
229
+ translator = Translator(aliases, recurse)
230
+ return translator.translate(kwargs)
231
+
232
+
233
+ # Note that this only affects hologram json validation.
234
+ # It has no effect on mashumaro serialization.
235
+ # Q: Can this be removed?
236
+ def restrict_to(*restrictions):
237
+ """Create the metadata for a restricted dataclass field"""
238
+ return {"restrict": list(restrictions)}
239
+
240
+
241
+ def coerce_dict_str(value: Any) -> Optional[Dict[str, Any]]:
242
+ """For annoying mypy reasons, this helper makes dealing with nested dicts
243
+ easier. You get either `None` if it's not a Dict[str, Any], or the
244
+ Dict[str, Any] you expected (to pass it to dbtClassMixin.from_dict(...)).
245
+ """
246
+ if isinstance(value, dict) and all(isinstance(k, str) for k in value):
247
+ return value
248
+ else:
249
+ return None
250
+
251
+
252
+ def _coerce_decimal(value):
253
+ if isinstance(value, DECIMALS):
254
+ return float(value)
255
+ return value
256
+
257
+
258
+ def fqn_search(root: Dict[str, Any], fqn: List[str]) -> Iterator[Dict[str, Any]]:
259
+ """Iterate into a nested dictionary, looking for keys in the fqn as levels.
260
+ Yield the level config.
261
+ """
262
+ yield root
263
+
264
+ for level in fqn:
265
+ level_config = root.get(level, None)
266
+ if not isinstance(level_config, dict):
267
+ break
268
+ # This used to do a 'deepcopy',
269
+ # but it didn't seem to be necessary
270
+ yield level_config
271
+ root = level_config
272
+
273
+
274
+ StringMap = Mapping[str, Any]
275
+ StringMapList = List[StringMap]
276
+ StringMapIter = Iterable[StringMap]
277
+
278
+
279
+ class MultiDict(Mapping[str, Any]):
280
+ """Implement the mapping protocol using a list of mappings. The most
281
+ recently added mapping "wins".
282
+ """
283
+
284
+ def __init__(self, sources: Optional[StringMapList] = None) -> None:
285
+ super().__init__()
286
+ self.sources: StringMapList
287
+
288
+ if sources is None:
289
+ self.sources = []
290
+ else:
291
+ self.sources = sources
292
+
293
+ def add_from(self, sources: StringMapIter):
294
+ self.sources.extend(sources)
295
+
296
+ def add(self, source: StringMap):
297
+ self.sources.append(source)
298
+
299
+ def _keyset(self) -> AbstractSet[str]:
300
+ # return the set of keys
301
+ keys: Set[str] = set()
302
+ for entry in self._itersource():
303
+ keys.update(entry)
304
+ return keys
305
+
306
+ def _itersource(self) -> StringMapIter:
307
+ return reversed(self.sources)
308
+
309
+ def __iter__(self) -> Iterator[str]:
310
+ # we need to avoid duplicate keys
311
+ return iter(self._keyset())
312
+
313
+ def __len__(self):
314
+ return len(self._keyset())
315
+
316
+ def __getitem__(self, name: str) -> Any:
317
+ for entry in self._itersource():
318
+ if name in entry:
319
+ return entry[name]
320
+ raise KeyError(name)
321
+
322
+ def __contains__(self, name) -> bool:
323
+ return any((name in entry for entry in self._itersource()))
324
+
325
+
326
+ # This is used to serialize the args in the run_results and in the logs.
327
+ # We do this separately because there are a few fields that don't serialize,
328
+ # i.e. PosixPath, WindowsPath, and types. It also includes args from both
329
+ # cli args and flags, which is more complete than just the cli args.
330
+ # If new args are added that are false by default (particularly in the
331
+ # global options) they should be added to the 'default_false_keys' list.
332
+ def args_to_dict(args):
333
+ var_args = vars(args).copy()
334
+ # update the args with the flags, which could also come from environment
335
+ # variables or project_flags
336
+ flag_dict = flags.get_flag_dict()
337
+ var_args.update(flag_dict)
338
+ dict_args = {}
339
+ # remove args keys that clutter up the dictionary
340
+ for key in var_args:
341
+ if key.lower() in var_args and key == key.upper():
342
+ # skip all capped keys being introduced by Flags in dbt.cli.flags
343
+ continue
344
+ if key in ["cls", "mp_context"]:
345
+ continue
346
+ if var_args[key] is None:
347
+ continue
348
+ # TODO: add more default_false_keys
349
+ default_false_keys = (
350
+ "debug",
351
+ "full_refresh",
352
+ "fail_fast",
353
+ "warn_error",
354
+ "single_threaded",
355
+ "log_cache_events",
356
+ "store_failures",
357
+ "use_experimental_parser",
358
+ )
359
+ default_empty_yaml_dict_keys = ("vars", "warn_error_options")
360
+ if key in default_false_keys and var_args[key] is False:
361
+ continue
362
+ if key in default_empty_yaml_dict_keys and var_args[key] == "{}":
363
+ continue
364
+ # this was required for a test case
365
+ if isinstance(var_args[key], PosixPath) or isinstance(var_args[key], WindowsPath):
366
+ var_args[key] = str(var_args[key])
367
+ if isinstance(var_args[key], WarnErrorOptionsV2):
368
+ var_args[key] = var_args[key].to_dict()
369
+
370
+ dict_args[key] = var_args[key]
371
+ return dict_args
372
+
373
+
374
+ # Taken from https://github.com/python/cpython/blob/3.11/Lib/distutils/util.py
375
+ # This is a copy of the function from distutils.util, which was removed in Python 3.12.
376
+ def strtobool(val: str) -> bool:
377
+ """Convert a string representation of truth to True or False.
378
+
379
+ True values are 'y', 'yes', 't', 'true', 'on', and '1'; false values
380
+ are 'n', 'no', 'f', 'false', 'off', and '0'. Raises ValueError if
381
+ 'val' is anything else.
382
+ """
383
+ val = val.lower()
384
+ if val in ("y", "yes", "t", "true", "on", "1"):
385
+ return True
386
+ elif val in ("n", "no", "f", "false", "off", "0"):
387
+ return False
388
+ else:
389
+ raise ValueError("invalid truth value %r" % (val,))
390
+
391
+
392
+ def try_get_max_rss_kb() -> Optional[int]:
393
+ """Attempts to get the high water mark for this process's memory use via
394
+ the most reliable and accurate mechanism available through the host OS.
395
+ Currently only implemented for Linux."""
396
+ if sys.platform == "linux" and os.path.isfile("/proc/self/status"):
397
+ try:
398
+ # On Linux, the most reliable documented mechanism for getting the RSS
399
+ # high-water-mark comes from the line confusingly labeled VmHWM in the
400
+ # /proc/self/status virtual file.
401
+ with open("/proc/self/status") as f:
402
+ for line in f:
403
+ if line.startswith("VmHWM:"):
404
+ return int(str.split(line)[1])
405
+ except Exception:
406
+ pass
407
+
408
+ return None
dbt/version.py ADDED
@@ -0,0 +1,268 @@
1
+ import glob
2
+ import importlib
3
+ import importlib.util
4
+ import json
5
+ import os
6
+ import re
7
+ from importlib import metadata as importlib_metadata
8
+ from pathlib import Path
9
+ from typing import Iterator, List, Optional, Tuple
10
+
11
+ import requests
12
+
13
+ import dbt_common.semver as semver
14
+ from dbt_common.ui import green, yellow
15
+
16
+ PYPI_VERSION_URL = "https://pypi.org/pypi/dbt-core/json"
17
+
18
+
19
+ def get_version_information() -> str:
20
+ installed = get_installed_version()
21
+ latest = get_latest_version()
22
+
23
+ core_msg_lines, core_info_msg = _get_core_msg_lines(installed, latest)
24
+
25
+ # Add DVT version info (must be [name, version, message] format)
26
+ core_msg_lines.append(["DVT", __dvt_version__, ""])
27
+
28
+ core_msg = _format_core_msg(core_msg_lines)
29
+ plugin_version_msg = _get_plugins_msg()
30
+
31
+ msg_lines = [core_msg]
32
+
33
+ if core_info_msg != "":
34
+ msg_lines.append(core_info_msg)
35
+
36
+ msg_lines.append(plugin_version_msg)
37
+ msg_lines.append("")
38
+
39
+ return "\n\n".join(msg_lines)
40
+
41
+
42
+ def get_installed_version() -> semver.VersionSpecifier:
43
+ return semver.VersionSpecifier.from_version_string(__version__)
44
+
45
+
46
+ def get_latest_version(
47
+ version_url: str = PYPI_VERSION_URL,
48
+ ) -> Optional[semver.VersionSpecifier]:
49
+ try:
50
+ resp = requests.get(version_url, timeout=1)
51
+ data = resp.json()
52
+ version_string = data["info"]["version"]
53
+ except (json.JSONDecodeError, KeyError, requests.RequestException):
54
+ return None
55
+
56
+ return semver.VersionSpecifier.from_version_string(version_string)
57
+
58
+
59
+ def _get_core_msg_lines(
60
+ installed: semver.VersionSpecifier,
61
+ latest: Optional[semver.VersionSpecifier],
62
+ ) -> Tuple[List[List[str]], str]:
63
+ installed_s = installed.to_version_string(skip_matcher=True)
64
+ installed_line = ["installed", installed_s, ""]
65
+ update_info = ""
66
+
67
+ if latest is None:
68
+ update_info = (
69
+ " The latest version of dbt-core could not be determined!\n"
70
+ " Make sure that the following URL is accessible:\n"
71
+ f" {PYPI_VERSION_URL}"
72
+ )
73
+ return [installed_line], update_info
74
+
75
+ latest_s = latest.to_version_string(skip_matcher=True)
76
+ latest_line = ["latest", latest_s, green("Up to date!")]
77
+
78
+ if installed > latest:
79
+ latest_line[2] = yellow("Ahead of latest version!")
80
+ elif installed < latest:
81
+ latest_line[2] = yellow("Update available!")
82
+ update_info = (
83
+ " Your version of dbt-core is out of date!\n"
84
+ " You can find instructions for upgrading here:\n"
85
+ " https://docs.getdbt.com/docs/installation"
86
+ )
87
+
88
+ return [
89
+ installed_line,
90
+ latest_line,
91
+ ], update_info
92
+
93
+
94
+ def _format_core_msg(lines: List[List[str]]) -> str:
95
+ msg = "Core:\n"
96
+ msg_lines = []
97
+
98
+ for name, version, update_msg in _pad_lines(lines, seperator=":"):
99
+ line_msg = f" - {name} {version}"
100
+ if update_msg != "":
101
+ line_msg += f" - {update_msg}"
102
+ msg_lines.append(line_msg)
103
+
104
+ return msg + "\n".join(msg_lines)
105
+
106
+
107
+ def _get_plugins_msg() -> str:
108
+ msg_lines = ["Plugins:"]
109
+
110
+ plugins = []
111
+ display_update_msg = False
112
+ for name, version_s in _get_dbt_plugins_info():
113
+ compatability_msg, needs_update = _get_plugin_msg_info(name, version_s, installed)
114
+ if needs_update:
115
+ display_update_msg = True
116
+ plugins.append([name, version_s, compatability_msg])
117
+
118
+ for plugin in _pad_lines(plugins, seperator=":"):
119
+ msg_lines.append(_format_single_plugin(plugin, ""))
120
+
121
+ if display_update_msg:
122
+ update_msg = (
123
+ " At least one plugin is out of date with dbt-core.\n"
124
+ " You can find instructions for upgrading here:\n"
125
+ " https://docs.getdbt.com/docs/installation"
126
+ )
127
+ msg_lines += ["", update_msg]
128
+
129
+ return "\n".join(msg_lines)
130
+
131
+
132
+ def _get_plugin_msg_info(
133
+ name: str, version_s: str, core: semver.VersionSpecifier
134
+ ) -> Tuple[str, bool]:
135
+ plugin = semver.VersionSpecifier.from_version_string(version_s)
136
+ latest_plugin = get_latest_version(version_url=get_package_pypi_url(name))
137
+
138
+ needs_update = False
139
+
140
+ if not latest_plugin:
141
+ compatibility_msg = yellow("Could not determine latest version")
142
+ return (compatibility_msg, needs_update)
143
+
144
+ if plugin < latest_plugin:
145
+ compatibility_msg = yellow("Update available!")
146
+ needs_update = True
147
+ elif plugin > latest_plugin:
148
+ compatibility_msg = yellow("Ahead of latest version!")
149
+ else:
150
+ compatibility_msg = green("Up to date!")
151
+
152
+ return (compatibility_msg, needs_update)
153
+
154
+
155
+ def _format_single_plugin(plugin: List[str], update_msg: str) -> str:
156
+ name, version_s, compatability_msg = plugin
157
+ msg = f" - {name} {version_s} - {compatability_msg}"
158
+ if update_msg != "":
159
+ msg += f"\n{update_msg}\n"
160
+ return msg
161
+
162
+
163
+ def _pad_lines(lines: List[List[str]], seperator: str = "") -> List[List[str]]:
164
+ if len(lines) == 0:
165
+ return []
166
+
167
+ # count the max line length for each column in the line
168
+ counter = [0] * len(lines[0])
169
+ for line in lines:
170
+ for i, item in enumerate(line):
171
+ counter[i] = max(counter[i], len(item))
172
+
173
+ result: List[List[str]] = []
174
+ for i, line in enumerate(lines):
175
+ # add another list to hold padded strings
176
+ if len(result) == i:
177
+ result.append([""] * len(line))
178
+
179
+ # iterate over columns in the line
180
+ for j, item in enumerate(line):
181
+ # the last column does not need padding
182
+ if j == len(line) - 1:
183
+ result[i][j] = item
184
+ continue
185
+
186
+ # if the following column has no length
187
+ # the string does not need padding
188
+ if counter[j + 1] == 0:
189
+ result[i][j] = item
190
+ continue
191
+
192
+ # only add the seperator to the first column
193
+ offset = 0
194
+ if j == 0 and seperator != "":
195
+ item += seperator
196
+ offset = len(seperator)
197
+
198
+ result[i][j] = item.ljust(counter[j] + offset)
199
+
200
+ return result
201
+
202
+
203
+ def get_package_pypi_url(package_name: str) -> str:
204
+ return f"https://pypi.org/pypi/dbt-{package_name}/json"
205
+
206
+
207
+ def _get_dbt_plugins_info() -> Iterator[Tuple[str, str]]:
208
+ for plugin_name in _get_adapter_plugin_names():
209
+ if plugin_name == "core":
210
+ continue
211
+ try:
212
+ mod = importlib.import_module(f"dbt.adapters.{plugin_name}.__version__")
213
+ except ImportError:
214
+ # not an adapter
215
+ continue
216
+ yield plugin_name, mod.version
217
+
218
+
219
+ def _get_adapter_plugin_names() -> Iterator[str]:
220
+ spec = importlib.util.find_spec("dbt.adapters")
221
+ # If None, then nothing provides an importable 'dbt.adapters', so we will
222
+ # not be reporting plugin versions today
223
+ if spec is None or spec.submodule_search_locations is None:
224
+ return
225
+
226
+ for adapters_path in spec.submodule_search_locations:
227
+ version_glob = os.path.join(adapters_path, "*", "__version__.py")
228
+ for version_path in glob.glob(version_glob):
229
+ # the path is like .../dbt/adapters/{plugin_name}/__version__.py
230
+ # except it could be \\ on windows!
231
+ plugin_root, _ = os.path.split(version_path)
232
+ _, plugin_name = os.path.split(plugin_root)
233
+ yield plugin_name
234
+
235
+
236
+ def _resolve_version() -> str:
237
+ """
238
+ Resolve dbt version for DVT-core.
239
+
240
+ DVT-core is built on dbt-core 1.10.15 and must report this version
241
+ for package compatibility. The actual DVT version is tracked separately.
242
+ """
243
+ # DVT-core is based on dbt-core 1.10.15
244
+ # We must report this version for dbt package ecosystem compatibility
245
+ return "1.10.15"
246
+
247
+
248
+ def _resolve_dvt_version() -> str:
249
+ """Get the actual DVT-core package version."""
250
+ try:
251
+ return importlib_metadata.version("dvt-core")
252
+ except importlib_metadata.PackageNotFoundError:
253
+ # Fall back to reading from pyproject.toml in development
254
+ pyproject_path = Path(__file__).resolve().parents[1] / "pyproject.toml"
255
+ if not pyproject_path.exists():
256
+ return "unknown"
257
+
258
+ text = pyproject_path.read_text(encoding="utf-8")
259
+ match = re.search(r'^version\s*=\s*"(?P<version>[^"]+)"', text, re.MULTILINE)
260
+ if match:
261
+ return match.group("version")
262
+
263
+ return "unknown"
264
+
265
+
266
+ __version__ = _resolve_version() # This is the dbt version (1.10.15)
267
+ __dvt_version__ = _resolve_dvt_version() # This is the actual DVT version (0.1.2)
268
+ installed = get_installed_version()