Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Semantic model configs - enable/disable + groups #8502

Merged
merged 20 commits into from
Aug 31, 2023
Merged
6 changes: 6 additions & 0 deletions .changes/unreleased/Features-20230828-092100.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
kind: Features
body: Support configuration of semantic models with the addition of enable/disable and group enablement.
time: 2023-08-28T09:21:00.551633-05:00
custom:
Author: emmyoop
Issue: "7968"
5 changes: 5 additions & 0 deletions core/dbt/config/project.py
Original file line number Diff line number Diff line change
Expand Up @@ -426,6 +426,7 @@ def create_project(self, rendered: RenderComponents) -> "Project":
sources: Dict[str, Any]
tests: Dict[str, Any]
metrics: Dict[str, Any]
semantic_models: Dict[str, Any]
exposures: Dict[str, Any]
vars_value: VarProvider

Expand All @@ -436,6 +437,7 @@ def create_project(self, rendered: RenderComponents) -> "Project":
sources = cfg.sources
tests = cfg.tests
metrics = cfg.metrics
semantic_models = cfg.semantic_models
exposures = cfg.exposures
if cfg.vars is None:
vars_dict: Dict[str, Any] = {}
Expand Down Expand Up @@ -492,6 +494,7 @@ def create_project(self, rendered: RenderComponents) -> "Project":
sources=sources,
tests=tests,
metrics=metrics,
semantic_models=semantic_models,
exposures=exposures,
vars=vars_value,
config_version=cfg.config_version,
Expand Down Expand Up @@ -598,6 +601,7 @@ class Project:
sources: Dict[str, Any]
tests: Dict[str, Any]
metrics: Dict[str, Any]
semantic_models: Dict[str, Any]
exposures: Dict[str, Any]
vars: VarProvider
dbt_version: List[VersionSpecifier]
Expand Down Expand Up @@ -673,6 +677,7 @@ def to_project_config(self, with_packages=False):
"sources": self.sources,
"tests": self.tests,
"metrics": self.metrics,
"semantic-models": self.semantic_models,
"exposures": self.exposures,
"vars": self.vars.to_dict(),
"require-dbt-version": [v.to_version_string() for v in self.dbt_version],
Expand Down
2 changes: 2 additions & 0 deletions core/dbt/config/runtime.py
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,7 @@ def from_parts(
sources=project.sources,
tests=project.tests,
metrics=project.metrics,
semantic_models=project.semantic_models,
exposures=project.exposures,
vars=project.vars,
config_version=project.config_version,
Expand Down Expand Up @@ -322,6 +323,7 @@ def get_resource_config_paths(self) -> Dict[str, PathSet]:
"sources": self._get_config_paths(self.sources),
"tests": self._get_config_paths(self.tests),
"metrics": self._get_config_paths(self.metrics),
"semantic_models": self._get_config_paths(self.semantic_models),
"exposures": self._get_config_paths(self.exposures),
}

Expand Down
4 changes: 4 additions & 0 deletions core/dbt/context/context_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@ def get_config_dict(self, resource_type: NodeType) -> Dict[str, Any]:
model_configs = unrendered.get("tests")
elif resource_type == NodeType.Metric:
model_configs = unrendered.get("metrics")
elif resource_type == NodeType.SemanticModel:
model_configs = unrendered.get("semantic_models")
elif resource_type == NodeType.Exposure:
model_configs = unrendered.get("exposures")
else:
Expand All @@ -70,6 +72,8 @@ def get_config_dict(self, resource_type: NodeType) -> Dict[str, Any]:
model_configs = self.project.tests
elif resource_type == NodeType.Metric:
model_configs = self.project.metrics
elif resource_type == NodeType.SemanticModel:
model_configs = self.project.semantic_models
elif resource_type == NodeType.Exposure:
model_configs = self.project.exposures
else:
Expand Down
26 changes: 20 additions & 6 deletions core/dbt/contracts/graph/manifest.py
Original file line number Diff line number Diff line change
Expand Up @@ -331,18 +331,29 @@
"""Populate storage with all the measure + package paths to the Manifest's SemanticModels"""
for semantic_model in manifest.semantic_models.values():
self.add(semantic_model=semantic_model)
for disabled in manifest.disabled.values():
for node in disabled:
if isinstance(node, SemanticModel):
self.add(semantic_model=node)

