gooddata-flexconnect 1.30.1.dev2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,17 @@
1
+ # (C) 2024 GoodData Corporation
2
+
3
+ from gooddata_flexconnect.function.execution_context import (
4
+ ExecutionContext,
5
+ ExecutionContextAbsoluteDateFilter,
6
+ ExecutionContextAttribute,
7
+ ExecutionContextAttributeSorting,
8
+ ExecutionContextNegativeAttributeFilter,
9
+ ExecutionContextPositiveAttributeFilter,
10
+ ExecutionContextRelativeDateFilter,
11
+ ExecutionRequest,
12
+ ExecutionType,
13
+ LabelElementsExecutionRequest,
14
+ ReportExecutionRequest,
15
+ )
16
+ from gooddata_flexconnect.function.flight_methods import create_flexconnect_flight_methods
17
+ from gooddata_flexconnect.function.function import FlexConnectFunction
@@ -0,0 +1 @@
1
+ # (C) 2024 GoodData Corporation
@@ -0,0 +1,576 @@
1
+ # (C) 2024 GoodData Corporation
2
+ import enum
3
+ from dataclasses import dataclass
4
+ from typing import Callable, Optional, Union
5
+
6
+ from gooddata_sdk import (
7
+ AbsoluteDateFilter,
8
+ Attribute,
9
+ CatalogDependsOn,
10
+ CatalogDependsOnDateFilter,
11
+ CatalogFilterBy,
12
+ CatalogValidateByItem,
13
+ ComputeToSdkConverter,
14
+ Filter,
15
+ Metric,
16
+ RelativeDateFilter,
17
+ )
18
+ from typing_extensions import TypeAlias, TypeVar
19
+
20
+ TInput = TypeVar("TInput")
21
+ TResult = TypeVar("TResult")
22
+
23
+
24
+ def none_safe(func: Callable[[TInput], TResult]) -> Callable[[Optional[TInput]], Optional[TResult]]:
25
+ """
26
+ Decorator that makes the unary function safe for None input.
27
+ If the only argument is None, the function returns None.
28
+ """
29
+
30
+ def wrapper(arg: Optional[TInput]) -> Optional[TResult]:
31
+ if arg is None:
32
+ return None
33
+ return func(arg)
34
+
35
+ return wrapper
36
+
37
+
38
+ def _dict_to_request_attributes(attributes: list[dict]) -> list[Attribute]:
39
+ return [ComputeToSdkConverter.convert_attribute(a) for a in attributes]
40
+
41
+
42
+ def _dict_to_request_metrics(metrics: list[dict]) -> list[Metric]:
43
+ return [ComputeToSdkConverter.convert_metric(m) for m in metrics]
44
+
45
+
46
+ def _dict_to_request_filters(filters: list[dict]) -> list[Filter]:
47
+ return [ComputeToSdkConverter.convert_filter(f) for f in filters]
48
+
49
+
50
+ class ExecutionType(enum.Enum):
51
+ """
52
+ Type of the execution.
53
+ """
54
+
55
+ REPORT = "REPORT"
56
+ """
57
+ Execution of the report.
58
+ """
59
+
60
+ LABEL_ELEMENTS = "LABEL_ELEMENTS"
61
+ """
62
+ Collect label elements execution.
63
+ """
64
+
65
+
66
+ @dataclass
67
+ class ExecutionContextAttributeSorting:
68
+ """
69
+ Information about the sorting of an attribute.
70
+ """
71
+
72
+ sort_column: str
73
+ """
74
+ Column to sort by.
75
+ """
76
+
77
+ sort_direction: str
78
+ """
79
+ Direction of the sorting.
80
+ """
81
+
82
+
83
+ @none_safe
84
+ def _dict_to_execution_context_attribute_sorting(d: dict) -> ExecutionContextAttributeSorting:
85
+ return ExecutionContextAttributeSorting(
86
+ sort_column=d["sortColumn"],
87
+ sort_direction=d["sortDirection"],
88
+ )
89
+
90
+
91
+ @dataclass
92
+ class ExecutionContextAttribute:
93
+ """
94
+ Information about an attribute used in the execution.
95
+ """
96
+
97
+ attribute_identifier: str
98
+ """
99
+ Identifier of the attribute used.
100
+ """
101
+
102
+ attribute_title: str
103
+ """
104
+ Title of the attribute used.
105
+ """
106
+
107
+ label_identifier: str
108
+ """
109
+ Identifier of the particular label used.
110
+ """
111
+
112
+ label_title: str
113
+ """
114
+ Title of the particular label used.
115
+ """
116
+
117
+ date_granularity: Optional[str]
118
+ """
119
+ Date granularity of the attribute if it is a date attribute.
120
+ """
121
+
122
+ sorting: Optional[ExecutionContextAttributeSorting]
123
+ """
124
+ Sorting of the attribute. If not present, the attribute is not sorted.
125
+ """
126
+
127
+ @staticmethod
128
+ def from_dict(d: Optional[dict]) -> Optional["ExecutionContextAttribute"]:
129
+ """
130
+ Create ExecutionContextAttribute from a dictionary.
131
+ :param d: the dictionary to parse
132
+ """
133
+ if not d:
134
+ return None
135
+ return ExecutionContextAttribute(
136
+ attribute_title=d["attributeTitle"],
137
+ attribute_identifier=d["attributeIdentifier"],
138
+ label_title=d["labelTitle"],
139
+ label_identifier=d["labelIdentifier"],
140
+ date_granularity=d.get("dateGranularity"),
141
+ sorting=_dict_to_execution_context_attribute_sorting(d.get("sorting")),
142
+ )
143
+
144
+
145
+ @dataclass
146
+ class ExecutionContextPositiveAttributeFilter:
147
+ """
148
+ Information about the positive attribute filter.
149
+ """
150
+
151
+ label_identifier: str
152
+ """
153
+ Identifier of the label used.
154
+ """
155
+
156
+ values: list[Optional[str]]
157
+ """
158
+ Values of the filter.
159
+ """
160
+
161
+
162
+ @dataclass
163
+ class ExecutionContextNegativeAttributeFilter:
164
+ """
165
+ Information about the negative attribute filter.
166
+ """
167
+
168
+ label_identifier: str
169
+ """
170
+ Identifier of the label used.
171
+ """
172
+
173
+ values: list[Optional[str]]
174
+ """
175
+ Values of the filter.
176
+ """
177
+
178
+
179
+ @dataclass
180
+ class ExecutionContextRelativeDateFilter:
181
+ """
182
+ Information about the relative date filter.
183
+ """
184
+
185
+ dataset_identifier: str
186
+ """
187
+ Identifier of the dataset used.
188
+ """
189
+
190
+ granularity: str
191
+ """
192
+ Granularity of the filter.
193
+ """
194
+
195
+ from_shift: int
196
+ """
197
+ Shift from the start of the period.
198
+ """
199
+
200
+ to_shift: int
201
+ """
202
+ Shift from the end of the period.
203
+ """
204
+
205
+
206
+ @dataclass
207
+ class ExecutionContextAbsoluteDateFilter:
208
+ """
209
+ Information about the absolute date filter.
210
+ """
211
+
212
+ dataset_identifier: str
213
+ """
214
+ Identifier of the dataset used.
215
+ """
216
+
217
+ from_date: str
218
+ """
219
+ Start date of the filter.
220
+ """
221
+
222
+ to_date: str
223
+ """
224
+ End date of the filter.
225
+ """
226
+
227
+
228
+ ExecutionContextFilter: TypeAlias = Union[
229
+ ExecutionContextPositiveAttributeFilter,
230
+ ExecutionContextNegativeAttributeFilter,
231
+ ExecutionContextRelativeDateFilter,
232
+ ExecutionContextAbsoluteDateFilter,
233
+ ]
234
+
235
+
236
+ @dataclass
237
+ class ExecutionRequest:
238
+ """
239
+ Information about the execution request that is sent to the FlexConnect function.
240
+ DEPRECATED: Use ReportExecutionRequest instead.
241
+ """
242
+
243
+ attributes: list[Attribute]
244
+ """
245
+ All the attributes that are part of the execution request.
246
+ """
247
+
248
+ metrics: list[Metric]
249
+ """
250
+ All the metrics that are part of the execution request.
251
+ """
252
+
253
+ filters: list[Filter]
254
+ """
255
+ All the filters that are part of the execution request.
256
+ """
257
+
258
+ @staticmethod
259
+ def from_dict(d: dict) -> "ExecutionRequest":
260
+ """
261
+ Create ExecutionRequest from a dictionary.
262
+ :param d: the dictionary to parse
263
+ """
264
+ return ExecutionRequest(
265
+ attributes=_dict_to_request_attributes(d.get("attributes", [])),
266
+ metrics=_dict_to_request_metrics(d.get("measures", [])),
267
+ filters=_dict_to_request_filters(d.get("filters", [])),
268
+ )
269
+
270
+
271
+ @dataclass
272
+ class ReportExecutionRequest:
273
+ """
274
+ Information about the report execution request.
275
+ """
276
+
277
+ attributes: list[Attribute]
278
+ """
279
+ All the attributes that are part of the execution request.
280
+ """
281
+
282
+ metrics: list[Metric]
283
+ """
284
+ All the metrics that are part of the execution request.
285
+ """
286
+
287
+ filters: list[Filter]
288
+ """
289
+ All the filters that are part of the execution request.
290
+ """
291
+
292
+ @staticmethod
293
+ @none_safe
294
+ def from_dict(d: dict) -> "ReportExecutionRequest":
295
+ """
296
+ Create ReportExecutionRequest from a dictionary.
297
+ :param d: the dictionary to parse
298
+ """
299
+ return ReportExecutionRequest(
300
+ attributes=_dict_to_request_attributes(d.get("attributes", [])),
301
+ metrics=_dict_to_request_metrics(d.get("measures", [])),
302
+ filters=_dict_to_request_filters(d.get("filters", [])),
303
+ )
304
+
305
+
306
+ DependsOn: TypeAlias = Union[CatalogDependsOn, CatalogDependsOnDateFilter]
307
+
308
+
309
+ def _dict_to_depends_on(d: dict) -> DependsOn:
310
+ if "label" in d:
311
+ return CatalogDependsOn(
312
+ label=d["label"],
313
+ values=d["values"],
314
+ complement_filter=d.get("complementFilter", False),
315
+ )
316
+
317
+ date_filter = d["dateFilter"]
318
+ if "from" in date_filter:
319
+ return CatalogDependsOnDateFilter(
320
+ date_filter=AbsoluteDateFilter(
321
+ dataset=date_filter["dataset"],
322
+ from_date=date_filter["from"],
323
+ to_date=date_filter["to"],
324
+ )
325
+ )
326
+
327
+ return CatalogDependsOnDateFilter(
328
+ date_filter=RelativeDateFilter(
329
+ dataset=date_filter["dataset"],
330
+ granularity=date_filter["granularity"],
331
+ from_shift=date_filter["from"],
332
+ to_shift=date_filter["to"],
333
+ )
334
+ )
335
+
336
+
337
+ @none_safe
338
+ def _list_to_depends_on(src: list[dict]) -> list[DependsOn]:
339
+ return [_dict_to_depends_on(i) for i in src]
340
+
341
+
342
+ @none_safe
343
+ def _dict_to_filter_by(src: dict) -> CatalogFilterBy:
344
+ return CatalogFilterBy(label_type=src.get("labelType"))
345
+
346
+
347
+ @none_safe
348
+ def _list_to_validate_by(validate_by: list[dict]) -> list[CatalogValidateByItem]:
349
+ return [
350
+ CatalogValidateByItem(
351
+ id=i["id"],
352
+ type=i["type"],
353
+ )
354
+ for i in validate_by
355
+ ]
356
+
357
+
358
+ @dataclass
359
+ class LabelElementsExecutionRequest:
360
+ """
361
+ Information about the label elements execution request.
362
+ """
363
+
364
+ label: str
365
+ """
366
+ The label to get the elements for.
367
+ """
368
+
369
+ offset: Optional[int]
370
+ """
371
+ The number of elements to skip before returning.
372
+ """
373
+
374
+ limit: Optional[int]
375
+ """
376
+ The maximum number of elements to return.
377
+ """
378
+
379
+ exclude_primary_label: Optional[bool]
380
+ """
381
+ Excludes items from the result that differ only by primary label
382
+
383
+ * false - return items with distinct primary label
384
+ * true - return items with distinct requested label
385
+ """
386
+
387
+ exact_filter: Optional[list[str]]
388
+ """
389
+ Exact values to filter the elements by.
390
+ """
391
+
392
+ pattern_filter: Optional[str]
393
+ """
394
+ Filter the elements by a pattern. The pattern is matched against the element values in a case-insensitive way.
395
+ """
396
+
397
+ complement_filter: Optional[bool]
398
+ """
399
+ Whether to invert the effects of exact_filter amd pattern_filter.
400
+ """
401
+
402
+ depends_on: Optional[list[DependsOn]]
403
+ """
404
+ Other labels or date filters that should be used to limit the elements.
405
+ """
406
+
407
+ filter_by: Optional[CatalogFilterBy]
408
+ """
409
+ Which label is used for filtering - primary or requested.
410
+ If omitted the server will use the default value of "REQUESTED".
411
+ """
412
+
413
+ validate_by: Optional[list[CatalogValidateByItem]]
414
+ """
415
+ Other metrics, attributes, labels or facts used to validate the elements.
416
+ """
417
+
418
+ @staticmethod
419
+ @none_safe
420
+ def from_dict(d: dict) -> "LabelElementsExecutionRequest":
421
+ """
422
+ Create LabelElementsExecutionRequest from a dictionary.
423
+ :param d: the dictionary to parse
424
+ """
425
+ return LabelElementsExecutionRequest(
426
+ label=d["label"],
427
+ offset=d.get("offset"),
428
+ limit=d.get("limit"),
429
+ exclude_primary_label=d.get("excludePrimaryLabel"),
430
+ exact_filter=d.get("exactFilter"),
431
+ pattern_filter=d.get("patternFilter"),
432
+ complement_filter=d.get("complementFilter"),
433
+ depends_on=_list_to_depends_on(d.get("dependsOn")),
434
+ filter_by=_dict_to_filter_by(d.get("filterBy")),
435
+ validate_by=_list_to_validate_by(d.get("validateBy")),
436
+ )
437
+
438
+
439
+ def _dict_to_filter(d: dict) -> ExecutionContextFilter:
440
+ filter_type = d.get("filterType")
441
+ if filter_type == "positiveAttributeFilter":
442
+ return ExecutionContextPositiveAttributeFilter(label_identifier=d["labelIdentifier"], values=d["values"])
443
+
444
+ if filter_type == "negativeAttributeFilter":
445
+ return ExecutionContextNegativeAttributeFilter(label_identifier=d["labelIdentifier"], values=d["values"])
446
+
447
+ if filter_type == "relativeDateFilter":
448
+ return ExecutionContextRelativeDateFilter(
449
+ dataset_identifier=d["datasetIdentifier"],
450
+ granularity=d["granularity"],
451
+ from_shift=d["from"],
452
+ to_shift=d["to"],
453
+ )
454
+
455
+ if filter_type == "absoluteDateFilter":
456
+ return ExecutionContextAbsoluteDateFilter(
457
+ dataset_identifier=d["datasetIdentifier"], from_date=d["from"], to_date=d["to"]
458
+ )
459
+
460
+ raise ValueError(f"Unsupported filter definition type: {d}")
461
+
462
+
463
+ def _dict_to_filters(filters: list[dict]) -> list[ExecutionContextFilter]:
464
+ return [_dict_to_filter(f) for f in filters]
465
+
466
+
467
+ def _dict_to_attributes(attributes: list[dict]) -> list[ExecutionContextAttribute]:
468
+ return [
469
+ ExecutionContextAttribute(
470
+ attribute_title=i["attributeTitle"],
471
+ attribute_identifier=i["attributeIdentifier"],
472
+ label_title=i["labelTitle"],
473
+ label_identifier=i["labelIdentifier"],
474
+ date_granularity=i.get("dateGranularity"),
475
+ sorting=i.get("sorting"),
476
+ )
477
+ for i in attributes
478
+ ]
479
+
480
+
481
+ @dataclass
482
+ class ExecutionContext:
483
+ """
484
+ Execution context of the FlexConnect function
485
+ """
486
+
487
+ execution_type: ExecutionType
488
+ """
489
+ Type of the execution.
490
+ """
491
+
492
+ organization_id: str
493
+ """
494
+ The ID of the organization that the FlexConnect function is executed in.
495
+ """
496
+
497
+ workspace_id: str
498
+ """
499
+ The ID of the workspace that the FlexConnect function is executed in.
500
+ """
501
+
502
+ user_id: str
503
+ """
504
+ The ID of the user that invoked the FlexConnect function.
505
+ """
506
+
507
+ timestamp: Optional[str]
508
+ """
509
+ The timestamp of the execution used as "now" in date filters.
510
+ For example 2020-06-03T10:15:30+01:00.
511
+ """
512
+
513
+ timezone: Optional[str]
514
+ """
515
+ The timezone of the execution.
516
+ """
517
+
518
+ week_start: Optional[str]
519
+ """
520
+ The start of the week. Either "monday" or "sunday".
521
+ """
522
+
523
+ attributes: list[ExecutionContextAttribute]
524
+ """
525
+ All the attributes that are part of the execution request.
526
+ """
527
+
528
+ filters: list[ExecutionContextFilter]
529
+ """
530
+ All the attribute and date filters that are part of the execution request.
531
+ """
532
+
533
+ report_execution_request: Optional[ReportExecutionRequest]
534
+ """
535
+ The report execution request that the FlexConnect function should process.
536
+ Only present if the execution type is "REPORT".
537
+ """
538
+
539
+ label_elements_execution_request: Optional[LabelElementsExecutionRequest]
540
+ """
541
+ The label elements execution request that the FlexConnect function should process.
542
+ Only present if the execution type is "LABEL_ELEMENTS".
543
+ """
544
+
545
+ @staticmethod
546
+ @none_safe
547
+ def from_dict(d: dict) -> "ExecutionContext":
548
+ """
549
+ Create ExecutionContext from a dictionary.
550
+ :param d: the dictionary to parse
551
+ """
552
+ return ExecutionContext(
553
+ execution_type=ExecutionType[d["executionType"]],
554
+ organization_id=d["organizationId"],
555
+ workspace_id=d["workspaceId"],
556
+ user_id=d["userId"],
557
+ timestamp=d.get("timestamp"),
558
+ timezone=d.get("timezone"),
559
+ week_start=d.get("weekStart"),
560
+ report_execution_request=ReportExecutionRequest.from_dict(d.get("reportExecutionRequest")),
561
+ label_elements_execution_request=LabelElementsExecutionRequest.from_dict(
562
+ d.get("labelElementsExecutionRequest")
563
+ ),
564
+ attributes=_dict_to_attributes(d.get("attributes", [])),
565
+ filters=_dict_to_filters(d.get("filters", [])),
566
+ )
567
+
568
+ @staticmethod
569
+ def from_parameters(parameters: dict) -> Optional["ExecutionContext"]:
570
+ """
571
+ Create ExecutionContext from FlexConnect function parameters.
572
+
573
+ :param parameters: the parameters dictionary of the FlexConnect function invocation
574
+ :return: None if the parameters do not contain the execution context, otherwise the execution context
575
+ """
576
+ return ExecutionContext.from_dict(parameters["executionContext"]) if "executionContext" in parameters else None