Skip to content

Commit

Permalink
Dedupe Jinja parsing logic
Browse files Browse the repository at this point in the history
  • Loading branch information
courtneyholcomb committed Apr 2, 2024
1 parent e4f029b commit eb0f0b7
Show file tree
Hide file tree
Showing 5 changed files with 43 additions and 216 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -43,16 +43,16 @@ def create_time_dimension(
"""Gets called by Jinja when rendering {{ TimeDimension(...) }}."""
group_by_item_name = DunderedNameFormatter.parse_name(time_dimension_name)

# metric_time is the only time dimension that does not have an associated primary entity, so the
# GroupByItemName would not have any entity links.
if is_metric_time_name(group_by_item_name.element_name):
if len(group_by_item_name.entity_links) != 0 or group_by_item_name.time_granularity is not None:
raise ParseWhereFilterException(
f"Name is in an incorrect format: {time_dimension_name} "
f"When referencing {METRIC_TIME_ELEMENT_NAME},"
"the name should not have any dunders (double underscores, or __)."
)
# `metric_time` is the only time dimension that does not have an associated primary entity, so the
# name should not have any entity links.
if is_metric_time_name(group_by_item_name.element_name) and len(group_by_item_name.entity_links) != 0:
raise ParseWhereFilterException(
f"Name is in an incorrect format: '{time_dimension_name}'. There should be no entity links "
f"included when referencing {METRIC_TIME_ELEMENT_NAME}."
)
else:
# Wait... why are we raising an exception if time_granularity is not None?
# Test this in YAML spec: if you add time_granularity to a where filter time dimension, does it error?
if len(group_by_item_name.entity_links) != 1 or group_by_item_name.time_granularity is not None:
raise ParseWhereFilterException(
ParameterSetFactory._exception_message_for_incorrect_format(time_dimension_name)
Expand Down Expand Up @@ -93,7 +93,7 @@ def create_entity(entity_name: str, entity_path: Sequence[str] = ()) -> EntityCa
structured_dundered_name = DunderedNameFormatter.parse_name(entity_name)
if structured_dundered_name.time_granularity is not None:
raise ParseWhereFilterException(
f"Name is in an incorrect format: {repr(entity_name)}. " f"It should not contain a time grain suffix."
f"Name is in an incorrect format: {entity_name}. Entities should not use a time grain suffix."
)

additional_entity_path_elements = tuple(
Expand All @@ -115,5 +115,6 @@ def create_metric(metric_name: str, group_by: Sequence[str] = ()) -> MetricCallP
)
return MetricCallParameterSet(
metric_reference=MetricReference(element_name=metric_name),
# Should this parse to entities & dimensions for simplicity down the line?
group_by=tuple([LinkableElementReference(element_name=group_by_name) for group_by_name in group_by]),
)
Original file line number Diff line number Diff line change
Expand Up @@ -2,63 +2,44 @@

from typing import List, Optional, Sequence

from typing_extensions import override

from dbt_semantic_interfaces.errors import InvalidQuerySyntax
from dbt_semantic_interfaces.protocols.protocol_hint import ProtocolHint
from dbt_semantic_interfaces.protocols.query_interface import (
QueryInterfaceDimension,
QueryInterfaceDimensionFactory,
)


class WhereFilterDimension(ProtocolHint[QueryInterfaceDimension]):
class WhereFilterDimension:
"""A dimension that is passed in through the where filter parameter."""

@override
def _implements_protocol(self) -> QueryInterfaceDimension:
return self

def __init__( # noqa
self,
name: str,
entity_path: Sequence[str],
time_granularity_name: Optional[str] = None,
date_part_name: Optional[str] = None,
) -> None:
self.name = name
self.entity_path = entity_path
self.time_granularity_name: Optional[str] = None
self.date_part_name: Optional[str] = None
self.time_granularity_name = time_granularity_name
self.date_part_name = date_part_name

def grain(self, time_granularity: str) -> QueryInterfaceDimension:
def grain(self, time_granularity: str) -> WhereFilterDimension:
"""The time granularity."""
self.time_granularity_name = time_granularity
return self

def descending(self, _is_descending: bool) -> QueryInterfaceDimension:
"""Set the sort order for order-by."""
raise InvalidQuerySyntax("descending is invalid in the where parameter and filter spec")

def date_part(self, date_part_name: str) -> QueryInterfaceDimension:
def date_part(self, date_part_name: str) -> WhereFilterDimension:
"""Date part to extract from the dimension."""
self.date_part_name = date_part_name
return self


class WhereFilterDimensionFactory(ProtocolHint[QueryInterfaceDimensionFactory]):
class WhereFilterDimensionFactory:
"""Creates a WhereFilterDimension.
Each call to `create` adds a WhereFilterDimension to `created`.
"""

@override
def _implements_protocol(self) -> QueryInterfaceDimensionFactory:
return self

def __init__(self) -> None: # noqa
self.created: List[WhereFilterDimension] = []

def create(self, dimension_name: str, entity_path: Sequence[str] = ()) -> WhereFilterDimension:
"""Gets called by Jinja when rendering {{ Dimension(...) }}."""
dimension = WhereFilterDimension(dimension_name, entity_path)
dimension = WhereFilterDimension(name=dimension_name, entity_path=entity_path)
self.created.append(dimension)
return dimension
Original file line number Diff line number Diff line change
Expand Up @@ -2,79 +2,35 @@

from typing import List, Sequence

from typing_extensions import override

from dbt_semantic_interfaces.call_parameter_sets import (
EntityCallParameterSet,
MetricCallParameterSet,
)
from dbt_semantic_interfaces.errors import InvalidQuerySyntax
from dbt_semantic_interfaces.parsing.where_filter.parameter_set_factory import (
ParameterSetFactory,
)
from dbt_semantic_interfaces.protocols.protocol_hint import ProtocolHint
from dbt_semantic_interfaces.protocols.query_interface import (
QueryInterfaceEntity,
QueryInterfaceEntityFactory,
QueryInterfaceMetric,
QueryInterfaceMetricFactory,
)


class EntityStub(ProtocolHint[QueryInterfaceEntity]):
"""An Entity implementation that just satisfies the protocol.
QueryInterfaceEntity currently has no methods and the parameter set is created in the factory.
So, there is nothing to do here.
"""

@override
def _implements_protocol(self) -> QueryInterfaceEntity:
return self


class MetricStub(ProtocolHint[QueryInterfaceMetric]):
"""A Metric implementation that just satisfies the protocol.

QueryInterfaceMetric currently has no methods and the parameter set is created in the factory.
"""

@override
def _implements_protocol(self) -> QueryInterfaceMetric:
return self

def descending(self, _is_descending: bool) -> QueryInterfaceMetric: # noqa: D
raise InvalidQuerySyntax("descending is invalid in the where parameter and filter spec")


class WhereFilterEntityFactory(ProtocolHint[QueryInterfaceEntityFactory]):
class WhereFilterEntityFactory:
"""Executes in the Jinja sandbox to produce parameter sets and append them to a list."""

@override
def _implements_protocol(self) -> QueryInterfaceEntityFactory:
return self

def __init__(self) -> None: # noqa
def __init__(self) -> None: # noqa: D
self.entity_call_parameter_sets: List[EntityCallParameterSet] = []

def create(self, entity_name: str, entity_path: Sequence[str] = ()) -> EntityStub:
def create(self, entity_name: str, entity_path: Sequence[str] = ()) -> None:
"""Gets called by Jinja when rendering {{ Entity(...) }}."""
self.entity_call_parameter_sets.append(ParameterSetFactory.create_entity(entity_name, entity_path))
return EntityStub()


class WhereFilterMetricFactory(ProtocolHint[QueryInterfaceMetricFactory]):
# TODO: move to its own file!
class WhereFilterMetricFactory:
"""Executes in the Jinja sandbox to produce parameter sets and append them to a list."""

@override
def _implements_protocol(self) -> QueryInterfaceMetricFactory:
return self

def __init__(self) -> None: # noqa: D
self.metric_call_parameter_sets: List[MetricCallParameterSet] = []

def create(self, metric_name: str, group_by: Sequence[str] = ()) -> MetricStub: # noqa: D
def create(self, metric_name: str, group_by: Sequence[str] = ()) -> None:
"""Build call_parameter_sets and store on factory object."""
self.metric_call_parameter_sets.append(
ParameterSetFactory.create_metric(metric_name=metric_name, group_by=group_by)
)
return MetricStub()
Original file line number Diff line number Diff line change
Expand Up @@ -2,39 +2,19 @@

from typing import List, Optional, Sequence

from typing_extensions import override

from dbt_semantic_interfaces.call_parameter_sets import TimeDimensionCallParameterSet
from dbt_semantic_interfaces.errors import InvalidQuerySyntax
from dbt_semantic_interfaces.parsing.where_filter.parameter_set_factory import (
ParameterSetFactory,
)
from dbt_semantic_interfaces.protocols.protocol_hint import ProtocolHint
from dbt_semantic_interfaces.protocols.query_interface import (
QueryInterfaceTimeDimension,
QueryInterfaceTimeDimensionFactory,
from dbt_semantic_interfaces.parsing.where_filter.where_filter_dimension import (
WhereFilterDimension,
)


class TimeDimensionStub(ProtocolHint[QueryInterfaceTimeDimension]):
"""A TimeDimension implementation that just satisfies the protocol.
QueryInterfaceTimeDimension currently has no methods and the parameter set is created in the factory.
So, there is nothing to do here.
"""

@override
def _implements_protocol(self) -> QueryInterfaceTimeDimension:
return self


class WhereFilterTimeDimensionFactory(ProtocolHint[QueryInterfaceTimeDimensionFactory]):
class WhereFilterTimeDimensionFactory:
"""Executes in the Jinja sandbox to produce parameter sets and append them to a list."""

@override
def _implements_protocol(self) -> QueryInterfaceTimeDimensionFactory:
return self

def __init__(self) -> None: # noqa
self.time_dimension_call_parameter_sets: List[TimeDimensionCallParameterSet] = []

Expand All @@ -43,15 +23,22 @@ def create(
time_dimension_name: str,
time_granularity_name: Optional[str] = None,
entity_path: Sequence[str] = (),
descending: Optional[bool] = None,
date_part_name: Optional[str] = None,
) -> TimeDimensionStub:
) -> WhereFilterDimension:
"""Gets called by Jinja when rendering {{ TimeDimension(...) }}."""
if descending is not None:
raise InvalidQuerySyntax("descending is invalid in the where parameter and filter spec")
self.time_dimension_call_parameter_sets.append(
ParameterSetFactory.create_time_dimension(
time_dimension_name, time_granularity_name, entity_path, date_part_name
time_dimension_name=time_dimension_name,
time_granularity_name=time_granularity_name,
entity_path=entity_path,
date_part_name=date_part_name,
)
)
return TimeDimensionStub()
return WhereFilterDimension(
name=time_dimension_name,
entity_path=entity_path,
time_granularity_name=time_granularity_name,
date_part_name=date_part_name,
)

# TODO: should we add grain() and date_part() methods here for consistency with WhereFilterDimensionFactory?
98 changes: 0 additions & 98 deletions dbt_semantic_interfaces/protocols/query_interface.py
Original file line number Diff line number Diff line change
@@ -1,99 +1 @@
from __future__ import annotations

from abc import abstractmethod
from typing import Optional, Protocol, Sequence


class QueryInterfaceMetric(Protocol):
"""Represents the interface for Metric in the query interface."""

@abstractmethod
def descending(self, _is_descending: bool) -> QueryInterfaceMetric:
"""Set the sort order for order-by."""
pass


class QueryInterfaceDimension(Protocol):
"""Represents the interface for Dimension in the query interface."""

@abstractmethod
def grain(self, _grain: str) -> QueryInterfaceDimension:
"""The time granularity."""
pass

@abstractmethod
def descending(self, _is_descending: bool) -> QueryInterfaceDimension:
"""Set the sort order for order-by."""
pass

@abstractmethod
def date_part(self, _date_part: str) -> QueryInterfaceDimension:
"""Date part to extract from the dimension."""
pass


class QueryInterfaceDimensionFactory(Protocol):
"""Creates a Dimension for the query interface.
Represented as the Dimension constructor in the Jinja sandbox.
"""

@abstractmethod
def create(self, name: str, entity_path: Sequence[str] = ()) -> QueryInterfaceDimension:
"""Create a QueryInterfaceDimension."""
pass


class QueryInterfaceTimeDimension(Protocol):
"""Represents the interface for TimeDimension in the query interface."""

pass


class QueryInterfaceTimeDimensionFactory(Protocol):
"""Creates a TimeDimension for the query interface.
Represented as the TimeDimension constructor in the Jinja sandbox.
"""

@abstractmethod
def create(
self,
time_dimension_name: str,
time_granularity_name: str,
entity_path: Sequence[str] = (),
descending: Optional[bool] = None,
date_part_name: Optional[str] = None,
) -> QueryInterfaceTimeDimension:
"""Create a TimeDimension."""
pass


class QueryInterfaceEntity(Protocol):
"""Represents the interface for Entity in the query interface."""

pass


class QueryInterfaceEntityFactory(Protocol):
"""Creates an Entity for the query interface.
Represented as the Entity constructor in the Jinja sandbox.
"""

@abstractmethod
def create(self, entity_name: str, entity_path: Sequence[str] = ()) -> QueryInterfaceEntity:
"""Create an Entity."""
pass


class QueryInterfaceMetricFactory(Protocol):
"""Creates an Metric for the query interface.
Represented as the Metric constructor in the Jinja sandbox.
"""

@abstractmethod
def create(self, metric_name: str, group_by: Sequence[str] = ()) -> QueryInterfaceMetric:
"""Create a Metric."""
pass

0 comments on commit eb0f0b7

Please sign in to comment.