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/flags.py ADDED
@@ -0,0 +1,89 @@
1
+ # Do not import the os package because we expose this package in jinja
2
+ from argparse import Namespace
3
+ from pathlib import Path
4
+
5
+ # this roughly follows the patten of EVENT_MANAGER in dbt/common/events/functions.py
6
+ # During de-globlization, we'll need to handle both similarly
7
+ # Match USE_COLORS default with default in dbt.cli.params.use_colors for use in --version
8
+ GLOBAL_FLAGS = Namespace(USE_COLORS=True) # type: ignore
9
+
10
+
11
+ def set_flags(flags):
12
+ global GLOBAL_FLAGS
13
+ GLOBAL_FLAGS = flags
14
+
15
+
16
+ def get_flags():
17
+ return GLOBAL_FLAGS
18
+
19
+
20
+ def set_from_args(args: Namespace, project_flags):
21
+ global GLOBAL_FLAGS
22
+ from dvt.cli.flags import Flags, convert_config
23
+ from dvt.cli.main import cli
24
+
25
+ # we set attributes of args after initialize the flags, but project_flags
26
+ # is being read in the Flags constructor, so we need to read it here and pass in
27
+ # to make sure we use the correct project_flags
28
+ profiles_dir = getattr(args, "PROFILES_DIR", None) or getattr(args, "profiles_dir", None)
29
+ project_dir = getattr(args, "PROJECT_DIR", None) or getattr(args, "project_dir", None)
30
+ if profiles_dir and project_dir:
31
+ from dvt.config.project import read_project_flags
32
+
33
+ project_flags = read_project_flags(project_dir, profiles_dir)
34
+
35
+ # make a dummy context to get the flags, totally arbitrary
36
+ ctx = cli.make_context("run", ["run"])
37
+ flags = Flags(ctx, project_flags)
38
+ for arg_name, args_param_value in vars(args).items():
39
+ args_param_value = convert_config(arg_name, args_param_value)
40
+ object.__setattr__(flags, arg_name.upper(), args_param_value)
41
+ object.__setattr__(flags, arg_name.lower(), args_param_value)
42
+ flags.set_common_global_flags()
43
+ GLOBAL_FLAGS = flags # type: ignore
44
+
45
+
46
+ def get_flag_dict():
47
+ flag_attr = {
48
+ "use_experimental_parser",
49
+ "static_parser",
50
+ "warn_error",
51
+ "warn_error_options",
52
+ "write_json",
53
+ "partial_parse",
54
+ "use_colors",
55
+ "profiles_dir",
56
+ "debug",
57
+ "log_format",
58
+ "version_check",
59
+ "fail_fast",
60
+ "send_anonymous_usage_stats",
61
+ "printer_width",
62
+ "indirect_selection",
63
+ "log_cache_events",
64
+ "quiet",
65
+ "no_print",
66
+ "cache_selected_only",
67
+ "introspect",
68
+ "target_path",
69
+ "log_path",
70
+ "invocation_command",
71
+ "empty",
72
+ }
73
+ return {key: getattr(GLOBAL_FLAGS, key.upper(), None) for key in flag_attr}
74
+
75
+
76
+ # This is used by core/dbt/context/base.py to return a flag object
77
+ # in Jinja.
78
+ def get_flag_obj():
79
+ new_flags = Namespace()
80
+ for key, val in get_flag_dict().items():
81
+ if isinstance(val, Path):
82
+ val = str(val)
83
+ setattr(new_flags, key.upper(), val)
84
+ # The following 3 are CLI arguments only so they're not full-fledged flags,
85
+ # but we put in flags for users.
86
+ setattr(new_flags, "FULL_REFRESH", getattr(GLOBAL_FLAGS, "FULL_REFRESH", None))
87
+ setattr(new_flags, "STORE_FAILURES", getattr(GLOBAL_FLAGS, "STORE_FAILURES", None))
88
+ setattr(new_flags, "WHICH", getattr(GLOBAL_FLAGS, "WHICH", None))
89
+ return new_flags
dvt/graph/__init__.py ADDED
@@ -0,0 +1,11 @@
1
+ from .cli import parse_difference, parse_from_selectors_definition # noqa: F401
2
+ from .graph import Graph, UniqueId # noqa: F401
3
+ from .queue import GraphQueue # noqa: F401
4
+ from .selector import NodeSelector, ResourceTypeSelector # noqa: F401
5
+ from .selector_spec import ( # noqa: F401
6
+ SelectionCriteria,
7
+ SelectionDifference,
8
+ SelectionIntersection,
9
+ SelectionSpec,
10
+ SelectionUnion,
11
+ )
dvt/graph/cli.py ADDED
@@ -0,0 +1,248 @@
1
+ # special support for CLI argument parsing.
2
+ # TODO: Remove as part of https://github.com/dbt-labs/dbt-core/issues/6701
3
+ import itertools
4
+ from copy import deepcopy
5
+ from typing import Any, Dict, List, Optional, Tuple, Union
6
+
7
+ from dvt.clients.yaml_helper import Dumper, Loader, yaml # noqa: F401
8
+ from dvt.contracts.selection import SelectorDefinition, SelectorFile
9
+ from dvt.flags import get_flags
10
+
11
+ from dbt_common.exceptions import DbtInternalError, DbtValidationError
12
+
13
+ from .selector_spec import (
14
+ IndirectSelection,
15
+ SelectionCriteria,
16
+ SelectionDifference,
17
+ SelectionIntersection,
18
+ SelectionSpec,
19
+ SelectionUnion,
20
+ )
21
+
22
+ INTERSECTION_DELIMITER = ","
23
+
24
+ DEFAULT_INCLUDES: List[str] = ["fqn:*", "source:*", "exposure:*", "metric:*", "semantic_model:*"]
25
+ DEFAULT_EXCLUDES: List[str] = []
26
+
27
+
28
+ def parse_union(
29
+ components: List[str],
30
+ expect_exists: bool,
31
+ ) -> SelectionUnion:
32
+ # turn ['a b', 'c'] -> ['a', 'b', 'c']
33
+ raw_specs = itertools.chain.from_iterable(r.split(" ") for r in components)
34
+ union_components: List[SelectionSpec] = []
35
+ flags = get_flags()
36
+ # ['a', 'b', 'c,d'] -> union('a', 'b', intersection('c', 'd'))
37
+ for raw_spec in raw_specs:
38
+ intersection_components: List[SelectionSpec] = [
39
+ SelectionCriteria.from_single_spec(part)
40
+ for part in raw_spec.split(INTERSECTION_DELIMITER)
41
+ ]
42
+ union_components.append(
43
+ SelectionIntersection(
44
+ components=intersection_components,
45
+ expect_exists=expect_exists,
46
+ raw=raw_spec,
47
+ indirect_selection=IndirectSelection(flags.INDIRECT_SELECTION),
48
+ )
49
+ )
50
+ return SelectionUnion(
51
+ components=union_components,
52
+ expect_exists=False,
53
+ raw=components,
54
+ indirect_selection=IndirectSelection(flags.INDIRECT_SELECTION),
55
+ )
56
+
57
+
58
+ def parse_union_from_default(raw: Optional[List[str]], default: List[str]) -> SelectionUnion:
59
+ components: List[str]
60
+ expect_exists: bool
61
+ if raw is None:
62
+ return parse_union(components=default, expect_exists=False)
63
+ else:
64
+ return parse_union(components=raw, expect_exists=True)
65
+
66
+
67
+ def parse_difference(
68
+ include: Optional[List[str]], exclude: Optional[List[str]]
69
+ ) -> SelectionDifference:
70
+
71
+ if include == ():
72
+ include = None
73
+
74
+ included = parse_union_from_default(include, DEFAULT_INCLUDES)
75
+ excluded = parse_union_from_default(exclude, DEFAULT_EXCLUDES)
76
+ return SelectionDifference(components=[included, excluded])
77
+
78
+
79
+ RawDefinition = Union[str, Dict[str, Any]]
80
+
81
+
82
+ def _get_list_dicts(dct: Dict[str, Any], key: str) -> List[RawDefinition]:
83
+ result: List[RawDefinition] = []
84
+ if key not in dct:
85
+ raise DbtInternalError(f"Expected to find key {key} in dict, only found {list(dct)}")
86
+ values = dct[key]
87
+ if not isinstance(values, list):
88
+ raise DbtValidationError(f'Invalid value for key "{key}". Expected a list.')
89
+ for value in values:
90
+ if isinstance(value, dict):
91
+ for value_key in value:
92
+ if not isinstance(value_key, str):
93
+ raise DbtValidationError(
94
+ f'Expected all keys to "{key}" dict to be strings, '
95
+ f'but "{value_key}" is a "{type(value_key)}"'
96
+ )
97
+ result.append(value)
98
+ elif isinstance(value, str):
99
+ result.append(value)
100
+ else:
101
+ raise DbtValidationError(
102
+ f'Invalid value type {type(value)} in key "{key}", expected '
103
+ f"dict or str (value: {value})."
104
+ )
105
+
106
+ return result
107
+
108
+
109
+ def _parse_exclusions(definition, result={}) -> Optional[SelectionSpec]:
110
+ exclusions = _get_list_dicts(definition, "exclude")
111
+ parsed_exclusions = [parse_from_definition(excl, result=result) for excl in exclusions]
112
+ if len(parsed_exclusions) == 1:
113
+ return parsed_exclusions[0]
114
+ elif len(parsed_exclusions) > 1:
115
+ return SelectionUnion(components=parsed_exclusions, raw=exclusions)
116
+ else:
117
+ return None
118
+
119
+
120
+ def _parse_include_exclude_subdefs(
121
+ definitions: List[RawDefinition], result={}
122
+ ) -> Tuple[List[SelectionSpec], Optional[SelectionSpec]]:
123
+ include_parts: List[SelectionSpec] = []
124
+ diff_arg: Optional[SelectionSpec] = None
125
+
126
+ for definition in definitions:
127
+ if isinstance(definition, dict) and "exclude" in definition:
128
+ # do not allow multiple exclude: defs at the same level
129
+ if diff_arg is not None:
130
+ yaml_sel_cfg = yaml.dump(definition)
131
+ raise DbtValidationError(
132
+ f"You cannot provide multiple exclude arguments to the "
133
+ f"same selector set operator:\n{yaml_sel_cfg}"
134
+ )
135
+ diff_arg = _parse_exclusions(definition, result=result)
136
+ else:
137
+ include_parts.append(parse_from_definition(definition, result=result))
138
+
139
+ return (include_parts, diff_arg)
140
+
141
+
142
+ def parse_union_definition(definition: Dict[str, Any], result={}) -> SelectionSpec:
143
+ union_def_parts = _get_list_dicts(definition, "union")
144
+ include, exclude = _parse_include_exclude_subdefs(union_def_parts, result=result)
145
+
146
+ union = SelectionUnion(components=include)
147
+
148
+ if exclude is None:
149
+ union.raw = definition
150
+ return union
151
+ else:
152
+ return SelectionDifference(components=[union, exclude], raw=definition)
153
+
154
+
155
+ def parse_intersection_definition(definition: Dict[str, Any], result={}) -> SelectionSpec:
156
+ intersection_def_parts = _get_list_dicts(definition, "intersection")
157
+ include, exclude = _parse_include_exclude_subdefs(intersection_def_parts, result=result)
158
+ intersection = SelectionIntersection(components=include)
159
+
160
+ if exclude is None:
161
+ intersection.raw = definition
162
+ return intersection
163
+ else:
164
+ return SelectionDifference(components=[intersection, exclude], raw=definition)
165
+
166
+
167
+ def parse_dict_definition(definition: Dict[str, Any], result={}) -> SelectionSpec:
168
+ diff_arg: Optional[SelectionSpec] = None
169
+ if len(definition) == 1:
170
+ key = list(definition)[0]
171
+ value = definition[key]
172
+ if not isinstance(key, str):
173
+ raise DbtValidationError(
174
+ f'Expected definition key to be a "str", got one of type ' f'"{type(key)}" ({key})'
175
+ )
176
+ dct = {
177
+ "method": key,
178
+ "value": value,
179
+ }
180
+ elif definition.get("method") == "selector":
181
+ sel_def = definition.get("value")
182
+ if sel_def not in result:
183
+ raise DbtValidationError(f"Existing selector definition for {sel_def} not found.")
184
+ return result[definition["value"]]["definition"]
185
+ elif "method" in definition and "value" in definition:
186
+ dct = definition
187
+ if "exclude" in definition:
188
+ diff_arg = _parse_exclusions(definition, result=result)
189
+ dct = {k: v for k, v in dct.items() if k != "exclude"}
190
+ else:
191
+ raise DbtValidationError(
192
+ f'Expected either 1 key or else "method" '
193
+ f'and "value" keys, but got {list(definition)}'
194
+ )
195
+
196
+ # if key isn't a valid method name, this will raise
197
+ base = SelectionCriteria.selection_criteria_from_dict(definition, dct)
198
+ if diff_arg is None:
199
+ return base
200
+ else:
201
+ return SelectionDifference(components=[base, diff_arg])
202
+
203
+
204
+ def parse_from_definition(
205
+ definition: RawDefinition,
206
+ rootlevel=False,
207
+ result: Dict[str, Dict[str, Union[SelectionSpec, bool]]] = {},
208
+ ) -> SelectionSpec:
209
+
210
+ if (
211
+ isinstance(definition, dict)
212
+ and ("union" in definition or "intersection" in definition)
213
+ and rootlevel
214
+ and len(definition) > 1
215
+ ):
216
+ keys = ",".join(definition.keys())
217
+ raise DbtValidationError(
218
+ f"Only a single 'union' or 'intersection' key is allowed "
219
+ f"in a root level selector definition; found {keys}."
220
+ )
221
+ if isinstance(definition, str):
222
+ return SelectionCriteria.from_single_spec(definition)
223
+ elif "union" in definition:
224
+ return parse_union_definition(definition, result=result)
225
+ elif "intersection" in definition:
226
+ return parse_intersection_definition(definition, result=result)
227
+ elif isinstance(definition, dict):
228
+ return parse_dict_definition(definition, result=result)
229
+ else:
230
+ raise DbtValidationError(
231
+ f"Expected to find union, intersection, str or dict, instead "
232
+ f"found {type(definition)}: {definition}"
233
+ )
234
+
235
+
236
+ def parse_from_selectors_definition(
237
+ source: SelectorFile,
238
+ ) -> Dict[str, Dict[str, Union[SelectionSpec, bool]]]:
239
+ result: Dict[str, Dict[str, Union[SelectionSpec, bool]]] = {}
240
+ selector: SelectorDefinition
241
+ for selector in source.selectors:
242
+ result[selector.name] = {
243
+ "default": selector.default,
244
+ "definition": parse_from_definition(
245
+ selector.definition, rootlevel=True, result=deepcopy(result)
246
+ ),
247
+ }
248
+ return result
dvt/graph/graph.py ADDED
@@ -0,0 +1,172 @@
1
+ from functools import partial
2
+ from itertools import product
3
+ from typing import Iterable, Iterator, NewType, Optional, Set
4
+
5
+ import networkx as nx # type: ignore
6
+
7
+ from dbt_common.exceptions import DbtInternalError
8
+
9
+ UniqueId = NewType("UniqueId", str)
10
+
11
+
12
+ class Graph:
13
+ """A wrapper around the networkx graph that understands SelectionCriteria
14
+ and how they interact with the graph.
15
+ """
16
+
17
+ def __init__(self, graph) -> None:
18
+ self.graph: nx.DiGraph = graph
19
+
20
+ def nodes(self) -> Set[UniqueId]:
21
+ return set(self.graph.nodes())
22
+
23
+ def edges(self):
24
+ return self.graph.edges()
25
+
26
+ def __iter__(self) -> Iterator[UniqueId]:
27
+ return iter(self.graph.nodes())
28
+
29
+ def ancestors(self, node: UniqueId, max_depth: Optional[int] = None) -> Set[UniqueId]:
30
+ """Returns all nodes having a path to `node` in `graph`"""
31
+ if not self.graph.has_node(node):
32
+ raise DbtInternalError(f"Node {node} not found in the graph!")
33
+ filtered_graph = self.exclude_edge_type("parent_test")
34
+ return {
35
+ child
36
+ for _, child in nx.bfs_edges(filtered_graph, node, reverse=True, depth_limit=max_depth)
37
+ }
38
+
39
+ def descendants(self, node: UniqueId, max_depth: Optional[int] = None) -> Set[UniqueId]:
40
+ """Returns all nodes reachable from `node` in `graph`"""
41
+ if not self.graph.has_node(node):
42
+ raise DbtInternalError(f"Node {node} not found in the graph!")
43
+ filtered_graph = self.exclude_edge_type("parent_test")
44
+ return {child for _, child in nx.bfs_edges(filtered_graph, node, depth_limit=max_depth)}
45
+
46
+ def exclude_edge_type(self, edge_type_to_exclude):
47
+ return nx.subgraph_view(
48
+ self.graph,
49
+ filter_edge=partial(self.filter_edges_by_type, edge_type=edge_type_to_exclude),
50
+ )
51
+
52
+ def filter_edges_by_type(self, first_node, second_node, edge_type):
53
+ return self.graph.get_edge_data(first_node, second_node).get("edge_type") != edge_type
54
+
55
+ def select_childrens_parents(self, selected: Set[UniqueId]) -> Set[UniqueId]:
56
+ ancestors_for = self.select_children(selected) | selected
57
+ return self.select_parents(ancestors_for) | ancestors_for
58
+
59
+ def select_children(
60
+ self, selected: Set[UniqueId], max_depth: Optional[int] = None
61
+ ) -> Set[UniqueId]:
62
+ """Returns all nodes which are descendants of the 'selected' set.
63
+ Nodes in the 'selected' set are counted as children only if
64
+ they are descendants of other nodes in the 'selected' set."""
65
+ children: Set[UniqueId] = set()
66
+ i = 0
67
+ while len(selected) > 0 and (max_depth is None or i < max_depth):
68
+ next_layer: Set[UniqueId] = set()
69
+ for node in selected:
70
+ next_layer.update(
71
+ iter(
72
+ e[1]
73
+ for e in self.graph.out_edges(node)
74
+ if e[1] not in children
75
+ and self.filter_edges_by_type(e[0], e[1], "parent_test")
76
+ )
77
+ )
78
+ children.update(next_layer)
79
+ selected = next_layer
80
+ i += 1
81
+
82
+ return children
83
+
84
+ def select_parents(
85
+ self, selected: Set[UniqueId], max_depth: Optional[int] = None
86
+ ) -> Set[UniqueId]:
87
+ """Returns all nodes which are ancestors of the 'selected' set.
88
+ Nodes in the 'selected' set are counted as parents only if
89
+ they are ancestors of other nodes in the 'selected' set."""
90
+ parents: Set[UniqueId] = set()
91
+ i = 0
92
+ while len(selected) > 0 and (max_depth is None or i < max_depth):
93
+ next_layer: Set[UniqueId] = set()
94
+ for node in selected:
95
+ next_layer.update(
96
+ iter(
97
+ e[0]
98
+ for e in self.graph.in_edges(node)
99
+ if e[0] not in parents
100
+ and self.filter_edges_by_type(e[0], e[1], "parent_test")
101
+ )
102
+ )
103
+ parents.update(next_layer)
104
+ selected = next_layer
105
+ i += 1
106
+
107
+ return parents
108
+
109
+ def select_successors(self, selected: Set[UniqueId]) -> Set[UniqueId]:
110
+ successors: Set[UniqueId] = set()
111
+ for node in selected:
112
+ successors.update(self.graph.successors(node))
113
+ return successors
114
+
115
+ def get_subset_graph(self, selected: Iterable[UniqueId]) -> "Graph":
116
+ """Create and return a new graph that is a shallow copy of the graph,
117
+ but with only the nodes in include_nodes. Transitive edges across
118
+ removed nodes are preserved as explicit new edges.
119
+ """
120
+
121
+ new_graph: nx.DiGraph = self.graph.copy()
122
+ include_nodes: Set[UniqueId] = set(selected)
123
+
124
+ still_removing: bool = True
125
+ while still_removing:
126
+ nodes_to_remove = list(
127
+ node
128
+ for node in new_graph
129
+ if node not in include_nodes
130
+ and (new_graph.in_degree(node) * new_graph.out_degree(node)) == 0
131
+ )
132
+ if len(nodes_to_remove) == 0:
133
+ still_removing = False
134
+ else:
135
+ new_graph.remove_nodes_from(nodes_to_remove)
136
+
137
+ # sort remaining nodes by degree
138
+ remaining_nodes = list(new_graph.nodes())
139
+ remaining_nodes.sort(
140
+ key=lambda node: new_graph.in_degree(node) * new_graph.out_degree(node)
141
+ )
142
+
143
+ for node in remaining_nodes:
144
+ if node not in include_nodes:
145
+ source_nodes = [x for x, _ in new_graph.in_edges(node)]
146
+ target_nodes = [x for _, x in new_graph.out_edges(node)]
147
+
148
+ new_edges = product(source_nodes, target_nodes)
149
+ non_cyclic_new_edges = [
150
+ (source, target)
151
+ for source, target in new_edges
152
+ if source != target and not new_graph.has_edge(source, target)
153
+ ] # removes cyclic refs and edges already existing in new graph
154
+
155
+ new_graph.add_edges_from(non_cyclic_new_edges)
156
+ new_graph.remove_node(node)
157
+
158
+ for node in include_nodes:
159
+ if node not in new_graph:
160
+ raise ValueError(
161
+ "Couldn't find model '{}' -- does it exist or is it disabled?".format(node)
162
+ )
163
+
164
+ return Graph(new_graph)
165
+
166
+ def subgraph(self, nodes: Iterable[UniqueId]) -> "Graph":
167
+ # Take the original networkx graph and return a subgraph containing only
168
+ # the selected unique_id nodes.
169
+ return Graph(self.graph.subgraph(nodes))
170
+
171
+ def get_dependent_nodes(self, node: UniqueId):
172
+ return nx.descendants(self.graph, node)