gooddata-flexconnect 1.60.1.dev1__tar.gz → 1.60.1.dev2__tar.gz

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.
Files changed (54) hide show
  1. {gooddata_flexconnect-1.60.1.dev1 → gooddata_flexconnect-1.60.1.dev2}/PKG-INFO +4 -4
  2. {gooddata_flexconnect-1.60.1.dev1 → gooddata_flexconnect-1.60.1.dev2}/json_schemas/execution-context/execution-context.json +55 -0
  3. {gooddata_flexconnect-1.60.1.dev1 → gooddata_flexconnect-1.60.1.dev2}/pyproject.toml +13 -4
  4. {gooddata_flexconnect-1.60.1.dev1 → gooddata_flexconnect-1.60.1.dev2}/src/gooddata_flexconnect/__init__.py +5 -0
  5. {gooddata_flexconnect-1.60.1.dev1 → gooddata_flexconnect-1.60.1.dev2}/src/gooddata_flexconnect/function/data_source_messages.py +3 -3
  6. {gooddata_flexconnect-1.60.1.dev1 → gooddata_flexconnect-1.60.1.dev2}/src/gooddata_flexconnect/function/execution_context.py +147 -21
  7. {gooddata_flexconnect-1.60.1.dev1 → gooddata_flexconnect-1.60.1.dev2}/src/gooddata_flexconnect/function/flight_methods.py +4 -7
  8. {gooddata_flexconnect-1.60.1.dev1 → gooddata_flexconnect-1.60.1.dev2}/src/gooddata_flexconnect/function/function.py +4 -5
  9. {gooddata_flexconnect-1.60.1.dev1 → gooddata_flexconnect-1.60.1.dev2}/src/gooddata_flexconnect/function/function_invocation.py +2 -2
  10. {gooddata_flexconnect-1.60.1.dev1 → gooddata_flexconnect-1.60.1.dev2}/src/gooddata_flexconnect/function/function_task.py +4 -4
  11. {gooddata_flexconnect-1.60.1.dev1 → gooddata_flexconnect-1.60.1.dev2}/tests/function/conftest.py +6 -0
  12. {gooddata_flexconnect-1.60.1.dev1 → gooddata_flexconnect-1.60.1.dev2}/tests/function/test_flex_fun_execution_context.py +65 -0
  13. {gooddata_flexconnect-1.60.1.dev1 → gooddata_flexconnect-1.60.1.dev2}/tests/function/testing_funs.py +2 -3
  14. {gooddata_flexconnect-1.60.1.dev1 → gooddata_flexconnect-1.60.1.dev2}/tests/json_schemas/test_execution_context_schema.py +106 -0
  15. {gooddata_flexconnect-1.60.1.dev1 → gooddata_flexconnect-1.60.1.dev2}/tests/server/funs/fun1.py +1 -2
  16. {gooddata_flexconnect-1.60.1.dev1 → gooddata_flexconnect-1.60.1.dev2}/tests/server/funs/fun2.py +2 -3
  17. {gooddata_flexconnect-1.60.1.dev1 → gooddata_flexconnect-1.60.1.dev2}/tests/server/funs/fun3.py +2 -3
  18. {gooddata_flexconnect-1.60.1.dev1 → gooddata_flexconnect-1.60.1.dev2}/tests/server/funs/fun4.py +2 -3
  19. {gooddata_flexconnect-1.60.1.dev1 → gooddata_flexconnect-1.60.1.dev2}/tox.ini +1 -1
  20. {gooddata_flexconnect-1.60.1.dev1 → gooddata_flexconnect-1.60.1.dev2}/.gitignore +0 -0
  21. {gooddata_flexconnect-1.60.1.dev1 → gooddata_flexconnect-1.60.1.dev2}/.readthedocs.yaml +0 -0
  22. {gooddata_flexconnect-1.60.1.dev1 → gooddata_flexconnect-1.60.1.dev2}/LICENSE.txt +0 -0
  23. {gooddata_flexconnect-1.60.1.dev1 → gooddata_flexconnect-1.60.1.dev2}/Makefile +0 -0
  24. {gooddata_flexconnect-1.60.1.dev1 → gooddata_flexconnect-1.60.1.dev2}/README.md +0 -0
  25. {gooddata_flexconnect-1.60.1.dev1 → gooddata_flexconnect-1.60.1.dev2}/json_schemas/execution-context/attribute.json +0 -0
  26. {gooddata_flexconnect-1.60.1.dev1 → gooddata_flexconnect-1.60.1.dev2}/json_schemas/execution-context/date-granularity.json +0 -0
  27. {gooddata_flexconnect-1.60.1.dev1 → gooddata_flexconnect-1.60.1.dev2}/json_schemas/execution-context/filter.json +0 -0
  28. {gooddata_flexconnect-1.60.1.dev1 → gooddata_flexconnect-1.60.1.dev2}/json_schemas/execution-context/label-elements/depends-on-date-filter.json +0 -0
  29. {gooddata_flexconnect-1.60.1.dev1 → gooddata_flexconnect-1.60.1.dev2}/json_schemas/execution-context/label-elements/depends-on.json +0 -0
  30. {gooddata_flexconnect-1.60.1.dev1 → gooddata_flexconnect-1.60.1.dev2}/json_schemas/execution-context/label-elements/execution-request.json +0 -0
  31. {gooddata_flexconnect-1.60.1.dev1 → gooddata_flexconnect-1.60.1.dev2}/src/gooddata_flexconnect/function/__init__.py +0 -0
  32. {gooddata_flexconnect-1.60.1.dev1 → gooddata_flexconnect-1.60.1.dev2}/src/gooddata_flexconnect/function/function_registry.py +0 -0
  33. {gooddata_flexconnect-1.60.1.dev1 → gooddata_flexconnect-1.60.1.dev2}/src/gooddata_flexconnect/py.typed +0 -0
  34. {gooddata_flexconnect-1.60.1.dev1 → gooddata_flexconnect-1.60.1.dev2}/tests/__init__.py +0 -0
  35. {gooddata_flexconnect-1.60.1.dev1 → gooddata_flexconnect-1.60.1.dev2}/tests/assert_error_info.py +0 -0
  36. {gooddata_flexconnect-1.60.1.dev1 → gooddata_flexconnect-1.60.1.dev2}/tests/function/__init__.py +0 -0
  37. {gooddata_flexconnect-1.60.1.dev1 → gooddata_flexconnect-1.60.1.dev2}/tests/function/test_data_source_messages.py +0 -0
  38. {gooddata_flexconnect-1.60.1.dev1 → gooddata_flexconnect-1.60.1.dev2}/tests/function/test_registry.py +0 -0
  39. {gooddata_flexconnect-1.60.1.dev1 → gooddata_flexconnect-1.60.1.dev2}/tests/json_schemas/__init__.py +0 -0
  40. {gooddata_flexconnect-1.60.1.dev1 → gooddata_flexconnect-1.60.1.dev2}/tests/json_schemas/conftest.py +0 -0
  41. {gooddata_flexconnect-1.60.1.dev1 → gooddata_flexconnect-1.60.1.dev2}/tests/json_schemas/test_attribute_schema.py +0 -0
  42. {gooddata_flexconnect-1.60.1.dev1 → gooddata_flexconnect-1.60.1.dev2}/tests/json_schemas/test_depends_on_date_filter_schema.py +0 -0
  43. {gooddata_flexconnect-1.60.1.dev1 → gooddata_flexconnect-1.60.1.dev2}/tests/json_schemas/test_depends_on_schema.py +0 -0
  44. {gooddata_flexconnect-1.60.1.dev1 → gooddata_flexconnect-1.60.1.dev2}/tests/json_schemas/test_filter_schema.py +0 -0
  45. {gooddata_flexconnect-1.60.1.dev1 → gooddata_flexconnect-1.60.1.dev2}/tests/json_schemas/test_label_elements_execution_request_schema.py +0 -0
  46. {gooddata_flexconnect-1.60.1.dev1 → gooddata_flexconnect-1.60.1.dev2}/tests/server/__init__.py +0 -0
  47. {gooddata_flexconnect-1.60.1.dev1 → gooddata_flexconnect-1.60.1.dev2}/tests/server/conftest.py +0 -0
  48. {gooddata_flexconnect-1.60.1.dev1 → gooddata_flexconnect-1.60.1.dev2}/tests/server/funs/__init__.py +0 -0
  49. {gooddata_flexconnect-1.60.1.dev1 → gooddata_flexconnect-1.60.1.dev2}/tests/server/test_flexconnect_server.py +0 -0
  50. {gooddata_flexconnect-1.60.1.dev1 → gooddata_flexconnect-1.60.1.dev2}/tests/server/tls/ca-cert.pem +0 -0
  51. {gooddata_flexconnect-1.60.1.dev1 → gooddata_flexconnect-1.60.1.dev2}/tests/server/tls/client-cert.pem +0 -0
  52. {gooddata_flexconnect-1.60.1.dev1 → gooddata_flexconnect-1.60.1.dev2}/tests/server/tls/client-key.pem +0 -0
  53. {gooddata_flexconnect-1.60.1.dev1 → gooddata_flexconnect-1.60.1.dev2}/tests/server/tls/server-cert.pem +0 -0
  54. {gooddata_flexconnect-1.60.1.dev1 → gooddata_flexconnect-1.60.1.dev2}/tests/server/tls/server-key.pem +0 -0
