-
Notifications
You must be signed in to change notification settings - Fork 67
Expand file tree
/
Copy pathmetric.py
More file actions
282 lines (217 loc) · 8.25 KB
/
metric.py
File metadata and controls
282 lines (217 loc) · 8.25 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
# (C) 2022 GoodData Corporation
from __future__ import annotations
from typing import Optional, Union
import gooddata_api_client.models as afm_models
from gooddata_api_client.model_utils import OpenApiModel
from gooddata_sdk.compute.model.attribute import Attribute
from gooddata_sdk.compute.model.base import ExecModelEntity, Filter, ObjId
def _extract_local_id(val: Union[str, Metric]) -> str:
if isinstance(val, str):
return val
else:
# if things bomb here it means bad input to model class
return val.local_id
class Metric(ExecModelEntity):
def __init__(self, local_id: str) -> None:
super().__init__()
self._local_id = local_id
@property
def local_id(self) -> str:
return self._local_id
def as_api_model(self) -> afm_models.MeasureItem:
definition = self._body_as_api_model()
return afm_models.MeasureItem(local_identifier=self._local_id, definition=definition)
def _body_as_api_model(self) -> OpenApiModel:
raise NotImplementedError()
SIMPLE_METRIC_AGGREGATION = {
"SUM",
"AVG",
"COUNT",
"APPROXIMATE_COUNT",
"MAX",
"MEDIAN",
"MIN",
"RUNSUM",
}
class SimpleMetric(Metric):
def __init__(
self,
local_id: str,
item: ObjId,
aggregation: Optional[str] = None,
compute_ratio: bool = False,
filters: Optional[list[Filter]] = None,
) -> None:
super().__init__(local_id)
_agg = aggregation.upper() if aggregation is not None else None
if _agg is not None and _agg not in SIMPLE_METRIC_AGGREGATION:
raise ValueError(
f"Invalid simple metric aggregation '{_agg}'. Valid operators: {SIMPLE_METRIC_AGGREGATION}"
)
self._item = item
if item.type == "metric":
self._aggregation = None
elif _agg is None:
self._aggregation = "SUM"
else:
self._aggregation = _agg
self._compute_ratio = compute_ratio
if filters is None:
self._filters = []
else:
self._filters = filters
@property
def item(self) -> ObjId:
return self._item
@property
def aggregation(self) -> Optional[str]:
return self._aggregation
@property
def compute_ratio(self) -> bool:
return self._compute_ratio
@property
def filters(self) -> list[Filter]:
return self._filters
def _body_as_api_model(self) -> afm_models.SimpleMeasureDefinition:
_filters = [f.as_api_model() for f in self.filters]
# aggregation is optional yet the model bombs if None is sent :(
if self.aggregation is not None:
return afm_models.SimpleMeasureDefinition(
afm_models.SimpleMeasureDefinitionMeasure(
item=self.item.as_afm_id(),
aggregation=self.aggregation,
compute_ratio=self.compute_ratio,
filters=_filters,
_check_type=False,
)
)
else:
return afm_models.SimpleMeasureDefinition(
afm_models.SimpleMeasureDefinitionMeasure(
item=self.item.as_afm_id(),
compute_ratio=self.compute_ratio,
filters=_filters,
_check_type=False,
)
)
def __repr__(self) -> str:
return (
f"compute_model.SimpleMetric("
f"item='{self.item}', "
f"aggregation='{self.aggregation}', "
f"compute_ratio='{self.compute_ratio}', "
f"filters='{self.filters}')"
)
class PopDate:
def __init__(self, attribute: Union[ObjId, Attribute], periods_ago: int) -> None:
if isinstance(attribute, Attribute):
self._attribute = attribute.label
else:
self._attribute = attribute
self._periods_ago = periods_ago
@property
def attribute(self) -> ObjId:
return self._attribute
@property
def periods_ago(self) -> int:
return self._periods_ago
def as_api_model(self) -> afm_models.PopDate:
return afm_models.PopDate(attribute=self.attribute.as_afm_id_attribute(), periods_ago=self.periods_ago)
class PopDateMetric(Metric):
def __init__(
self,
local_id: str,
metric: Union[str, Metric],
date_attributes: list[PopDate],
) -> None:
super().__init__(local_id)
self._metric = _extract_local_id(metric)
self._date_attributes = date_attributes
@property
def metric_local_id(self) -> str:
return self._metric
@property
def date_attributes(self) -> list[PopDate]:
return self._date_attributes
def _body_as_api_model(self) -> afm_models.PopDateMeasureDefinition:
measure_identifier = afm_models.AfmLocalIdentifier(local_identifier=self.metric_local_id)
date_attributes = list([a.as_api_model() for a in self.date_attributes])
return afm_models.PopDateMeasureDefinition(
afm_models.PopDateMeasureDefinitionOverPeriodMeasure(
measure_identifier=measure_identifier, date_attributes=date_attributes
)
)
class PopDateDataset:
def __init__(self, dataset: Union[ObjId, str], periods_ago: int) -> None:
self._dataset = ObjId(dataset, "dataset") if isinstance(dataset, str) else dataset
self._periods_ago = periods_ago
@property
def dataset(self) -> ObjId:
return self._dataset
@property
def periods_ago(self) -> int:
return self._periods_ago
def as_api_model(self) -> afm_models.PopDataset:
return afm_models.PopDataset(dataset=self.dataset.as_afm_id_dataset(), periods_ago=self.periods_ago)
class PopDatesetMetric(Metric):
def __init__(
self,
local_id: str,
metric: Union[str, Metric],
date_datasets: list[PopDateDataset],
) -> None:
super().__init__(local_id)
self._metric = _extract_local_id(metric)
self._date_datasets = date_datasets
@property
def metric_local_id(self) -> str:
return self._metric
@property
def date_datasets(self) -> list[PopDateDataset]:
return self._date_datasets
def _body_as_api_model(self) -> afm_models.PopDatasetMeasureDefinition:
measure_identifier = afm_models.AfmLocalIdentifier(local_identifier=self.metric_local_id)
date_datasets = list([d.as_api_model() for d in self.date_datasets])
return afm_models.PopDatasetMeasureDefinition(
afm_models.PopDatasetMeasureDefinitionPreviousPeriodMeasure(
measure_identifier=measure_identifier, date_datasets=date_datasets
)
)
ARITHMETIC_METRIC_OPERATORS = {
"SUM",
"DIFFERENCE",
"MULTIPLICATION",
"RATIO",
"CHANGE",
}
class ArithmeticMetric(Metric):
def __init__(self, local_id: str, operator: str, operands: list[Union[str, Metric]]) -> None:
super().__init__(local_id)
if operator not in ARITHMETIC_METRIC_OPERATORS:
raise ValueError(
f"Invalid arithmetic metric operator '{operator}'. Valid operators: {ARITHMETIC_METRIC_OPERATORS}"
)
self._operator = operator
self._operands = list([_extract_local_id(o) for o in operands])
@property
def operator(self) -> str:
return self._operator
@property
def operand_local_ids(self) -> list[str]:
return self._operands
def _body_as_api_model(self) -> afm_models.ArithmeticMeasureDefinition:
measure_identifiers = [afm_models.AfmLocalIdentifier(local_identifier=local_d) for local_d in self._operands]
return afm_models.ArithmeticMeasureDefinition(
afm_models.ArithmeticMeasureDefinitionArithmeticMeasure(
operator=self.operator, measure_identifiers=measure_identifiers
)
)
class InlineMetric(Metric):
def __init__(self, maql: str, local_id: str) -> None:
super().__init__(local_id)
self._maql = maql
@property
def maql(self) -> str:
return self._maql
def _body_as_api_model(self) -> afm_models.InlineMeasureDefinition:
return afm_models.InlineMeasureDefinition(inline=afm_models.InlineMeasureDefinitionInline(maql=self.maql))