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.
- dvt/__init__.py +7 -0
- dvt/_pydantic_shim.py +26 -0
- dvt/adapters/__init__.py +16 -0
- dvt/adapters/multi_adapter_manager.py +268 -0
- dvt/artifacts/__init__.py +0 -0
- dvt/artifacts/exceptions/__init__.py +1 -0
- dvt/artifacts/exceptions/schemas.py +31 -0
- dvt/artifacts/resources/__init__.py +116 -0
- dvt/artifacts/resources/base.py +68 -0
- dvt/artifacts/resources/types.py +93 -0
- dvt/artifacts/resources/v1/analysis.py +10 -0
- dvt/artifacts/resources/v1/catalog.py +23 -0
- dvt/artifacts/resources/v1/components.py +275 -0
- dvt/artifacts/resources/v1/config.py +282 -0
- dvt/artifacts/resources/v1/documentation.py +11 -0
- dvt/artifacts/resources/v1/exposure.py +52 -0
- dvt/artifacts/resources/v1/function.py +53 -0
- dvt/artifacts/resources/v1/generic_test.py +32 -0
- dvt/artifacts/resources/v1/group.py +22 -0
- dvt/artifacts/resources/v1/hook.py +11 -0
- dvt/artifacts/resources/v1/macro.py +30 -0
- dvt/artifacts/resources/v1/metric.py +173 -0
- dvt/artifacts/resources/v1/model.py +146 -0
- dvt/artifacts/resources/v1/owner.py +10 -0
- dvt/artifacts/resources/v1/saved_query.py +112 -0
- dvt/artifacts/resources/v1/seed.py +42 -0
- dvt/artifacts/resources/v1/semantic_layer_components.py +72 -0
- dvt/artifacts/resources/v1/semantic_model.py +315 -0
- dvt/artifacts/resources/v1/singular_test.py +14 -0
- dvt/artifacts/resources/v1/snapshot.py +92 -0
- dvt/artifacts/resources/v1/source_definition.py +85 -0
- dvt/artifacts/resources/v1/sql_operation.py +10 -0
- dvt/artifacts/resources/v1/unit_test_definition.py +78 -0
- dvt/artifacts/schemas/__init__.py +0 -0
- dvt/artifacts/schemas/base.py +191 -0
- dvt/artifacts/schemas/batch_results.py +24 -0
- dvt/artifacts/schemas/catalog/__init__.py +12 -0
- dvt/artifacts/schemas/catalog/v1/__init__.py +0 -0
- dvt/artifacts/schemas/catalog/v1/catalog.py +60 -0
- dvt/artifacts/schemas/freshness/__init__.py +1 -0
- dvt/artifacts/schemas/freshness/v3/__init__.py +0 -0
- dvt/artifacts/schemas/freshness/v3/freshness.py +159 -0
- dvt/artifacts/schemas/manifest/__init__.py +2 -0
- dvt/artifacts/schemas/manifest/v12/__init__.py +0 -0
- dvt/artifacts/schemas/manifest/v12/manifest.py +212 -0
- dvt/artifacts/schemas/results.py +148 -0
- dvt/artifacts/schemas/run/__init__.py +2 -0
- dvt/artifacts/schemas/run/v5/__init__.py +0 -0
- dvt/artifacts/schemas/run/v5/run.py +184 -0
- dvt/artifacts/schemas/upgrades/__init__.py +4 -0
- dvt/artifacts/schemas/upgrades/upgrade_manifest.py +174 -0
- dvt/artifacts/schemas/upgrades/upgrade_manifest_dbt_version.py +2 -0
- dvt/artifacts/utils/validation.py +153 -0
- dvt/cli/__init__.py +1 -0
- dvt/cli/context.py +16 -0
- dvt/cli/exceptions.py +56 -0
- dvt/cli/flags.py +558 -0
- dvt/cli/main.py +971 -0
- dvt/cli/option_types.py +121 -0
- dvt/cli/options.py +79 -0
- dvt/cli/params.py +803 -0
- dvt/cli/requires.py +478 -0
- dvt/cli/resolvers.py +32 -0
- dvt/cli/types.py +40 -0
- dvt/clients/__init__.py +0 -0
- dvt/clients/checked_load.py +82 -0
- dvt/clients/git.py +164 -0
- dvt/clients/jinja.py +206 -0
- dvt/clients/jinja_static.py +245 -0
- dvt/clients/registry.py +192 -0
- dvt/clients/yaml_helper.py +68 -0
- dvt/compilation.py +833 -0
- dvt/compute/__init__.py +26 -0
- dvt/compute/base.py +288 -0
- dvt/compute/engines/__init__.py +13 -0
- dvt/compute/engines/duckdb_engine.py +368 -0
- dvt/compute/engines/spark_engine.py +273 -0
- dvt/compute/query_analyzer.py +212 -0
- dvt/compute/router.py +483 -0
- dvt/config/__init__.py +4 -0
- dvt/config/catalogs.py +95 -0
- dvt/config/compute_config.py +406 -0
- dvt/config/profile.py +411 -0
- dvt/config/profiles_v2.py +464 -0
- dvt/config/project.py +893 -0
- dvt/config/renderer.py +232 -0
- dvt/config/runtime.py +491 -0
- dvt/config/selectors.py +209 -0
- dvt/config/utils.py +78 -0
- dvt/connectors/.gitignore +6 -0
- dvt/connectors/README.md +306 -0
- dvt/connectors/catalog.yml +217 -0
- dvt/connectors/download_connectors.py +300 -0
- dvt/constants.py +29 -0
- dvt/context/__init__.py +0 -0
- dvt/context/base.py +746 -0
- dvt/context/configured.py +136 -0
- dvt/context/context_config.py +350 -0
- dvt/context/docs.py +82 -0
- dvt/context/exceptions_jinja.py +179 -0
- dvt/context/macro_resolver.py +195 -0
- dvt/context/macros.py +171 -0
- dvt/context/manifest.py +73 -0
- dvt/context/providers.py +2198 -0
- dvt/context/query_header.py +14 -0
- dvt/context/secret.py +59 -0
- dvt/context/target.py +74 -0
- dvt/contracts/__init__.py +0 -0
- dvt/contracts/files.py +413 -0
- dvt/contracts/graph/__init__.py +0 -0
- dvt/contracts/graph/manifest.py +1904 -0
- dvt/contracts/graph/metrics.py +98 -0
- dvt/contracts/graph/model_config.py +71 -0
- dvt/contracts/graph/node_args.py +42 -0
- dvt/contracts/graph/nodes.py +1806 -0
- dvt/contracts/graph/semantic_manifest.py +233 -0
- dvt/contracts/graph/unparsed.py +812 -0
- dvt/contracts/project.py +417 -0
- dvt/contracts/results.py +53 -0
- dvt/contracts/selection.py +23 -0
- dvt/contracts/sql.py +86 -0
- dvt/contracts/state.py +69 -0
- dvt/contracts/util.py +46 -0
- dvt/deprecations.py +347 -0
- dvt/deps/__init__.py +0 -0
- dvt/deps/base.py +153 -0
- dvt/deps/git.py +196 -0
- dvt/deps/local.py +80 -0
- dvt/deps/registry.py +131 -0
- dvt/deps/resolver.py +149 -0
- dvt/deps/tarball.py +121 -0
- dvt/docs/source/_ext/dbt_click.py +118 -0
- dvt/docs/source/conf.py +32 -0
- dvt/env_vars.py +64 -0
- dvt/event_time/event_time.py +40 -0
- dvt/event_time/sample_window.py +60 -0
- dvt/events/__init__.py +16 -0
- dvt/events/base_types.py +37 -0
- dvt/events/core_types_pb2.py +2 -0
- dvt/events/logging.py +109 -0
- dvt/events/types.py +2534 -0
- dvt/exceptions.py +1487 -0
- dvt/flags.py +89 -0
- dvt/graph/__init__.py +11 -0
- dvt/graph/cli.py +248 -0
- dvt/graph/graph.py +172 -0
- dvt/graph/queue.py +213 -0
- dvt/graph/selector.py +375 -0
- dvt/graph/selector_methods.py +976 -0
- dvt/graph/selector_spec.py +223 -0
- dvt/graph/thread_pool.py +18 -0
- dvt/hooks.py +21 -0
- dvt/include/README.md +49 -0
- dvt/include/__init__.py +3 -0
- dvt/include/global_project.py +4 -0
- dvt/include/starter_project/.gitignore +4 -0
- dvt/include/starter_project/README.md +15 -0
- dvt/include/starter_project/__init__.py +3 -0
- dvt/include/starter_project/analyses/.gitkeep +0 -0
- dvt/include/starter_project/dvt_project.yml +36 -0
- dvt/include/starter_project/macros/.gitkeep +0 -0
- dvt/include/starter_project/models/example/my_first_dbt_model.sql +27 -0
- dvt/include/starter_project/models/example/my_second_dbt_model.sql +6 -0
- dvt/include/starter_project/models/example/schema.yml +21 -0
- dvt/include/starter_project/seeds/.gitkeep +0 -0
- dvt/include/starter_project/snapshots/.gitkeep +0 -0
- dvt/include/starter_project/tests/.gitkeep +0 -0
- dvt/internal_deprecations.py +27 -0
- dvt/jsonschemas/__init__.py +3 -0
- dvt/jsonschemas/jsonschemas.py +309 -0
- dvt/jsonschemas/project/0.0.110.json +4717 -0
- dvt/jsonschemas/project/0.0.85.json +2015 -0
- dvt/jsonschemas/resources/0.0.110.json +2636 -0
- dvt/jsonschemas/resources/0.0.85.json +2536 -0
- dvt/jsonschemas/resources/latest.json +6773 -0
- dvt/links.py +4 -0
- dvt/materializations/__init__.py +0 -0
- dvt/materializations/incremental/__init__.py +0 -0
- dvt/materializations/incremental/microbatch.py +235 -0
- dvt/mp_context.py +8 -0
- dvt/node_types.py +37 -0
- dvt/parser/__init__.py +23 -0
- dvt/parser/analysis.py +21 -0
- dvt/parser/base.py +549 -0
- dvt/parser/common.py +267 -0
- dvt/parser/docs.py +52 -0
- dvt/parser/fixtures.py +51 -0
- dvt/parser/functions.py +30 -0
- dvt/parser/generic_test.py +100 -0
- dvt/parser/generic_test_builders.py +334 -0
- dvt/parser/hooks.py +119 -0
- dvt/parser/macros.py +137 -0
- dvt/parser/manifest.py +2204 -0
- dvt/parser/models.py +574 -0
- dvt/parser/partial.py +1179 -0
- dvt/parser/read_files.py +445 -0
- dvt/parser/schema_generic_tests.py +423 -0
- dvt/parser/schema_renderer.py +111 -0
- dvt/parser/schema_yaml_readers.py +936 -0
- dvt/parser/schemas.py +1467 -0
- dvt/parser/search.py +149 -0
- dvt/parser/seeds.py +28 -0
- dvt/parser/singular_test.py +20 -0
- dvt/parser/snapshots.py +44 -0
- dvt/parser/sources.py +557 -0
- dvt/parser/sql.py +63 -0
- dvt/parser/unit_tests.py +622 -0
- dvt/plugins/__init__.py +20 -0
- dvt/plugins/contracts.py +10 -0
- dvt/plugins/exceptions.py +2 -0
- dvt/plugins/manager.py +164 -0
- dvt/plugins/manifest.py +21 -0
- dvt/profiler.py +20 -0
- dvt/py.typed +1 -0
- dvt/runners/__init__.py +2 -0
- dvt/runners/exposure_runner.py +7 -0
- dvt/runners/no_op_runner.py +46 -0
- dvt/runners/saved_query_runner.py +7 -0
- dvt/selected_resources.py +8 -0
- dvt/task/__init__.py +0 -0
- dvt/task/base.py +504 -0
- dvt/task/build.py +197 -0
- dvt/task/clean.py +57 -0
- dvt/task/clone.py +162 -0
- dvt/task/compile.py +151 -0
- dvt/task/compute.py +366 -0
- dvt/task/debug.py +650 -0
- dvt/task/deps.py +280 -0
- dvt/task/docs/__init__.py +3 -0
- dvt/task/docs/generate.py +408 -0
- dvt/task/docs/index.html +250 -0
- dvt/task/docs/serve.py +28 -0
- dvt/task/freshness.py +323 -0
- dvt/task/function.py +122 -0
- dvt/task/group_lookup.py +46 -0
- dvt/task/init.py +374 -0
- dvt/task/list.py +237 -0
- dvt/task/printer.py +176 -0
- dvt/task/profiles.py +256 -0
- dvt/task/retry.py +175 -0
- dvt/task/run.py +1146 -0
- dvt/task/run_operation.py +142 -0
- dvt/task/runnable.py +802 -0
- dvt/task/seed.py +104 -0
- dvt/task/show.py +150 -0
- dvt/task/snapshot.py +57 -0
- dvt/task/sql.py +111 -0
- dvt/task/test.py +464 -0
- dvt/tests/fixtures/__init__.py +1 -0
- dvt/tests/fixtures/project.py +620 -0
- dvt/tests/util.py +651 -0
- dvt/tracking.py +529 -0
- dvt/utils/__init__.py +3 -0
- dvt/utils/artifact_upload.py +151 -0
- dvt/utils/utils.py +408 -0
- dvt/version.py +249 -0
- dvt_core-1.11.0b4.dist-info/METADATA +252 -0
- dvt_core-1.11.0b4.dist-info/RECORD +261 -0
- dvt_core-1.11.0b4.dist-info/WHEEL +5 -0
- dvt_core-1.11.0b4.dist-info/entry_points.txt +2 -0
- dvt_core-1.11.0b4.dist-info/top_level.txt +1 -0
dvt/config/runtime.py
ADDED
|
@@ -0,0 +1,491 @@
|
|
|
1
|
+
import itertools
|
|
2
|
+
import os
|
|
3
|
+
from copy import deepcopy
|
|
4
|
+
from dataclasses import dataclass
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import (
|
|
7
|
+
Any,
|
|
8
|
+
Dict,
|
|
9
|
+
Iterable,
|
|
10
|
+
Iterator,
|
|
11
|
+
Mapping,
|
|
12
|
+
MutableSet,
|
|
13
|
+
Optional,
|
|
14
|
+
Tuple,
|
|
15
|
+
Type,
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
from dvt import tracking
|
|
19
|
+
from dvt.artifacts.resources import Quoting
|
|
20
|
+
from dvt.config.project import load_raw_project
|
|
21
|
+
from dvt.contracts.graph.manifest import ManifestMetadata
|
|
22
|
+
from dvt.contracts.project import Configuration
|
|
23
|
+
from dvt.events.types import UnusedResourceConfigPath
|
|
24
|
+
from dvt.exceptions import (
|
|
25
|
+
ConfigContractBrokenError,
|
|
26
|
+
DbtProjectError,
|
|
27
|
+
DbtRuntimeError,
|
|
28
|
+
NonUniquePackageNameError,
|
|
29
|
+
UninstalledPackagesFoundError,
|
|
30
|
+
)
|
|
31
|
+
from dvt.flags import get_flags
|
|
32
|
+
|
|
33
|
+
from dbt.adapters.contracts.connection import (
|
|
34
|
+
AdapterRequiredConfig,
|
|
35
|
+
Credentials,
|
|
36
|
+
HasCredentials,
|
|
37
|
+
)
|
|
38
|
+
from dbt.adapters.contracts.relation import ComponentName
|
|
39
|
+
from dbt.adapters.factory import get_include_paths, get_relation_class_by_name
|
|
40
|
+
from dbt_common.dataclass_schema import ValidationError
|
|
41
|
+
from dbt_common.events.functions import warn_or_error
|
|
42
|
+
from dbt_common.helper_types import DictDefaultEmptyStr, FQNPath, PathSet
|
|
43
|
+
|
|
44
|
+
from .profile import Profile
|
|
45
|
+
from .project import Project
|
|
46
|
+
from .renderer import DbtProjectYamlRenderer, ProfileRenderer
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
# Called by RuntimeConfig.collect_parts class method
|
|
50
|
+
def load_project(
|
|
51
|
+
project_root: str,
|
|
52
|
+
version_check: bool,
|
|
53
|
+
profile: HasCredentials,
|
|
54
|
+
cli_vars: Optional[Dict[str, Any]] = None,
|
|
55
|
+
validate: bool = False,
|
|
56
|
+
) -> Project:
|
|
57
|
+
# get the project with all of the provided information
|
|
58
|
+
project_renderer = DbtProjectYamlRenderer(profile, cli_vars)
|
|
59
|
+
project = Project.from_project_root(
|
|
60
|
+
project_root, project_renderer, verify_version=version_check, validate=validate
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
# Save env_vars encountered in rendering for partial parsing
|
|
64
|
+
project.project_env_vars = project_renderer.ctx_obj.env_vars
|
|
65
|
+
return project
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def load_profile(
|
|
69
|
+
project_root: str,
|
|
70
|
+
cli_vars: Dict[str, Any],
|
|
71
|
+
profile_name_override: Optional[str] = None,
|
|
72
|
+
target_override: Optional[str] = None,
|
|
73
|
+
threads_override: Optional[int] = None,
|
|
74
|
+
) -> Profile:
|
|
75
|
+
raw_project = load_raw_project(project_root)
|
|
76
|
+
raw_profile_name = raw_project.get("profile")
|
|
77
|
+
profile_renderer = ProfileRenderer(cli_vars)
|
|
78
|
+
profile_name = profile_renderer.render_value(raw_profile_name)
|
|
79
|
+
profile = Profile.render(
|
|
80
|
+
profile_renderer, profile_name, profile_name_override, target_override, threads_override
|
|
81
|
+
)
|
|
82
|
+
# Save env_vars encountered in rendering for partial parsing
|
|
83
|
+
profile.profile_env_vars = profile_renderer.ctx_obj.env_vars
|
|
84
|
+
return profile
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def _project_quoting_dict(proj: Project, profile: Profile) -> Dict[ComponentName, bool]:
|
|
88
|
+
src: Dict[str, Any] = profile.credentials.translate_aliases(proj.quoting)
|
|
89
|
+
result: Dict[ComponentName, bool] = {}
|
|
90
|
+
for key in ComponentName:
|
|
91
|
+
if key in src:
|
|
92
|
+
value = src[key]
|
|
93
|
+
if isinstance(value, bool):
|
|
94
|
+
result[key] = value
|
|
95
|
+
return result
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
@dataclass
|
|
99
|
+
class RuntimeConfig(Project, Profile, AdapterRequiredConfig):
|
|
100
|
+
args: Any
|
|
101
|
+
profile_name: str
|
|
102
|
+
cli_vars: Dict[str, Any]
|
|
103
|
+
dependencies: Optional[Mapping[str, "RuntimeConfig"]] = None
|
|
104
|
+
|
|
105
|
+
def __post_init__(self):
|
|
106
|
+
self.validate()
|
|
107
|
+
|
|
108
|
+
@classmethod
|
|
109
|
+
def get_profile(
|
|
110
|
+
cls,
|
|
111
|
+
project_root: str,
|
|
112
|
+
cli_vars: Dict[str, Any],
|
|
113
|
+
args: Any,
|
|
114
|
+
) -> Profile:
|
|
115
|
+
return load_profile(
|
|
116
|
+
project_root,
|
|
117
|
+
cli_vars,
|
|
118
|
+
args.profile,
|
|
119
|
+
args.target,
|
|
120
|
+
args.threads,
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
# Called by 'new_project' and 'from_args'
|
|
124
|
+
@classmethod
|
|
125
|
+
def from_parts(
|
|
126
|
+
cls,
|
|
127
|
+
project: Project,
|
|
128
|
+
profile: Profile,
|
|
129
|
+
args: Any,
|
|
130
|
+
dependencies: Optional[Mapping[str, "RuntimeConfig"]] = None,
|
|
131
|
+
) -> "RuntimeConfig":
|
|
132
|
+
"""Instantiate a RuntimeConfig from its components.
|
|
133
|
+
|
|
134
|
+
:param profile: A parsed dbt Profile.
|
|
135
|
+
:param project: A parsed dbt Project.
|
|
136
|
+
:param args: The parsed command-line arguments.
|
|
137
|
+
:returns RuntimeConfig: The new configuration.
|
|
138
|
+
"""
|
|
139
|
+
quoting: Dict[str, Any] = (
|
|
140
|
+
get_relation_class_by_name(profile.credentials.type)
|
|
141
|
+
.get_default_quote_policy()
|
|
142
|
+
.replace_dict(_project_quoting_dict(project, profile))
|
|
143
|
+
).to_dict(omit_none=True)
|
|
144
|
+
|
|
145
|
+
cli_vars: Dict[str, Any] = getattr(args, "vars", {})
|
|
146
|
+
log_cache_events: bool = getattr(args, "log_cache_events", profile.log_cache_events)
|
|
147
|
+
|
|
148
|
+
return cls(
|
|
149
|
+
project_name=project.project_name,
|
|
150
|
+
version=project.version,
|
|
151
|
+
project_root=project.project_root,
|
|
152
|
+
model_paths=project.model_paths,
|
|
153
|
+
macro_paths=project.macro_paths,
|
|
154
|
+
seed_paths=project.seed_paths,
|
|
155
|
+
test_paths=project.test_paths,
|
|
156
|
+
analysis_paths=project.analysis_paths,
|
|
157
|
+
docs_paths=project.docs_paths,
|
|
158
|
+
asset_paths=project.asset_paths,
|
|
159
|
+
function_paths=project.function_paths,
|
|
160
|
+
target_path=project.target_path,
|
|
161
|
+
snapshot_paths=project.snapshot_paths,
|
|
162
|
+
clean_targets=project.clean_targets,
|
|
163
|
+
log_path=project.log_path,
|
|
164
|
+
packages_install_path=project.packages_install_path,
|
|
165
|
+
packages_specified_path=project.packages_specified_path,
|
|
166
|
+
quoting=quoting,
|
|
167
|
+
models=project.models,
|
|
168
|
+
on_run_start=project.on_run_start,
|
|
169
|
+
on_run_end=project.on_run_end,
|
|
170
|
+
dispatch=project.dispatch,
|
|
171
|
+
seeds=project.seeds,
|
|
172
|
+
snapshots=project.snapshots,
|
|
173
|
+
dbt_version=project.dbt_version,
|
|
174
|
+
packages=project.packages,
|
|
175
|
+
manifest_selectors=project.manifest_selectors,
|
|
176
|
+
selectors=project.selectors,
|
|
177
|
+
query_comment=project.query_comment,
|
|
178
|
+
sources=project.sources,
|
|
179
|
+
data_tests=project.data_tests,
|
|
180
|
+
unit_tests=project.unit_tests,
|
|
181
|
+
metrics=project.metrics,
|
|
182
|
+
semantic_models=project.semantic_models,
|
|
183
|
+
saved_queries=project.saved_queries,
|
|
184
|
+
exposures=project.exposures,
|
|
185
|
+
functions=project.functions,
|
|
186
|
+
vars=project.vars,
|
|
187
|
+
config_version=project.config_version,
|
|
188
|
+
unrendered=project.unrendered,
|
|
189
|
+
project_env_vars=project.project_env_vars,
|
|
190
|
+
restrict_access=project.restrict_access,
|
|
191
|
+
profile_env_vars=profile.profile_env_vars,
|
|
192
|
+
profile_name=profile.profile_name,
|
|
193
|
+
target_name=profile.target_name,
|
|
194
|
+
threads=profile.threads,
|
|
195
|
+
credentials=profile.credentials,
|
|
196
|
+
args=args,
|
|
197
|
+
cli_vars=cli_vars,
|
|
198
|
+
log_cache_events=log_cache_events,
|
|
199
|
+
dependencies=dependencies,
|
|
200
|
+
dbt_cloud=project.dbt_cloud,
|
|
201
|
+
flags=project.flags,
|
|
202
|
+
)
|
|
203
|
+
|
|
204
|
+
# Called by 'load_projects' in this class
|
|
205
|
+
def new_project(self, project_root: str) -> "RuntimeConfig":
|
|
206
|
+
"""Given a new project root, read in its project dictionary, supply the
|
|
207
|
+
existing project's profile info, and create a new project file.
|
|
208
|
+
|
|
209
|
+
:param project_root: A filepath to a dbt project.
|
|
210
|
+
:raises DbtProfileError: If the profile is invalid.
|
|
211
|
+
:raises DbtProjectError: If project is missing or invalid.
|
|
212
|
+
:returns: The new configuration.
|
|
213
|
+
"""
|
|
214
|
+
# copy profile
|
|
215
|
+
profile = Profile(**self.to_profile_info())
|
|
216
|
+
profile.validate()
|
|
217
|
+
|
|
218
|
+
# load the new project and its packages. Don't pass cli variables.
|
|
219
|
+
renderer = DbtProjectYamlRenderer(profile)
|
|
220
|
+
project = Project.from_project_root(
|
|
221
|
+
project_root,
|
|
222
|
+
renderer,
|
|
223
|
+
verify_version=bool(getattr(self.args, "VERSION_CHECK", True)),
|
|
224
|
+
)
|
|
225
|
+
|
|
226
|
+
runtime_config = self.from_parts(
|
|
227
|
+
project=project,
|
|
228
|
+
profile=profile,
|
|
229
|
+
args=deepcopy(self.args),
|
|
230
|
+
)
|
|
231
|
+
# force our quoting back onto the new project.
|
|
232
|
+
runtime_config.quoting = deepcopy(self.quoting)
|
|
233
|
+
return runtime_config
|
|
234
|
+
|
|
235
|
+
def serialize(self) -> Dict[str, Any]:
|
|
236
|
+
"""Serialize the full configuration to a single dictionary. For any
|
|
237
|
+
instance that has passed validate() (which happens in __init__), it
|
|
238
|
+
matches the Configuration contract.
|
|
239
|
+
|
|
240
|
+
Note that args are not serialized.
|
|
241
|
+
|
|
242
|
+
:returns dict: The serialized configuration.
|
|
243
|
+
"""
|
|
244
|
+
result = self.to_project_config(with_packages=True)
|
|
245
|
+
result.update(self.to_profile_info(serialize_credentials=True))
|
|
246
|
+
result["cli_vars"] = deepcopy(self.cli_vars)
|
|
247
|
+
return result
|
|
248
|
+
|
|
249
|
+
def validate(self):
|
|
250
|
+
"""Validate the configuration against its contract.
|
|
251
|
+
|
|
252
|
+
:raises DbtProjectError: If the configuration fails validation.
|
|
253
|
+
"""
|
|
254
|
+
try:
|
|
255
|
+
Configuration.validate(self.serialize())
|
|
256
|
+
except ValidationError as e:
|
|
257
|
+
raise ConfigContractBrokenError(e) from e
|
|
258
|
+
|
|
259
|
+
# Called by RuntimeConfig.from_args
|
|
260
|
+
@classmethod
|
|
261
|
+
def collect_parts(cls: Type["RuntimeConfig"], args: Any) -> Tuple[Project, Profile]:
|
|
262
|
+
# profile_name from the project
|
|
263
|
+
project_root = args.project_dir if args.project_dir else os.getcwd()
|
|
264
|
+
cli_vars: Dict[str, Any] = getattr(args, "vars", {})
|
|
265
|
+
profile = cls.get_profile(
|
|
266
|
+
project_root,
|
|
267
|
+
cli_vars,
|
|
268
|
+
args,
|
|
269
|
+
)
|
|
270
|
+
flags = get_flags()
|
|
271
|
+
project = load_project(project_root, bool(flags.VERSION_CHECK), profile, cli_vars)
|
|
272
|
+
return project, profile
|
|
273
|
+
|
|
274
|
+
# Called in task/base.py, in BaseTask.from_args
|
|
275
|
+
@classmethod
|
|
276
|
+
def from_args(cls, args: Any) -> "RuntimeConfig":
|
|
277
|
+
"""Given arguments, read in dbt_project.yml from the current directory,
|
|
278
|
+
read in packages.yml if it exists, and use them to find the profile to
|
|
279
|
+
load.
|
|
280
|
+
|
|
281
|
+
:param args: The arguments as parsed from the cli.
|
|
282
|
+
:raises DbtProjectError: If the project is invalid or missing.
|
|
283
|
+
:raises DbtProfileError: If the profile is invalid or missing.
|
|
284
|
+
:raises DbtValidationError: If the cli variables are invalid.
|
|
285
|
+
"""
|
|
286
|
+
project, profile = cls.collect_parts(args)
|
|
287
|
+
|
|
288
|
+
return cls.from_parts(
|
|
289
|
+
project=project,
|
|
290
|
+
profile=profile,
|
|
291
|
+
args=args,
|
|
292
|
+
)
|
|
293
|
+
|
|
294
|
+
def get_metadata(self) -> ManifestMetadata:
|
|
295
|
+
return ManifestMetadata(
|
|
296
|
+
project_name=self.project_name,
|
|
297
|
+
project_id=self.hashed_name(),
|
|
298
|
+
user_id=tracking.active_user.id if tracking.active_user else None,
|
|
299
|
+
send_anonymous_usage_stats=(
|
|
300
|
+
get_flags().SEND_ANONYMOUS_USAGE_STATS if tracking.active_user else None
|
|
301
|
+
),
|
|
302
|
+
adapter_type=self.credentials.type,
|
|
303
|
+
quoting=Quoting(
|
|
304
|
+
database=self.quoting.get("database", None),
|
|
305
|
+
schema=self.quoting.get("schema", None),
|
|
306
|
+
identifier=self.quoting.get("identifier", None),
|
|
307
|
+
column=self.quoting.get("column", None),
|
|
308
|
+
),
|
|
309
|
+
run_started_at=(
|
|
310
|
+
tracking.active_user.run_started_at if tracking.active_user is not None else None
|
|
311
|
+
),
|
|
312
|
+
)
|
|
313
|
+
|
|
314
|
+
def _get_v2_config_paths(
|
|
315
|
+
self,
|
|
316
|
+
config,
|
|
317
|
+
path: FQNPath,
|
|
318
|
+
paths: MutableSet[FQNPath],
|
|
319
|
+
) -> PathSet:
|
|
320
|
+
for key, value in config.items():
|
|
321
|
+
if isinstance(value, dict) and not key.startswith("+"):
|
|
322
|
+
self._get_config_paths(value, path + (key,), paths)
|
|
323
|
+
else:
|
|
324
|
+
paths.add(path)
|
|
325
|
+
return frozenset(paths)
|
|
326
|
+
|
|
327
|
+
def _get_config_paths(
|
|
328
|
+
self,
|
|
329
|
+
config: Dict[str, Any],
|
|
330
|
+
path: FQNPath = (),
|
|
331
|
+
paths: Optional[MutableSet[FQNPath]] = None,
|
|
332
|
+
) -> PathSet:
|
|
333
|
+
if paths is None:
|
|
334
|
+
paths = set()
|
|
335
|
+
|
|
336
|
+
for key, value in config.items():
|
|
337
|
+
if isinstance(value, dict) and not key.startswith("+"):
|
|
338
|
+
self._get_v2_config_paths(value, path + (key,), paths)
|
|
339
|
+
else:
|
|
340
|
+
paths.add(path)
|
|
341
|
+
return frozenset(paths)
|
|
342
|
+
|
|
343
|
+
def get_resource_config_paths(self) -> Dict[str, PathSet]:
|
|
344
|
+
"""Return a dictionary with resource type keys whose values are
|
|
345
|
+
lists of lists of strings, where each inner list of strings represents
|
|
346
|
+
a configured path in the resource.
|
|
347
|
+
"""
|
|
348
|
+
return {
|
|
349
|
+
"models": self._get_config_paths(self.models),
|
|
350
|
+
"seeds": self._get_config_paths(self.seeds),
|
|
351
|
+
"snapshots": self._get_config_paths(self.snapshots),
|
|
352
|
+
"sources": self._get_config_paths(self.sources),
|
|
353
|
+
"data_tests": self._get_config_paths(self.data_tests),
|
|
354
|
+
"unit_tests": self._get_config_paths(self.unit_tests),
|
|
355
|
+
"metrics": self._get_config_paths(self.metrics),
|
|
356
|
+
"semantic_models": self._get_config_paths(self.semantic_models),
|
|
357
|
+
"saved_queries": self._get_config_paths(self.saved_queries),
|
|
358
|
+
"exposures": self._get_config_paths(self.exposures),
|
|
359
|
+
"functions": self._get_config_paths(self.functions),
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
def warn_for_unused_resource_config_paths(
|
|
363
|
+
self,
|
|
364
|
+
resource_fqns: Mapping[str, PathSet],
|
|
365
|
+
disabled: PathSet,
|
|
366
|
+
) -> None:
|
|
367
|
+
"""Return a list of lists of strings, where each inner list of strings
|
|
368
|
+
represents a type + FQN path of a resource configuration that is not
|
|
369
|
+
used.
|
|
370
|
+
"""
|
|
371
|
+
disabled_fqns = frozenset(tuple(fqn) for fqn in disabled)
|
|
372
|
+
resource_config_paths = self.get_resource_config_paths()
|
|
373
|
+
unused_resource_config_paths = []
|
|
374
|
+
for resource_type, config_paths in resource_config_paths.items():
|
|
375
|
+
used_fqns = resource_fqns.get(resource_type, frozenset())
|
|
376
|
+
fqns = used_fqns | disabled_fqns
|
|
377
|
+
|
|
378
|
+
for config_path in config_paths:
|
|
379
|
+
if not _is_config_used(config_path, fqns):
|
|
380
|
+
resource_path = ".".join(i for i in ((resource_type,) + config_path))
|
|
381
|
+
unused_resource_config_paths.append(resource_path)
|
|
382
|
+
|
|
383
|
+
if len(unused_resource_config_paths) == 0:
|
|
384
|
+
return
|
|
385
|
+
|
|
386
|
+
warn_or_error(UnusedResourceConfigPath(unused_config_paths=unused_resource_config_paths))
|
|
387
|
+
|
|
388
|
+
def load_dependencies(self, base_only=False) -> Mapping[str, "RuntimeConfig"]:
|
|
389
|
+
if self.dependencies is None:
|
|
390
|
+
all_projects = {self.project_name: self}
|
|
391
|
+
internal_packages = get_include_paths(self.credentials.type)
|
|
392
|
+
if base_only:
|
|
393
|
+
# Test setup -- we want to load macros without dependencies
|
|
394
|
+
project_paths = itertools.chain(internal_packages)
|
|
395
|
+
else:
|
|
396
|
+
# raise exception if fewer installed packages than in packages.yml
|
|
397
|
+
count_packages_specified = len(self.packages.packages) # type: ignore
|
|
398
|
+
count_packages_installed = len(tuple(self._get_project_directories()))
|
|
399
|
+
if count_packages_specified > count_packages_installed:
|
|
400
|
+
raise UninstalledPackagesFoundError(
|
|
401
|
+
count_packages_specified,
|
|
402
|
+
count_packages_installed,
|
|
403
|
+
self.packages_specified_path,
|
|
404
|
+
self.packages_install_path,
|
|
405
|
+
)
|
|
406
|
+
project_paths = itertools.chain(internal_packages, self._get_project_directories())
|
|
407
|
+
for project_name, project in self.load_projects(project_paths):
|
|
408
|
+
if project_name in all_projects:
|
|
409
|
+
raise NonUniquePackageNameError(project_name)
|
|
410
|
+
all_projects[project_name] = project
|
|
411
|
+
self.dependencies = all_projects
|
|
412
|
+
return self.dependencies
|
|
413
|
+
|
|
414
|
+
def clear_dependencies(self):
|
|
415
|
+
self.dependencies = None
|
|
416
|
+
|
|
417
|
+
# Called by 'load_dependencies' in this class
|
|
418
|
+
def load_projects(self, paths: Iterable[Path]) -> Iterator[Tuple[str, "RuntimeConfig"]]:
|
|
419
|
+
for path in paths:
|
|
420
|
+
try:
|
|
421
|
+
project = self.new_project(str(path))
|
|
422
|
+
except DbtProjectError as e:
|
|
423
|
+
raise DbtProjectError(
|
|
424
|
+
f"Failed to read package: {e}",
|
|
425
|
+
result_type="invalid_project",
|
|
426
|
+
path=path,
|
|
427
|
+
) from e
|
|
428
|
+
else:
|
|
429
|
+
yield project.project_name, project
|
|
430
|
+
|
|
431
|
+
def _get_project_directories(self) -> Iterator[Path]:
|
|
432
|
+
root = Path(self.project_root) / self.packages_install_path
|
|
433
|
+
|
|
434
|
+
if root.exists():
|
|
435
|
+
for path in root.iterdir():
|
|
436
|
+
if path.is_dir() and not path.name.startswith("__"):
|
|
437
|
+
yield path
|
|
438
|
+
|
|
439
|
+
|
|
440
|
+
class UnsetCredentials(Credentials):
|
|
441
|
+
def __init__(self) -> None:
|
|
442
|
+
super().__init__("", "")
|
|
443
|
+
|
|
444
|
+
@property
|
|
445
|
+
def type(self):
|
|
446
|
+
return None
|
|
447
|
+
|
|
448
|
+
@property
|
|
449
|
+
def unique_field(self):
|
|
450
|
+
return None
|
|
451
|
+
|
|
452
|
+
def connection_info(self, *args, **kwargs):
|
|
453
|
+
return {}
|
|
454
|
+
|
|
455
|
+
def _connection_keys(self):
|
|
456
|
+
return ()
|
|
457
|
+
|
|
458
|
+
|
|
459
|
+
# This is used by commands which do not require
|
|
460
|
+
# a profile, i.e. dbt deps and clean
|
|
461
|
+
class UnsetProfile(Profile):
|
|
462
|
+
def __init__(self):
|
|
463
|
+
self.credentials = UnsetCredentials()
|
|
464
|
+
self.profile_name = ""
|
|
465
|
+
self.target_name = ""
|
|
466
|
+
self.threads = -1
|
|
467
|
+
|
|
468
|
+
def to_target_dict(self):
|
|
469
|
+
return DictDefaultEmptyStr({})
|
|
470
|
+
|
|
471
|
+
def __getattribute__(self, name):
|
|
472
|
+
if name in {"profile_name", "target_name", "threads"}:
|
|
473
|
+
raise DbtRuntimeError(f'Error: disallowed attribute "{name}" - no profile!')
|
|
474
|
+
|
|
475
|
+
return Profile.__getattribute__(self, name)
|
|
476
|
+
|
|
477
|
+
|
|
478
|
+
UNUSED_RESOURCE_CONFIGURATION_PATH_MESSAGE = """\
|
|
479
|
+
Configuration paths exist in your dbt_project.yml file which do not \
|
|
480
|
+
apply to any resources.
|
|
481
|
+
There are {} unused configuration paths:
|
|
482
|
+
{}
|
|
483
|
+
"""
|
|
484
|
+
|
|
485
|
+
|
|
486
|
+
def _is_config_used(path, fqns):
|
|
487
|
+
if fqns:
|
|
488
|
+
for fqn in fqns:
|
|
489
|
+
if len(path) <= len(fqn) and fqn[: len(path)] == path:
|
|
490
|
+
return True
|
|
491
|
+
return False
|
dvt/config/selectors.py
ADDED
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
from copy import deepcopy
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
from typing import Any, Dict, Optional, Union
|
|
4
|
+
|
|
5
|
+
from dvt.clients.yaml_helper import Dumper, Loader, load_yaml_text, yaml # noqa: F401
|
|
6
|
+
from dvt.contracts.selection import SelectorFile
|
|
7
|
+
from dvt.exceptions import DbtSelectorsError
|
|
8
|
+
from dvt.graph import SelectionSpec, parse_from_selectors_definition
|
|
9
|
+
from dvt.graph.selector_spec import SelectionCriteria
|
|
10
|
+
|
|
11
|
+
from dbt_common.clients.system import (
|
|
12
|
+
load_file_contents,
|
|
13
|
+
path_exists,
|
|
14
|
+
resolve_path_from_base,
|
|
15
|
+
)
|
|
16
|
+
from dbt_common.dataclass_schema import ValidationError
|
|
17
|
+
from dbt_common.exceptions import DbtRuntimeError
|
|
18
|
+
|
|
19
|
+
from .renderer import BaseRenderer
|
|
20
|
+
|
|
21
|
+
MALFORMED_SELECTOR_ERROR = """\
|
|
22
|
+
The selectors.yml file in this project is malformed. Please double check
|
|
23
|
+
the contents of this file and fix any errors before retrying.
|
|
24
|
+
|
|
25
|
+
You can find more information on the syntax for this file here:
|
|
26
|
+
https://docs.getdbt.com/reference/node-selection/yaml-selectors
|
|
27
|
+
|
|
28
|
+
Validator Error:
|
|
29
|
+
{error}
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class SelectorConfig(Dict[str, Dict[str, Union[SelectionSpec, bool]]]):
|
|
34
|
+
@classmethod
|
|
35
|
+
def selectors_from_dict(cls, data: Dict[str, Any]) -> "SelectorConfig":
|
|
36
|
+
try:
|
|
37
|
+
SelectorFile.validate(data)
|
|
38
|
+
selector_file = SelectorFile.from_dict(data)
|
|
39
|
+
validate_selector_default(selector_file)
|
|
40
|
+
selectors = parse_from_selectors_definition(selector_file)
|
|
41
|
+
except ValidationError as exc:
|
|
42
|
+
yaml_sel_cfg = yaml.dump(exc.instance)
|
|
43
|
+
raise DbtSelectorsError(
|
|
44
|
+
f"Could not parse selector file data: \n{yaml_sel_cfg}\n"
|
|
45
|
+
f"Valid root-level selector definitions: "
|
|
46
|
+
f"union, intersection, string, dictionary. No lists. "
|
|
47
|
+
f"\nhttps://docs.getdbt.com/reference/node-selection/"
|
|
48
|
+
f"yaml-selectors",
|
|
49
|
+
result_type="invalid_selector",
|
|
50
|
+
) from exc
|
|
51
|
+
except DbtRuntimeError as exc:
|
|
52
|
+
raise DbtSelectorsError(
|
|
53
|
+
f"Could not read selector file data: {exc}",
|
|
54
|
+
result_type="invalid_selector",
|
|
55
|
+
) from exc
|
|
56
|
+
|
|
57
|
+
return cls(selectors)
|
|
58
|
+
|
|
59
|
+
@classmethod
|
|
60
|
+
def render_from_dict(
|
|
61
|
+
cls,
|
|
62
|
+
data: Dict[str, Any],
|
|
63
|
+
renderer: BaseRenderer,
|
|
64
|
+
) -> "SelectorConfig":
|
|
65
|
+
try:
|
|
66
|
+
rendered = renderer.render_data(data)
|
|
67
|
+
except (ValidationError, DbtRuntimeError) as exc:
|
|
68
|
+
raise DbtSelectorsError(
|
|
69
|
+
f"Could not render selector data: {exc}",
|
|
70
|
+
result_type="invalid_selector",
|
|
71
|
+
) from exc
|
|
72
|
+
return cls.selectors_from_dict(rendered)
|
|
73
|
+
|
|
74
|
+
@classmethod
|
|
75
|
+
def from_path(
|
|
76
|
+
cls,
|
|
77
|
+
path: Path,
|
|
78
|
+
renderer: BaseRenderer,
|
|
79
|
+
) -> "SelectorConfig":
|
|
80
|
+
try:
|
|
81
|
+
data = load_yaml_text(load_file_contents(str(path)))
|
|
82
|
+
if data is None:
|
|
83
|
+
raise ValidationError("No data found in selector file at path: {path}")
|
|
84
|
+
except (ValidationError, DbtRuntimeError) as exc:
|
|
85
|
+
raise DbtSelectorsError(
|
|
86
|
+
f"Could not read selector file: {exc}",
|
|
87
|
+
result_type="invalid_selector",
|
|
88
|
+
path=path,
|
|
89
|
+
) from exc
|
|
90
|
+
|
|
91
|
+
try:
|
|
92
|
+
return cls.render_from_dict(data, renderer)
|
|
93
|
+
except DbtSelectorsError as exc:
|
|
94
|
+
exc.path = path
|
|
95
|
+
raise
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def selector_data_from_root(project_root: str) -> Optional[Dict[str, Any]]:
|
|
99
|
+
selector_filepath = resolve_path_from_base("selectors.yml", project_root)
|
|
100
|
+
|
|
101
|
+
if path_exists(selector_filepath):
|
|
102
|
+
selectors_dict = load_yaml_text(load_file_contents(selector_filepath))
|
|
103
|
+
else:
|
|
104
|
+
selectors_dict = None
|
|
105
|
+
return selectors_dict
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def selector_config_from_data(selectors_data: Dict[str, Any]) -> SelectorConfig:
|
|
109
|
+
if not selectors_data:
|
|
110
|
+
selectors_data = {"selectors": []}
|
|
111
|
+
|
|
112
|
+
try:
|
|
113
|
+
selectors = SelectorConfig.selectors_from_dict(selectors_data)
|
|
114
|
+
except ValidationError as e:
|
|
115
|
+
raise DbtSelectorsError(
|
|
116
|
+
MALFORMED_SELECTOR_ERROR.format(error=str(e.message)),
|
|
117
|
+
result_type="invalid_selector",
|
|
118
|
+
) from e
|
|
119
|
+
return selectors
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def validate_selector_default(selector_file: SelectorFile) -> None:
|
|
123
|
+
"""Check if a selector.yml file has more than 1 default key set to true"""
|
|
124
|
+
default_set: bool = False
|
|
125
|
+
default_selector_name: Union[str, None] = None
|
|
126
|
+
|
|
127
|
+
for selector in selector_file.selectors:
|
|
128
|
+
if selector.default is True and default_set is False:
|
|
129
|
+
default_set = True
|
|
130
|
+
default_selector_name = selector.name
|
|
131
|
+
continue
|
|
132
|
+
if selector.default is True and default_set is True:
|
|
133
|
+
raise DbtSelectorsError(
|
|
134
|
+
"Error when parsing the selector file. "
|
|
135
|
+
"Found multiple selectors with `default: true`:"
|
|
136
|
+
f"{default_selector_name} and {selector.name}"
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
# These are utilities to clean up the dictionary created from
|
|
141
|
+
# selectors.yml by turning the cli-string format entries into
|
|
142
|
+
# normalized dictionary entries. It parallels the flow in
|
|
143
|
+
# dbt/graph/cli.py. If changes are made there, it might
|
|
144
|
+
# be necessary to make changes here. Ideally it would be
|
|
145
|
+
# good to combine the two flows into one at some point.
|
|
146
|
+
class SelectorDict:
|
|
147
|
+
@classmethod
|
|
148
|
+
def parse_dict_definition(cls, definition, selector_dict={}):
|
|
149
|
+
key = list(definition)[0]
|
|
150
|
+
value = definition[key]
|
|
151
|
+
if isinstance(value, list):
|
|
152
|
+
new_values = []
|
|
153
|
+
for sel_def in value:
|
|
154
|
+
new_value = cls.parse_from_definition(sel_def, selector_dict=selector_dict)
|
|
155
|
+
new_values.append(new_value)
|
|
156
|
+
value = new_values
|
|
157
|
+
if key == "exclude":
|
|
158
|
+
definition = {key: value}
|
|
159
|
+
elif len(definition) == 1:
|
|
160
|
+
definition = {"method": key, "value": value}
|
|
161
|
+
elif key == "method" and value == "selector":
|
|
162
|
+
sel_def = definition.get("value")
|
|
163
|
+
if sel_def not in selector_dict:
|
|
164
|
+
raise DbtSelectorsError(f"Existing selector definition for {sel_def} not found.")
|
|
165
|
+
return selector_dict[definition["value"]]["definition"]
|
|
166
|
+
return definition
|
|
167
|
+
|
|
168
|
+
@classmethod
|
|
169
|
+
def parse_a_definition(cls, def_type, definition, selector_dict={}):
|
|
170
|
+
# this definition must be a list
|
|
171
|
+
new_dict = {def_type: []}
|
|
172
|
+
for sel_def in definition[def_type]:
|
|
173
|
+
if isinstance(sel_def, dict):
|
|
174
|
+
sel_def = cls.parse_from_definition(sel_def, selector_dict=selector_dict)
|
|
175
|
+
new_dict[def_type].append(sel_def)
|
|
176
|
+
elif isinstance(sel_def, str):
|
|
177
|
+
sel_def = SelectionCriteria.dict_from_single_spec(sel_def)
|
|
178
|
+
new_dict[def_type].append(sel_def)
|
|
179
|
+
else:
|
|
180
|
+
new_dict[def_type].append(sel_def)
|
|
181
|
+
return new_dict
|
|
182
|
+
|
|
183
|
+
@classmethod
|
|
184
|
+
def parse_from_definition(cls, definition, selector_dict={}):
|
|
185
|
+
if isinstance(definition, str):
|
|
186
|
+
definition = SelectionCriteria.dict_from_single_spec(definition)
|
|
187
|
+
elif "union" in definition:
|
|
188
|
+
definition = cls.parse_a_definition("union", definition, selector_dict=selector_dict)
|
|
189
|
+
elif "intersection" in definition:
|
|
190
|
+
definition = cls.parse_a_definition(
|
|
191
|
+
"intersection", definition, selector_dict=selector_dict
|
|
192
|
+
)
|
|
193
|
+
elif isinstance(definition, dict):
|
|
194
|
+
definition = cls.parse_dict_definition(definition, selector_dict=selector_dict)
|
|
195
|
+
return definition
|
|
196
|
+
|
|
197
|
+
# This is the normal entrypoint of this code. Give it the
|
|
198
|
+
# list of selectors generated from the selectors.yml file.
|
|
199
|
+
@classmethod
|
|
200
|
+
def parse_from_selectors_list(cls, selectors):
|
|
201
|
+
selector_dict = {}
|
|
202
|
+
for selector in selectors:
|
|
203
|
+
sel_name = selector["name"]
|
|
204
|
+
selector_dict[sel_name] = selector
|
|
205
|
+
definition = cls.parse_from_definition(
|
|
206
|
+
selector["definition"], selector_dict=deepcopy(selector_dict)
|
|
207
|
+
)
|
|
208
|
+
selector_dict[sel_name]["definition"] = definition
|
|
209
|
+
return selector_dict
|