@@ -1,8 +1,8 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: gooddata-flexconnect
3
- Version: 1.60.1.dev1
3
+ Version: 1.60.1.dev2
4
4
  Summary: Build your own data source for GoodData Cloud and GoodData Cloud Native.
5
- Project-URL: Documentation, https://gooddata-flexconnect.readthedocs.io/en/v1.60.1.dev1
5
+ Project-URL: Documentation, https://gooddata-flexconnect.readthedocs.io/en/v1.60.1.dev2
6
6
  Project-URL: Source, https://github.com/gooddata/gooddata-python-sdk
7
7
  Author-email: GoodData <support@gooddata.com>
8
8
  License-Expression: MIT
@@ -21,8 +21,8 @@ Classifier: Topic :: Software Development
21
21
  Classifier: Typing :: Typed
22
22
  Requires-Python: >=3.10
23
23
  Requires-Dist: dynaconf<4.0.0,>=3.1.11
24
- Requires-Dist: gooddata-flight-server~=1.60.1.dev1
25
- Requires-Dist: gooddata-sdk~=1.60.1.dev1
24
+ Requires-Dist: gooddata-flight-server~=1.60.1.dev2
25
+ Requires-Dist: gooddata-sdk~=1.60.1.dev2
26
26
  Requires-Dist: orjson<4.0.0,>=3.9.15
27
27
  Requires-Dist: pyarrow>=16.1.0
28
28
  Requires-Dist: structlog<25.0.0,>=24.0.0
@@ -52,6 +52,61 @@
52
52
  "items": {
53
53
  "$ref": "filter.json"
54
54
  }
