Skip to content
Draft
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 @@ -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