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

Allow custom granularity names in string name to struct conversion #1387

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
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
from dbt_semantic_interfaces.type_enums.date_part import DatePart
from dbt_semantic_interfaces.type_enums.time_granularity import TimeGranularity

from metricflow_semantics.time.granularity import ExpandedTimeGranularity

DUNDER = "__"

logger = logging.getLogger(__name__)
Expand All @@ -24,9 +26,36 @@ class StructuredLinkableSpecName:

entity_link_names: Tuple[str, ...]
element_name: str
time_granularity: Optional[TimeGranularity] = None
time_granularity_name: Optional[str] = None
date_part: Optional[DatePart] = None

def __post_init__(self) -> None:
"""Post init method to ensure all name values are lower-case.

This implies that all values in the TimeGranularity enumeration must be lower-case.
"""
assert self.element_name.lower() == self.element_name, (
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This means that custom granularities will be required to be lowercase. Is that a necessary restriction?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In my opinion, yes. We do not want Fortnight and fortnight to mean different things in the same model. But also this this the standard we've used for ages.

f"Element name {self.element_name} is not all lower-case. This should have been addressed at model "
"parse time!"
)
assert all([link_name.lower() == link_name for link_name in self.entity_link_names]), (
f"Found entity link name that includes upper-case letters. Entity link names: {self.entity_link_names}. "
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this enforced already? That entity names must be lowercase?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In theory, yes.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Excellent! And a helpful link pointing me to how to add this validation for custom granularities too 🙃

"This should have been addressed at model parse time!"
)

if self.time_granularity_name:
is_standard_name = ExpandedTimeGranularity.is_standard_granularity_name(self.time_granularity_name)
if is_standard_name:
follow_up = (
"This indicates a bug where someone added a non-lowercase value to the TimeGranularity "
"enum in dbt-semantic-interfaces!"
)
else:
follow_up = "This should have been addressed at model parse time!"
assert (
self.time_granularity_name.lower() == self.time_granularity_name
), f"The time_granularity_name {self.time_granularity_name} includes upper-case letters. {follow_up}"

@staticmethod
def from_name(qualified_name: str) -> StructuredLinkableSpecName:
"""Construct from a name e.g. listing__ds__month."""
Expand All @@ -43,6 +72,7 @@ def from_name(qualified_name: str) -> StructuredLinkableSpecName:
)

associated_granularity = None
# TODO: [custom granularity] Update parsing to account for custom granularities
for granularity in TimeGranularity:
if name_parts[-1] == granularity.value:
associated_granularity = granularity
Expand All @@ -52,13 +82,13 @@ def from_name(qualified_name: str) -> StructuredLinkableSpecName:
# e.g. "ds__month"
if len(name_parts) == 2:
return StructuredLinkableSpecName(
entity_link_names=(), element_name=name_parts[0], time_granularity=associated_granularity
entity_link_names=(), element_name=name_parts[0], time_granularity_name=associated_granularity.value
)
# e.g. "messages__ds__month"
return StructuredLinkableSpecName(
entity_link_names=tuple(name_parts[:-2]),
element_name=name_parts[-2],
time_granularity=associated_granularity,
time_granularity_name=associated_granularity.value,
)

# e.g. "messages__ds"
Expand All @@ -74,8 +104,8 @@ def qualified_name(self) -> str:
items = list(self.entity_link_names) + [self.element_name]
if self.date_part:
items.append(self.date_part_suffix(date_part=self.date_part))
elif self.time_granularity:
items.append(self.time_granularity.value)
elif self.time_granularity_name:
items.append(self.time_granularity_name)
return DUNDER.join(items)

@property
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ def qualified_name(self) -> str: # noqa: D102
return StructuredLinkableSpecName(
entity_link_names=tuple(x.element_name for x in self.entity_links),
element_name=self.element_name,
time_granularity=self.time_granularity,
time_granularity_name=self.time_granularity.value,
date_part=self.date_part,
).qualified_name

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,12 @@ def is_custom_granularity(self) -> bool: # noqa: D102
def from_time_granularity(cls, granularity: TimeGranularity) -> ExpandedTimeGranularity:
"""Factory method for creating an ExpandedTimeGranularity from a standard TimeGranularity enumeration value."""
return ExpandedTimeGranularity(name=granularity.value, base_granularity=granularity)

@staticmethod
def is_standard_granularity_name(time_granularity_name: str) -> bool:
"""Helper for checking if a given time granularity name is part of the standard TimeGranularity enumeration."""
for granularity in TimeGranularity:
if time_granularity_name == granularity.value:
return True

return False
5 changes: 2 additions & 3 deletions metricflow/engine/metricflow_engine.py
Original file line number Diff line number Diff line change
Expand Up @@ -595,9 +595,8 @@ def simple_dimensions_for_metrics( # noqa: D102
entity_reference.element_name
for entity_reference in linkable_dimension.entity_links
),
# TODO: Update StructuredLinkableSpecName to use ExpandedTimeGranularity
time_granularity=(
linkable_dimension.time_granularity.base_granularity
time_granularity_name=(
linkable_dimension.time_granularity.name
if linkable_dimension.time_granularity is not None
else None
),
Expand Down
Loading