55
+ },
56
+ "executionInitiator": {
57
+ "type": "object",
58
+ "description": "Information about what triggered this execution.",
59
+ "required": ["type"],
60
+ "discriminator": {
61
+ "propertyName": "type",
62
+ "mapping": {
63
+ "display": "#/properties/executionInitiator/oneOf/0",
64
+ "adhocExport": "#/properties/executionInitiator/oneOf/1",
65
+ "automation": "#/properties/executionInitiator/oneOf/2",
66
+ "alert": "#/properties/executionInitiator/oneOf/3"
67
+ }
68
+ },
69
+ "oneOf": [
70
+ {
71
+ "description": "Execution triggered by displaying data in the UI.",
72
+ "properties": {
73
+ "type": { "const": "display" },
74
+ "dashboardId": { "type": "string" },
75
+ "visualizationId": { "type": "string" },
76
+ "widgetId": { "type": "string" }
77
+ },
78
+ "additionalProperties": false
79
+ },
80
+ {
81
+ "description": "Execution triggered by an ad-hoc export from the UI.",
82
+ "properties": {
83
+ "type": { "const": "adhocExport" },
84
+ "dashboardId": { "type": "string" },
85
+ "visualizationId": { "type": "string" },
86
+ "widgetId": { "type": "string" },
87
+ "exportType": { "type": "string" }
88
+ },
89
+ "additionalProperties": false
90
+ },
91
+ {
92
+ "description": "Execution triggered by an automation.",
93
+ "properties": {
94
+ "type": { "const": "automation" },
95
+ "automationId": { "type": "string" }
96
+ },
97
+ "additionalProperties": false
98
+ },
99
+ {
100
+ "description": "Execution triggered by an alert evaluation.",
101
+ "properties": {
102
+ "type": { "const": "alert" },
103
+ "dashboardId": { "type": "string" },
104
+ "visualizationId": { "type": "string" },
105
+ "widgetId": { "type": "string" }
106
+ },
107
+ "additionalProperties": false
108
+ }
109
+ ]
55
110
  }
56
111
  },
