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
dvt/deps/local.py ADDED
@@ -0,0 +1,80 @@
1
+ import shutil
2
+ from typing import Dict
3
+
4
+ from dvt.config.project import PartialProject, Project
5
+ from dvt.config.renderer import PackageRenderer
6
+ from dvt.contracts.project import LocalPackage, ProjectPackageMetadata
7
+ from dvt.deps.base import PinnedPackage, UnpinnedPackage
8
+ from dvt.events.types import DepsCreatingLocalSymlink, DepsSymlinkNotAvailable
9
+
10
+ from dbt_common.clients import system
11
+ from dbt_common.events.functions import fire_event
12
+
13
+
14
+ class LocalPackageMixin:
15
+ def __init__(self, local: str) -> None:
16
+ super().__init__()
17
+ self.local = local
18
+
19
+ @property
20
+ def name(self):
21
+ return self.local
22
+
23
+ def source_type(self):
24
+ return "local"
25
+
26
+
27
+ class LocalPinnedPackage(LocalPackageMixin, PinnedPackage):
28
+ def __init__(self, local: str) -> None:
29
+ super().__init__(local)
30
+
31
+ def to_dict(self) -> Dict[str, str]:
32
+ return {
33
+ "local": self.local,
34
+ }
35
+
36
+ def get_version(self):
37
+ return None
38
+
39
+ def nice_version_name(self):
40
+ return "<local @ {}>".format(self.local)
41
+
42
+ def resolve_path(self, project):
43
+ return system.resolve_path_from_base(
44
+ self.local,
45
+ project.project_root,
46
+ )
47
+
48
+ def _fetch_metadata(
49
+ self, project: Project, renderer: PackageRenderer
50
+ ) -> ProjectPackageMetadata:
51
+ partial = PartialProject.from_project_root(self.resolve_path(project))
52
+ return partial.render_package_metadata(renderer)
53
+
54
+ def install(self, project, renderer):
55
+ src_path = self.resolve_path(project)
56
+ dest_path = self.get_installation_path(project, renderer)
57
+
58
+ if system.path_exists(dest_path):
59
+ if not system.path_is_symlink(dest_path):
60
+ system.rmdir(dest_path)
61
+ else:
62
+ system.remove_file(dest_path)
63
+ try:
64
+ fire_event(DepsCreatingLocalSymlink())
65
+ system.make_symlink(src_path, dest_path)
66
+ except OSError:
67
+ fire_event(DepsSymlinkNotAvailable())
68
+ shutil.copytree(src_path, dest_path)
69
+
70
+
71
+ class LocalUnpinnedPackage(LocalPackageMixin, UnpinnedPackage[LocalPinnedPackage]):
72
+ @classmethod
73
+ def from_contract(cls, contract: LocalPackage) -> "LocalUnpinnedPackage":
74
+ return cls(local=contract.local)
75
+
76
+ def incorporate(self, other: "LocalUnpinnedPackage") -> "LocalUnpinnedPackage":
77
+ return LocalUnpinnedPackage(local=self.local)
78
+
79
+ def resolved(self) -> LocalPinnedPackage:
80
+ return LocalPinnedPackage(local=self.local)
dvt/deps/registry.py ADDED
@@ -0,0 +1,131 @@
1
+ from typing import Dict, List
2
+
3
+ from dvt.clients import registry
4
+ from dvt.contracts.project import RegistryPackage, RegistryPackageMetadata
5
+ from dvt.deps.base import PinnedPackage, UnpinnedPackage
6
+ from dvt.exceptions import (
7
+ DependencyError,
8
+ PackageNotFoundError,
9
+ PackageVersionNotFoundError,
10
+ )
11
+ from dvt.flags import get_flags
12
+ from dvt.version import get_installed_version
13
+
14
+ from dbt_common import semver
15
+ from dbt_common.exceptions import VersionsNotCompatibleError
16
+
17
+
18
+ class RegistryPackageMixin:
19
+ def __init__(self, package: str) -> None:
20
+ super().__init__()
21
+ self.package = package
22
+
23
+ @property
24
+ def name(self):
25
+ return self.package
26
+
27
+ def source_type(self) -> str:
28
+ return "hub"
29
+
30
+
31
+ class RegistryPinnedPackage(RegistryPackageMixin, PinnedPackage):
32
+ def __init__(self, package: str, version: str, version_latest: str) -> None:
33
+ super().__init__(package)
34
+ self.version = version
35
+ self.version_latest = version_latest
36
+
37
+ @property
38
+ def name(self):
39
+ return self.package
40
+
41
+ def to_dict(self) -> Dict[str, str]:
42
+ return {
43
+ "package": self.package,
44
+ "version": self.version,
45
+ }
46
+
47
+ def source_type(self):
48
+ return "hub"
49
+
50
+ def get_version(self):
51
+ return self.version
52
+
53
+ def get_version_latest(self):
54
+ return self.version_latest
55
+
56
+ def nice_version_name(self):
57
+ return "version {}".format(self.version)
58
+
59
+ def _fetch_metadata(self, project, renderer) -> RegistryPackageMetadata:
60
+ dct = registry.package_version(self.package, self.version)
61
+ return RegistryPackageMetadata.from_dict(dct)
62
+
63
+ def install(self, project, renderer):
64
+ self._install(project, renderer)
65
+
66
+
67
+ class RegistryUnpinnedPackage(RegistryPackageMixin, UnpinnedPackage[RegistryPinnedPackage]):
68
+ def __init__(
69
+ self, package: str, versions: List[semver.VersionSpecifier], install_prerelease: bool
70
+ ) -> None:
71
+ super().__init__(package)
72
+ self.versions = versions
73
+ self.install_prerelease = install_prerelease
74
+
75
+ def _check_in_index(self):
76
+ index = registry.index_cached()
77
+ if self.package not in index:
78
+ raise PackageNotFoundError(self.package)
79
+
80
+ @classmethod
81
+ def from_contract(cls, contract: RegistryPackage) -> "RegistryUnpinnedPackage":
82
+ raw_version = contract.get_versions()
83
+
84
+ versions = [semver.VersionSpecifier.from_version_string(v) for v in raw_version]
85
+ return cls(
86
+ package=contract.package,
87
+ versions=versions,
88
+ install_prerelease=bool(contract.install_prerelease),
89
+ )
90
+
91
+ def incorporate(self, other: "RegistryUnpinnedPackage") -> "RegistryUnpinnedPackage":
92
+ return RegistryUnpinnedPackage(
93
+ package=self.package,
94
+ install_prerelease=self.install_prerelease,
95
+ versions=self.versions + other.versions,
96
+ )
97
+
98
+ def resolved(self) -> RegistryPinnedPackage:
99
+ self._check_in_index()
100
+ try:
101
+ range_ = semver.reduce_versions(*self.versions)
102
+ except VersionsNotCompatibleError as e:
103
+ new_msg = "Version error for package {}: {}".format(self.name, e)
104
+ raise DependencyError(new_msg) from e
105
+ flags = get_flags()
106
+ should_version_check = bool(flags.VERSION_CHECK)
107
+ dbt_version = get_installed_version()
108
+ compatible_versions = registry.get_compatible_versions(
109
+ self.package, dbt_version, should_version_check
110
+ )
111
+ prerelease_version_specified = any(bool(version.prerelease) for version in self.versions)
112
+ installable = semver.filter_installable(
113
+ compatible_versions, self.install_prerelease or prerelease_version_specified
114
+ )
115
+ if installable:
116
+ # for now, pick a version and then recurse. later on,
117
+ # we'll probably want to traverse multiple options
118
+ # so we can match packages. not going to make a difference
119
+ # right now.
120
+ target = semver.resolve_to_specific_version(range_, installable)
121
+ else:
122
+ target = None
123
+ if not target:
124
+ # raise an exception if no installable target version is found
125
+ raise PackageVersionNotFoundError(
126
+ self.package, range_, installable, should_version_check
127
+ )
128
+ latest_compatible = installable[-1]
129
+ return RegistryPinnedPackage(
130
+ package=self.package, version=target, version_latest=latest_compatible
131
+ )
dvt/deps/resolver.py ADDED
@@ -0,0 +1,149 @@
1
+ from dataclasses import dataclass, field
2
+ from typing import Any, Dict, Iterator, List, NoReturn, Set, Type
3
+
4
+ from dvt.config import Project
5
+ from dvt.config.renderer import PackageRenderer
6
+ from dvt.contracts.project import (
7
+ GitPackage,
8
+ LocalPackage,
9
+ PackageSpec,
10
+ PrivatePackage,
11
+ RegistryPackage,
12
+ TarballPackage,
13
+ )
14
+ from dvt.deps.base import BasePackage, PinnedPackage, UnpinnedPackage
15
+ from dvt.deps.git import GitUnpinnedPackage
16
+ from dvt.deps.local import LocalUnpinnedPackage
17
+ from dvt.deps.registry import RegistryUnpinnedPackage
18
+ from dvt.deps.tarball import TarballUnpinnedPackage
19
+ from dvt.exceptions import (
20
+ DependencyError,
21
+ DuplicateDependencyToRootError,
22
+ DuplicateProjectDependencyError,
23
+ MismatchedDependencyTypeError,
24
+ )
25
+
26
+
27
+ @dataclass
28
+ class PackageListing:
29
+ packages: Dict[str, UnpinnedPackage] = field(default_factory=dict)
30
+
31
+ def __len__(self):
32
+ return len(self.packages)
33
+
34
+ def __bool__(self):
35
+ return bool(self.packages)
36
+
37
+ def _pick_key(self, key: BasePackage) -> str:
38
+ for name in key.all_names():
39
+ if name in self.packages:
40
+ return name
41
+ return key.name
42
+
43
+ def __contains__(self, key: BasePackage):
44
+ for name in key.all_names():
45
+ if name in self.packages:
46
+ return True
47
+
48
+ def __getitem__(self, key: BasePackage):
49
+ key_str: str = self._pick_key(key)
50
+ return self.packages[key_str]
51
+
52
+ def __setitem__(self, key: BasePackage, value):
53
+ key_str: str = self._pick_key(key)
54
+ self.packages[key_str] = value
55
+
56
+ def _mismatched_types(self, old: UnpinnedPackage, new: UnpinnedPackage) -> NoReturn:
57
+ raise MismatchedDependencyTypeError(new, old)
58
+
59
+ def incorporate(self, package: UnpinnedPackage):
60
+ key: str = self._pick_key(package)
61
+ if key in self.packages:
62
+ existing: UnpinnedPackage = self.packages[key]
63
+ if not isinstance(existing, type(package)):
64
+ self._mismatched_types(existing, package)
65
+ self.packages[key] = existing.incorporate(package)
66
+ else:
67
+ self.packages[key] = package
68
+
69
+ def update_from(self, src: List[PackageSpec]) -> None:
70
+ pkg: UnpinnedPackage
71
+ for contract in src:
72
+ if isinstance(contract, LocalPackage):
73
+ pkg = LocalUnpinnedPackage.from_contract(contract)
74
+ elif isinstance(contract, TarballPackage):
75
+ pkg = TarballUnpinnedPackage.from_contract(contract)
76
+ elif isinstance(contract, GitPackage):
77
+ pkg = GitUnpinnedPackage.from_contract(contract)
78
+ elif isinstance(contract, PrivatePackage):
79
+ raise DependencyError(
80
+ f'Cannot resolve private package {contract.private} because git provider integration is missing. Please use a "git" package instead.'
81
+ )
82
+ elif isinstance(contract, RegistryPackage):
83
+ pkg = RegistryUnpinnedPackage.from_contract(contract)
84
+ else:
85
+ raise DependencyError("Invalid package type {}".format(type(contract)))
86
+ self.incorporate(pkg)
87
+
88
+ @classmethod
89
+ def from_contracts(cls: Type["PackageListing"], src: List[PackageSpec]) -> "PackageListing":
90
+ self = cls({})
91
+ self.update_from(src)
92
+ return self
93
+
94
+ def resolved(self) -> List[PinnedPackage]:
95
+ return [p.resolved() for p in self.packages.values()]
96
+
97
+ def __iter__(self) -> Iterator[UnpinnedPackage]:
98
+ return iter(self.packages.values())
99
+
100
+
101
+ def _check_for_duplicate_project_names(
102
+ final_deps: List[PinnedPackage],
103
+ project: Project,
104
+ renderer: PackageRenderer,
105
+ ):
106
+ seen: Set[str] = set()
107
+ for package in final_deps:
108
+ project_name = package.get_project_name(project, renderer)
109
+ if project_name in seen:
110
+ raise DuplicateProjectDependencyError(project_name)
111
+ elif project_name == project.project_name:
112
+ raise DuplicateDependencyToRootError(project_name)
113
+ seen.add(project_name)
114
+
115
+
116
+ def resolve_packages(
117
+ packages: List[PackageSpec],
118
+ project: Project,
119
+ cli_vars: Dict[str, Any],
120
+ ) -> List[PinnedPackage]:
121
+ pending = PackageListing.from_contracts(packages)
122
+ final = PackageListing()
123
+
124
+ renderer = PackageRenderer(cli_vars)
125
+
126
+ while pending:
127
+ next_pending = PackageListing()
128
+ # resolve the dependency in question
129
+ for package in pending:
130
+ final.incorporate(package)
131
+ target = final[package].resolved().fetch_metadata(project, renderer)
132
+ next_pending.update_from(target.packages)
133
+ pending = next_pending
134
+
135
+ resolved = final.resolved()
136
+ _check_for_duplicate_project_names(resolved, project, renderer)
137
+ return resolved
138
+
139
+
140
+ def resolve_lock_packages(packages: List[PackageSpec]) -> List[PinnedPackage]:
141
+ lock_packages = PackageListing.from_contracts(packages)
142
+ final = PackageListing()
143
+
144
+ for package in lock_packages:
145
+ final.incorporate(package)
146
+
147
+ resolved = final.resolved()
148
+
149
+ return resolved
dvt/deps/tarball.py ADDED
@@ -0,0 +1,121 @@
1
+ import functools
2
+ import os
3
+ from pathlib import Path
4
+ from typing import Dict
5
+
6
+ from dvt.config.project import PartialProject
7
+ from dvt.contracts.project import TarballPackage
8
+ from dvt.deps.base import PinnedPackage, UnpinnedPackage, get_downloads_path
9
+ from dvt.events.types import DepsScrubbedPackageName
10
+ from dvt.exceptions import DependencyError, env_secrets, scrub_secrets
11
+
12
+ from dbt_common.clients import system
13
+ from dbt_common.events.functions import warn_or_error
14
+ from dbt_common.utils.connection import connection_exception_retry
15
+
16
+
17
+ class TarballPackageMixin:
18
+ def __init__(self, tarball: str, tarball_unrendered: str) -> None:
19
+ super().__init__()
20
+ self.tarball = tarball
21
+ self.tarball_unrendered = tarball_unrendered
22
+
23
+ @property
24
+ def name(self):
25
+ return self.tarball
26
+
27
+ def source_type(self) -> str:
28
+ return "tarball"
29
+
30
+
31
+ class TarballPinnedPackage(TarballPackageMixin, PinnedPackage):
32
+ def __init__(self, tarball: str, tarball_unrendered: str, package: str) -> None:
33
+ super().__init__(tarball, tarball_unrendered)
34
+ self.package = package
35
+ self.version = "tarball"
36
+ self.tar_path = os.path.join(Path(get_downloads_path()), self.package)
37
+ self.untarred_path = f"{self.tar_path}_untarred"
38
+
39
+ @property
40
+ def name(self):
41
+ return self.package
42
+
43
+ def to_dict(self) -> Dict[str, str]:
44
+ tarball_scrubbed = scrub_secrets(self.tarball_unrendered, env_secrets())
45
+ if self.tarball_unrendered != tarball_scrubbed:
46
+ warn_or_error(DepsScrubbedPackageName(package_name=tarball_scrubbed))
47
+ return {
48
+ "tarball": tarball_scrubbed,
49
+ "name": self.package,
50
+ }
51
+
52
+ def get_version(self):
53
+ return self.version
54
+
55
+ def nice_version_name(self):
56
+ return f"tarball (url: {self.tarball})"
57
+
58
+ def _fetch_metadata(self, project, renderer):
59
+ """Download and untar the project and parse metadata from the project folder."""
60
+ download_untar_fn = functools.partial(
61
+ self.download_and_untar, self.tarball, self.tar_path, self.untarred_path, self.name
62
+ )
63
+ connection_exception_retry(download_untar_fn, 5)
64
+
65
+ tar_contents = os.listdir(self.untarred_path)
66
+ if len(tar_contents) != 1:
67
+ raise DependencyError(
68
+ f"Incorrect structure for package extracted from {self.tarball}."
69
+ f"The extracted package needs to follow the structure {self.name}/<package_contents>."
70
+ )
71
+ child_folder = os.listdir(self.untarred_path)[0]
72
+
73
+ self.untarred_path = os.path.join(self.untarred_path, child_folder)
74
+ partial = PartialProject.from_project_root(self.untarred_path)
75
+ metadata = partial.render_package_metadata(renderer)
76
+ metadata.name = self.package if self.package else metadata.name
77
+ return metadata
78
+
79
+ def install(self, project, renderer):
80
+ download_untar_fn = functools.partial(
81
+ self.download_and_untar, self.tarball, self.tar_path, self.untarred_path, self.name
82
+ )
83
+ connection_exception_retry(download_untar_fn, 5)
84
+ dest_path = self.get_installation_path(project, renderer)
85
+ if os.path.exists(dest_path):
86
+ if system.path_is_symlink(dest_path):
87
+ system.remove_file(dest_path)
88
+ else:
89
+ system.rmdir(dest_path)
90
+ system.move(self.untarred_path, dest_path)
91
+
92
+
93
+ class TarballUnpinnedPackage(TarballPackageMixin, UnpinnedPackage[TarballPinnedPackage]):
94
+ def __init__(
95
+ self,
96
+ tarball: str,
97
+ tarball_unrendered: str,
98
+ package: str,
99
+ ) -> None:
100
+ super().__init__(tarball, tarball_unrendered)
101
+ # setup to recycle RegistryPinnedPackage fns
102
+ self.package = package
103
+ self.version = "tarball"
104
+
105
+ @classmethod
106
+ def from_contract(cls, contract: TarballPackage) -> "TarballUnpinnedPackage":
107
+ return cls(
108
+ tarball=contract.tarball,
109
+ tarball_unrendered=(contract.unrendered.get("tarball") or contract.tarball),
110
+ package=contract.name,
111
+ )
112
+
113
+ def incorporate(self, other: "TarballUnpinnedPackage") -> "TarballUnpinnedPackage":
114
+ return TarballUnpinnedPackage(
115
+ tarball=self.tarball, tarball_unrendered=self.tarball_unrendered, package=self.package
116
+ )
117
+
118
+ def resolved(self) -> TarballPinnedPackage:
119
+ return TarballPinnedPackage(
120
+ tarball=self.tarball, tarball_unrendered=self.tarball_unrendered, package=self.package
121
+ )
@@ -0,0 +1,118 @@
1
+ import traceback
2
+ import typing as t
3
+
4
+ import click
5
+ import click.types as click_t
6
+ import dvt.cli.option_types as dbt_t
7
+ from docutils import nodes
8
+ from docutils.parsers.rst import Directive
9
+
10
+ PARAM_TYPE_MAP = {
11
+ click_t.BoolParamType: lambda _: "boolean",
12
+ click_t.Choice: lambda c: f"choice: {c.choices}",
13
+ click_t.IntParamType: lambda _: "int",
14
+ click_t.Path: lambda _: "path",
15
+ click_t.StringParamType: lambda _: "string",
16
+ dbt_t.YAML: lambda _: "YAML",
17
+ }
18
+
19
+
20
+ def format_command(cmd) -> nodes.section:
21
+ cmd_name = cmd.name.replace("-", "_")
22
+ section = nodes.section(
23
+ "",
24
+ nodes.title(text=f"Command: {cmd_name}"),
25
+ ids=[cmd_name],
26
+ names=[cmd_name],
27
+ )
28
+ section.extend(format_params(cmd))
29
+ return section
30
+
31
+
32
+ def format_params(cmd) -> t.List[nodes.section]:
33
+ lines = []
34
+ for param in cmd.params:
35
+ uid = f"{cmd.name}|{param.name}"
36
+ param_section = nodes.section(
37
+ "",
38
+ nodes.title(text=param.name),
39
+ ids=[uid],
40
+ names=[uid],
41
+ )
42
+
43
+ get_type_str = PARAM_TYPE_MAP.get(type(param.type), lambda _: "unknown")
44
+ type_str = get_type_str(param.type)
45
+
46
+ param_section.append(nodes.paragraph(text=f"Type: {type_str}"))
47
+ help_txt = getattr(param, "help", None)
48
+ if help_txt is not None:
49
+ param_section.append(nodes.paragraph(text=help_txt))
50
+ lines.append(param_section)
51
+ return lines
52
+
53
+
54
+ def load_module(module_path: str, error) -> t.Union[click.Command, click.Group]:
55
+ try:
56
+ module_name, attr_name = module_path.split(":", 1)
57
+ except ValueError: # noqa
58
+ raise error(f'"{module_path}" is not of format "module:parser"')
59
+
60
+ try:
61
+ mod = __import__(module_name, globals(), locals(), [attr_name])
62
+ except Exception: # noqa
63
+ raise error(
64
+ f'Failed to import "{attr_name}" from "{module_name}". '
65
+ f"The following exception was raised:\n{traceback.format_exc()}"
66
+ )
67
+
68
+ if not hasattr(mod, attr_name):
69
+ raise error(f'Module "{module_name}" has no attribute "{attr_name}"')
70
+
71
+ parser = getattr(mod, attr_name)
72
+
73
+ if not isinstance(parser, (click.Command, click.Group)):
74
+ raise error(
75
+ f'"{type(parser)}" of type "{module_path}" is not click.Command'
76
+ ' or click.Group."click.BaseCommand"'
77
+ )
78
+ return parser
79
+
80
+
81
+ class DBTClick(Directive):
82
+ has_content = False
83
+ required_arguments = 1
84
+
85
+ def run(self):
86
+ section = nodes.section(
87
+ "",
88
+ ids=["dbt-section"],
89
+ names=["dbt-section"],
90
+ )
91
+ cmds = self._get_commands(self.arguments[0])
92
+ for cmd in cmds:
93
+ command_section = format_command(cmd)
94
+ section.extend(command_section)
95
+ return [section]
96
+
97
+ def _get_commands(self, module: str) -> t.List[click.Command]:
98
+ click_group = load_module(module, self.error)
99
+ if type(click_group) is not click.Group:
100
+ raise self.error('Type "click.Group" not supported in dbt_click extension')
101
+ cmd_strs = [cmd for cmd in click_group.commands]
102
+ cmd_strs.sort()
103
+ cmds = []
104
+ for cmd_str in cmd_strs:
105
+ cmd = click_group.commands.get(cmd_str)
106
+ if cmd is not None:
107
+ cmds.append(cmd)
108
+ return cmds
109
+
110
+
111
+ def setup(app) -> t.Dict[str, t.Any]:
112
+ app.add_directive("dbt_click", DBTClick)
113
+
114
+ return {
115
+ "version": "0.1",
116
+ "parallel_read_safe": True,
117
+ "parallel_write_safe": True,
118
+ }
@@ -0,0 +1,32 @@
1
+ import os
2
+ import sys
3
+ import typing as t
4
+
5
+ # Configuration file for the Sphinx documentation builder.
6
+ #
7
+ # For the full list of built-in configuration values, see the documentation:
8
+ # https://www.sphinx-doc.org/en/master/usage/configuration.html
9
+
10
+ sys.path.insert(0, os.path.abspath("../../.."))
11
+ sys.path.insert(0, os.path.abspath("./_ext"))
12
+
13
+ # -- Project information -----------------------------------------------------
14
+ # https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information
15
+
16
+ project = "dbt-core"
17
+ copyright = "2022, dbt Labs"
18
+ author = "dbt Labs"
19
+
20
+ # -- General configuration ---------------------------------------------------
21
+ # https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration
22
+
23
+ extensions = ["dbt_click"]
24
+
25
+ templates_path = ["_templates"]
26
+ exclude_patterns: t.List[str] = []
27
+
28
+ # -- Options for HTML output -------------------------------------------------
29
+ # https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output
30
+
31
+ html_theme = "alabaster"
32
+ html_static_path = ["_static"]
dvt/env_vars.py ADDED
@@ -0,0 +1,64 @@
1
+ from dataclasses import dataclass
2
+ from typing import List, Optional
3
+
4
+ from dvt.cli import params
5
+ from dvt.deprecations import warn
6
+ from dvt.exceptions import DbtInternalError
7
+
8
+ from dbt_common.constants import ENGINE_ENV_PREFIX
9
+ from dbt_common.context import get_invocation_context
10
+
11
+ # These are env vars that are not in the params module, but are still allowed to be set.
12
+ # New additions to this list should use the new naming scheme, unless they are being added because
13
+ # they already existed, but we didn't know about them previously.
14
+ # TODO: Should at least some of these become (undocumented) cli param options?
15
+ _ADDITIONAL_ENGINE_ENV_VARS: List[str] = [
16
+ "DBT_INVOCATION_ENV",
17
+ "DBT_RECORDED_FILE_PATH",
18
+ "DBT_TEST_STATE_MODIFIED", # TODO: This is testing related, should we do this differently?
19
+ "DBT_PACKAGE_HUB_URL",
20
+ "DBT_DOWNLOAD_DIR",
21
+ "DBT_PP_FILE_DIFF_TEST", # TODO: This is testing related, should we do this differently?
22
+ "DBT_PP_TEST", # TODO: This is testing related, should we do this differently?
23
+ ]
24
+
25
+
26
+ @dataclass(frozen=True, init=False)
27
+ class EngineEnvVar:
28
+ name: str
29
+ old_name: Optional[str] = None
30
+
31
+ def __init__(self, envvar: str) -> None:
32
+ if envvar.startswith(ENGINE_ENV_PREFIX):
33
+ object.__setattr__(self, "name", envvar)
34
+ object.__setattr__(self, "old_name", None)
35
+ elif envvar.startswith("DBT"):
36
+ object.__setattr__(self, "name", envvar.replace("DBT", f"{ENGINE_ENV_PREFIX}"))
37
+ object.__setattr__(self, "old_name", envvar)
38
+ else:
39
+ raise DbtInternalError(
40
+ f"Invalid environment variable: {envvar}, this will only happen if we add a new option to dbt that has an envvar that doesn't start with DBT_ or {ENGINE_ENV_PREFIX}"
41
+ )
42
+
43
+
44
+ # Here we are creating a set of all known engine env vars. This is used in this moduleto create an allow list of dbt
45
+ # engine env vars. We also use it in the cli flags module to cross propagate engine env vars with their old non-engine prefixed names.
46
+ KNOWN_ENGINE_ENV_VARS: set[EngineEnvVar] = {
47
+ EngineEnvVar(envvar=envvar)
48
+ for envvar in [*params.KNOWN_ENV_VARS, *_ADDITIONAL_ENGINE_ENV_VARS]
49
+ }
50
+ _ALLOWED_ENV_VARS: set[str] = {envvar.name for envvar in KNOWN_ENGINE_ENV_VARS}
51
+
52
+
53
+ def validate_engine_env_vars() -> None:
54
+ """
55
+ Validate that any set environment variables that begin with the engine prefix are allowed.
56
+ """
57
+ env_vars = get_invocation_context()._env
58
+ for env_var in env_vars.keys():
59
+ if env_var.startswith(ENGINE_ENV_PREFIX) and env_var not in _ALLOWED_ENV_VARS:
60
+ warn(
61
+ "environment-variable-namespace-deprecation",
62
+ env_var=env_var,
63
+ reserved_prefix=ENGINE_ENV_PREFIX,
64
+ )