Check warning on line 337 in core/dbt/contracts/graph/manifest.py

View check run for this annotation

Codecov / codecov/patch

core/dbt/contracts/graph/manifest.py#L337

Added line #L337 was not covered by tests

def perform_lookup(self, unique_id: UniqueID, manifest: "Manifest") -> SemanticModel:
"""Tries to get a SemanticModel from the Manifest"""
semantic_model = manifest.semantic_models.get(unique_id)
if semantic_model is None:
enabled_semantic_model: Optional[SemanticModel] = manifest.semantic_models.get(unique_id)
disabled_semantic_model: Optional[List] = manifest.disabled.get(unique_id)

if isinstance(enabled_semantic_model, SemanticModel):
return enabled_semantic_model
elif disabled_semantic_model is not None and isinstance(

Check warning on line 346 in core/dbt/contracts/graph/manifest.py

View check run for this annotation

Codecov / codecov/patch

core/dbt/contracts/graph/manifest.py#L346

Added line #L346 was not covered by tests
disabled_semantic_model[0], SemanticModel
):
return disabled_semantic_model[0]

Check warning on line 349 in core/dbt/contracts/graph/manifest.py

View check run for this annotation

Codecov / codecov/patch

core/dbt/contracts/graph/manifest.py#L349

Added line #L349 was not covered by tests
else:
raise dbt.exceptions.DbtInternalError(
f"Semantic model `{unique_id}` found in cache but not found in manifest"
)
return semantic_model


# This handles both models/seeds/snapshots and sources/metrics/exposures
# This handles both models/seeds/snapshots and sources/metrics/exposures/semantic_models
class DisabledLookup(dbtClassMixin):
def __init__(self, manifest: "Manifest"):
self.storage: Dict[str, Dict[PackageName, List[Any]]] = {}
Expand Down Expand Up @@ -927,6 +938,7 @@
groupable_nodes = list(
chain(
self.nodes.values(),
self.semantic_models.values(),
self.metrics.values(),
)
)
Expand Down Expand Up @@ -1056,8 +1068,7 @@

return resolved_refs