57
112
  "allOf": [
@@ -1,7 +1,7 @@
1
1
  # (C) 2025 GoodData Corporation
2
2
  [project]
3
3
  name = "gooddata-flexconnect"
4
- version = "1.60.1.dev1"
4
+ version = "1.60.1.dev2"
5
5
  description = "Build your own data source for GoodData Cloud and GoodData Cloud Native."
6
6
  readme = "README.md"
7
7
  license = "MIT"
@@ -29,8 +29,8 @@ keywords = [
29
29
  requires-python = ">=3.10"
30
30
  dependencies = [
31
31
  "dynaconf>=3.1.11,<4.0.0",
32
- "gooddata-flight-server~=1.60.1.dev1",
33
- "gooddata-sdk~=1.60.1.dev1",
32
+ "gooddata-flight-server~=1.60.1.dev2",
33
+ "gooddata-sdk~=1.60.1.dev2",
34
34
  "orjson>=3.9.15,<4.0.0",
35
35
  "pyarrow>=16.1.0",
36
36
  "structlog>=24.0.0,<25.0.0",
@@ -50,7 +50,7 @@ classifiers = [
50
50
  ]
51
51
 
52
52
  [project.urls]
53
- Documentation = "https://gooddata-flexconnect.readthedocs.io/en/v1.60.1.dev1"
53
+ Documentation = "https://gooddata-flexconnect.readthedocs.io/en/v1.60.1.dev2"
54
54
  Source = "https://github.com/gooddata/gooddata-python-sdk"
55
55
 
56
56
  [dependency-groups]
@@ -67,6 +67,15 @@ allowed-unresolved-imports = ["jsonschema.**", "referencing"]
67
67
  [tool.hatch.build.targets.wheel]
68
68
  packages = ["src/gooddata_flexconnect"]
69
69
 
70
+ [tool.coverage.run]
71
+ source = ["gooddata_flexconnect"]
72
+
73
+ [tool.coverage.paths]
74
+ source = [
75
+ "src/gooddata_flexconnect",
76
+ "**/site-packages/gooddata_flexconnect",
77
+ ]
78
+
70
79
  [build-system]
71
80
  requires = ["hatchling"]
72
81
  build-backend = "hatchling.build"
@@ -14,6 +14,11 @@ from gooddata_flexconnect.function.execution_context import (
14
14
  ExecutionContextNegativeAttributeFilter,
15
15
  ExecutionContextPositiveAttributeFilter,
16
16
  ExecutionContextRelativeDateFilter,
17
+ ExecutionInitiator,
18
+ ExecutionInitiatorAdHocExport,
19
+ ExecutionInitiatorAlert,
20
+ ExecutionInitiatorAutomation,
21
+ ExecutionInitiatorDisplay,
17
22
  ExecutionRequest,
18
23
  ExecutionType,
19
24
  LabelElementsExecutionRequest,
@@ -1,7 +1,7 @@
1
1
  # (C) 2025 GoodData Corporation
2
2
  from collections.abc import Iterable
3
3
  from dataclasses import dataclass
4
- from typing import Any, Optional
4
+ from typing import Any
5
5
 
6
6
  import orjson
7
7
  import pyarrow
@@ -33,7 +33,7 @@ class DataSourceMessage:
33
33
  Type of the message, currently free-form, we might define some enum for these in the future.
34
34
  """
35
35
 
36
- data: Optional[Any] = None
36
+ data: Any | None = None
37
37
  """
38
38
  Optional message-specific data. This can be anything that can be JSON-serialized.
39
39
  Try to keep this as small as possible: the backend has a quite strict size limit on the messages.
@@ -47,7 +47,7 @@ class DataSourceMessage:
47
47
 
48
48
 
49
49
  def add_data_source_messages_metadata(
50
- data_source_messages: Iterable[DataSourceMessage], original_metadata: Optional[dict] = None
50
+ data_source_messages: Iterable[DataSourceMessage], original_metadata: dict | None = None
51
51
  ) -> dict[bytes, bytes]:
52
52
  """
53
53
  Given a list of DataSourceMessages, creates a PyArrow-compatible metadata dictionary.
@@ -21,13 +21,13 @@ TInput = TypeVar("TInput")
21
21
  TResult = TypeVar("TResult")
22
22
 
23
23
 
24
- def none_safe(func: Callable[[TInput], TResult]) -> Callable[[Optional[TInput]], Optional[TResult]]:
24
+ def none_safe(func: Callable[[TInput], TResult]) -> Callable[[TInput | None], TResult | None]:
25
25
  """
26
26
  Decorator that makes the unary function safe for None input.
27
27
  If the only argument is None, the function returns None.
28
28
  """
29
29
 
30
- def wrapper(arg: Optional[TInput]) -> Optional[TResult]:
30
+ def wrapper(arg: TInput | None) -> TResult | None:
31
31
  if arg is None:
32
32
  return None
33
33
  return func(arg)
@@ -114,18 +114,18 @@ class ExecutionContextAttribute:
114
114
  Title of the particular label used.
115
115
  """
116
116
 
117
- date_granularity: Optional[str]
117
+ date_granularity: str | None
118
118
  """
119
119
  Date granularity of the attribute if it is a date attribute.
120
120
  """
121
121
 
122
- sorting: Optional[ExecutionContextAttributeSorting]
122
+ sorting: ExecutionContextAttributeSorting | None
123
123
  """
124
124
  Sorting of the attribute. If not present, the attribute is not sorted.
125
125
  """
126
126
 
127
127
  @staticmethod
128
- def from_dict(d: Optional[dict]) -> Optional["ExecutionContextAttribute"]:
128
+ def from_dict(d: dict | None) -> Optional["ExecutionContextAttribute"]:
129
129
  """
130
130
  Create ExecutionContextAttribute from a dictionary.
131
131
  :param d: the dictionary to parse
@@ -153,7 +153,7 @@ class ExecutionContextPositiveAttributeFilter:
153
153
  Identifier of the label used.
154
154
  """
155
155
 
156
- values: list[Optional[str]]
156
+ values: list[str | None]
157
157
  """
158
158
  Values of the filter.
159
159
  """
@@ -170,7 +170,7 @@ class ExecutionContextNegativeAttributeFilter:
170
170
  Identifier of the label used.
171
171
  """
172
172
 
173
- values: list[Optional[str]]
173
+ values: list[str | None]
174
174
  """
175
175
  Values of the filter.
176
176
  """
@@ -366,17 +366,17 @@ class LabelElementsExecutionRequest:
366
366
  The label to get the elements for.
367
367
  """
368
368
 
369
- offset: Optional[int]
369
+ offset: int | None
370
370
  """
371
371
  The number of elements to skip before returning.
372
372
  """
373
373
 
374
- limit: Optional[int]
374
+ limit: int | None
375
375
  """
376
376
  The maximum number of elements to return.
377
377
  """
378
378
 
379
- exclude_primary_label: Optional[bool]
379
+ exclude_primary_label: bool | None
380
380
  """
381
381
  Excludes items from the result that differ only by primary label
382
382
 
@@ -384,33 +384,33 @@ class LabelElementsExecutionRequest:
384
384
  * true - return items with distinct requested label
385
385
  """
386
386
 
387
- exact_filter: Optional[list[str]]
387
+ exact_filter: list[str] | None
388
388
  """
389
389
  Exact values to filter the elements by.
390
390
  """
391
391
 
392
- pattern_filter: Optional[str]
392
+ pattern_filter: str | None
393
393
  """
394
394
  Filter the elements by a pattern. The pattern is matched against the element values in a case-insensitive way.
395
395
  """
396
396
 
397
- complement_filter: Optional[bool]
397
+ complement_filter: bool | None
398
398
  """
399
399
  Whether to invert the effects of exact_filter amd pattern_filter.
400
400
  """
401
401
 
402
- depends_on: Optional[list[DependsOn]]
402
+ depends_on: list[DependsOn] | None
403
403
  """
404
404
  Other labels or date filters that should be used to limit the elements.
405
405
  """
406
406
 
407
- filter_by: Optional[CatalogFilterBy]
407
+ filter_by: CatalogFilterBy | None
408
408
  """
409
409
  Which label is used for filtering - primary or requested.
410
410
  If omitted the server will use the default value of "REQUESTED".
411
411
  """
412
412
 
413
- validate_by: Optional[list[CatalogValidateByItem]]
413
+ validate_by: list[CatalogValidateByItem] | None
414
414
  """
415
415
  Other metrics, attributes, labels or facts used to validate the elements.
416
416
  """
@@ -436,6 +436,126 @@ class LabelElementsExecutionRequest:
436
436
  )
437
437
 
438
438
 
439
+ @dataclass
440
+ class ExecutionInitiatorDisplay:
441
+ """
442
+ Information about an execution being run in order to display the data in the UI.
443
+ """
444
+
445
+ dashboard_id: str | None
446
+ """
447
+ The id of the dashboard the execution was run as a part of.
448
+ """
449
+
450
+ visualization_id: str | None
451
+ """
452
+ The id of the visualization the execution was run as a part of.
453
+ """
454
+
455
+ widget_id: str | None
456
+ """
457
+ The id of the widget the execution was run as a part of.
458
+ """
459
+
460
+
461
+ @dataclass
462
+ class ExecutionInitiatorAdHocExport:
463
+ """
464
+ Information about an execution being run in order to export the data by a user in the UI.
465
+ """
466
+
467
+ dashboard_id: str | None
468
+ """
469
+ The id of the dashboard the execution was run as a part of.
470
+ """
471
+
472
+ visualization_id: str | None
473
+ """
474
+ The id of the visualization the execution was run as a part of.
475
+ """
476
+
477
+ widget_id: str | None
478
+ """
479
+ The id of the widget the execution was run as a part of.
480
+ """
481
+
482
+ export_type: str | None
483
+ """
484
+ The type of the exported file (CSV, RAW_CSV, etc.).
485
+ """
486
+
487
+
488
+ @dataclass
489
+ class ExecutionInitiatorAutomation:
490
+ """
491
+ Information about an execution being run because of an automation.
492
+ """
493
+
494
+ automation_id: str | None
495
+ """
496
+ The id of the automation initiating this execution.
497
+ """
498
+
499
+
500
+ @dataclass
501
+ class ExecutionInitiatorAlert:
502
+ """
503
+ Information about an execution being run in order to evaluate an alert.
504
+ """
505
+
506
+ dashboard_id: str | None
507
+ """
508
+ The id of the dashboard the execution was run as a part of.
509
+ """
510
+
511
+ visualization_id: str | None
512
+ """
513
+ The id of the visualization the execution was run as a part of.
514
+ """
515
+
516
+ widget_id: str | None
517
+ """
518
+ The id of the widget the execution was run as a part of.
519
+ """
520
+
521
+
522
+ ExecutionInitiator: TypeAlias = Union[
523
+ ExecutionInitiatorDisplay,
524
+ ExecutionInitiatorAdHocExport,
525
+ ExecutionInitiatorAutomation,
526
+ ExecutionInitiatorAlert,
527
+ ]
528
+
529
+
530
+ @none_safe
531
+ def _dict_to_execution_initiator(d: dict) -> ExecutionInitiator:
532
+ initiator_type = d.get("type")
533
+ if initiator_type == "display":
534
+ return ExecutionInitiatorDisplay(
535
+ dashboard_id=d.get("dashboardId"),
536
+ visualization_id=d.get("visualizationId"),
537
+ widget_id=d.get("widgetId"),
538
+ )
539
+ if initiator_type == "adhocExport":
540
+ return ExecutionInitiatorAdHocExport(
541
+ export_type=d.get("exportType"),
542
+ dashboard_id=d.get("dashboardId"),
543
+ visualization_id=d.get("visualizationId"),
544
+ widget_id=d.get("widgetId"),
545
+ )
546
+ if initiator_type == "automation":
547
+ return ExecutionInitiatorAutomation(
548
+ automation_id=d.get("automationId"),
549
+ )
550
+ if initiator_type == "alert":
551
+ return ExecutionInitiatorAlert(
552
+ dashboard_id=d.get("dashboardId"),
553
+ visualization_id=d.get("visualizationId"),
554
+ widget_id=d.get("widgetId"),
555
+ )
556
+ raise ValueError(f"Unsupported execution initiator type: {initiator_type}")
557
+
558
+
439
559
  def _dict_to_filter(d: dict) -> ExecutionContextFilter:
440
560
  filter_type = d.get("filterType")
441
561
  if filter_type == "positiveAttributeFilter":
@@ -504,18 +624,18 @@ class ExecutionContext:
504
624
  The ID of the user that invoked the FlexConnect function.
505
625
  """
506
626
 
507
- timestamp: Optional[str]
627
+ timestamp: str | None
508
628
  """
509
629
  The timestamp of the execution used as "now" in date filters.
510
630
  For example 2020-06-03T10:15:30+01:00.
511
631
  """
512
632
 
513
- timezone: Optional[str]
633
+ timezone: str | None
514
634
  """
515
635
  The timezone of the execution.
516
636
  """
517
637
 
518
- week_start: Optional[str]
638
+ week_start: str | None
519
639
  """
520
640
  The start of the week. Either "monday" or "sunday".
521
641
  """
@@ -530,18 +650,23 @@ class ExecutionContext:
530
650
  All the attribute and date filters that are part of the execution request.
531
651
  """
532
652
 
533
- report_execution_request: Optional[ReportExecutionRequest]
653
+ report_execution_request: ReportExecutionRequest | None
534
654
  """
535
655
  The report execution request that the FlexConnect function should process.
536
656
  Only present if the execution type is "REPORT".
537
657
  """
538
658
 
539
- label_elements_execution_request: Optional[LabelElementsExecutionRequest]
659
+ label_elements_execution_request: LabelElementsExecutionRequest | None
540
660
  """
541
661
  The label elements execution request that the FlexConnect function should process.
542
662
  Only present if the execution type is "LABEL_ELEMENTS".
543
663
  """
544
664
 
665
+ execution_initiator: ExecutionInitiator | None
666
+ """
667
+ Information about what triggered this execution (e.g. display, export, automation, alert).
668
+ """
669
+
545
670
  @staticmethod
546
671
  @none_safe
547
672
  def from_dict(d: dict) -> "ExecutionContext":
@@ -563,6 +688,7 @@ class ExecutionContext:
563
688
  ),
564
689
  attributes=_dict_to_attributes(d.get("attributes", [])),
565
690
  filters=_dict_to_filters(d.get("filters", [])),
691
+ execution_initiator=_dict_to_execution_initiator(d.get("executionInitiator")),
566
692
  )
567
693
 
568
694
  @staticmethod
@@ -1,7 +1,6 @@
1
1
  # (C) 2024 GoodData Corporation
2
2
  import time
3
3
  from collections.abc import Generator
4
- from typing import Optional
5
4
 
6
5
  import orjson
7
6
  import pyarrow.flight
@@ -59,7 +58,7 @@ class _FlexConnectServerMethods(FlightServerMethods):
59
58
  self._poll_interval = poll_interval_ms / 1000
60
59
 
61
60
  @staticmethod
62
- def _create_descriptor(fun_name: str, metadata: Optional[dict]) -> pyarrow.flight.FlightDescriptor:
61
+ def _create_descriptor(fun_name: str, metadata: dict | None) -> pyarrow.flight.FlightDescriptor:
63
62
  cmd = {
64
63
  "functionName": fun_name,
65
64
  "metadata": metadata,
@@ -97,9 +96,7 @@ class _FlexConnectServerMethods(FlightServerMethods):
97
96
  cmd=submit_invocation.command,
98
97
  )
99
98
 
100
- def _prepare_flight_info(
101
- self, task_id: str, task_result: Optional[TaskExecutionResult]
102
- ) -> pyarrow.flight.FlightInfo:
99
+ def _prepare_flight_info(self, task_id: str, task_result: TaskExecutionResult | None) -> pyarrow.flight.FlightInfo:
103
100
  if task_result is None:
104
101
  raise ErrorInfo.for_reason(
105
102
  ErrorCode.BAD_ARGUMENT, f"Task with id '{task_id}' does not exist."
@@ -142,7 +139,7 @@ class _FlexConnectServerMethods(FlightServerMethods):
142
139
  structlog.contextvars.bind_contextvars(peer=context.peer())
143
140
  invocation = extract_submit_invocation_from_descriptor(descriptor)
144
141
 
145
- task: Optional[FlexConnectFunctionTask] = None
142
+ task: FlexConnectFunctionTask | None = None
146
143
 
147
144
  try:
148
145
  task = self._prepare_task(context, invocation)
@@ -192,7 +189,7 @@ class _FlexConnectServerMethods(FlightServerMethods):
192
189
  invocation = extract_pollable_invocation_from_descriptor(descriptor)
193
190
 
194
191
  task_id: str
195
- fun_name: Optional[str] = None
192
+ fun_name: str | None = None
196
193
 
197
194
  if isinstance(invocation, CancelInvocation):
198
195
  # cancel the given task and raise cancellation exception
@@ -1,6 +1,5 @@
1
1
  # (C) 2024 GoodData Corporation
2
2
  import abc
3
- from typing import Optional
4
3
 
5
4
  import pyarrow
6
5
  from gooddata_flight_server import ArrowData, ServerContext
@@ -37,17 +36,17 @@ class FlexConnectFunction(abc.ABC):
37
36
  for every call using the `create` method.
38
37
  """
39
38
 
40
- Name: Optional[str] = None
39
+ Name: str | None = None
41
40
  """
42
41
  Function MUST define a unique name
43
42
  """
44
43
 
45
- Schema: Optional[pyarrow.Schema] = None
44
+ Schema: pyarrow.Schema | None = None
46
45
  """
47
46
  Function MUST define schema describing its data.
48
47
  """
49
48
 
50
- Metadata: Optional[dict] = None
49
+ Metadata: dict | None = None
51
50
  """
52
51
  Function MAY provide additional metadata about themselves. These then
53
52
  influence how the function is used by and called from GoodData Cloud & FlexQuery.
@@ -79,7 +78,7 @@ class FlexConnectFunction(abc.ABC):
79
78
  def call(
80
79
  self,
81
80
  parameters: dict,
82
- columns: Optional[tuple[str, ...]],
81
+ columns: tuple[str, ...] | None,
83
82
  headers: dict[str, list[str]],
84
83
  ) -> ArrowData:
85
84
  """
@@ -1,6 +1,6 @@
1
1
  # (C) 2025 GoodData Corporation
2
2
  from dataclasses import dataclass
3
- from typing import Optional, Union
3
+ from typing import Union
4
4
 
5
5
  import orjson
6
6
  import pyarrow.flight
@@ -46,7 +46,7 @@ class SubmitInvocation:
46
46
  Parameters to pass to the FlexConnect function.
47
47
  """
48
48
 
49
- columns: Optional[tuple[str, ...]]
49
+ columns: tuple[str, ...] | None
50
50
  """
51
51
  Columns to get from the FlexConnect function result.
52
52
  This may be used for column trimming by the function: the function must return at least those columns.
@@ -1,5 +1,5 @@
1
1
  # (C) 2024 GoodData Corporation
2
- from typing import Optional, Union
2
+ from typing import Union
3
3
 
4
4
  import structlog
5
5
  from gooddata_flight_server import FlightDataTaskResult, Task, TaskError, TaskResult
@@ -16,11 +16,11 @@ class FlexConnectFunctionTask(Task):
16
16
  self,
17
17
  fun: FlexConnectFunction,
18
18
  parameters: dict,
19
- columns: Optional[tuple[str, ...]],
19
+ columns: tuple[str, ...] | None,
20
20
  headers: dict[str, list[str]],
21
21
  cmd: bytes,
22
22
  cancellable: bool = True,
23
- task_id: Optional[str] = None,
23
+ task_id: str | None = None,
24
24
  ):
25
25
  super().__init__(cmd, cancellable, task_id)
26
26
 
@@ -32,7 +32,7 @@ class FlexConnectFunctionTask(Task):
32
32
  _LOGGER.info("flexconnect_task_created", fun=fun.Name, task_id=self._task_id)
33
33
 
34
34
  @property
35
- def fun_name(self) -> Optional[str]:
35
+ def fun_name(self) -> str | None:
36
36
  return self._fun.Name
37
37
 
38
38
  def run(self) -> Union[TaskResult, TaskError]:
@@ -88,6 +88,12 @@ def sample_report_execution_context_dict():
88
88
  }
89
89
  ],
90
90
  "filters": [{"filterType": "negativeAttributeFilter", "labelIdentifier": "attribute1", "values": ["id1"]}],
91
+ "executionInitiator": {
92
+ "type": "display",
93
+ "dashboardId": "b2f2d436-9831-4fe0-81df-8c59fd33242b",
94
+ "visualizationId": "bf21d8ec-742c-48d7-8100-80663b43622b",
95
+ "widgetId": "453844a7-4aa8-4456-be23-ac62b9b3b98a",
96
+ },
91
97
  }
92
98
 
93
99
 
@@ -1,9 +1,15 @@
1
1
  # (C) 2024 GoodData Corporation
2
+ import pytest
2
3
  from gooddata_flexconnect.function.execution_context import (
3
4
  ExecutionContext,
4
5
  ExecutionContextAttribute,
5
6
  ExecutionContextNegativeAttributeFilter,
7
+ ExecutionInitiatorAdHocExport,
8
+ ExecutionInitiatorAlert,
9
+ ExecutionInitiatorAutomation,
10
+ ExecutionInitiatorDisplay,
6
11
  ExecutionType,
12
+ _dict_to_execution_initiator,
7
13
  )
8
14
  from gooddata_sdk import (
9
15
  Attribute,
@@ -35,6 +41,12 @@ def test_report_execution_context_deser(sample_report_execution_context_dict):
35
41
  assert isinstance(deserialized.attributes[0], ExecutionContextAttribute)
36
42
  assert isinstance(deserialized.filters[0], ExecutionContextNegativeAttributeFilter)
37
43
 
44
+ assert deserialized.execution_initiator is not None
45
+ assert isinstance(deserialized.execution_initiator, ExecutionInitiatorDisplay)
46
+ assert deserialized.execution_initiator.dashboard_id == "b2f2d436-9831-4fe0-81df-8c59fd33242b"
47
+ assert deserialized.execution_initiator.visualization_id == "bf21d8ec-742c-48d7-8100-80663b43622b"
48
+ assert deserialized.execution_initiator.widget_id == "453844a7-4aa8-4456-be23-ac62b9b3b98a"
49
+
38
50
 
39
51
  def test_label_elements_execution_context_deser(sample_label_execution_context_dict):
40
52
  """
@@ -58,3 +70,56 @@ def test_label_elements_execution_context_deser(sample_label_execution_context_d
58
70
 
59
71
  assert isinstance(deserialized.attributes[0], ExecutionContextAttribute)
60
72
  assert isinstance(deserialized.filters[0], ExecutionContextNegativeAttributeFilter)
73
+
74
+ assert deserialized.execution_initiator is None
75
+
76
+
77
+ @pytest.mark.parametrize(
78
+ "initiator_dict, expected_type",
79
+ [
80
+ (
81
+ {
82
+ "type": "display",
83
+ "dashboardId": "d1",
84
+ "visualizationId": "v1",
85
+ "widgetId": "w1",
86
+ },
87
+ ExecutionInitiatorDisplay,
88
+ ),
89
+ (
90
+ {
91
+ "type": "adhocExport",
92
+ "dashboardId": "d1",
93
+ "visualizationId": "v1",
94
+ "widgetId": "w1",
95
+ "exportType": "CSV",
96
+ },
97
+ ExecutionInitiatorAdHocExport,
98
+ ),
99
+ (
100
+ {
101
+ "type": "automation",
102
+ "automationId": "a1",
103
+ },
104
+ ExecutionInitiatorAutomation,
105
+ ),
106
+ (
107
+ {
108
+ "type": "alert",
109
+ "dashboardId": "d1",
110
+ "visualizationId": "v1",
111
+ "widgetId": "w1",
112
+ },
113
+ ExecutionInitiatorAlert,
114
+ ),
115
+ ],
116
+ ids=["display", "adhocExport", "automation", "alert"],
117
+ )
118
+ def test_execution_initiator_deser(initiator_dict, expected_type):
119
+ result = _dict_to_execution_initiator(initiator_dict)
120
+ assert isinstance(result, expected_type)
121
+
122
+
123
+ def test_execution_initiator_unknown_type():
124
+ with pytest.raises(ValueError, match="Unsupported execution initiator type"):
125
+ _dict_to_execution_initiator({"type": "unknown"})
@@ -1,5 +1,4 @@
1
1
  # (C) 2024 GoodData Corporation
2
- from typing import Optional
3
2
 
4
3
  import pyarrow
5
4
  from gooddata_flexconnect.function.function import FlexConnectFunction
@@ -13,7 +12,7 @@ class Fun1(FlexConnectFunction):
13
12
  def call(
14
13
  self,
15
14
  parameters: dict,
16
- columns: Optional[tuple[str, ...]],
15
+ columns: tuple[str, ...] | None,
17
16
  headers: dict[str, list[str]],
18
17
  ) -> ArrowData:
19
18
  pass
@@ -28,7 +27,7 @@ class Fun2(FlexConnectFunction):
28
27
  def call(
29
28
  self,
30
29
  parameters: dict,
31
- columns: Optional[tuple[str, ...]],
30
+ columns: tuple[str, ...] | None,
32
31
  headers: dict[str, list[str]],
33
32
  ) -> ArrowData:
34
33
  pass
@@ -16,6 +16,69 @@ from jsonschema.exceptions import ValidationError
16
16
  "filters": [],
17
17
  "reportExecutionRequest": {},
18
18
  },
19
+ # REPORT with executionInitiator display
20
+ {
21
+ "executionType": "REPORT",
22
+ "organizationId": "org1",
23
+ "workspaceId": "ws1",
24
+ "userId": "user1",
25
+ "attributes": [],
26
+ "filters": [],
27
+ "reportExecutionRequest": {},
28
+ "executionInitiator": {
29
+ "type": "display",
30
+ "dashboardId": "d1",
31
+ "visualizationId": "v1",
32
+ "widgetId": "w1",
33
+ },
34
+ },
35
+ # REPORT with executionInitiator adhocExport
36
+ {
37
+ "executionType": "REPORT",
38
+ "organizationId": "org1",
39
+ "workspaceId": "ws1",
40
+ "userId": "user1",
41
+ "attributes": [],
42
+ "filters": [],
43
+ "reportExecutionRequest": {},
44
+ "executionInitiator": {
45
+ "type": "adhocExport",
46
+ "dashboardId": "d1",
47
+ "visualizationId": "v1",
48
+ "widgetId": "w1",
49
+ "exportType": "CSV",
50
+ },
51
+ },
52
+ # REPORT with executionInitiator automation
53
+ {
54
+ "executionType": "REPORT",
55
+ "organizationId": "org1",
56
+ "workspaceId": "ws1",
57
+ "userId": "user1",
58
+ "attributes": [],
59
+ "filters": [],
60
+ "reportExecutionRequest": {},
61
+ "executionInitiator": {
62
+ "type": "automation",
63
+ "automationId": "a1",
64
+ },
65
+ },
66
+ # REPORT with executionInitiator alert
67
+ {
68
+ "executionType": "REPORT",
69
+ "organizationId": "org1",
70
+ "workspaceId": "ws1",
71
+ "userId": "user1",
72
+ "attributes": [],
73
+ "filters": [],
74
+ "reportExecutionRequest": {},
75
+ "executionInitiator": {
76
+ "type": "alert",
77
+ "dashboardId": "d1",
78
+ "visualizationId": "v1",
79
+ "widgetId": "w1",
80
+ },
81
+ },
19
82
  # minimal valid schema for LABEL_ELEMENTS execution type
20
83
  {
21
84
  "executionType": "LABEL_ELEMENTS",
@@ -110,6 +173,49 @@ def test_valid_execution_context_schema(value, get_validator):
110
173
  "filters": [],
111
174
  "labelElementsExecutionRequest": {"label": "label2"},
112
175
  },
176
+ # invalid executionInitiator type
177
+ {
178
+ "executionType": "REPORT",
179
+ "organizationId": "org1",
180
+ "workspaceId": "ws1",
181
+ "userId": "user1",
182
+ "attributes": [],
183
+ "filters": [],
184
+ "reportExecutionRequest": {},
185
+ "executionInitiator": {
186
+ "type": "INVALID",
187
+ },
188
+ },
189
+ # automation initiator must not contain dashboardId
190
+ {
191
+ "executionType": "REPORT",
192
+ "organizationId": "org1",
193
+ "workspaceId": "ws1",
194
+ "userId": "user1",
195
+ "attributes": [],
196
+ "filters": [],
197
+ "reportExecutionRequest": {},
198
+ "executionInitiator": {
199
+ "type": "automation",
200
+ "automationId": "a1",
201
+ "dashboardId": "d1",
202
+ },
203
+ },
204
+ # display initiator must not contain exportType
205
+ {
206
+ "executionType": "REPORT",
207
+ "organizationId": "org1",
208
+ "workspaceId": "ws1",
209
+ "userId": "user1",
210
+ "attributes": [],
211
+ "filters": [],
212
+ "reportExecutionRequest": {},
213
+ "executionInitiator": {
214
+ "type": "display",
215
+ "dashboardId": "d1",
216
+ "exportType": "CSV",
217
+ },
218
+ },
113
219
  ],
114
220
  )
