Skip to content

Commit

Permalink
Add config to disable metrics/exposures (#5815)
Browse files Browse the repository at this point in the history
* first pass adding disabled functionality to metrics and exposures

* first pass at getting metrics disabled

* add unsaved file

* fix up comments

* Delete tmp.csv

* fix test

* add exposure logic, fix merge from main

* change when nodes are added to manifest, finish tests

* add changelog

* removed unused code

* minor cleanup
  • Loading branch information
emmyoop authored Sep 14, 2022
1 parent 89ee596 commit 6c4577f
Show file tree
Hide file tree
Showing 20 changed files with 919 additions and 137 deletions.
7 changes: 7 additions & 0 deletions .changes/unreleased/Features-20220913-095924.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
kind: Features
body: Add enabled config to exposures and metrics
time: 2022-09-13T09:59:24.445918-05:00
custom:
Author: emmyoop
Issue: "5422"
PR: "5815"
10 changes: 10 additions & 0 deletions core/dbt/config/project.py
Original file line number Diff line number Diff line change
Expand Up @@ -380,6 +380,8 @@ def create_project(self, rendered: RenderComponents) -> "Project":
snapshots: Dict[str, Any]
sources: Dict[str, Any]
tests: Dict[str, Any]
metrics: Dict[str, Any]
exposures: Dict[str, Any]
vars_value: VarProvider

dispatch = cfg.dispatch
Expand All @@ -388,6 +390,8 @@ def create_project(self, rendered: RenderComponents) -> "Project":
snapshots = cfg.snapshots
sources = cfg.sources
tests = cfg.tests
metrics = cfg.metrics
exposures = cfg.exposures
if cfg.vars is None:
vars_dict: Dict[str, Any] = {}
else:
Expand Down Expand Up @@ -441,6 +445,8 @@ def create_project(self, rendered: RenderComponents) -> "Project":
query_comment=query_comment,
sources=sources,
tests=tests,
metrics=metrics,
exposures=exposures,
vars=vars_value,
config_version=cfg.config_version,
unrendered=unrendered,
Expand Down Expand Up @@ -543,6 +549,8 @@ class Project:
snapshots: Dict[str, Any]
sources: Dict[str, Any]
tests: Dict[str, Any]
metrics: Dict[str, Any]
exposures: Dict[str, Any]
vars: VarProvider
dbt_version: List[VersionSpecifier]
packages: Dict[str, Any]
Expand Down Expand Up @@ -615,6 +623,8 @@ def to_project_config(self, with_packages=False):
"snapshots": self.snapshots,
"sources": self.sources,
"tests": self.tests,
"metrics": self.metrics,
"exposures": self.exposures,
"vars": self.vars.to_dict(),
"require-dbt-version": [v.to_version_string() for v in self.dbt_version],
"config-version": self.config_version,
Expand Down
8 changes: 8 additions & 0 deletions core/dbt/config/runtime.py
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,8 @@ def from_parts(
query_comment=project.query_comment,
sources=project.sources,
tests=project.tests,
metrics=project.metrics,
exposures=project.exposures,
vars=project.vars,
config_version=project.config_version,
unrendered=project.unrendered,
Expand Down Expand Up @@ -274,6 +276,8 @@ def get_resource_config_paths(self) -> Dict[str, PathSet]:
"snapshots": self._get_config_paths(self.snapshots),
"sources": self._get_config_paths(self.sources),
"tests": self._get_config_paths(self.tests),
"metrics": self._get_config_paths(self.metrics),
"exposures": self._get_config_paths(self.exposures),
}

def get_unused_resource_config_paths(
Expand Down Expand Up @@ -477,6 +481,8 @@ def to_project_config(self, with_packages=False):
"snapshots": self.snapshots,
"sources": self.sources,
"tests": self.tests,
"metrics": self.metrics,
"exposures": self.exposures,
"vars": self.vars.to_dict(),
"require-dbt-version": [v.to_version_string() for v in self.dbt_version],
"config-version": self.config_version,
Expand Down Expand Up @@ -537,6 +543,8 @@ def from_parts(
query_comment=project.query_comment,
sources=project.sources,
tests=project.tests,
metrics=project.metrics,
exposures=project.exposures,
vars=project.vars,
config_version=project.config_version,
unrendered=project.unrendered,
Expand Down
9 changes: 8 additions & 1 deletion core/dbt/context/context_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,9 +43,12 @@ def get_config_dict(self, resource_type: NodeType) -> Dict[str, Any]:
model_configs = unrendered.get("sources")
elif resource_type == NodeType.Test:
model_configs = unrendered.get("tests")
elif resource_type == NodeType.Metric:
model_configs = unrendered.get("metrics")
elif resource_type == NodeType.Exposure:
model_configs = unrendered.get("exposures")
else:
model_configs = unrendered.get("models")

if model_configs is None:
return {}
else:
Expand All @@ -65,6 +68,10 @@ def get_config_dict(self, resource_type: NodeType) -> Dict[str, Any]:
model_configs = self.project.sources
elif resource_type == NodeType.Test:
model_configs = self.project.tests
elif resource_type == NodeType.Metric:
model_configs = self.project.metrics
elif resource_type == NodeType.Exposure:
model_configs = self.project.exposures
else:
model_configs = self.project.models
return model_configs
Expand Down
19 changes: 14 additions & 5 deletions core/dbt/contracts/graph/manifest.py
Original file line number Diff line number Diff line change
Expand Up @@ -216,7 +216,7 @@ def perform_lookup(self, unique_id: UniqueID, manifest: "Manifest") -> ParsedMet
return manifest.metrics[unique_id]


# This handles both models/seeds/snapshots and sources
# This handles both models/seeds/snapshots and sources/metrics/exposures
class DisabledLookup(dbtClassMixin):
def __init__(self, manifest: "Manifest"):
self.storage: Dict[str, Dict[PackageName, List[Any]]] = {}
Expand Down Expand Up @@ -464,7 +464,7 @@ class Disabled(Generic[D]):
target: D


MaybeMetricNode = Optional[ParsedMetric]
MaybeMetricNode = Optional[Union[ParsedMetric, Disabled[ParsedMetric]]]


MaybeDocumentation = Optional[ParsedDocumentation]
Expand Down Expand Up @@ -616,7 +616,7 @@ class Manifest(MacroMethods, DataClassMessagePackMixin, dbtClassMixin):
flat_graph: Dict[str, Any] = field(default_factory=dict)
state_check: ManifestStateCheck = field(default_factory=ManifestStateCheck)
source_patches: MutableMapping[SourceKey, SourcePatch] = field(default_factory=dict)
disabled: MutableMapping[str, List[CompileResultNode]] = field(default_factory=dict)
disabled: MutableMapping[str, List[GraphMemberNode]] = field(default_factory=dict)
env_vars: MutableMapping[str, str] = field(default_factory=dict)

_doc_lookup: Optional[DocLookup] = field(
Expand Down Expand Up @@ -964,13 +964,22 @@ def resolve_metric(
current_project: str,
node_package: str,
) -> MaybeMetricNode:

metric: Optional[ParsedMetric] = None
disabled: Optional[List[ParsedMetric]] = None

candidates = _search_packages(current_project, node_package, target_metric_package)
for pkg in candidates:
metric = self.metric_lookup.find(target_metric_name, pkg, self)
if metric is not None:

if metric is not None and metric.config.enabled:
return metric

# it's possible that the node is disabled
if disabled is None:
disabled = self.disabled_lookup.find(f"{target_metric_name}", pkg)
if disabled:
return Disabled(disabled[0])
return None

# Called by DocsRuntimeContext.doc
Expand Down Expand Up @@ -1093,7 +1102,7 @@ def add_metric(self, source_file: SchemaSourceFile, metric: ParsedMetric):
self.metrics[metric.unique_id] = metric
source_file.metrics.append(metric.unique_id)

def add_disabled_nofile(self, node: CompileResultNode):
def add_disabled_nofile(self, node: GraphMemberNode):
# There can be multiple disabled nodes for the same unique_id
if node.unique_id in self.disabled:
self.disabled[node.unique_id].append(node)
Expand Down
12 changes: 12 additions & 0 deletions core/dbt/contracts/graph/model_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -363,6 +363,16 @@ def replace(self, **kwargs):
return self.from_dict(dct)


@dataclass
class MetricConfig(BaseConfig):
enabled: bool = True


@dataclass
class ExposureConfig(BaseConfig):
enabled: bool = True


@dataclass
class SourceConfig(BaseConfig):
enabled: bool = True
Expand Down Expand Up @@ -613,6 +623,8 @@ def finalize_and_validate(self):


RESOURCE_TYPES: Dict[NodeType, Type[BaseConfig]] = {
NodeType.Metric: MetricConfig,
NodeType.Exposure: ExposureConfig,
NodeType.Source: SourceConfig,
NodeType.Seed: SeedConfig,
NodeType.Test: TestConfig,
Expand Down
20 changes: 20 additions & 0 deletions core/dbt/contracts/graph/parsed.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@
SeedConfig,
TestConfig,
SourceConfig,
MetricConfig,
ExposureConfig,
EmptySnapshotConfig,
SnapshotConfig,
)
Expand Down Expand Up @@ -746,6 +748,8 @@ class ParsedExposure(UnparsedBaseNode, HasUniqueID, HasFqn):
maturity: Optional[MaturityType] = None
meta: Dict[str, Any] = field(default_factory=dict)
tags: List[str] = field(default_factory=list)
config: ExposureConfig = field(default_factory=ExposureConfig)
unrendered_config: Dict[str, Any] = field(default_factory=dict)
url: Optional[str] = None
depends_on: DependsOn = field(default_factory=DependsOn)
refs: List[List[str]] = field(default_factory=list)
Expand Down Expand Up @@ -778,6 +782,12 @@ def same_exposure_type(self, old: "ParsedExposure") -> bool:
def same_url(self, old: "ParsedExposure") -> bool:
return self.url == old.url

def same_config(self, old: "ParsedExposure") -> bool:
return self.config.same_contents(
self.unrendered_config,
old.unrendered_config,
)

def same_contents(self, old: Optional["ParsedExposure"]) -> bool:
# existing when it didn't before is a change!
# metadata/tags changes are not "changes"
Expand All @@ -792,6 +802,7 @@ def same_contents(self, old: Optional["ParsedExposure"]) -> bool:
and self.same_url(old)
and self.same_description(old)
and self.same_depends_on(old)
and self.same_config(old)
and True
)

Expand Down Expand Up @@ -819,6 +830,8 @@ class ParsedMetric(UnparsedBaseNode, HasUniqueID, HasFqn):
resource_type: NodeType = NodeType.Metric
meta: Dict[str, Any] = field(default_factory=dict)
tags: List[str] = field(default_factory=list)
config: MetricConfig = field(default_factory=MetricConfig)
unrendered_config: Dict[str, Any] = field(default_factory=dict)
sources: List[List[str]] = field(default_factory=list)
depends_on: DependsOn = field(default_factory=DependsOn)
refs: List[List[str]] = field(default_factory=list)
Expand Down Expand Up @@ -863,6 +876,12 @@ def same_timestamp(self, old: "ParsedMetric") -> bool:
def same_time_grains(self, old: "ParsedMetric") -> bool:
return self.time_grains == old.time_grains

def same_config(self, old: "ParsedMetric") -> bool:
return self.config.same_contents(
self.unrendered_config,
old.unrendered_config,
)

def same_contents(self, old: Optional["ParsedMetric"]) -> bool:
# existing when it didn't before is a change!
# metadata/tags changes are not "changes"
Expand All @@ -880,6 +899,7 @@ def same_contents(self, old: Optional["ParsedMetric"]) -> bool:
and self.same_expression(old)
and self.same_timestamp(old)
and self.same_time_grains(old)
and self.same_config(old)
and True
)

Expand Down
7 changes: 2 additions & 5 deletions core/dbt/contracts/graph/unparsed.py
Original file line number Diff line number Diff line change
Expand Up @@ -434,6 +434,7 @@ class UnparsedExposure(dbtClassMixin, Replaceable):
tags: List[str] = field(default_factory=list)
url: Optional[str] = None
depends_on: List[str] = field(default_factory=list)
config: Dict[str, Any] = field(default_factory=dict)


@dataclass
Expand Down Expand Up @@ -465,9 +466,6 @@ def __bool__(self):

@dataclass
class UnparsedMetric(dbtClassMixin, Replaceable):
# TODO : verify that this disallows metric names with spaces
# TODO: fix validation that you broke :p
# name: Identifier
name: str
label: str
calculation_method: str
Expand All @@ -481,11 +479,10 @@ class UnparsedMetric(dbtClassMixin, Replaceable):
filters: List[MetricFilter] = field(default_factory=list)
meta: Dict[str, Any] = field(default_factory=dict)
tags: List[str] = field(default_factory=list)
config: Dict[str, Any] = field(default_factory=dict)

@classmethod
def validate(cls, data):
# super().validate(data)
# TODO: putting this back for now to get tests passing. Do we want to implement name: Identifier?
super(UnparsedMetric, cls).validate(data)
if "name" in data and " " in data["name"]:
raise ParsingException(f"Metrics name '{data['name']}' cannot contain spaces")
Expand Down
2 changes: 2 additions & 0 deletions core/dbt/contracts/project.py
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,8 @@ class Project(HyphenatedDbtClassMixin, Replaceable):
analyses: Dict[str, Any] = field(default_factory=dict)
sources: Dict[str, Any] = field(default_factory=dict)
tests: Dict[str, Any] = field(default_factory=dict)
metrics: Dict[str, Any] = field(default_factory=dict)
exposures: Dict[str, Any] = field(default_factory=dict)
vars: Optional[Dict[str, Any]] = field(
default=None,
metadata=dict(
Expand Down
57 changes: 40 additions & 17 deletions core/dbt/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -631,46 +631,69 @@ def ref_target_not_found(
raise_compiler_error(msg, model)


def get_source_not_found_or_disabled_msg(
model,
def get_not_found_or_disabled_msg(
node,
target_name: str,
target_table_name: str,
target_kind: str,
target_package: Optional[str] = None,
disabled: Optional[bool] = None,
) -> str:
full_name = f"{target_name}.{target_table_name}"
if disabled is None:
reason = "was not found or is disabled"
elif disabled is True:
reason = "is disabled"
else:
reason = "was not found"
return _get_target_failure_msg(
model, full_name, None, include_path=True, reason=reason, target_kind="source"
node,
target_name,
target_package,
include_path=True,
reason=reason,
target_kind=target_kind,
)


def source_target_not_found(
model, target_name: str, target_table_name: str, disabled: Optional[bool] = None
) -> NoReturn:
msg = get_source_not_found_or_disabled_msg(model, target_name, target_table_name, disabled)
msg = get_not_found_or_disabled_msg(
node=model,
target_name=f"{target_name}.{target_table_name}",
target_kind="source",
disabled=disabled,
)
raise_compiler_error(msg, model)


def get_metric_not_found_msg(
model,
target_name: str,
target_package: Optional[str],
) -> str:
reason = "was not found"
return _get_target_failure_msg(
model, target_name, target_package, include_path=True, reason=reason, target_kind="metric"
def metric_target_not_found(
metric, target_name: str, target_package: Optional[str], disabled: Optional[bool] = None
) -> NoReturn:

msg = get_not_found_or_disabled_msg(
node=metric,
target_name=target_name,
target_kind="metric",
target_package=target_package,
disabled=disabled,
)

raise_compiler_error(msg, metric)

def metric_target_not_found(metric, target_name: str, target_package: Optional[str]) -> NoReturn:
msg = get_metric_not_found_msg(metric, target_name, target_package)

raise_compiler_error(msg, metric)
def exposure_target_not_found(
exposure, target_name: str, target_package: Optional[str], disabled: Optional[bool] = None
) -> NoReturn:

msg = get_not_found_or_disabled_msg(
node=exposure,
target_name=target_name,
target_kind="exposure",
target_package=target_package,
disabled=disabled,
)

raise_compiler_error(msg, exposure)


def dependency_not_found(model, target_model_name):
Expand Down
Loading

0 comments on commit 6c4577f

Please sign in to comment.