# Called by dbt.parser.manifest._process_refs_for_exposure, _process_refs_for_metric,
# and dbt.parser.manifest._process_refs_for_node
# Called by dbt.parser.manifest._process_refs & ManifestLoader.check_for_model_deprecations
def resolve_ref(
self,
source_node: GraphMemberNode,
Expand Down Expand Up @@ -1156,6 +1167,7 @@
semantic_model = self.semantic_model_by_measure_lookup.find(
target_measure_name, pkg, self
)
# need to return it even if it's disabled so know it's not fully missing
if semantic_model is not None:
return semantic_model

Expand Down Expand Up @@ -1359,6 +1371,8 @@
source_file.add_test(node.unique_id, test_from)
if isinstance(node, Metric):
source_file.metrics.append(node.unique_id)
if isinstance(node, SemanticModel):
source_file.semantic_models.append(node.unique_id)

Check warning on line 1375 in core/dbt/contracts/graph/manifest.py

View check run for this annotation

Codecov / codecov/patch

core/dbt/contracts/graph/manifest.py#L1375

Added line #L1375 was not covered by tests
if isinstance(node, Exposure):
source_file.exposures.append(node.unique_id)
else:
Expand Down
10 changes: 9 additions & 1 deletion core/dbt/contracts/graph/model_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -378,12 +378,19 @@ def finalize_and_validate(self: T) -> T:
@dataclass
class SemanticModelConfig(BaseConfig):
enabled: bool = True
group: Optional[str] = field(
default=None,
metadata=CompareBehavior.Exclude.meta(),
)


@dataclass
class MetricConfig(BaseConfig):
enabled: bool = True
group: Optional[str] = None
group: Optional[str] = field(
default=None,
metadata=CompareBehavior.Exclude.meta(),
)


@dataclass
Expand Down Expand Up @@ -635,6 +642,7 @@ def finalize_and_validate(self):

RESOURCE_TYPES: Dict[NodeType, Type[BaseConfig]] = {
NodeType.Metric: MetricConfig,
NodeType.SemanticModel: SemanticModelConfig,
NodeType.Exposure: ExposureConfig,
NodeType.Source: SourceConfig,
NodeType.Seed: SeedConfig,
Expand Down
2 changes: 2 additions & 0 deletions core/dbt/contracts/graph/nodes.py
Original file line number Diff line number Diff line change
Expand Up @@ -1578,7 +1578,9 @@ class SemanticModel(GraphNode):
refs: List[RefArgs] = field(default_factory=list)
created_at: float = field(default_factory=lambda: time.time())
config: SemanticModelConfig = field(default_factory=SemanticModelConfig)
unrendered_config: Dict[str, Any] = field(default_factory=dict)
primary_entity: Optional[str] = None
group: Optional[str] = None

@property
def entity_references(self) -> List[LinkableElementReference]:
Expand Down
1 change: 1 addition & 0 deletions core/dbt/contracts/graph/unparsed.py
Original file line number Diff line number Diff line change
Expand Up @@ -711,6 +711,7 @@ class UnparsedDimension(dbtClassMixin):
class UnparsedSemanticModel(dbtClassMixin):
name: str
model: str # looks like "ref(...)"
config: Dict[str, Any] = field(default_factory=dict)
description: Optional[str] = None
defaults: Optional[Defaults] = None
entities: List[UnparsedEntity] = field(default_factory=list)
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 @@ -214,6 +214,7 @@ class Project(dbtClassMixin, Replaceable):
sources: Dict[str, Any] = field(default_factory=dict)
tests: Dict[str, Any] = field(default_factory=dict)
metrics: Dict[str, Any] = field(default_factory=dict)
semantic_models: Dict[str, Any] = field(default_factory=dict)
exposures: Dict[str, Any] = field(default_factory=dict)
vars: Optional[Dict[str, Any]] = field(
default=None,
Expand Down Expand Up @@ -249,6 +250,7 @@ class Config(dbtMashConfig):
"require_dbt_version": "require-dbt-version",
"query_comment": "query-comment",
"restrict_access": "restrict-access",
"semantic_models": "semantic-models",
}

@classmethod
Expand Down
3 changes: 2 additions & 1 deletion core/dbt/graph/selector.py
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,8 @@ def _is_graph_member(self, unique_id: UniqueId) -> bool:
metric = self.manifest.metrics[unique_id]
return metric.config.enabled
elif unique_id in self.manifest.semantic_models:
return True
semantic_model = self.manifest.semantic_models[unique_id]
return semantic_model.config.enabled
node = self.manifest.nodes[unique_id]

if self.include_empty_nodes:
Expand Down
13 changes: 12 additions & 1 deletion core/dbt/parser/manifest.py
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@
Exposure,
Metric,
SeedNode,
SemanticModel,
ManifestNode,
ResultNode,
ModelNode,
Expand Down Expand Up @@ -1220,11 +1221,16 @@
for metric in manifest.metrics.values():
self.check_valid_group_config_node(metric, group_names)

for semantic_model in manifest.semantic_models.values():
self.check_valid_group_config_node(semantic_model, group_names)

for node in manifest.nodes.values():
self.check_valid_group_config_node(node, group_names)

def check_valid_group_config_node(
self, groupable_node: Union[Metric, ManifestNode], valid_group_names: Set[str]
self,
groupable_node: Union[Metric, SemanticModel, ManifestNode],
valid_group_names: Set[str],
):
groupable_node_group = groupable_node.group
if groupable_node_group and groupable_node_group not in valid_group_names:
Expand Down Expand Up @@ -1493,6 +1499,11 @@
f"A semantic model having a measure `{metric.type_params.measure.name}` does not exist but was referenced.",
node=metric,
)
if target_semantic_model.config.enabled is False:
raise dbt.exceptions.ParsingError(

Check warning on line 1503 in core/dbt/parser/manifest.py

View check run for this annotation

Codecov / codecov/patch

core/dbt/parser/manifest.py#L1503

Added line #L1503 was not covered by tests
f"The measure `{metric.type_params.measure.name}` is referenced on disabled semantic model `{target_semantic_model.name}`.",
node=metric,
)

metric.depends_on.add_node(target_semantic_model.unique_id)

Expand Down
53 changes: 50 additions & 3 deletions core/dbt/parser/schema_yaml_readers.py
Original file line number Diff line number Diff line change
Expand Up @@ -522,6 +522,30 @@
parser = MetricParser(self.schema_parser, yaml=self.yaml)
parser.parse_metric(unparsed=unparsed_metric, generated=True)

def _generate_semantic_model_config(
self, target: UnparsedSemanticModel, fqn: List[str], package_name: str, rendered: bool
):
generator: BaseContextConfigGenerator
if rendered:
generator = ContextConfigGenerator(self.root_project)
else:
generator = UnrenderedConfigGenerator(self.root_project)

# configs with precendence set
precedence_configs = dict()
# first apply semantic model configs
precedence_configs.update(target.config)

config = generator.calculate_node_config(
config_call_dict={},
fqn=fqn,
resource_type=NodeType.SemanticModel,
project_name=package_name,
base=False,
patch_config_dict=precedence_configs,
)
return config

def parse_semantic_model(self, unparsed: UnparsedSemanticModel):
package_name = self.project.project_name
unique_id = f"{NodeType.SemanticModel}.{package_name}.{unparsed.name}"
Expand All @@ -530,6 +554,22 @@
fqn = self.schema_parser.get_fqn_prefix(path)
fqn.append(unparsed.name)

config = self._generate_semantic_model_config(
target=unparsed,
fqn=fqn,
package_name=package_name,
rendered=True,
)

config = config.finalize_and_validate()

unrendered_config = self._generate_semantic_model_config(
target=unparsed,
fqn=fqn,
package_name=package_name,
rendered=False,
)

parsed = SemanticModel(
description=unparsed.description,
fqn=fqn,
Expand All @@ -546,6 +586,9 @@
dimensions=self._get_dimensions(unparsed.dimensions),
defaults=unparsed.defaults,
primary_entity=unparsed.primary_entity,
config=config,
unrendered_config=unrendered_config,
group=config.group,
)

ctx = generate_parse_semantic_models(
Expand All @@ -557,11 +600,15 @@

if parsed.model is not None:
model_ref = "{{ " + parsed.model + " }}"
# This sets the "refs" in the SemanticModel from the MetricRefResolver in context/providers.py
# This sets the "refs" in the SemanticModel from the SemanticModelRefResolver in context/providers.py
get_rendered(model_ref, ctx, parsed)

# No ability to disable a semantic model at this time
self.manifest.add_semantic_model(self.yaml.file, parsed)
# if the semantic model is disabled we do not want it included in the manifest,
# only in the disabled dict
if parsed.config.enabled:
self.manifest.add_semantic_model(self.yaml.file, parsed)
else:
self.manifest.add_disabled(self.yaml.file, parsed)

Check warning on line 611 in core/dbt/parser/schema_yaml_readers.py

View check run for this annotation

Codecov / codecov/patch

core/dbt/parser/schema_yaml_readers.py#L611

Added line #L611 was not covered by tests

# Create a metric for each measure with `create_metric = True`
for measure in unparsed.measures:
Expand Down
Loading
Loading