115
221
  def test_invalid_execution_context_schema(value, get_validator):
@@ -1,5 +1,4 @@
1
1
  # (C) 2024 GoodData Corporation
2
- from typing import Optional
3
2
 
4
3
  import pyarrow
5
4
  from gooddata_flexconnect.function.function import FlexConnectFunction
@@ -19,7 +18,7 @@ class _SimpleFun1(FlexConnectFunction):
19
18
  def call(
20
19
  self,
21
20
  parameters: dict,
22
- columns: Optional[tuple[str, ...]],
21
+ columns: tuple[str, ...] | None,
23
22
  headers: dict[str, list[str]],
24
23
  ) -> ArrowData:
25
24
  return pyarrow.table(
@@ -1,11 +1,10 @@
1
1
  # (C) 2024 GoodData Corporation
2
- from typing import Optional
3
2
 
4
3
  import pyarrow
5
4
  from gooddata_flexconnect.function.function import FlexConnectFunction
6
5
  from gooddata_flight_server import ArrowData, ServerContext
7
6
 
8
- _DATA: Optional[pyarrow.Table] = None
7
+ _DATA: pyarrow.Table | None = None
9
8
 
10
9
 
11
10
  class _SimpleFun2(FlexConnectFunction):
@@ -21,7 +20,7 @@ class _SimpleFun2(FlexConnectFunction):
21
20
  def call(
22
21
  self,
23
22
  parameters: dict,
24
- columns: Optional[tuple[str, ...]],
23
+ columns: tuple[str, ...] | None,
25
24
  headers: dict[str, list[str]],
26
25
  ) -> ArrowData:
27
26
  assert _DATA is not None
@@ -1,12 +1,11 @@
1
1
  # (C) 2024 GoodData Corporation
2
2
  import time
3
- from typing import Optional
4
3
 
5
4
  import pyarrow
6
5
  from gooddata_flexconnect.function.function import FlexConnectFunction
7
6
  from gooddata_flight_server import ArrowData
8
7
 
9
- _DATA: Optional[pyarrow.Table] = None
8
+ _DATA: pyarrow.Table | None = None
10
9
 
11
10
 
12
11
  class _LongRunningFun(FlexConnectFunction):
@@ -22,7 +21,7 @@ class _LongRunningFun(FlexConnectFunction):
22
21
  def call(
23
22
  self,
24
23
  parameters: dict,
25
- columns: Optional[tuple[str, ...]],
24
+ columns: tuple[str, ...] | None,
26
25
  headers: dict[str, list[str]],
27
26
  ) -> ArrowData:
28
27
  # sleep is intentionally setup to be longer than the deadline for
@@ -1,12 +1,11 @@
1
1
  # (C) 2024 GoodData Corporation
2
2
  import time
3
- from typing import Optional
4
3
 
5
4
  import pyarrow
6
5
  from gooddata_flexconnect.function.function import FlexConnectFunction
7
6
  from gooddata_flight_server import ArrowData
8
7
 
9
- _DATA: Optional[pyarrow.Table] = None
8
+ _DATA: pyarrow.Table | None = None
10
9
 
11
10
 
12
11
  class _PollableFun(FlexConnectFunction):
@@ -22,7 +21,7 @@ class _PollableFun(FlexConnectFunction):
22
21
  def call(
23
22
  self,
24
23
  parameters: dict,
25
- columns: Optional[tuple[str, ...]],
24
+ columns: tuple[str, ...] | None,
26
25
  headers: dict[str, list[str]],
27
26
  ) -> ArrowData:
28
27
  # sleep is intentionally setup to be longer than one polling interval
@@ -11,7 +11,7 @@ dependency_groups =
11
11
  setenv =
12
12
  COVERAGE_CORE=sysmon
13
13
  commands =
14
- pytest -v --cov=src/gooddata_flexconnect --cov-report=xml tests {posargs} --json-report --json-report-file=.json-report-{envname}.json
14
+ pytest -v --cov --cov-report=xml tests {posargs} --json-report --json-report-file=.json-report-{envname}.json
15
15
 
16
16
  [testenv:docs]
17
17
  basepython = python3.14