Skip to content

Commit eb0f0b7

Browse files
Dedupe Jinja parsing logic
1 parent e4f029b commit eb0f0b7

File tree

5 files changed

+43
-216
lines changed

5 files changed

+43
-216
lines changed

dbt_semantic_interfaces/parsing/where_filter/parameter_set_factory.py

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -43,16 +43,16 @@ def create_time_dimension(
4343
"""Gets called by Jinja when rendering {{ TimeDimension(...) }}."""
4444
group_by_item_name = DunderedNameFormatter.parse_name(time_dimension_name)
4545

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

9999
additional_entity_path_elements = tuple(
@@ -115,5 +115,6 @@ def create_metric(metric_name: str, group_by: Sequence[str] = ()) -> MetricCallP
115115
)
116116
return MetricCallParameterSet(
117117
metric_reference=MetricReference(element_name=metric_name),
118+
# Should this parse to entities & dimensions for simplicity down the line?
118119
group_by=tuple([LinkableElementReference(element_name=group_by_name) for group_by_name in group_by]),
119120
)

dbt_semantic_interfaces/parsing/where_filter/where_filter_dimension.py

Lines changed: 9 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -2,63 +2,44 @@
22

33
from typing import List, Optional, Sequence
44

5-
from typing_extensions import override
65

7-
from dbt_semantic_interfaces.errors import InvalidQuerySyntax
8-
from dbt_semantic_interfaces.protocols.protocol_hint import ProtocolHint
9-
from dbt_semantic_interfaces.protocols.query_interface import (
10-
QueryInterfaceDimension,
11-
QueryInterfaceDimensionFactory,
12-
)
13-
14-
15-
class WhereFilterDimension(ProtocolHint[QueryInterfaceDimension]):
6+
class WhereFilterDimension:
167
"""A dimension that is passed in through the where filter parameter."""
178

18-
@override
19-
def _implements_protocol(self) -> QueryInterfaceDimension:
20-
return self
21-
229
def __init__( # noqa
2310
self,
2411
name: str,
2512
entity_path: Sequence[str],
13+
time_granularity_name: Optional[str] = None,
14+
date_part_name: Optional[str] = None,
2615
) -> None:
2716
self.name = name
2817
self.entity_path = entity_path
29-
self.time_granularity_name: Optional[str] = None
30-
self.date_part_name: Optional[str] = None
18+
self.time_granularity_name = time_granularity_name
19+
self.date_part_name = date_part_name
3120

32-
def grain(self, time_granularity: str) -> QueryInterfaceDimension:
21+
def grain(self, time_granularity: str) -> WhereFilterDimension:
3322
"""The time granularity."""
3423
self.time_granularity_name = time_granularity
3524
return self
3625

37-
def descending(self, _is_descending: bool) -> QueryInterfaceDimension:
38-
"""Set the sort order for order-by."""
39-
raise InvalidQuerySyntax("descending is invalid in the where parameter and filter spec")
40-
41-
def date_part(self, date_part_name: str) -> QueryInterfaceDimension:
26+
def date_part(self, date_part_name: str) -> WhereFilterDimension:
4227
"""Date part to extract from the dimension."""
4328
self.date_part_name = date_part_name
4429
return self
4530

4631

47-
class WhereFilterDimensionFactory(ProtocolHint[QueryInterfaceDimensionFactory]):
32+
class WhereFilterDimensionFactory:
4833
"""Creates a WhereFilterDimension.
4934
5035
Each call to `create` adds a WhereFilterDimension to `created`.
5136
"""
5237

53-
@override
54-
def _implements_protocol(self) -> QueryInterfaceDimensionFactory:
55-
return self
56-
5738
def __init__(self) -> None: # noqa
5839
self.created: List[WhereFilterDimension] = []
5940

6041
def create(self, dimension_name: str, entity_path: Sequence[str] = ()) -> WhereFilterDimension:
6142
"""Gets called by Jinja when rendering {{ Dimension(...) }}."""
62-
dimension = WhereFilterDimension(dimension_name, entity_path)
43+
dimension = WhereFilterDimension(name=dimension_name, entity_path=entity_path)
6344
self.created.append(dimension)
6445
return dimension

dbt_semantic_interfaces/parsing/where_filter/where_filter_entity.py

Lines changed: 7 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -2,79 +2,35 @@
22

33
from typing import List, Sequence
44

5-
from typing_extensions import override
6-
75
from dbt_semantic_interfaces.call_parameter_sets import (
86
EntityCallParameterSet,
97
MetricCallParameterSet,
108
)
11-
from dbt_semantic_interfaces.errors import InvalidQuerySyntax
129
from dbt_semantic_interfaces.parsing.where_filter.parameter_set_factory import (
1310
ParameterSetFactory,
1411
)
15-
from dbt_semantic_interfaces.protocols.protocol_hint import ProtocolHint
16-
from dbt_semantic_interfaces.protocols.query_interface import (
17-
QueryInterfaceEntity,
18-
QueryInterfaceEntityFactory,
19-
QueryInterfaceMetric,
20-
QueryInterfaceMetricFactory,
21-
)
22-
23-
24-
class EntityStub(ProtocolHint[QueryInterfaceEntity]):
25-
"""An Entity implementation that just satisfies the protocol.
26-
27-
QueryInterfaceEntity currently has no methods and the parameter set is created in the factory.
28-
So, there is nothing to do here.
29-
"""
30-
31-
@override
32-
def _implements_protocol(self) -> QueryInterfaceEntity:
33-
return self
34-
3512

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

39-
QueryInterfaceMetric currently has no methods and the parameter set is created in the factory.
40-
"""
41-
42-
@override
43-
def _implements_protocol(self) -> QueryInterfaceMetric:
44-
return self
45-
46-
def descending(self, _is_descending: bool) -> QueryInterfaceMetric: # noqa: D
47-
raise InvalidQuerySyntax("descending is invalid in the where parameter and filter spec")
48-
49-
50-
class WhereFilterEntityFactory(ProtocolHint[QueryInterfaceEntityFactory]):
14+
class WhereFilterEntityFactory:
5115
"""Executes in the Jinja sandbox to produce parameter sets and append them to a list."""
5216

53-
@override
54-
def _implements_protocol(self) -> QueryInterfaceEntityFactory:
55-
return self
56-
57-
def __init__(self) -> None: # noqa
17+
def __init__(self) -> None: # noqa: D
5818
self.entity_call_parameter_sets: List[EntityCallParameterSet] = []
5919

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

6524

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

69-
@override
70-
def _implements_protocol(self) -> QueryInterfaceMetricFactory:
71-
return self
72-
7329
def __init__(self) -> None: # noqa: D
7430
self.metric_call_parameter_sets: List[MetricCallParameterSet] = []
7531

76-
def create(self, metric_name: str, group_by: Sequence[str] = ()) -> MetricStub: # noqa: D
32+
def create(self, metric_name: str, group_by: Sequence[str] = ()) -> None:
33+
"""Build call_parameter_sets and store on factory object."""
7734
self.metric_call_parameter_sets.append(
7835
ParameterSetFactory.create_metric(metric_name=metric_name, group_by=group_by)
7936
)
80-
return MetricStub()

dbt_semantic_interfaces/parsing/where_filter/where_filter_time_dimension.py

Lines changed: 16 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -2,39 +2,19 @@
22

33
from typing import List, Optional, Sequence
44

5-
from typing_extensions import override
6-
75
from dbt_semantic_interfaces.call_parameter_sets import TimeDimensionCallParameterSet
86
from dbt_semantic_interfaces.errors import InvalidQuerySyntax
97
from dbt_semantic_interfaces.parsing.where_filter.parameter_set_factory import (
108
ParameterSetFactory,
119
)
12-
from dbt_semantic_interfaces.protocols.protocol_hint import ProtocolHint
13-
from dbt_semantic_interfaces.protocols.query_interface import (
14-
QueryInterfaceTimeDimension,
15-
QueryInterfaceTimeDimensionFactory,
10+
from dbt_semantic_interfaces.parsing.where_filter.where_filter_dimension import (
11+
WhereFilterDimension,
1612
)
1713

1814

19-
class TimeDimensionStub(ProtocolHint[QueryInterfaceTimeDimension]):
20-
"""A TimeDimension implementation that just satisfies the protocol.
21-
22-
QueryInterfaceTimeDimension currently has no methods and the parameter set is created in the factory.
23-
So, there is nothing to do here.
24-
"""
25-
26-
@override
27-
def _implements_protocol(self) -> QueryInterfaceTimeDimension:
28-
return self
29-
30-
31-
class WhereFilterTimeDimensionFactory(ProtocolHint[QueryInterfaceTimeDimensionFactory]):
15+
class WhereFilterTimeDimensionFactory:
3216
"""Executes in the Jinja sandbox to produce parameter sets and append them to a list."""
3317

34-
@override
35-
def _implements_protocol(self) -> QueryInterfaceTimeDimensionFactory:
36-
return self
37-
3818
def __init__(self) -> None: # noqa
3919
self.time_dimension_call_parameter_sets: List[TimeDimensionCallParameterSet] = []
4020

@@ -43,15 +23,22 @@ def create(
4323
time_dimension_name: str,
4424
time_granularity_name: Optional[str] = None,
4525
entity_path: Sequence[str] = (),
46-
descending: Optional[bool] = None,
4726
date_part_name: Optional[str] = None,
48-
) -> TimeDimensionStub:
27+
) -> WhereFilterDimension:
4928
"""Gets called by Jinja when rendering {{ TimeDimension(...) }}."""
50-
if descending is not None:
51-
raise InvalidQuerySyntax("descending is invalid in the where parameter and filter spec")
5229
self.time_dimension_call_parameter_sets.append(
5330
ParameterSetFactory.create_time_dimension(
54-
time_dimension_name, time_granularity_name, entity_path, date_part_name
31+
time_dimension_name=time_dimension_name,
32+
time_granularity_name=time_granularity_name,
33+
entity_path=entity_path,
34+
date_part_name=date_part_name,
5535
)
5636
)
57-
return TimeDimensionStub()
37+
return WhereFilterDimension(
38+
name=time_dimension_name,
39+
entity_path=entity_path,
40+
time_granularity_name=time_granularity_name,
41+
date_part_name=date_part_name,
42+
)
43+
44+
# TODO: should we add grain() and date_part() methods here for consistency with WhereFilterDimensionFactory?
Lines changed: 0 additions & 98 deletions
Original file line numberDiff line numberDiff line change
@@ -1,99 +1 @@
11
from __future__ import annotations
2-
3-
from abc import abstractmethod
4-
from typing import Optional, Protocol, Sequence
5-
6-
7-
class QueryInterfaceMetric(Protocol):
8-
"""Represents the interface for Metric in the query interface."""
9-
10-
@abstractmethod
11-
def descending(self, _is_descending: bool) -> QueryInterfaceMetric:
12-
"""Set the sort order for order-by."""
13-
pass
14-
15-
16-
class QueryInterfaceDimension(Protocol):
17-
"""Represents the interface for Dimension in the query interface."""
18-
19-
@abstractmethod
20-
def grain(self, _grain: str) -> QueryInterfaceDimension:
21-
"""The time granularity."""
22-
pass
23-
24-
@abstractmethod
25-
def descending(self, _is_descending: bool) -> QueryInterfaceDimension:
26-
"""Set the sort order for order-by."""
27-
pass
28-
29-
@abstractmethod
30-
def date_part(self, _date_part: str) -> QueryInterfaceDimension:
31-
"""Date part to extract from the dimension."""
32-
pass
33-
34-
35-
class QueryInterfaceDimensionFactory(Protocol):
36-
"""Creates a Dimension for the query interface.
37-
38-
Represented as the Dimension constructor in the Jinja sandbox.
39-
"""
40-
41-
@abstractmethod
42-
def create(self, name: str, entity_path: Sequence[str] = ()) -> QueryInterfaceDimension:
43-
"""Create a QueryInterfaceDimension."""
44-
pass
45-
46-
47-
class QueryInterfaceTimeDimension(Protocol):
48-
"""Represents the interface for TimeDimension in the query interface."""
49-
50-
pass
51-
52-
53-
class QueryInterfaceTimeDimensionFactory(Protocol):
54-
"""Creates a TimeDimension for the query interface.
55-
56-
Represented as the TimeDimension constructor in the Jinja sandbox.
57-
"""
58-
59-
@abstractmethod
60-
def create(
61-
self,
62-
time_dimension_name: str,
63-
time_granularity_name: str,
64-
entity_path: Sequence[str] = (),
65-
descending: Optional[bool] = None,
66-
date_part_name: Optional[str] = None,
67-
) -> QueryInterfaceTimeDimension:
68-
"""Create a TimeDimension."""
69-
pass
70-
71-
72-
class QueryInterfaceEntity(Protocol):
73-
"""Represents the interface for Entity in the query interface."""
74-
75-
pass
76-
77-
78-
class QueryInterfaceEntityFactory(Protocol):
79-
"""Creates an Entity for the query interface.
80-
81-
Represented as the Entity constructor in the Jinja sandbox.
82-
"""
83-
84-
@abstractmethod
85-
def create(self, entity_name: str, entity_path: Sequence[str] = ()) -> QueryInterfaceEntity:
86-
"""Create an Entity."""
87-
pass
88-
89-
90-
class QueryInterfaceMetricFactory(Protocol):
91-
"""Creates an Metric for the query interface.
92-
93-
Represented as the Metric constructor in the Jinja sandbox.
94-
"""
95-
96-
@abstractmethod
97-
def create(self, metric_name: str, group_by: Sequence[str] = ()) -> QueryInterfaceMetric:
98-
"""Create a Metric."""
99-
pass

0 commit comments

Comments
 (0)