Skip to content

Commit 6c8de95

Browse files
author
Charles Larivier
committed
feat: add filter.py
Signed-off-by: Charles Larivier <charles@dribbble.com>
1 parent 488a4aa commit 6c8de95

File tree

2 files changed

+195
-0
lines changed

2 files changed

+195
-0
lines changed

src/metabase/mbql/filter.py

Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
from typing import Any, List
2+
3+
from metabase.mbql.base import Mbql, Option
4+
5+
6+
class CaseOption(Option):
7+
CASE_SENSITIVE = {"case-sensitive": True}
8+
CASE_INSENSITIVE = {"case-sensitive": False}
9+
10+
11+
class TimeGrainOption(Option):
12+
MINUTE = "minute"
13+
HOUR = "hour"
14+
DAY = "day"
15+
WEEK = "week"
16+
MONTH = "month"
17+
QUARTER = "quarter"
18+
YEAR = "year"
19+
20+
21+
class Filter(Mbql):
22+
function: str
23+
24+
def __init__(self, id: int, option: Option = None):
25+
self.id = id
26+
self.option = None
27+
self.filter_option = option
28+
29+
def compile(self) -> List:
30+
compiled = [self.function, super(Filter, self).compile()]
31+
32+
if self.filter_option is not None:
33+
compiled = compiled + [self.filter_option]
34+
35+
return compiled
36+
37+
38+
class ValueFilter(Filter):
39+
def __init__(self, id: int, value: Any, option: Option = None):
40+
self.id = id
41+
self.value = value
42+
self.option = None
43+
self.filter_option = option
44+
45+
def compile(self) -> List:
46+
compiled = [self.function, super(Filter, self).compile(), self.value]
47+
48+
if self.filter_option is not None:
49+
compiled = compiled + [self.filter_option]
50+
51+
return compiled
52+
53+
54+
class Equal(ValueFilter):
55+
function = "="
56+
57+
58+
class NotEqual(ValueFilter):
59+
function = "!="
60+
61+
62+
class Greater(ValueFilter):
63+
function = ">"
64+
65+
66+
class Less(ValueFilter):
67+
function = "<"
68+
69+
70+
class Between(Filter):
71+
function = "between"
72+
73+
def __init__(
74+
self, id: int, lower_bound: float, upper_bound: float, option: Option = None
75+
):
76+
self.id = id
77+
self.option = None
78+
self.filter_option = option
79+
self.lower_bound = lower_bound
80+
self.upper_bound = upper_bound
81+
82+
def compile(self) -> List:
83+
return super(Between, self).compile() + [self.lower_bound, self.upper_bound]
84+
85+
86+
class GreaterEqual(ValueFilter):
87+
function = ">="
88+
89+
90+
class LessEqual(ValueFilter):
91+
function = "<="
92+
93+
94+
class IsNull(Filter):
95+
function = "is-null"
96+
97+
98+
class IsNotNull(Filter):
99+
function = "not-null"
100+
101+
102+
class Contains(ValueFilter):
103+
function = "contains"
104+
105+
106+
class StartsWith(ValueFilter):
107+
function = "starts-with"
108+
109+
110+
class EndsWith(ValueFilter):
111+
function = "ends-with"
112+
113+
114+
class TimeInterval(Filter):
115+
function = "time-interval"
116+
117+
def __init__(
118+
self,
119+
id: int,
120+
value: Any,
121+
time_grain: TimeGrainOption,
122+
include_current: bool = True,
123+
):
124+
self.id = id
125+
self.value = value
126+
self.option = None
127+
self.time_grain = time_grain
128+
self.include_current = include_current
129+
130+
def compile(self) -> List:
131+
compiled = [
132+
self.function,
133+
super(Filter, self).compile(),
134+
self.value,
135+
self.time_grain,
136+
]
137+
138+
if self.include_current:
139+
compiled = compiled + [{"include-current": True}]
140+
141+
return compiled

tests/mbql/test_filter.py

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
from unittest import TestCase
2+
3+
from metabase.mbql.filter import (
4+
Between,
5+
CaseOption,
6+
TimeGrainOption,
7+
TimeInterval,
8+
ValueFilter,
9+
)
10+
11+
12+
class FilterTests(TestCase):
13+
def test_value_filter_compile(self):
14+
"""
15+
Ensure ValueFilter.compile() returns
16+
[self.function, ['field', self.id, None], self.value, self.filter_option].
17+
"""
18+
19+
class Mock(ValueFilter):
20+
function = "mock"
21+
22+
filter = Mock(id=5, value="gmail", option=CaseOption.CASE_SENSITIVE)
23+
self.assertEqual(
24+
["mock", ["field", 5, None], "gmail", {"case-sensitive": True}],
25+
filter.compile(),
26+
)
27+
28+
def test_between_compile(self):
29+
"""
30+
Ensure Between.compile() returns
31+
['between', ['field', self.id', None], self.lower_bound, self.upper_bound].
32+
"""
33+
between = Between(id=2, lower_bound=2.5, upper_bound=6.7)
34+
self.assertEqual(["between", ["field", 2, None], 2.5, 6.7], between.compile())
35+
36+
def test_time_interval(self):
37+
interval = TimeInterval(id=4, value=30, time_grain=TimeGrainOption.YEAR)
38+
self.assertEqual(
39+
[
40+
"time-interval",
41+
["field", 4, None],
42+
30,
43+
"year",
44+
{"include-current": True},
45+
],
46+
interval.compile(),
47+
)
48+
49+
interval = TimeInterval(
50+
id=4, value=30, time_grain=TimeGrainOption.YEAR, include_current=False
51+
)
52+
self.assertEqual(
53+
["time-interval", ["field", 4, None], 30, "year"], interval.compile()
54+
)

0 commit comments

Comments
 (0)