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/project.py
ADDED
|
@@ -0,0 +1,893 @@
|
|
|
1
|
+
import os
|
|
2
|
+
from copy import deepcopy
|
|
3
|
+
from dataclasses import dataclass, field
|
|
4
|
+
from itertools import chain
|
|
5
|
+
from typing import Any, Dict, List, Mapping, Optional, TypeVar, Union
|
|
6
|
+
|
|
7
|
+
from dvt import deprecations
|
|
8
|
+
from dvt.clients.checked_load import (
|
|
9
|
+
checked_load,
|
|
10
|
+
issue_deprecation_warnings_for_failures,
|
|
11
|
+
)
|
|
12
|
+
from dvt.clients.yaml_helper import load_yaml_text
|
|
13
|
+
from dvt.config.selectors import SelectorDict
|
|
14
|
+
from dvt.config.utils import normalize_warn_error_options
|
|
15
|
+
from dvt.constants import (
|
|
16
|
+
DBT_PROJECT_FILE_NAME,
|
|
17
|
+
DEPENDENCIES_FILE_NAME,
|
|
18
|
+
DVT_PROJECT_FILE_NAME,
|
|
19
|
+
PACKAGE_LOCK_HASH_KEY,
|
|
20
|
+
PACKAGES_FILE_NAME,
|
|
21
|
+
)
|
|
22
|
+
from dvt.contracts.project import PackageConfig
|
|
23
|
+
from dvt.contracts.project import Project as ProjectContract
|
|
24
|
+
from dvt.contracts.project import ProjectFlags, ProjectPackageMetadata, SemverString
|
|
25
|
+
from dvt.exceptions import (
|
|
26
|
+
DbtExclusivePropertyUseError,
|
|
27
|
+
DbtProjectError,
|
|
28
|
+
DbtRuntimeError,
|
|
29
|
+
ProjectContractBrokenError,
|
|
30
|
+
ProjectContractError,
|
|
31
|
+
)
|
|
32
|
+
from dvt.flags import get_flags
|
|
33
|
+
from dvt.graph import SelectionSpec
|
|
34
|
+
from dvt.node_types import NodeType
|
|
35
|
+
from dvt.utils import MultiDict, coerce_dict_str, md5
|
|
36
|
+
from dvt.version import get_installed_version
|
|
37
|
+
from typing_extensions import Protocol, runtime_checkable
|
|
38
|
+
|
|
39
|
+
from dbt.adapters.contracts.connection import QueryComment
|
|
40
|
+
from dbt_common.clients.system import load_file_contents, path_exists
|
|
41
|
+
from dbt_common.dataclass_schema import ValidationError
|
|
42
|
+
from dbt_common.exceptions import SemverError
|
|
43
|
+
from dbt_common.helper_types import NoValue
|
|
44
|
+
from dbt_common.semver import VersionSpecifier, versions_compatible
|
|
45
|
+
|
|
46
|
+
from .renderer import DbtProjectYamlRenderer, PackageRenderer
|
|
47
|
+
from .selectors import (
|
|
48
|
+
SelectorConfig,
|
|
49
|
+
selector_config_from_data,
|
|
50
|
+
selector_data_from_root,
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
INVALID_VERSION_ERROR = """\
|
|
54
|
+
This version of dbt is not supported with the '{package}' package.
|
|
55
|
+
Installed version of dbt: {installed}
|
|
56
|
+
Required version of dbt for '{package}': {version_spec}
|
|
57
|
+
Check for a different version of the '{package}' package, or run dbt again with \
|
|
58
|
+
--no-version-check
|
|
59
|
+
"""
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
IMPOSSIBLE_VERSION_ERROR = """\
|
|
63
|
+
The package version requirement can never be satisfied for the '{package}
|
|
64
|
+
package.
|
|
65
|
+
Required versions of dbt for '{package}': {version_spec}
|
|
66
|
+
Check for a different version of the '{package}' package, or run dbt again with \
|
|
67
|
+
--no-version-check
|
|
68
|
+
"""
|
|
69
|
+
|
|
70
|
+
MALFORMED_PACKAGE_ERROR = """\
|
|
71
|
+
The packages.yml file in this project is malformed. Please double check
|
|
72
|
+
the contents of this file and fix any errors before retrying.
|
|
73
|
+
|
|
74
|
+
You can find more information on the syntax for this file here:
|
|
75
|
+
https://docs.getdbt.com/docs/package-management
|
|
76
|
+
|
|
77
|
+
Validator Error:
|
|
78
|
+
{error}
|
|
79
|
+
"""
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
@runtime_checkable
|
|
83
|
+
class IsFQNResource(Protocol):
|
|
84
|
+
fqn: List[str]
|
|
85
|
+
resource_type: NodeType
|
|
86
|
+
package_name: str
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def _load_yaml(path, validate: bool = False):
|
|
90
|
+
contents = load_file_contents(path)
|
|
91
|
+
if validate:
|
|
92
|
+
result, failures = checked_load(contents)
|
|
93
|
+
issue_deprecation_warnings_for_failures(failures=failures, file=path)
|
|
94
|
+
return result
|
|
95
|
+
else:
|
|
96
|
+
return load_yaml_text(contents)
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def load_yml_dict(file_path):
|
|
100
|
+
ret = {}
|
|
101
|
+
if path_exists(file_path):
|
|
102
|
+
ret = _load_yaml(file_path) or {}
|
|
103
|
+
return ret
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def package_and_project_data_from_root(project_root):
|
|
107
|
+
packages_yml_dict = load_yml_dict(f"{project_root}/{PACKAGES_FILE_NAME}")
|
|
108
|
+
dependencies_yml_dict = load_yml_dict(f"{project_root}/{DEPENDENCIES_FILE_NAME}")
|
|
109
|
+
|
|
110
|
+
if "packages" in packages_yml_dict and "packages" in dependencies_yml_dict:
|
|
111
|
+
msg = "The 'packages' key cannot be specified in both packages.yml and dependencies.yml"
|
|
112
|
+
raise DbtProjectError(msg)
|
|
113
|
+
if "projects" in packages_yml_dict:
|
|
114
|
+
msg = "The 'projects' key cannot be specified in packages.yml"
|
|
115
|
+
raise DbtProjectError(msg)
|
|
116
|
+
|
|
117
|
+
packages_specified_path = PACKAGES_FILE_NAME
|
|
118
|
+
packages_dict = {}
|
|
119
|
+
if "packages" in dependencies_yml_dict:
|
|
120
|
+
packages_dict["packages"] = dependencies_yml_dict["packages"]
|
|
121
|
+
packages_specified_path = DEPENDENCIES_FILE_NAME
|
|
122
|
+
else: # don't check for "packages" here so we capture invalid keys in packages.yml
|
|
123
|
+
packages_dict = packages_yml_dict
|
|
124
|
+
|
|
125
|
+
return packages_dict, packages_specified_path
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def package_config_from_data(
|
|
129
|
+
packages_data: Dict[str, Any],
|
|
130
|
+
unrendered_packages_data: Optional[Dict[str, Any]] = None,
|
|
131
|
+
) -> PackageConfig:
|
|
132
|
+
if not packages_data:
|
|
133
|
+
packages_data = {"packages": []}
|
|
134
|
+
|
|
135
|
+
# this depends on the two lists being in the same order
|
|
136
|
+
if unrendered_packages_data:
|
|
137
|
+
unrendered_packages_data = deepcopy(unrendered_packages_data)
|
|
138
|
+
for i in range(0, len(packages_data.get("packages", []))):
|
|
139
|
+
packages_data["packages"][i]["unrendered"] = unrendered_packages_data["packages"][i]
|
|
140
|
+
|
|
141
|
+
if PACKAGE_LOCK_HASH_KEY in packages_data:
|
|
142
|
+
packages_data.pop(PACKAGE_LOCK_HASH_KEY)
|
|
143
|
+
try:
|
|
144
|
+
PackageConfig.validate(packages_data)
|
|
145
|
+
packages = PackageConfig.from_dict(packages_data)
|
|
146
|
+
except ValidationError as e:
|
|
147
|
+
raise DbtProjectError(MALFORMED_PACKAGE_ERROR.format(error=str(e.message))) from e
|
|
148
|
+
return packages
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
def _parse_versions(versions: Union[List[str], str]) -> List[VersionSpecifier]:
|
|
152
|
+
"""Parse multiple versions as read from disk. The versions value may be any
|
|
153
|
+
one of:
|
|
154
|
+
- a single version string ('>0.12.1')
|
|
155
|
+
- a single string specifying multiple comma-separated versions
|
|
156
|
+
('>0.11.1,<=0.12.2')
|
|
157
|
+
- an array of single-version strings (['>0.11.1', '<=0.12.2'])
|
|
158
|
+
|
|
159
|
+
Regardless, this will return a list of VersionSpecifiers
|
|
160
|
+
"""
|
|
161
|
+
if isinstance(versions, str):
|
|
162
|
+
versions = versions.split(",")
|
|
163
|
+
return [VersionSpecifier.from_version_string(v) for v in versions]
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
def _all_source_paths(*args: List[str]) -> List[str]:
|
|
167
|
+
paths = chain(*args)
|
|
168
|
+
# Strip trailing slashes since the path is the same even though the name is not
|
|
169
|
+
stripped_paths = map(lambda s: s.rstrip("/"), paths)
|
|
170
|
+
return list(set(stripped_paths))
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
T = TypeVar("T")
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
def flag_or(flag: Optional[T], value: Optional[T], default: T) -> T:
|
|
177
|
+
if flag is None:
|
|
178
|
+
return value_or(value, default)
|
|
179
|
+
else:
|
|
180
|
+
return flag
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
def value_or(value: Optional[T], default: T) -> T:
|
|
184
|
+
if value is None:
|
|
185
|
+
return default
|
|
186
|
+
else:
|
|
187
|
+
return value
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
def load_raw_project(project_root: str, validate: bool = False) -> Dict[str, Any]:
|
|
191
|
+
from dbt_common.events.functions import fire_event
|
|
192
|
+
from dbt_common.events.types import Note
|
|
193
|
+
|
|
194
|
+
project_root = os.path.normpath(project_root)
|
|
195
|
+
|
|
196
|
+
# Try new filename first (dvt_project.yml)
|
|
197
|
+
dvt_project_path = os.path.join(project_root, DVT_PROJECT_FILE_NAME)
|
|
198
|
+
dbt_project_path = os.path.join(project_root, DBT_PROJECT_FILE_NAME)
|
|
199
|
+
|
|
200
|
+
if path_exists(dvt_project_path):
|
|
201
|
+
project_yaml_filepath = dvt_project_path
|
|
202
|
+
elif path_exists(dbt_project_path):
|
|
203
|
+
# TODO: Use DbtProjectFileDeprecated event once protobuf is regenerated
|
|
204
|
+
fire_event(
|
|
205
|
+
Note(
|
|
206
|
+
msg="The `dbt_project.yml` filename is deprecated. "
|
|
207
|
+
"Please rename your project file to `dvt_project.yml`."
|
|
208
|
+
)
|
|
209
|
+
)
|
|
210
|
+
project_yaml_filepath = dbt_project_path
|
|
211
|
+
else:
|
|
212
|
+
raise DbtProjectError(
|
|
213
|
+
f"No {DVT_PROJECT_FILE_NAME} or {DBT_PROJECT_FILE_NAME} found at {project_root}. "
|
|
214
|
+
f"Please create a project configuration file."
|
|
215
|
+
)
|
|
216
|
+
|
|
217
|
+
project_dict = _load_yaml(project_yaml_filepath, validate=validate)
|
|
218
|
+
|
|
219
|
+
if validate:
|
|
220
|
+
from dvt.jsonschemas.jsonschemas import jsonschema_validate, project_schema
|
|
221
|
+
|
|
222
|
+
jsonschema_validate(
|
|
223
|
+
schema=project_schema(), json=project_dict, file_path=project_yaml_filepath
|
|
224
|
+
)
|
|
225
|
+
|
|
226
|
+
if not isinstance(project_dict, dict):
|
|
227
|
+
raise DbtProjectError(f"Project file does not parse to a dictionary")
|
|
228
|
+
|
|
229
|
+
if "tests" in project_dict and "data_tests" not in project_dict:
|
|
230
|
+
project_dict["data_tests"] = project_dict.pop("tests")
|
|
231
|
+
|
|
232
|
+
return project_dict
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
def _query_comment_from_cfg(
|
|
236
|
+
cfg_query_comment: Union[QueryComment, NoValue, str, None]
|
|
237
|
+
) -> QueryComment:
|
|
238
|
+
if not cfg_query_comment:
|
|
239
|
+
return QueryComment(comment="")
|
|
240
|
+
|
|
241
|
+
if isinstance(cfg_query_comment, str):
|
|
242
|
+
return QueryComment(comment=cfg_query_comment)
|
|
243
|
+
|
|
244
|
+
if isinstance(cfg_query_comment, NoValue):
|
|
245
|
+
return QueryComment()
|
|
246
|
+
|
|
247
|
+
return cfg_query_comment
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
def validate_version(dbt_version: List[VersionSpecifier], project_name: str):
|
|
251
|
+
"""Ensure this package works with the installed version of dbt."""
|
|
252
|
+
installed = get_installed_version()
|
|
253
|
+
if not versions_compatible(*dbt_version):
|
|
254
|
+
msg = IMPOSSIBLE_VERSION_ERROR.format(
|
|
255
|
+
package=project_name, version_spec=[x.to_version_string() for x in dbt_version]
|
|
256
|
+
)
|
|
257
|
+
raise DbtProjectError(msg)
|
|
258
|
+
|
|
259
|
+
if not versions_compatible(installed, *dbt_version):
|
|
260
|
+
msg = INVALID_VERSION_ERROR.format(
|
|
261
|
+
package=project_name,
|
|
262
|
+
installed=installed.to_version_string(),
|
|
263
|
+
version_spec=[x.to_version_string() for x in dbt_version],
|
|
264
|
+
)
|
|
265
|
+
raise DbtProjectError(msg)
|
|
266
|
+
|
|
267
|
+
|
|
268
|
+
def _get_required_version(
|
|
269
|
+
project_dict: Dict[str, Any],
|
|
270
|
+
verify_version: bool,
|
|
271
|
+
) -> List[VersionSpecifier]:
|
|
272
|
+
dbt_raw_version: Union[List[str], str] = ">=0.0.0"
|
|
273
|
+
required = project_dict.get("require-dbt-version")
|
|
274
|
+
if required is not None:
|
|
275
|
+
dbt_raw_version = required
|
|
276
|
+
|
|
277
|
+
try:
|
|
278
|
+
dbt_version = _parse_versions(dbt_raw_version)
|
|
279
|
+
except SemverError as e:
|
|
280
|
+
raise DbtProjectError(str(e)) from e
|
|
281
|
+
|
|
282
|
+
if verify_version:
|
|
283
|
+
# no name is also an error that we want to raise
|
|
284
|
+
if "name" not in project_dict:
|
|
285
|
+
raise DbtProjectError(
|
|
286
|
+
'Required "name" field not present in project',
|
|
287
|
+
)
|
|
288
|
+
validate_version(dbt_version, project_dict["name"])
|
|
289
|
+
|
|
290
|
+
return dbt_version
|
|
291
|
+
|
|
292
|
+
|
|
293
|
+
@dataclass
|
|
294
|
+
class RenderComponents:
|
|
295
|
+
project_dict: Dict[str, Any] = field(metadata=dict(description="The project dictionary"))
|
|
296
|
+
packages_dict: Dict[str, Any] = field(metadata=dict(description="The packages dictionary"))
|
|
297
|
+
selectors_dict: Dict[str, Any] = field(metadata=dict(description="The selectors dictionary"))
|
|
298
|
+
|
|
299
|
+
|
|
300
|
+
@dataclass
|
|
301
|
+
class PartialProject(RenderComponents):
|
|
302
|
+
# This class includes the project_dict, packages_dict, selectors_dict, etc from RenderComponents
|
|
303
|
+
profile_name: Optional[str] = field(
|
|
304
|
+
metadata=dict(description="The unrendered profile name in the project, if set")
|
|
305
|
+
)
|
|
306
|
+
project_name: Optional[str] = field(
|
|
307
|
+
metadata=dict(
|
|
308
|
+
description=(
|
|
309
|
+
"The name of the project. This should always be set and will not be rendered"
|
|
310
|
+
)
|
|
311
|
+
)
|
|
312
|
+
)
|
|
313
|
+
project_root: str = field(
|
|
314
|
+
metadata=dict(description="The root directory of the project"),
|
|
315
|
+
)
|
|
316
|
+
verify_version: bool = field(
|
|
317
|
+
metadata=dict(description=("If True, verify the dbt version matches the required version"))
|
|
318
|
+
)
|
|
319
|
+
packages_specified_path: str = field(
|
|
320
|
+
metadata=dict(description="The filename where packages were specified")
|
|
321
|
+
)
|
|
322
|
+
|
|
323
|
+
def render_profile_name(self, renderer) -> Optional[str]:
|
|
324
|
+
if self.profile_name is None:
|
|
325
|
+
return None
|
|
326
|
+
return renderer.render_value(self.profile_name)
|
|
327
|
+
|
|
328
|
+
def get_rendered(
|
|
329
|
+
self,
|
|
330
|
+
renderer: DbtProjectYamlRenderer,
|
|
331
|
+
) -> RenderComponents:
|
|
332
|
+
rendered_project = renderer.render_project(self.project_dict, self.project_root)
|
|
333
|
+
rendered_packages = renderer.render_packages(
|
|
334
|
+
self.packages_dict, self.packages_specified_path
|
|
335
|
+
)
|
|
336
|
+
rendered_selectors = renderer.render_selectors(self.selectors_dict)
|
|
337
|
+
|
|
338
|
+
return RenderComponents(
|
|
339
|
+
project_dict=rendered_project,
|
|
340
|
+
packages_dict=rendered_packages,
|
|
341
|
+
selectors_dict=rendered_selectors,
|
|
342
|
+
)
|
|
343
|
+
|
|
344
|
+
# Called by Project.from_project_root which first calls PartialProject.from_project_root
|
|
345
|
+
def render(self, renderer: DbtProjectYamlRenderer) -> "Project":
|
|
346
|
+
try:
|
|
347
|
+
rendered = self.get_rendered(renderer)
|
|
348
|
+
return self.create_project(rendered)
|
|
349
|
+
except DbtProjectError as exc:
|
|
350
|
+
if exc.path is None:
|
|
351
|
+
# Try to find which project file exists for better error messages
|
|
352
|
+
dvt_project_path = os.path.join(self.project_root, DVT_PROJECT_FILE_NAME)
|
|
353
|
+
dbt_project_path = os.path.join(self.project_root, DBT_PROJECT_FILE_NAME)
|
|
354
|
+
if path_exists(dvt_project_path):
|
|
355
|
+
exc.path = dvt_project_path
|
|
356
|
+
else:
|
|
357
|
+
exc.path = dbt_project_path
|
|
358
|
+
raise
|
|
359
|
+
|
|
360
|
+
def render_package_metadata(self, renderer: PackageRenderer) -> ProjectPackageMetadata:
|
|
361
|
+
packages_data = renderer.render_data(self.packages_dict)
|
|
362
|
+
packages_config = package_config_from_data(packages_data, self.packages_dict)
|
|
363
|
+
if not self.project_name:
|
|
364
|
+
raise DbtProjectError(
|
|
365
|
+
"Package defined in project configuration file must have a name!"
|
|
366
|
+
)
|
|
367
|
+
return ProjectPackageMetadata(self.project_name, packages_config.packages)
|
|
368
|
+
|
|
369
|
+
def check_config_path(
|
|
370
|
+
self, project_dict, deprecated_path, expected_path=None, default_value=None
|
|
371
|
+
):
|
|
372
|
+
if deprecated_path in project_dict:
|
|
373
|
+
if expected_path in project_dict:
|
|
374
|
+
msg = (
|
|
375
|
+
"{deprecated_path} and {expected_path} cannot both be defined. The "
|
|
376
|
+
"`{deprecated_path}` config has been deprecated in favor of `{expected_path}`. "
|
|
377
|
+
"Please update your project configuration to reflect this change."
|
|
378
|
+
)
|
|
379
|
+
raise DbtProjectError(
|
|
380
|
+
msg.format(deprecated_path=deprecated_path, expected_path=expected_path)
|
|
381
|
+
)
|
|
382
|
+
# this field is no longer supported, but many projects may specify it with the default value
|
|
383
|
+
# if so, let's only raise this deprecation warning if they set a custom value
|
|
384
|
+
if not default_value or project_dict[deprecated_path] != default_value:
|
|
385
|
+
kwargs = {"deprecated_path": deprecated_path}
|
|
386
|
+
if expected_path:
|
|
387
|
+
kwargs.update({"exp_path": expected_path})
|
|
388
|
+
deprecations.warn(f"project-config-{deprecated_path}", **kwargs)
|
|
389
|
+
|
|
390
|
+
def create_project(self, rendered: RenderComponents) -> "Project":
|
|
391
|
+
unrendered = RenderComponents(
|
|
392
|
+
project_dict=self.project_dict,
|
|
393
|
+
packages_dict=self.packages_dict,
|
|
394
|
+
selectors_dict=self.selectors_dict,
|
|
395
|
+
)
|
|
396
|
+
dbt_version = _get_required_version(
|
|
397
|
+
rendered.project_dict,
|
|
398
|
+
verify_version=self.verify_version,
|
|
399
|
+
)
|
|
400
|
+
|
|
401
|
+
self.check_config_path(rendered.project_dict, "source-paths", "model-paths")
|
|
402
|
+
self.check_config_path(rendered.project_dict, "data-paths", "seed-paths")
|
|
403
|
+
self.check_config_path(rendered.project_dict, "log-path", default_value="logs")
|
|
404
|
+
self.check_config_path(rendered.project_dict, "target-path", default_value="target")
|
|
405
|
+
|
|
406
|
+
try:
|
|
407
|
+
ProjectContract.validate(rendered.project_dict)
|
|
408
|
+
cfg = ProjectContract.from_dict(rendered.project_dict)
|
|
409
|
+
except ValidationError as e:
|
|
410
|
+
raise ProjectContractError(e) from e
|
|
411
|
+
# name/version are required in the Project definition, so we can assume
|
|
412
|
+
# they are present
|
|
413
|
+
name = cfg.name
|
|
414
|
+
version = cfg.version
|
|
415
|
+
# this is added at project_dict parse time and should always be here
|
|
416
|
+
# once we see it.
|
|
417
|
+
if cfg.project_root is None:
|
|
418
|
+
raise DbtProjectError("cfg must have a project root!")
|
|
419
|
+
else:
|
|
420
|
+
project_root = cfg.project_root
|
|
421
|
+
# this is only optional in the sense that if it's not present, it needs
|
|
422
|
+
# to have been a cli argument.
|
|
423
|
+
profile_name = cfg.profile
|
|
424
|
+
# these are all the defaults
|
|
425
|
+
|
|
426
|
+
# `source_paths` is deprecated but still allowed. Copy it into
|
|
427
|
+
# `model_paths` to simlify logic throughout the rest of the system.
|
|
428
|
+
model_paths: List[str] = value_or(
|
|
429
|
+
cfg.model_paths if "model-paths" in rendered.project_dict else cfg.source_paths,
|
|
430
|
+
["models"],
|
|
431
|
+
)
|
|
432
|
+
macro_paths: List[str] = value_or(cfg.macro_paths, ["macros"])
|
|
433
|
+
# `data_paths` is deprecated but still allowed. Copy it into
|
|
434
|
+
# `seed_paths` to simlify logic throughout the rest of the system.
|
|
435
|
+
seed_paths: List[str] = value_or(
|
|
436
|
+
cfg.seed_paths if "seed-paths" in rendered.project_dict else cfg.data_paths, ["seeds"]
|
|
437
|
+
)
|
|
438
|
+
test_paths: List[str] = value_or(cfg.test_paths, ["tests"])
|
|
439
|
+
analysis_paths: List[str] = value_or(cfg.analysis_paths, ["analyses"])
|
|
440
|
+
snapshot_paths: List[str] = value_or(cfg.snapshot_paths, ["snapshots"])
|
|
441
|
+
function_paths: List[str] = value_or(cfg.function_paths, ["functions"])
|
|
442
|
+
|
|
443
|
+
all_source_paths: List[str] = _all_source_paths(
|
|
444
|
+
model_paths,
|
|
445
|
+
seed_paths,
|
|
446
|
+
snapshot_paths,
|
|
447
|
+
analysis_paths,
|
|
448
|
+
macro_paths,
|
|
449
|
+
test_paths,
|
|
450
|
+
function_paths,
|
|
451
|
+
)
|
|
452
|
+
|
|
453
|
+
docs_paths: List[str] = value_or(cfg.docs_paths, all_source_paths)
|
|
454
|
+
asset_paths: List[str] = value_or(cfg.asset_paths, [])
|
|
455
|
+
global_flags = get_flags()
|
|
456
|
+
|
|
457
|
+
flag_target_path = str(global_flags.TARGET_PATH) if global_flags.TARGET_PATH else None
|
|
458
|
+
target_path: str = flag_or(flag_target_path, cfg.target_path, "target")
|
|
459
|
+
log_path: str = str(global_flags.LOG_PATH)
|
|
460
|
+
|
|
461
|
+
clean_targets: List[str] = value_or(cfg.clean_targets, [target_path])
|
|
462
|
+
packages_install_path: str = value_or(cfg.packages_install_path, "dbt_packages")
|
|
463
|
+
# in the default case we'll populate this once we know the adapter type
|
|
464
|
+
# It would be nice to just pass along a Quoting here, but that would
|
|
465
|
+
# break many things
|
|
466
|
+
quoting: Dict[str, Any] = {}
|
|
467
|
+
if cfg.quoting is not None:
|
|
468
|
+
quoting = cfg.quoting.to_dict(omit_none=True)
|
|
469
|
+
|
|
470
|
+
dispatch: List[Dict[str, Any]]
|
|
471
|
+
models: Dict[str, Any]
|
|
472
|
+
seeds: Dict[str, Any]
|
|
473
|
+
snapshots: Dict[str, Any]
|
|
474
|
+
sources: Dict[str, Any]
|
|
475
|
+
data_tests: Dict[str, Any]
|
|
476
|
+
unit_tests: Dict[str, Any]
|
|
477
|
+
metrics: Dict[str, Any]
|
|
478
|
+
semantic_models: Dict[str, Any]
|
|
479
|
+
saved_queries: Dict[str, Any]
|
|
480
|
+
exposures: Dict[str, Any]
|
|
481
|
+
functions: Dict[str, Any]
|
|
482
|
+
vars_value: VarProvider
|
|
483
|
+
dbt_cloud: Dict[str, Any]
|
|
484
|
+
|
|
485
|
+
dispatch = cfg.dispatch
|
|
486
|
+
models = cfg.models
|
|
487
|
+
seeds = cfg.seeds
|
|
488
|
+
snapshots = cfg.snapshots
|
|
489
|
+
sources = cfg.sources
|
|
490
|
+
# the `tests` config is deprecated but still allowed. Copy it into
|
|
491
|
+
# `data_tests` to simplify logic throughout the rest of the system.
|
|
492
|
+
data_tests = cfg.data_tests if "data_tests" in rendered.project_dict else cfg.tests
|
|
493
|
+
unit_tests = cfg.unit_tests
|
|
494
|
+
metrics = cfg.metrics
|
|
495
|
+
semantic_models = cfg.semantic_models
|
|
496
|
+
saved_queries = cfg.saved_queries
|
|
497
|
+
exposures = cfg.exposures
|
|
498
|
+
functions = cfg.functions
|
|
499
|
+
if cfg.vars is None:
|
|
500
|
+
vars_dict: Dict[str, Any] = {}
|
|
501
|
+
else:
|
|
502
|
+
vars_dict = cfg.vars
|
|
503
|
+
|
|
504
|
+
vars_value = VarProvider(vars_dict)
|
|
505
|
+
# There will never be any project_env_vars when it's first created
|
|
506
|
+
project_env_vars: Dict[str, Any] = {}
|
|
507
|
+
on_run_start: List[str] = value_or(cfg.on_run_start, [])
|
|
508
|
+
on_run_end: List[str] = value_or(cfg.on_run_end, [])
|
|
509
|
+
|
|
510
|
+
query_comment = _query_comment_from_cfg(cfg.query_comment)
|
|
511
|
+
packages: PackageConfig = package_config_from_data(
|
|
512
|
+
rendered.packages_dict, unrendered.packages_dict
|
|
513
|
+
)
|
|
514
|
+
selectors = selector_config_from_data(rendered.selectors_dict)
|
|
515
|
+
manifest_selectors: Dict[str, Any] = {}
|
|
516
|
+
if rendered.selectors_dict and rendered.selectors_dict["selectors"]:
|
|
517
|
+
# this is a dict with a single key 'selectors' pointing to a list
|
|
518
|
+
# of dicts.
|
|
519
|
+
manifest_selectors = SelectorDict.parse_from_selectors_list(
|
|
520
|
+
rendered.selectors_dict["selectors"]
|
|
521
|
+
)
|
|
522
|
+
dbt_cloud = cfg.dbt_cloud
|
|
523
|
+
flags: Dict[str, Any] = cfg.flags
|
|
524
|
+
|
|
525
|
+
project = Project(
|
|
526
|
+
project_name=name,
|
|
527
|
+
version=version,
|
|
528
|
+
project_root=project_root,
|
|
529
|
+
profile_name=profile_name,
|
|
530
|
+
model_paths=model_paths,
|
|
531
|
+
macro_paths=macro_paths,
|
|
532
|
+
seed_paths=seed_paths,
|
|
533
|
+
test_paths=test_paths,
|
|
534
|
+
analysis_paths=analysis_paths,
|
|
535
|
+
docs_paths=docs_paths,
|
|
536
|
+
asset_paths=asset_paths,
|
|
537
|
+
target_path=target_path,
|
|
538
|
+
snapshot_paths=snapshot_paths,
|
|
539
|
+
function_paths=function_paths,
|
|
540
|
+
clean_targets=clean_targets,
|
|
541
|
+
log_path=log_path,
|
|
542
|
+
packages_install_path=packages_install_path,
|
|
543
|
+
packages_specified_path=self.packages_specified_path,
|
|
544
|
+
quoting=quoting,
|
|
545
|
+
models=models,
|
|
546
|
+
on_run_start=on_run_start,
|
|
547
|
+
on_run_end=on_run_end,
|
|
548
|
+
dispatch=dispatch,
|
|
549
|
+
seeds=seeds,
|
|
550
|
+
snapshots=snapshots,
|
|
551
|
+
dbt_version=dbt_version,
|
|
552
|
+
packages=packages,
|
|
553
|
+
manifest_selectors=manifest_selectors,
|
|
554
|
+
selectors=selectors,
|
|
555
|
+
query_comment=query_comment,
|
|
556
|
+
sources=sources,
|
|
557
|
+
data_tests=data_tests,
|
|
558
|
+
unit_tests=unit_tests,
|
|
559
|
+
metrics=metrics,
|
|
560
|
+
semantic_models=semantic_models,
|
|
561
|
+
saved_queries=saved_queries,
|
|
562
|
+
exposures=exposures,
|
|
563
|
+
functions=functions,
|
|
564
|
+
vars=vars_value,
|
|
565
|
+
config_version=cfg.config_version,
|
|
566
|
+
unrendered=unrendered,
|
|
567
|
+
project_env_vars=project_env_vars,
|
|
568
|
+
restrict_access=cfg.restrict_access,
|
|
569
|
+
dbt_cloud=dbt_cloud,
|
|
570
|
+
flags=flags,
|
|
571
|
+
)
|
|
572
|
+
# sanity check - this means an internal issue
|
|
573
|
+
project.validate()
|
|
574
|
+
return project
|
|
575
|
+
|
|
576
|
+
@classmethod
|
|
577
|
+
def from_dicts(
|
|
578
|
+
cls,
|
|
579
|
+
project_root: str,
|
|
580
|
+
project_dict: Dict[str, Any],
|
|
581
|
+
packages_dict: Dict[str, Any],
|
|
582
|
+
selectors_dict: Optional[Dict[str, Any]],
|
|
583
|
+
*,
|
|
584
|
+
verify_version: bool = False,
|
|
585
|
+
packages_specified_path: str = PACKAGES_FILE_NAME,
|
|
586
|
+
):
|
|
587
|
+
"""Construct a partial project from its constituent dicts."""
|
|
588
|
+
project_name = project_dict.get("name")
|
|
589
|
+
profile_name = project_dict.get("profile")
|
|
590
|
+
|
|
591
|
+
# Create a PartialProject
|
|
592
|
+
return cls(
|
|
593
|
+
profile_name=profile_name,
|
|
594
|
+
project_name=project_name,
|
|
595
|
+
project_root=project_root,
|
|
596
|
+
project_dict=project_dict,
|
|
597
|
+
packages_dict=packages_dict,
|
|
598
|
+
selectors_dict=selectors_dict, # type: ignore
|
|
599
|
+
verify_version=verify_version,
|
|
600
|
+
packages_specified_path=packages_specified_path,
|
|
601
|
+
)
|
|
602
|
+
|
|
603
|
+
@classmethod
|
|
604
|
+
def from_project_root(
|
|
605
|
+
cls, project_root: str, *, verify_version: bool = False, validate: bool = False
|
|
606
|
+
) -> "PartialProject":
|
|
607
|
+
project_root = os.path.normpath(project_root)
|
|
608
|
+
project_dict = load_raw_project(project_root, validate=validate)
|
|
609
|
+
(
|
|
610
|
+
packages_dict,
|
|
611
|
+
packages_specified_path,
|
|
612
|
+
) = package_and_project_data_from_root(project_root)
|
|
613
|
+
selectors_dict = selector_data_from_root(project_root)
|
|
614
|
+
|
|
615
|
+
return cls.from_dicts(
|
|
616
|
+
project_root=project_root,
|
|
617
|
+
project_dict=project_dict,
|
|
618
|
+
selectors_dict=selectors_dict,
|
|
619
|
+
packages_dict=packages_dict,
|
|
620
|
+
verify_version=verify_version,
|
|
621
|
+
packages_specified_path=packages_specified_path,
|
|
622
|
+
)
|
|
623
|
+
|
|
624
|
+
|
|
625
|
+
class VarProvider:
|
|
626
|
+
"""Var providers are tied to a particular Project."""
|
|
627
|
+
|
|
628
|
+
def __init__(self, vars: Dict[str, Dict[str, Any]]) -> None:
|
|
629
|
+
self.vars = vars
|
|
630
|
+
|
|
631
|
+
def vars_for(self, node: IsFQNResource, adapter_type: str) -> Mapping[str, Any]:
|
|
632
|
+
# in v2, vars are only either project or globally scoped
|
|
633
|
+
merged = MultiDict([self.vars])
|
|
634
|
+
merged.add(self.vars.get(node.package_name, {}))
|
|
635
|
+
return merged
|
|
636
|
+
|
|
637
|
+
def to_dict(self):
|
|
638
|
+
return self.vars
|
|
639
|
+
|
|
640
|
+
|
|
641
|
+
# The Project class is included in RuntimeConfig, so any attribute
|
|
642
|
+
# additions must also be set where the RuntimeConfig class is created
|
|
643
|
+
@dataclass
|
|
644
|
+
class Project:
|
|
645
|
+
project_name: str
|
|
646
|
+
version: Optional[Union[SemverString, float]]
|
|
647
|
+
project_root: str
|
|
648
|
+
profile_name: Optional[str]
|
|
649
|
+
model_paths: List[str]
|
|
650
|
+
macro_paths: List[str]
|
|
651
|
+
seed_paths: List[str]
|
|
652
|
+
test_paths: List[str]
|
|
653
|
+
analysis_paths: List[str]
|
|
654
|
+
docs_paths: List[str]
|
|
655
|
+
asset_paths: List[str]
|
|
656
|
+
target_path: str
|
|
657
|
+
snapshot_paths: List[str]
|
|
658
|
+
function_paths: List[str]
|
|
659
|
+
clean_targets: List[str]
|
|
660
|
+
log_path: str
|
|
661
|
+
packages_install_path: str
|
|
662
|
+
packages_specified_path: str
|
|
663
|
+
quoting: Dict[str, Any]
|
|
664
|
+
models: Dict[str, Any]
|
|
665
|
+
on_run_start: List[str]
|
|
666
|
+
on_run_end: List[str]
|
|
667
|
+
dispatch: List[Dict[str, Any]]
|
|
668
|
+
seeds: Dict[str, Any]
|
|
669
|
+
snapshots: Dict[str, Any]
|
|
670
|
+
sources: Dict[str, Any]
|
|
671
|
+
data_tests: Dict[str, Any]
|
|
672
|
+
unit_tests: Dict[str, Any]
|
|
673
|
+
metrics: Dict[str, Any]
|
|
674
|
+
semantic_models: Dict[str, Any]
|
|
675
|
+
saved_queries: Dict[str, Any]
|
|
676
|
+
exposures: Dict[str, Any]
|
|
677
|
+
functions: Dict[str, Any]
|
|
678
|
+
vars: VarProvider
|
|
679
|
+
dbt_version: List[VersionSpecifier]
|
|
680
|
+
packages: PackageConfig
|
|
681
|
+
manifest_selectors: Dict[str, Any]
|
|
682
|
+
selectors: SelectorConfig
|
|
683
|
+
query_comment: QueryComment
|
|
684
|
+
config_version: int
|
|
685
|
+
unrendered: RenderComponents
|
|
686
|
+
project_env_vars: Dict[str, Any]
|
|
687
|
+
restrict_access: bool
|
|
688
|
+
dbt_cloud: Dict[str, Any]
|
|
689
|
+
flags: Dict[str, Any]
|
|
690
|
+
|
|
691
|
+
@property
|
|
692
|
+
def all_source_paths(self) -> List[str]:
|
|
693
|
+
return _all_source_paths(
|
|
694
|
+
self.model_paths,
|
|
695
|
+
self.seed_paths,
|
|
696
|
+
self.snapshot_paths,
|
|
697
|
+
self.analysis_paths,
|
|
698
|
+
self.macro_paths,
|
|
699
|
+
self.test_paths,
|
|
700
|
+
self.function_paths,
|
|
701
|
+
)
|
|
702
|
+
|
|
703
|
+
@property
|
|
704
|
+
def generic_test_paths(self):
|
|
705
|
+
generic_test_paths = []
|
|
706
|
+
for test_path in self.test_paths:
|
|
707
|
+
generic_test_paths.append(os.path.join(test_path, "generic"))
|
|
708
|
+
return generic_test_paths
|
|
709
|
+
|
|
710
|
+
@property
|
|
711
|
+
def fixture_paths(self):
|
|
712
|
+
fixture_paths = []
|
|
713
|
+
for test_path in self.test_paths:
|
|
714
|
+
fixture_paths.append(os.path.join(test_path, "fixtures"))
|
|
715
|
+
return fixture_paths
|
|
716
|
+
|
|
717
|
+
def __str__(self):
|
|
718
|
+
cfg = self.to_project_config(with_packages=True)
|
|
719
|
+
return str(cfg)
|
|
720
|
+
|
|
721
|
+
def __eq__(self, other):
|
|
722
|
+
if not (isinstance(other, self.__class__) and isinstance(self, other.__class__)):
|
|
723
|
+
return False
|
|
724
|
+
return self.to_project_config(with_packages=True) == other.to_project_config(
|
|
725
|
+
with_packages=True
|
|
726
|
+
)
|
|
727
|
+
|
|
728
|
+
def to_project_config(self, with_packages=False):
|
|
729
|
+
"""Return a dict representation of the config that could be written to
|
|
730
|
+
disk with `yaml.safe_dump` to get this configuration.
|
|
731
|
+
|
|
732
|
+
:param with_packages bool: If True, include the serialized packages
|
|
733
|
+
file in the root.
|
|
734
|
+
:returns dict: The serialized profile.
|
|
735
|
+
"""
|
|
736
|
+
result = deepcopy(
|
|
737
|
+
{
|
|
738
|
+
"name": self.project_name,
|
|
739
|
+
"version": self.version,
|
|
740
|
+
"project-root": self.project_root,
|
|
741
|
+
"profile": self.profile_name,
|
|
742
|
+
"model-paths": self.model_paths,
|
|
743
|
+
"macro-paths": self.macro_paths,
|
|
744
|
+
"seed-paths": self.seed_paths,
|
|
745
|
+
"test-paths": self.test_paths,
|
|
746
|
+
"analysis-paths": self.analysis_paths,
|
|
747
|
+
"docs-paths": self.docs_paths,
|
|
748
|
+
"asset-paths": self.asset_paths,
|
|
749
|
+
"target-path": self.target_path,
|
|
750
|
+
"snapshot-paths": self.snapshot_paths,
|
|
751
|
+
"clean-targets": self.clean_targets,
|
|
752
|
+
"log-path": self.log_path,
|
|
753
|
+
"quoting": self.quoting,
|
|
754
|
+
"models": self.models,
|
|
755
|
+
"on-run-start": self.on_run_start,
|
|
756
|
+
"on-run-end": self.on_run_end,
|
|
757
|
+
"dispatch": self.dispatch,
|
|
758
|
+
"seeds": self.seeds,
|
|
759
|
+
"snapshots": self.snapshots,
|
|
760
|
+
"sources": self.sources,
|
|
761
|
+
"data_tests": self.data_tests,
|
|
762
|
+
"unit_tests": self.unit_tests,
|
|
763
|
+
"metrics": self.metrics,
|
|
764
|
+
"semantic-models": self.semantic_models,
|
|
765
|
+
"saved-queries": self.saved_queries,
|
|
766
|
+
"exposures": self.exposures,
|
|
767
|
+
"functions": self.functions,
|
|
768
|
+
"vars": self.vars.to_dict(),
|
|
769
|
+
"require-dbt-version": [v.to_version_string() for v in self.dbt_version],
|
|
770
|
+
"restrict-access": self.restrict_access,
|
|
771
|
+
"dbt-cloud": self.dbt_cloud,
|
|
772
|
+
"flags": self.flags,
|
|
773
|
+
}
|
|
774
|
+
)
|
|
775
|
+
if self.query_comment:
|
|
776
|
+
result["query-comment"] = self.query_comment.to_dict(omit_none=True)
|
|
777
|
+
|
|
778
|
+
if with_packages:
|
|
779
|
+
result.update(self.packages.to_dict(omit_none=True))
|
|
780
|
+
|
|
781
|
+
return result
|
|
782
|
+
|
|
783
|
+
def validate(self):
|
|
784
|
+
try:
|
|
785
|
+
ProjectContract.validate(self.to_project_config())
|
|
786
|
+
except ValidationError as e:
|
|
787
|
+
raise ProjectContractBrokenError(e) from e
|
|
788
|
+
|
|
789
|
+
# Called by:
|
|
790
|
+
# RtConfig.load_dependencies => RtConfig.load_projects => RtConfig.new_project => Project.from_project_root
|
|
791
|
+
# RtConfig.from_args => RtConfig.collect_parts => load_project => Project.from_project_root
|
|
792
|
+
@classmethod
|
|
793
|
+
def from_project_root(
|
|
794
|
+
cls,
|
|
795
|
+
project_root: str,
|
|
796
|
+
renderer: DbtProjectYamlRenderer,
|
|
797
|
+
*,
|
|
798
|
+
verify_version: bool = False,
|
|
799
|
+
validate: bool = False,
|
|
800
|
+
) -> "Project":
|
|
801
|
+
partial = PartialProject.from_project_root(
|
|
802
|
+
project_root, verify_version=verify_version, validate=validate
|
|
803
|
+
)
|
|
804
|
+
return partial.render(renderer)
|
|
805
|
+
|
|
806
|
+
def hashed_name(self):
|
|
807
|
+
return md5(self.project_name)
|
|
808
|
+
|
|
809
|
+
def get_selector(self, name: str) -> Union[SelectionSpec, bool]:
|
|
810
|
+
if name not in self.selectors:
|
|
811
|
+
raise DbtRuntimeError(
|
|
812
|
+
f"Could not find selector named {name}, expected one of {list(self.selectors)}"
|
|
813
|
+
)
|
|
814
|
+
return self.selectors[name]["definition"]
|
|
815
|
+
|
|
816
|
+
def get_default_selector_name(self) -> Union[str, None]:
|
|
817
|
+
"""This function fetch the default selector to use on `dbt run` (if any)
|
|
818
|
+
:return: either a selector if default is set or None
|
|
819
|
+
:rtype: Union[SelectionSpec, None]
|
|
820
|
+
"""
|
|
821
|
+
for selector_name, selector in self.selectors.items():
|
|
822
|
+
if selector["default"] is True:
|
|
823
|
+
return selector_name
|
|
824
|
+
|
|
825
|
+
return None
|
|
826
|
+
|
|
827
|
+
def get_macro_search_order(self, macro_namespace: str):
|
|
828
|
+
for dispatch_entry in self.dispatch:
|
|
829
|
+
if dispatch_entry["macro_namespace"] == macro_namespace:
|
|
830
|
+
return dispatch_entry["search_order"]
|
|
831
|
+
return None
|
|
832
|
+
|
|
833
|
+
@property
|
|
834
|
+
def project_target_path(self):
|
|
835
|
+
# If target_path is absolute, project_root will not be included
|
|
836
|
+
return os.path.join(self.project_root, self.target_path)
|
|
837
|
+
|
|
838
|
+
|
|
839
|
+
def read_project_flags(project_dir: str, profiles_dir: str) -> ProjectFlags:
|
|
840
|
+
try:
|
|
841
|
+
project_flags: Dict[str, Any] = {}
|
|
842
|
+
# Read project_flags from dvt_project.yml (or dbt_project.yml for backward compatibility)
|
|
843
|
+
# Flags are instantiated before the project, so we don't
|
|
844
|
+
# want to throw an error for non-existence of project file here
|
|
845
|
+
# because it breaks things.
|
|
846
|
+
project_root = os.path.normpath(project_dir)
|
|
847
|
+
dvt_project_path = os.path.join(project_root, DVT_PROJECT_FILE_NAME)
|
|
848
|
+
dbt_project_path = os.path.join(project_root, DBT_PROJECT_FILE_NAME)
|
|
849
|
+
|
|
850
|
+
if path_exists(dvt_project_path) or path_exists(dbt_project_path):
|
|
851
|
+
try:
|
|
852
|
+
project_dict = load_raw_project(project_root)
|
|
853
|
+
if "flags" in project_dict:
|
|
854
|
+
project_flags = project_dict.pop("flags")
|
|
855
|
+
except Exception:
|
|
856
|
+
# This is probably a yaml load error.The error will be reported
|
|
857
|
+
# later, when the project loads.
|
|
858
|
+
pass
|
|
859
|
+
|
|
860
|
+
from dvt.config.profile import read_profile
|
|
861
|
+
|
|
862
|
+
profile = read_profile(profiles_dir)
|
|
863
|
+
profile_project_flags: Optional[Dict[str, Any]] = {}
|
|
864
|
+
if profile:
|
|
865
|
+
profile_project_flags = coerce_dict_str(profile.get("config", {}))
|
|
866
|
+
|
|
867
|
+
if project_flags and profile_project_flags:
|
|
868
|
+
raise DbtProjectError(
|
|
869
|
+
"Do not specify both 'config' in profiles.yml and 'flags' in project file. "
|
|
870
|
+
"Using 'config' in profiles.yml is deprecated."
|
|
871
|
+
)
|
|
872
|
+
|
|
873
|
+
if profile_project_flags:
|
|
874
|
+
# This can't use WARN_ERROR or WARN_ERROR_OPTIONS because they're in
|
|
875
|
+
# the config that we're loading. Uses special "buffer" method and fired after flags are initialized in preflight.
|
|
876
|
+
deprecations.buffer("project-flags-moved")
|
|
877
|
+
project_flags = profile_project_flags
|
|
878
|
+
|
|
879
|
+
if project_flags is not None:
|
|
880
|
+
# handle collapsing `include` and `error` as well as collapsing `exclude` and `warn`
|
|
881
|
+
# for warn_error_options
|
|
882
|
+
warn_error_options = project_flags.get("warn_error_options", {})
|
|
883
|
+
normalize_warn_error_options(warn_error_options)
|
|
884
|
+
|
|
885
|
+
ProjectFlags.validate(project_flags)
|
|
886
|
+
return ProjectFlags.from_dict(project_flags)
|
|
887
|
+
except (DbtProjectError, DbtExclusivePropertyUseError) as exc:
|
|
888
|
+
# We don't want to eat the DbtProjectError for UserConfig to ProjectFlags or
|
|
889
|
+
# DbtConfigError for warn_error_options munging
|
|
890
|
+
raise exc
|
|
891
|
+
except (DbtRuntimeError, ValidationError):
|
|
892
|
+
pass
|
|
893
|
+
return ProjectFlags()
|