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

Adding Metric Helper Functions #5607

Merged
merged 6 commits into from
Aug 8, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .changes/unreleased/Features-20220804-120936.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
kind: Features
body: Adding ResolvedMetricReference helper functions and tests
time: 2022-08-04T12:09:36.202919-04:00
custom:
Author: callum-mcdata
Issue: "5567"
PR: "5607"
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -95,3 +95,7 @@ venv/

# vscode
.vscode/

# poetry
pyproject.toml
poetry.lock
69 changes: 44 additions & 25 deletions core/dbt/contracts/graph/metrics.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,33 +38,52 @@ def parent_metrics(cls, metric_node, manifest):
if node and node.resource_type == NodeType.Metric:
yield from cls.parent_metrics(node, manifest)

def parent_models(self):
@classmethod
def parent_metrics_names(cls, metric_node, manifest):
yield metric_node.name

for parent_unique_id in metric_node.depends_on.nodes:
node = manifest.metrics.get(parent_unique_id)
if node and node.resource_type == NodeType.Metric:
yield from cls.parent_metrics_names(node, manifest)

@classmethod
def reverse_dag_parsing(cls, metric_node, manifest, metric_depth_count):
if metric_node.type == "expression":
yield {metric_node.name: metric_depth_count}
metric_depth_count = metric_depth_count + 1

for parent_unique_id in metric_node.depends_on.nodes:
node = manifest.metrics.get(parent_unique_id)
if node and node.resource_type == NodeType.Metric and node.type == "expression":
yield from cls.reverse_dag_parsing(node, manifest, metric_depth_count)

def full_metric_dependency(self):
to_return = list(set(self.parent_metrics_names(self.node, self.manifest)))
return to_return

def base_metric_dependency(self):
in_scope_metrics = list(self.parent_metrics(self.node, self.manifest))

to_return = {
"base": [],
"derived": [],
}
to_return = []
for metric in in_scope_metrics:
if metric.type == "expression":
to_return["derived"].append(
{"metric_source": None, "metric": metric, "is_derived": True}
)
else:
for node_unique_id in metric.depends_on.nodes:
node = self.manifest.nodes.get(node_unique_id)
if node and node.resource_type in NodeType.refable():
to_return["base"].append(
{
"metric_relation_node": node,
"metric_relation": self.Relation.create(
database=node.database,
schema=node.schema,
identifier=node.alias,
),
"metric": metric,
"is_derived": False,
}
)
if metric.type != "expression" and metric.name not in to_return:
to_return.append(metric.name)

return to_return

def derived_metric_dependency(self):
in_scope_metrics = list(self.parent_metrics(self.node, self.manifest))

to_return = []
for metric in in_scope_metrics:
if metric.type == "expression" and metric.name not in to_return:
to_return.append(metric.name)

return to_return

def derived_metric_dependency_depth(self):
metric_depth_count = 1
to_return = list(self.reverse_dag_parsing(self.node, self.manifest, metric_depth_count))

return to_return
103 changes: 103 additions & 0 deletions tests/functional/metrics/test_metric_helper_functions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
import pytest

from dbt.tests.util import run_dbt, get_manifest
from dbt.contracts.graph.metrics import ResolvedMetricReference

metrics__yml = """
version: 2

metrics:

- name: number_of_people
label: "Number of people"
description: Total count of people
model: "ref('people')"
type: count
sql: "*"
timestamp: created_at
time_grains: [day, week, month]
dimensions:
- favorite_color
- loves_dbt
meta:
my_meta: 'testing'

- name: collective_tenure
label: "Collective tenure"
description: Total number of years of team experience
model: "ref('people')"
type: sum
sql: tenure
timestamp: created_at
time_grains: [day, week, month]
filters:
- field: loves_dbt
operator: 'is'
value: 'true'

- name: average_tenure
label: "Average tenure"
description: "The average tenure per person"
type: expression
sql: "{{metric('collective_tenure')}} / {{metric('number_of_people')}} "
timestamp: created_at
time_grains: [day, week, month]

- name: average_tenure_plus_one
label: "Average tenure"
description: "The average tenure per person"
type: expression
sql: "{{metric('average_tenure')}} + 1 "
timestamp: created_at
time_grains: [day, week, month]
"""

models__people_sql = """
select 1 as id, 'Drew' as first_name, 'Banin' as last_name, 'yellow' as favorite_color, true as loves_dbt, 5 as tenure, current_timestamp as created_at
union all
select 1 as id, 'Jeremy' as first_name, 'Cohen' as last_name, 'indigo' as favorite_color, true as loves_dbt, 4 as tenure, current_timestamp as created_at
union all
select 1 as id, 'Callum' as first_name, 'McCann' as last_name, 'emerald' as favorite_color, true as loves_dbt, 0 as tenure, current_timestamp as created_at
"""


class TestMetricHelperFunctions:
@pytest.fixture(scope="class")
def models(self):
return {
"metrics.yml": metrics__yml,
"people.sql": models__people_sql,
}

def test_expression_metric(
self,
project,
):

# initial parse
run_dbt(["compile"])

# make sure all the metrics are in the manifest
manifest = get_manifest(project.project_root)
parsed_metric = manifest.metrics["metric.test.average_tenure_plus_one"]
testing_metric = ResolvedMetricReference(parsed_metric, manifest, None)

full_metric_dependency = set(testing_metric.full_metric_dependency())
expected_full_metric_dependency = set(
["average_tenure_plus_one", "average_tenure", "collective_tenure", "number_of_people"]
)
assert full_metric_dependency == expected_full_metric_dependency

base_metric_dependency = set(testing_metric.base_metric_dependency())
expected_base_metric_dependency = set(["collective_tenure", "number_of_people"])
assert base_metric_dependency == expected_base_metric_dependency

derived_metric_dependency = set(testing_metric.derived_metric_dependency())
expected_derived_metric_dependency = set(["average_tenure_plus_one", "average_tenure"])
assert derived_metric_dependency == expected_derived_metric_dependency

derived_metric_dependency_depth = list(testing_metric.derived_metric_dependency_depth())
expected_derived_metric_dependency_depth = list(
[{"average_tenure_plus_one": 1}, {"average_tenure": 2}]
)
assert derived_metric_dependency_depth == expected_derived_metric_dependency_depth