-
Notifications
You must be signed in to change notification settings - Fork 14
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add Primary Entity Prefix When Specifying Dimensions in the WhereFilter
With the invariant that all dimensions are associated with a primary entity, require the specification of the entity when using dimensions in the where filter. e.g. ``` dimension('capacity_latest') > 10 -> dimension('listing__capacity_latest') > 10 ```
- Loading branch information
Showing
6 changed files
with
247 additions
and
21 deletions.
There are no files selected for viewing
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,138 @@ | ||
from __future__ import annotations | ||
|
||
import logging | ||
from dataclasses import dataclass | ||
from typing import Optional, Tuple | ||
|
||
from dbt_semantic_interfaces.naming.keywords import DUNDER | ||
from dbt_semantic_interfaces.references import EntityReference | ||
from dbt_semantic_interfaces.type_enums.time_granularity import TimeGranularity | ||
|
||
logger = logging.getLogger(__name__) | ||
|
||
|
||
@dataclass(frozen=True) | ||
class StructuredDunderedName: | ||
"""Group by items (e.g. dimensions / entities) in a query that are named using a double underscore as a seperator. | ||
e.g. listing__ds__week -> | ||
entity_links: ["listing"] | ||
element_name: "ds" | ||
granularity: TimeGranularity.WEEK | ||
The time granularity is part of legacy query syntax and there are plans to migrate away from this format. | ||
""" | ||
|
||
entity_links: Tuple[EntityReference, ...] | ||
element_name: str | ||
time_granularity: Optional[TimeGranularity] = None | ||
|
||
@staticmethod | ||
def parse_name(name: str) -> StructuredDunderedName: | ||
"""Construct from a string like 'listing__ds__month'.""" | ||
name_parts = name.split(DUNDER) | ||
|
||
# No dunder, e.g. "ds" | ||
if len(name_parts) == 1: | ||
return StructuredDunderedName((), name_parts[0]) | ||
|
||
associated_granularity = None | ||
granularity: TimeGranularity | ||
for granularity in TimeGranularity: | ||
if name_parts[-1] == granularity.value: | ||
associated_granularity = granularity | ||
|
||
# Has a time granularity | ||
if associated_granularity: | ||
# e.g. "ds__month" | ||
if len(name_parts) == 2: | ||
return StructuredDunderedName((), name_parts[0], associated_granularity) | ||
# e.g. "messages__ds__month" | ||
return StructuredDunderedName( | ||
entity_links=tuple(EntityReference(element_name=entity_name) for entity_name in name_parts[:-2]), | ||
element_name=name_parts[-2], | ||
time_granularity=associated_granularity, | ||
) | ||
# e.g. "messages__ds" | ||
else: | ||
return StructuredDunderedName( | ||
entity_links=tuple(EntityReference(element_name=entity_name) for entity_name in name_parts[:-1]), | ||
element_name=name_parts[-1], | ||
) | ||
|
||
@property | ||
def dundered_name(self) -> str: | ||
"""Return the full name form. e.g. ds or listing__ds__month.""" | ||
items = [entity_reference.element_name for entity_reference in self.entity_links] + [self.element_name] | ||
if self.time_granularity and self.time_granularity != TimeGranularity.DAY: | ||
items.append(self.time_granularity.value) | ||
return DUNDER.join(items) | ||
|
||
@property | ||
def dundered_name_without_granularity(self) -> str: | ||
"""Return the name without the time granularity. e.g. listing__ds__month -> listing__ds.""" | ||
return DUNDER.join( | ||
tuple(entity_reference.element_name for entity_reference in self.entity_links) + (self.element_name,) | ||
) | ||
|
||
@property | ||
def dundered_name_without_entity(self) -> str: | ||
"""Return the name without the entity. e.g. listing__ds__month -> ds__month.""" | ||
return DUNDER.join((self.element_name,) + ((self.time_granularity.value,) if self.time_granularity else ())) | ||
|
||
@property | ||
def entity_prefix(self) -> Optional[str]: | ||
"""Return the entity prefix. e.g. listing__ds__month -> listing.""" | ||
if len(self.entity_links) > 0: | ||
return DUNDER.join(tuple(entity_reference.element_name for entity_reference in self.entity_links)) | ||
|
||
return None | ||
|
||
|
||
class DunderedNameFormatter: | ||
"""Helps to parse names into StructuredDunderedName and vice versa.""" | ||
|
||
@staticmethod | ||
def parse_name(name: str) -> StructuredDunderedName: | ||
"""Construct from a string like 'listing__ds__month'.""" | ||
name_parts = name.split(DUNDER) | ||
|
||
# No dunder, e.g. "ds" | ||
if len(name_parts) == 1: | ||
return StructuredDunderedName((), name_parts[0]) | ||
|
||
associated_granularity = None | ||
granularity: TimeGranularity | ||
for granularity in TimeGranularity: | ||
if name_parts[-1] == granularity.value: | ||
associated_granularity = granularity | ||
|
||
# Has a time granularity | ||
if associated_granularity: | ||
# e.g. "ds__month" | ||
if len(name_parts) == 2: | ||
return StructuredDunderedName((), name_parts[0], associated_granularity) | ||
# e.g. "messages__ds__month" | ||
return StructuredDunderedName( | ||
entity_links=tuple(EntityReference(element_name=entity_name) for entity_name in name_parts[:-2]), | ||
element_name=name_parts[-2], | ||
time_granularity=associated_granularity, | ||
) | ||
# e.g. "messages__ds" | ||
else: | ||
return StructuredDunderedName( | ||
entity_links=tuple(EntityReference(element_name=entity_name) for entity_name in name_parts[:-1]), | ||
element_name=name_parts[-1], | ||
) | ||
|
||
@staticmethod | ||
def create_structured_name( # noqa: D | ||
element_name: str, | ||
entity_links: Tuple[EntityReference, ...] = (), | ||
time_granularity: Optional[TimeGranularity] = None, | ||
) -> StructuredDunderedName: | ||
return StructuredDunderedName( | ||
entity_links=entity_links, | ||
element_name=element_name, | ||
time_granularity=time_granularity, | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
# A double underscore used as a seperator in group by item names. | ||
# e.g. user__country | ||
DUNDER = "__" | ||
|
||
# The name for the time dimension used to tabulate / plot metrics. | ||
METRIC_TIME_ELEMENT_NAME = "metric_time" | ||
|
||
|
||
def is_metric_time_name(element_name: str) -> bool: | ||
"""Returns True if the given element name corresponds to metric time.""" | ||
return element_name == METRIC_TIME_ELEMENT_NAME |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.