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,179 @@
1
+ import functools
2
+ from typing import NoReturn
3
+
4
+ from dvt.events.types import JinjaLogWarning, SnapshotTimestampWarning
5
+ from dvt.exceptions import (
6
+ AmbiguousAliasError,
7
+ AmbiguousCatalogMatchError,
8
+ CompilationError,
9
+ ContractError,
10
+ DependencyError,
11
+ DependencyNotFoundError,
12
+ DuplicatePatchPathError,
13
+ DuplicateResourceNameError,
14
+ FailFastError,
15
+ MissingRelationError,
16
+ PropertyYMLError,
17
+ env_secrets,
18
+ scrub_secrets,
19
+ )
20
+
21
+ from dbt.adapters.exceptions import (
22
+ ColumnTypeMissingError,
23
+ MissingConfigError,
24
+ MissingMaterializationError,
25
+ RelationWrongTypeError,
26
+ )
27
+ from dbt.adapters.exceptions.cache import CacheInconsistencyError
28
+ from dbt_common.events.functions import warn_or_error
29
+ from dbt_common.exceptions import (
30
+ DataclassNotDictError,
31
+ DbtDatabaseError,
32
+ DbtRuntimeError,
33
+ NotImplementedError,
34
+ )
35
+
36
+
37
+ def warn(msg, node=None):
38
+ warn_or_error(JinjaLogWarning(msg=msg), node=node)
39
+ return ""
40
+
41
+
42
+ def missing_config(model, name) -> NoReturn:
43
+ raise MissingConfigError(unique_id=model.unique_id, name=name)
44
+
45
+
46
+ def missing_materialization(model, adapter_type) -> NoReturn:
47
+ raise MissingMaterializationError(
48
+ materialization=model.config.materialized, adapter_type=adapter_type
49
+ )
50
+
51
+
52
+ def missing_relation(relation, model=None) -> NoReturn:
53
+ raise MissingRelationError(relation, model)
54
+
55
+
56
+ def raise_ambiguous_alias(node_1, node_2, duped_name=None) -> NoReturn:
57
+ raise AmbiguousAliasError(node_1, node_2, duped_name)
58
+
59
+
60
+ def raise_ambiguous_catalog_match(unique_id, match_1, match_2) -> NoReturn:
61
+ raise AmbiguousCatalogMatchError(unique_id, match_1, match_2)
62
+
63
+
64
+ def raise_cache_inconsistent(message) -> NoReturn:
65
+ raise CacheInconsistencyError(message)
66
+
67
+
68
+ def raise_dataclass_not_dict(obj) -> NoReturn:
69
+ raise DataclassNotDictError(obj)
70
+
71
+
72
+ def raise_compiler_error(msg, node=None) -> NoReturn:
73
+ raise CompilationError(msg, node)
74
+
75
+
76
+ def raise_contract_error(yaml_columns, sql_columns) -> NoReturn:
77
+ raise ContractError(yaml_columns, sql_columns)
78
+
79
+
80
+ def raise_database_error(msg, node=None) -> NoReturn:
81
+ raise DbtDatabaseError(msg, node)
82
+
83
+
84
+ def raise_dep_not_found(node, node_description, required_pkg) -> NoReturn:
85
+ raise DependencyNotFoundError(node, node_description, required_pkg)
86
+
87
+
88
+ def raise_dependency_error(msg) -> NoReturn:
89
+ raise DependencyError(scrub_secrets(msg, env_secrets()))
90
+
91
+
92
+ def raise_duplicate_patch_name(patch_1, existing_patch_path) -> NoReturn:
93
+ raise DuplicatePatchPathError(patch_1, existing_patch_path)
94
+
95
+
96
+ def raise_duplicate_resource_name(node_1, node_2) -> NoReturn:
97
+ raise DuplicateResourceNameError(node_1, node_2)
98
+
99
+
100
+ def raise_invalid_property_yml_version(path, issue) -> NoReturn:
101
+ raise PropertyYMLError(path, issue)
102
+
103
+
104
+ def raise_not_implemented(msg) -> NoReturn:
105
+ raise NotImplementedError(msg)
106
+
107
+
108
+ def relation_wrong_type(relation, expected_type, model=None) -> NoReturn:
109
+ raise RelationWrongTypeError(relation, expected_type, model)
110
+
111
+
112
+ def column_type_missing(column_names) -> NoReturn:
113
+ raise ColumnTypeMissingError(column_names)
114
+
115
+
116
+ def raise_fail_fast_error(msg, node=None) -> NoReturn:
117
+ raise FailFastError(msg, node=node)
118
+
119
+
120
+ def warn_snapshot_timestamp_data_types(
121
+ snapshot_time_data_type: str, updated_at_data_type: str
122
+ ) -> None:
123
+ warn_or_error(
124
+ SnapshotTimestampWarning(
125
+ snapshot_time_data_type=snapshot_time_data_type,
126
+ updated_at_data_type=updated_at_data_type,
127
+ )
128
+ )
129
+
130
+
131
+ # Update this when a new function should be added to the
132
+ # dbt context's `exceptions` key!
133
+ CONTEXT_EXPORTS = {
134
+ fn.__name__: fn
135
+ for fn in [
136
+ warn,
137
+ missing_config,
138
+ missing_materialization,
139
+ missing_relation,
140
+ raise_ambiguous_alias,
141
+ raise_ambiguous_catalog_match,
142
+ raise_cache_inconsistent,
143
+ raise_dataclass_not_dict,
144
+ raise_compiler_error,
145
+ raise_database_error,
146
+ raise_dep_not_found,
147
+ raise_dependency_error,
148
+ raise_duplicate_patch_name,
149
+ raise_duplicate_resource_name,
150
+ raise_invalid_property_yml_version,
151
+ raise_not_implemented,
152
+ relation_wrong_type,
153
+ raise_contract_error,
154
+ column_type_missing,
155
+ raise_fail_fast_error,
156
+ warn_snapshot_timestamp_data_types,
157
+ ]
158
+ }
159
+
160
+
161
+ # wraps context based exceptions in node info
162
+ def wrapper(model):
163
+ def wrap(func):
164
+ @functools.wraps(func)
165
+ def inner(*args, **kwargs):
166
+ try:
167
+ return func(*args, **kwargs)
168
+ except DbtRuntimeError as exc:
169
+ exc.add_node(model)
170
+ raise exc
171
+
172
+ return inner
173
+
174
+ return wrap
175
+
176
+
177
+ def wrapped_exports(model):
178
+ wrap = wrapper(model)
179
+ return {name: wrap(export) for name, export in CONTEXT_EXPORTS.items()}
@@ -0,0 +1,195 @@
1
+ from typing import Dict, MutableMapping, Optional
2
+
3
+ from dvt.clients.jinja import MacroGenerator
4
+ from dvt.contracts.graph.nodes import Macro
5
+ from dvt.exceptions import DuplicateMacroNameError, PackageNotFoundForMacroError
6
+ from dvt.include.global_project import PROJECT_NAME as GLOBAL_PROJECT_NAME
7
+
8
+ MacroNamespace = Dict[str, Macro]
9
+
10
+
11
+ # This class builds the MacroResolver by adding macros
12
+ # to various categories for finding macros in the right order,
13
+ # so that higher precedence macros are found first.
14
+ # This functionality is also provided by the MacroNamespace,
15
+ # but the intention is to eventually replace that class.
16
+ # This enables us to get the macro unique_id without
17
+ # processing every macro in the project.
18
+ # Note: the root project macros override everything in the
19
+ # dbt internal projects. External projects (dependencies) will
20
+ # use their own macros first, then pull from the root project
21
+ # followed by dbt internal projects.
22
+ class MacroResolver:
23
+ def __init__(
24
+ self,
25
+ macros: MutableMapping[str, Macro],
26
+ root_project_name: str,
27
+ internal_package_names,
28
+ ) -> None:
29
+ self.root_project_name = root_project_name
30
+ self.macros = macros
31
+ # internal packages comes from get_adapter_package_names
32
+ self.internal_package_names = internal_package_names
33
+
34
+ # To be filled in from macros.
35
+ self.internal_packages: Dict[str, MacroNamespace] = {}
36
+ self.packages: Dict[str, MacroNamespace] = {}
37
+ self.root_package_macros: MacroNamespace = {}
38
+
39
+ # add the macros to internal_packages, packages, and root packages
40
+ self.add_macros()
41
+ self._build_internal_packages_namespace()
42
+ self._build_macros_by_name()
43
+
44
+ def _build_internal_packages_namespace(self) -> None:
45
+ # Iterate in reverse-order and overwrite: the packages that are first
46
+ # in the list are the ones we want to "win".
47
+ self.internal_packages_namespace: MacroNamespace = {}
48
+ for pkg in reversed(self.internal_package_names):
49
+ if pkg in self.internal_packages:
50
+ # Turn the internal packages into a flat namespace
51
+ self.internal_packages_namespace.update(self.internal_packages[pkg])
52
+
53
+ # search order:
54
+ # local_namespace (package of particular node), not including
55
+ # the internal packages or the root package
56
+ # This means that within an extra package, it uses its own macros
57
+ # root package namespace
58
+ # non-internal packages (that aren't local or root)
59
+ # dbt internal packages
60
+ def _build_macros_by_name(self) -> None:
61
+ macros_by_name = {}
62
+
63
+ # all internal packages (already in the right order)
64
+ for macro in self.internal_packages_namespace.values():
65
+ macros_by_name[macro.name] = macro
66
+
67
+ # non-internal packages
68
+ for fnamespace in self.packages.values():
69
+ for macro in fnamespace.values():
70
+ macros_by_name[macro.name] = macro
71
+
72
+ # root package macros
73
+ for macro in self.root_package_macros.values():
74
+ macros_by_name[macro.name] = macro
75
+
76
+ self.macros_by_name = macros_by_name
77
+
78
+ def _add_macro_to(
79
+ self,
80
+ package_namespaces: Dict[str, MacroNamespace],
81
+ macro: Macro,
82
+ ) -> None:
83
+ if macro.package_name in package_namespaces:
84
+ namespace = package_namespaces[macro.package_name]
85
+ else:
86
+ namespace = {}
87
+ package_namespaces[macro.package_name] = namespace
88
+
89
+ if macro.name in namespace:
90
+ raise DuplicateMacroNameError(macro, macro, macro.package_name)
91
+ package_namespaces[macro.package_name][macro.name] = macro
92
+
93
+ def add_macro(self, macro: Macro) -> None:
94
+ macro_name: str = macro.name
95
+
96
+ # internal macros (from plugins) will be processed separately from
97
+ # project macros, so store them in a different place
98
+ if macro.package_name in self.internal_package_names:
99
+ self._add_macro_to(self.internal_packages, macro)
100
+ else:
101
+ # if it's not an internal package
102
+ self._add_macro_to(self.packages, macro)
103
+ # add to root_package_macros if it's in the root package
104
+ if macro.package_name == self.root_project_name:
105
+ self.root_package_macros[macro_name] = macro
106
+
107
+ def add_macros(self) -> None:
108
+ for macro in self.macros.values():
109
+ self.add_macro(macro)
110
+
111
+ def get_macro(self, local_package, macro_name) -> Optional[Macro]:
112
+ local_package_macros = {}
113
+ # If the macro is explicitly prefixed with an internal namespace
114
+ # (e.g. 'dbt.some_macro'), look there first
115
+ if local_package in self.internal_package_names:
116
+ local_package_macros = self.internal_packages[local_package]
117
+ # If the macro is explicitly prefixed with a different package name
118
+ # (e.g. 'dbt_utils.some_macro'), look there first
119
+ if local_package not in self.internal_package_names and local_package in self.packages:
120
+ local_package_macros = self.packages[local_package]
121
+ # First: search the specified package for this macro
122
+ if macro_name in local_package_macros:
123
+ return local_package_macros[macro_name]
124
+ # Now look up in the standard search order
125
+ if macro_name in self.macros_by_name:
126
+ return self.macros_by_name[macro_name]
127
+ return None
128
+
129
+ def get_macro_id(self, local_package, macro_name) -> Optional[str]:
130
+ macro = self.get_macro(local_package, macro_name)
131
+ if macro is None:
132
+ return None
133
+ else:
134
+ return macro.unique_id
135
+
136
+
137
+ # Currently this is just used by test processing in the schema
138
+ # parser (in connection with the MacroResolver). Future work
139
+ # will extend the use of these classes to other parsing areas.
140
+ # One of the features of this class compared to the MacroNamespace
141
+ # is that you can limit the number of macros provided to the
142
+ # context dictionary in the 'to_dict' manifest method.
143
+ class TestMacroNamespace:
144
+ def __init__(self, macro_resolver, ctx, node, thread_ctx, depends_on_macros):
145
+ self.macro_resolver = macro_resolver
146
+ self.ctx = ctx
147
+ self.node = node # can be none
148
+ self.thread_ctx = thread_ctx
149
+ self.local_namespace = {}
150
+ self.project_namespace = {}
151
+ if depends_on_macros:
152
+ dep_macros = []
153
+ self.recursively_get_depends_on_macros(depends_on_macros, dep_macros)
154
+ for macro_unique_id in dep_macros:
155
+ if macro_unique_id in self.macro_resolver.macros:
156
+ # Split up the macro unique_id to get the project_name
157
+ (_, project_name, macro_name) = macro_unique_id.split(".")
158
+ # Save the plain macro_name in the local_namespace
159
+ macro = self.macro_resolver.macros[macro_unique_id]
160
+ macro_gen = MacroGenerator(
161
+ macro,
162
+ self.ctx,
163
+ self.node,
164
+ self.thread_ctx,
165
+ )
166
+ self.local_namespace[macro_name] = macro_gen
167
+ # We also need the two part macro name
168
+ if project_name not in self.project_namespace:
169
+ self.project_namespace[project_name] = {}
170
+ self.project_namespace[project_name][macro_name] = macro_gen
171
+
172
+ def recursively_get_depends_on_macros(self, depends_on_macros, dep_macros):
173
+ for macro_unique_id in depends_on_macros:
174
+ if macro_unique_id in dep_macros:
175
+ continue
176
+ dep_macros.append(macro_unique_id)
177
+ if macro_unique_id in self.macro_resolver.macros:
178
+ macro = self.macro_resolver.macros[macro_unique_id]
179
+ if macro.depends_on.macros:
180
+ self.recursively_get_depends_on_macros(macro.depends_on.macros, dep_macros)
181
+
182
+ def get_from_package(self, package_name: Optional[str], name: str) -> Optional[MacroGenerator]:
183
+ macro = None
184
+ if package_name is None:
185
+ macro = self.macro_resolver.macros_by_name.get(name)
186
+ elif package_name == GLOBAL_PROJECT_NAME:
187
+ macro = self.macro_resolver.internal_packages_namespace.get(name)
188
+ elif package_name in self.macro_resolver.packages:
189
+ macro = self.macro_resolver.packages[package_name].get(name)
190
+ else:
191
+ raise PackageNotFoundForMacroError(package_name)
192
+ if not macro:
193
+ return None
194
+ macro_func = MacroGenerator(macro, self.ctx, self.node, self.thread_ctx)
195
+ return macro_func
dvt/context/macros.py ADDED
@@ -0,0 +1,171 @@
1
+ from typing import Any, Dict, Iterable, Iterator, List, Mapping, Optional, Set, Union
2
+
3
+ from dvt.clients.jinja import MacroGenerator, MacroStack
4
+ from dvt.contracts.graph.nodes import Macro
5
+ from dvt.exceptions import DuplicateMacroNameError, PackageNotFoundForMacroError
6
+ from dvt.include.global_project import PROJECT_NAME as GLOBAL_PROJECT_NAME
7
+
8
+ FlatNamespace = Dict[str, MacroGenerator]
9
+ NamespaceMember = Union[FlatNamespace, MacroGenerator]
10
+ FullNamespace = Dict[str, NamespaceMember]
11
+
12
+
13
+ # The point of this class is to collect the various macros
14
+ # and provide the ability to flatten them into the ManifestContexts
15
+ # that are created for jinja, so that macro calls can be resolved.
16
+ # Creates special iterators and _keys methods to flatten the lists.
17
+ # When this class is created it has a static 'local_namespace' which
18
+ # depends on the package of the node, so it only works for one
19
+ # particular local package at a time for "flattening" into a context.
20
+ # 'get_by_package' should work for any macro.
21
+ class MacroNamespace(Mapping):
22
+ def __init__(
23
+ self,
24
+ global_namespace: FlatNamespace, # root package macros
25
+ local_namespace: FlatNamespace, # packages for *this* node
26
+ global_project_namespace: FlatNamespace, # internal packages
27
+ packages: Dict[str, FlatNamespace], # non-internal packages
28
+ ):
29
+ self.global_namespace: FlatNamespace = global_namespace
30
+ self.local_namespace: FlatNamespace = local_namespace
31
+ self.packages: Dict[str, FlatNamespace] = packages
32
+ self.global_project_namespace: FlatNamespace = global_project_namespace
33
+
34
+ def _search_order(self) -> Iterable[Union[FullNamespace, FlatNamespace]]:
35
+ yield self.local_namespace # local package
36
+ yield self.global_namespace # root package
37
+ # TODO CT-211
38
+ yield self.packages # type: ignore[misc] # non-internal packages
39
+ yield {
40
+ # TODO CT-211
41
+ GLOBAL_PROJECT_NAME: self.global_project_namespace, # type: ignore[misc] # dbt
42
+ }
43
+ yield self.global_project_namespace # other internal project besides dbt
44
+
45
+ # provides special keys method for MacroNamespace iterator
46
+ # returns keys from local_namespace, global_namespace, packages,
47
+ # global_project_namespace
48
+ def _keys(self) -> Set[str]:
49
+ keys: Set[str] = set()
50
+ for search in self._search_order():
51
+ keys.update(search)
52
+ return keys
53
+
54
+ # special iterator using special keys
55
+ def __iter__(self) -> Iterator[str]:
56
+ for key in self._keys():
57
+ yield key
58
+
59
+ def __len__(self):
60
+ return len(self._keys())
61
+
62
+ def __getitem__(self, key: str) -> NamespaceMember:
63
+ for dct in self._search_order():
64
+ if key in dct:
65
+ return dct[key]
66
+ raise KeyError(key)
67
+
68
+ def get_from_package(self, package_name: Optional[str], name: str) -> Optional[MacroGenerator]:
69
+ if package_name is None:
70
+ return self.get(name)
71
+ elif package_name == GLOBAL_PROJECT_NAME:
72
+ return self.global_project_namespace.get(name)
73
+ elif package_name in self.packages:
74
+ return self.packages[package_name].get(name)
75
+ else:
76
+ raise PackageNotFoundForMacroError(package_name)
77
+
78
+
79
+ # This class builds the MacroNamespace by adding macros to
80
+ # internal_packages or packages, and locals/globals.
81
+ # Call 'build_namespace' to return a MacroNamespace.
82
+ # This is used by ManifestContext (and subclasses)
83
+ class MacroNamespaceBuilder:
84
+ def __init__(
85
+ self,
86
+ root_package: str,
87
+ search_package: str,
88
+ thread_ctx: MacroStack,
89
+ internal_packages: List[str],
90
+ node: Optional[Any] = None,
91
+ ) -> None:
92
+ self.root_package = root_package
93
+ self.search_package = search_package
94
+ # internal packages comes from get_adapter_package_names
95
+ self.internal_package_names = set(internal_packages)
96
+ self.internal_package_names_order = internal_packages
97
+ # macro_func is added here if in root package, since
98
+ # the root package acts as a "global" namespace, overriding
99
+ # everything else except local external package macro calls
100
+ self.globals: FlatNamespace = {}
101
+ # macro_func is added here if it's the package for this node
102
+ self.locals: FlatNamespace = {}
103
+ # Create a dictionary of [package name][macro name] =
104
+ # MacroGenerator object which acts like a function
105
+ self.internal_packages: Dict[str, FlatNamespace] = {}
106
+ self.packages: Dict[str, FlatNamespace] = {}
107
+ self.thread_ctx = thread_ctx
108
+ self.node = node
109
+
110
+ def _add_macro_to(
111
+ self,
112
+ hierarchy: Dict[str, FlatNamespace],
113
+ macro: Macro,
114
+ macro_func: MacroGenerator,
115
+ ):
116
+ if macro.package_name in hierarchy:
117
+ namespace = hierarchy[macro.package_name]
118
+ else:
119
+ namespace = {}
120
+ hierarchy[macro.package_name] = namespace
121
+
122
+ if macro.name in namespace:
123
+ raise DuplicateMacroNameError(macro_func.macro, macro, macro.package_name)
124
+ hierarchy[macro.package_name][macro.name] = macro_func
125
+
126
+ def add_macro(self, macro: Macro, ctx: Dict[str, Any]) -> None:
127
+ macro_name: str = macro.name
128
+
129
+ # MacroGenerator is in clients/jinja.py
130
+ # a MacroGenerator object is a callable object that will
131
+ # execute the MacroGenerator.__call__ function
132
+ macro_func: MacroGenerator = MacroGenerator(macro, ctx, self.node, self.thread_ctx)
133
+
134
+ # internal macros (from plugins) will be processed separately from
135
+ # project macros, so store them in a different place
136
+ if macro.package_name in self.internal_package_names:
137
+ self._add_macro_to(self.internal_packages, macro, macro_func)
138
+ else:
139
+ # if it's not an internal package
140
+ self._add_macro_to(self.packages, macro, macro_func)
141
+ # add to locals if it's the package this node is in
142
+ if macro.package_name == self.search_package:
143
+ self.locals[macro_name] = macro_func
144
+ # add to globals if it's in the root package
145
+ elif macro.package_name == self.root_package:
146
+ self.globals[macro_name] = macro_func
147
+
148
+ def add_macros(self, macros: Iterable[Macro], ctx: Dict[str, Any]) -> None:
149
+ for macro in macros:
150
+ self.add_macro(macro, ctx)
151
+
152
+ def build_namespace(
153
+ self, macros_by_package: Dict[str, Dict[str, Macro]], ctx: Dict[str, Any]
154
+ ) -> MacroNamespace:
155
+ for package in macros_by_package.values():
156
+ self.add_macros(package.values(), ctx)
157
+
158
+ # Iterate in reverse-order and overwrite: the packages that are first
159
+ # in the list are the ones we want to "win".
160
+ global_project_namespace: FlatNamespace = {}
161
+ for pkg in reversed(self.internal_package_names_order):
162
+ if pkg in self.internal_packages:
163
+ # add the macros pointed to by this package name
164
+ global_project_namespace.update(self.internal_packages[pkg])
165
+
166
+ return MacroNamespace(
167
+ global_namespace=self.globals, # root package macros
168
+ local_namespace=self.locals, # packages for *this* node
169
+ global_project_namespace=global_project_namespace, # internal packages
170
+ packages=self.packages, # non internal_packages
171
+ )
@@ -0,0 +1,73 @@
1
+ from typing import List
2
+
3
+ from dvt.clients.jinja import MacroStack
4
+ from dvt.context.macro_resolver import TestMacroNamespace
5
+ from dvt.contracts.graph.manifest import Manifest
6
+
7
+ from dbt.adapters.contracts.connection import AdapterRequiredConfig
8
+
9
+ from .base import contextproperty
10
+ from .configured import ConfiguredContext
11
+ from .macros import MacroNamespace, MacroNamespaceBuilder
12
+
13
+
14
+ class ManifestContext(ConfiguredContext):
15
+ """The Macro context has everything in the target context, plus the macros
16
+ in the manifest.
17
+
18
+ The given macros can override any previous context values, which will be
19
+ available as if they were accessed relative to the package name.
20
+ """
21
+
22
+ # subclasses are QueryHeaderContext and ProviderContext
23
+ def __init__(
24
+ self,
25
+ config: AdapterRequiredConfig,
26
+ manifest: Manifest,
27
+ search_package: str,
28
+ ) -> None:
29
+ super().__init__(config)
30
+ self.manifest = manifest
31
+ # this is the package of the node for which this context was built
32
+ self.search_package = search_package
33
+ self.macro_stack = MacroStack()
34
+ # This namespace is used by the BaseDatabaseWrapper in jinja rendering.
35
+ # The namespace is passed to it when it's constructed. It expects
36
+ # to be able to do: namespace.get_from_package(..)
37
+ self.namespace = self._build_namespace()
38
+
39
+ def _build_namespace(self) -> MacroNamespace:
40
+ # this takes all the macros in the manifest and adds them
41
+ # to the MacroNamespaceBuilder stored in self.namespace
42
+ builder = self._get_namespace_builder()
43
+ return builder.build_namespace(self.manifest.get_macros_by_package(), self._ctx)
44
+
45
+ def _get_namespace_builder(self) -> MacroNamespaceBuilder:
46
+ # avoid an import loop
47
+ from dbt.adapters.factory import get_adapter_package_names
48
+
49
+ internal_packages: List[str] = get_adapter_package_names(self.config.credentials.type)
50
+ return MacroNamespaceBuilder(
51
+ self.config.project_name,
52
+ self.search_package,
53
+ self.macro_stack,
54
+ internal_packages,
55
+ None,
56
+ )
57
+
58
+ # This does not use the Mashumaro code
59
+ def to_dict(self):
60
+ dct = super().to_dict()
61
+ # This moves all of the macros in the 'namespace' into top level
62
+ # keys in the manifest dictionary
63
+ if isinstance(self.namespace, TestMacroNamespace):
64
+ dct.update(self.namespace.local_namespace)
65
+ dct.update(self.namespace.project_namespace)
66
+ else:
67
+ dct.update(self.namespace)
68
+
69
+ return dct
70
+
71
+ @contextproperty()
72
+ def context_macro_stack(self):
73
+ return self.macro_stack