gooddata-sdk 1.15.1.dev2__tar.gz → 1.15.1.dev3__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 (119) hide show
  1. {gooddata-sdk-1.15.1.dev2/gooddata_sdk.egg-info → gooddata-sdk-1.15.1.dev3}/PKG-INFO +4 -4
  2. {gooddata-sdk-1.15.1.dev2 → gooddata-sdk-1.15.1.dev3}/README.md +1 -1
  3. {gooddata-sdk-1.15.1.dev2 → gooddata-sdk-1.15.1.dev3}/gooddata_sdk/__init__.py +6 -0
  4. {gooddata-sdk-1.15.1.dev2 → gooddata-sdk-1.15.1.dev3}/gooddata_sdk/catalog/catalog_service_base.py +1 -0
  5. {gooddata-sdk-1.15.1.dev2 → gooddata-sdk-1.15.1.dev3}/gooddata_sdk/catalog/data_source/declarative_model/data_source.py +1 -0
  6. {gooddata-sdk-1.15.1.dev2 → gooddata-sdk-1.15.1.dev3}/gooddata_sdk/catalog/data_source/entity_model/data_source.py +11 -1
  7. gooddata-sdk-1.15.1.dev3/gooddata_sdk/catalog/user/management_model/__init__.py +1 -0
  8. gooddata-sdk-1.15.1.dev3/gooddata_sdk/catalog/user/management_model/management.py +60 -0
  9. {gooddata-sdk-1.15.1.dev2 → gooddata-sdk-1.15.1.dev3}/gooddata_sdk/catalog/user/service.py +28 -0
  10. {gooddata-sdk-1.15.1.dev2 → gooddata-sdk-1.15.1.dev3}/gooddata_sdk/client.py +5 -0
  11. {gooddata-sdk-1.15.1.dev2 → gooddata-sdk-1.15.1.dev3/gooddata_sdk.egg-info}/PKG-INFO +4 -4
  12. {gooddata-sdk-1.15.1.dev2 → gooddata-sdk-1.15.1.dev3}/gooddata_sdk.egg-info/SOURCES.txt +2 -0
  13. {gooddata-sdk-1.15.1.dev2 → gooddata-sdk-1.15.1.dev3}/gooddata_sdk.egg-info/requires.txt +1 -1
  14. {gooddata-sdk-1.15.1.dev2 → gooddata-sdk-1.15.1.dev3}/setup.py +3 -3
  15. {gooddata-sdk-1.15.1.dev2 → gooddata-sdk-1.15.1.dev3}/LICENSE.txt +0 -0
  16. {gooddata-sdk-1.15.1.dev2 → gooddata-sdk-1.15.1.dev3}/MANIFEST.in +0 -0
  17. {gooddata-sdk-1.15.1.dev2 → gooddata-sdk-1.15.1.dev3}/gooddata_sdk/_version.py +0 -0
  18. {gooddata-sdk-1.15.1.dev2 → gooddata-sdk-1.15.1.dev3}/gooddata_sdk/catalog/__init__.py +0 -0
  19. {gooddata-sdk-1.15.1.dev2 → gooddata-sdk-1.15.1.dev3}/gooddata_sdk/catalog/base.py +0 -0
  20. {gooddata-sdk-1.15.1.dev2 → gooddata-sdk-1.15.1.dev3}/gooddata_sdk/catalog/data_source/__init__.py +0 -0
  21. {gooddata-sdk-1.15.1.dev2 → gooddata-sdk-1.15.1.dev3}/gooddata_sdk/catalog/data_source/action_model/__init__.py +0 -0
  22. {gooddata-sdk-1.15.1.dev2 → gooddata-sdk-1.15.1.dev3}/gooddata_sdk/catalog/data_source/action_model/requests/__init__.py +0 -0
  23. {gooddata-sdk-1.15.1.dev2 → gooddata-sdk-1.15.1.dev3}/gooddata_sdk/catalog/data_source/action_model/requests/ldm_request.py +0 -0
  24. {gooddata-sdk-1.15.1.dev2 → gooddata-sdk-1.15.1.dev3}/gooddata_sdk/catalog/data_source/action_model/requests/scan_model_request.py +0 -0
  25. {gooddata-sdk-1.15.1.dev2 → gooddata-sdk-1.15.1.dev3}/gooddata_sdk/catalog/data_source/action_model/requests/scan_sql_request.py +0 -0
  26. {gooddata-sdk-1.15.1.dev2 → gooddata-sdk-1.15.1.dev3}/gooddata_sdk/catalog/data_source/action_model/responses/__init__.py +0 -0
  27. {gooddata-sdk-1.15.1.dev2 → gooddata-sdk-1.15.1.dev3}/gooddata_sdk/catalog/data_source/action_model/responses/scan_sql_response.py +0 -0
  28. {gooddata-sdk-1.15.1.dev2 → gooddata-sdk-1.15.1.dev3}/gooddata_sdk/catalog/data_source/action_model/sql_column.py +0 -0
  29. {gooddata-sdk-1.15.1.dev2 → gooddata-sdk-1.15.1.dev3}/gooddata_sdk/catalog/data_source/declarative_model/__init__.py +0 -0
  30. {gooddata-sdk-1.15.1.dev2 → gooddata-sdk-1.15.1.dev3}/gooddata_sdk/catalog/data_source/declarative_model/physical_model/__init__.py +0 -0
  31. {gooddata-sdk-1.15.1.dev2 → gooddata-sdk-1.15.1.dev3}/gooddata_sdk/catalog/data_source/declarative_model/physical_model/column.py +0 -0
  32. {gooddata-sdk-1.15.1.dev2 → gooddata-sdk-1.15.1.dev3}/gooddata_sdk/catalog/data_source/declarative_model/physical_model/pdm.py +0 -0
  33. {gooddata-sdk-1.15.1.dev2 → gooddata-sdk-1.15.1.dev3}/gooddata_sdk/catalog/data_source/declarative_model/physical_model/table.py +0 -0
  34. {gooddata-sdk-1.15.1.dev2 → gooddata-sdk-1.15.1.dev3}/gooddata_sdk/catalog/data_source/entity_model/__init__.py +0 -0
  35. {gooddata-sdk-1.15.1.dev2 → gooddata-sdk-1.15.1.dev3}/gooddata_sdk/catalog/data_source/entity_model/content_objects/__init__.py +0 -0
  36. {gooddata-sdk-1.15.1.dev2 → gooddata-sdk-1.15.1.dev3}/gooddata_sdk/catalog/data_source/entity_model/content_objects/table.py +0 -0
  37. {gooddata-sdk-1.15.1.dev2 → gooddata-sdk-1.15.1.dev3}/gooddata_sdk/catalog/data_source/service.py +0 -0
  38. {gooddata-sdk-1.15.1.dev2 → gooddata-sdk-1.15.1.dev3}/gooddata_sdk/catalog/data_source/validation/__init__.py +0 -0
  39. {gooddata-sdk-1.15.1.dev2 → gooddata-sdk-1.15.1.dev3}/gooddata_sdk/catalog/data_source/validation/data_source.py +0 -0
  40. {gooddata-sdk-1.15.1.dev2 → gooddata-sdk-1.15.1.dev3}/gooddata_sdk/catalog/depends_on.py +0 -0
  41. {gooddata-sdk-1.15.1.dev2 → gooddata-sdk-1.15.1.dev3}/gooddata_sdk/catalog/entity.py +0 -0
  42. {gooddata-sdk-1.15.1.dev2 → gooddata-sdk-1.15.1.dev3}/gooddata_sdk/catalog/export/__init__.py +0 -0
  43. {gooddata-sdk-1.15.1.dev2 → gooddata-sdk-1.15.1.dev3}/gooddata_sdk/catalog/export/request.py +0 -0
  44. {gooddata-sdk-1.15.1.dev2 → gooddata-sdk-1.15.1.dev3}/gooddata_sdk/catalog/export/service.py +0 -0
  45. {gooddata-sdk-1.15.1.dev2 → gooddata-sdk-1.15.1.dev3}/gooddata_sdk/catalog/identifier.py +0 -0
  46. {gooddata-sdk-1.15.1.dev2 → gooddata-sdk-1.15.1.dev3}/gooddata_sdk/catalog/organization/__init__.py +0 -0
  47. {gooddata-sdk-1.15.1.dev2 → gooddata-sdk-1.15.1.dev3}/gooddata_sdk/catalog/organization/entity_model/__init__.py +0 -0
  48. {gooddata-sdk-1.15.1.dev2 → gooddata-sdk-1.15.1.dev3}/gooddata_sdk/catalog/organization/entity_model/directive.py +0 -0
  49. {gooddata-sdk-1.15.1.dev2 → gooddata-sdk-1.15.1.dev3}/gooddata_sdk/catalog/organization/entity_model/jwk.py +0 -0
  50. {gooddata-sdk-1.15.1.dev2 → gooddata-sdk-1.15.1.dev3}/gooddata_sdk/catalog/organization/entity_model/organization.py +0 -0
  51. {gooddata-sdk-1.15.1.dev2 → gooddata-sdk-1.15.1.dev3}/gooddata_sdk/catalog/organization/entity_model/setting.py +0 -0
  52. {gooddata-sdk-1.15.1.dev2 → gooddata-sdk-1.15.1.dev3}/gooddata_sdk/catalog/organization/service.py +0 -0
  53. {gooddata-sdk-1.15.1.dev2 → gooddata-sdk-1.15.1.dev3}/gooddata_sdk/catalog/parameter.py +0 -0
  54. {gooddata-sdk-1.15.1.dev2 → gooddata-sdk-1.15.1.dev3}/gooddata_sdk/catalog/permission/__init__.py +0 -0
  55. {gooddata-sdk-1.15.1.dev2 → gooddata-sdk-1.15.1.dev3}/gooddata_sdk/catalog/permission/declarative_model/__init__.py +0 -0
  56. {gooddata-sdk-1.15.1.dev2 → gooddata-sdk-1.15.1.dev3}/gooddata_sdk/catalog/permission/declarative_model/dashboard_assignees.py +0 -0
  57. {gooddata-sdk-1.15.1.dev2 → gooddata-sdk-1.15.1.dev3}/gooddata_sdk/catalog/permission/declarative_model/dashboard_permissions.py +0 -0
  58. {gooddata-sdk-1.15.1.dev2 → gooddata-sdk-1.15.1.dev3}/gooddata_sdk/catalog/permission/declarative_model/manage_dashboard_permissions.py +0 -0
  59. {gooddata-sdk-1.15.1.dev2 → gooddata-sdk-1.15.1.dev3}/gooddata_sdk/catalog/permission/declarative_model/permission.py +0 -0
  60. {gooddata-sdk-1.15.1.dev2 → gooddata-sdk-1.15.1.dev3}/gooddata_sdk/catalog/permission/service.py +0 -0
  61. {gooddata-sdk-1.15.1.dev2 → gooddata-sdk-1.15.1.dev3}/gooddata_sdk/catalog/rule.py +0 -0
  62. {gooddata-sdk-1.15.1.dev2 → gooddata-sdk-1.15.1.dev3}/gooddata_sdk/catalog/setting.py +0 -0
  63. {gooddata-sdk-1.15.1.dev2 → gooddata-sdk-1.15.1.dev3}/gooddata_sdk/catalog/types.py +0 -0
  64. {gooddata-sdk-1.15.1.dev2 → gooddata-sdk-1.15.1.dev3}/gooddata_sdk/catalog/user/__init__.py +0 -0
  65. {gooddata-sdk-1.15.1.dev2 → gooddata-sdk-1.15.1.dev3}/gooddata_sdk/catalog/user/declarative_model/__init__.py +0 -0
  66. {gooddata-sdk-1.15.1.dev2 → gooddata-sdk-1.15.1.dev3}/gooddata_sdk/catalog/user/declarative_model/user.py +0 -0
  67. {gooddata-sdk-1.15.1.dev2 → gooddata-sdk-1.15.1.dev3}/gooddata_sdk/catalog/user/declarative_model/user_and_user_groups.py +0 -0
  68. {gooddata-sdk-1.15.1.dev2 → gooddata-sdk-1.15.1.dev3}/gooddata_sdk/catalog/user/declarative_model/user_group.py +0 -0
  69. {gooddata-sdk-1.15.1.dev2 → gooddata-sdk-1.15.1.dev3}/gooddata_sdk/catalog/user/entity_model/__init__.py +0 -0
  70. {gooddata-sdk-1.15.1.dev2 → gooddata-sdk-1.15.1.dev3}/gooddata_sdk/catalog/user/entity_model/user.py +0 -0
  71. {gooddata-sdk-1.15.1.dev2 → gooddata-sdk-1.15.1.dev3}/gooddata_sdk/catalog/user/entity_model/user_group.py +0 -0
  72. {gooddata-sdk-1.15.1.dev2 → gooddata-sdk-1.15.1.dev3}/gooddata_sdk/catalog/validate_by_item.py +0 -0
  73. {gooddata-sdk-1.15.1.dev2 → gooddata-sdk-1.15.1.dev3}/gooddata_sdk/catalog/workspace/__init__.py +0 -0
  74. {gooddata-sdk-1.15.1.dev2 → gooddata-sdk-1.15.1.dev3}/gooddata_sdk/catalog/workspace/content_service.py +0 -0
  75. {gooddata-sdk-1.15.1.dev2 → gooddata-sdk-1.15.1.dev3}/gooddata_sdk/catalog/workspace/declarative_model/__init__.py +0 -0
  76. {gooddata-sdk-1.15.1.dev2 → gooddata-sdk-1.15.1.dev3}/gooddata_sdk/catalog/workspace/declarative_model/workspace/__init__.py +0 -0
  77. {gooddata-sdk-1.15.1.dev2 → gooddata-sdk-1.15.1.dev3}/gooddata_sdk/catalog/workspace/declarative_model/workspace/analytics_model/__init__.py +0 -0
  78. {gooddata-sdk-1.15.1.dev2 → gooddata-sdk-1.15.1.dev3}/gooddata_sdk/catalog/workspace/declarative_model/workspace/analytics_model/analytics_model.py +0 -0
  79. {gooddata-sdk-1.15.1.dev2 → gooddata-sdk-1.15.1.dev3}/gooddata_sdk/catalog/workspace/declarative_model/workspace/logical_model/__init__.py +0 -0
  80. {gooddata-sdk-1.15.1.dev2 → gooddata-sdk-1.15.1.dev3}/gooddata_sdk/catalog/workspace/declarative_model/workspace/logical_model/data_filter_references.py +0 -0
  81. {gooddata-sdk-1.15.1.dev2 → gooddata-sdk-1.15.1.dev3}/gooddata_sdk/catalog/workspace/declarative_model/workspace/logical_model/dataset/__init__.py +0 -0
  82. {gooddata-sdk-1.15.1.dev2 → gooddata-sdk-1.15.1.dev3}/gooddata_sdk/catalog/workspace/declarative_model/workspace/logical_model/dataset/dataset.py +0 -0
  83. {gooddata-sdk-1.15.1.dev2 → gooddata-sdk-1.15.1.dev3}/gooddata_sdk/catalog/workspace/declarative_model/workspace/logical_model/dataset_extensions/__init__.py +0 -0
  84. {gooddata-sdk-1.15.1.dev2 → gooddata-sdk-1.15.1.dev3}/gooddata_sdk/catalog/workspace/declarative_model/workspace/logical_model/dataset_extensions/dataset_extension.py +0 -0
  85. {gooddata-sdk-1.15.1.dev2 → gooddata-sdk-1.15.1.dev3}/gooddata_sdk/catalog/workspace/declarative_model/workspace/logical_model/date_dataset/__init__.py +0 -0
  86. {gooddata-sdk-1.15.1.dev2 → gooddata-sdk-1.15.1.dev3}/gooddata_sdk/catalog/workspace/declarative_model/workspace/logical_model/date_dataset/date_dataset.py +0 -0
  87. {gooddata-sdk-1.15.1.dev2 → gooddata-sdk-1.15.1.dev3}/gooddata_sdk/catalog/workspace/declarative_model/workspace/logical_model/ldm.py +0 -0
  88. {gooddata-sdk-1.15.1.dev2 → gooddata-sdk-1.15.1.dev3}/gooddata_sdk/catalog/workspace/declarative_model/workspace/workspace.py +0 -0
  89. {gooddata-sdk-1.15.1.dev2 → gooddata-sdk-1.15.1.dev3}/gooddata_sdk/catalog/workspace/entity_model/__init__.py +0 -0
  90. {gooddata-sdk-1.15.1.dev2 → gooddata-sdk-1.15.1.dev3}/gooddata_sdk/catalog/workspace/entity_model/content_objects/__init__.py +0 -0
  91. {gooddata-sdk-1.15.1.dev2 → gooddata-sdk-1.15.1.dev3}/gooddata_sdk/catalog/workspace/entity_model/content_objects/dataset.py +0 -0
  92. {gooddata-sdk-1.15.1.dev2 → gooddata-sdk-1.15.1.dev3}/gooddata_sdk/catalog/workspace/entity_model/content_objects/metric.py +0 -0
  93. {gooddata-sdk-1.15.1.dev2 → gooddata-sdk-1.15.1.dev3}/gooddata_sdk/catalog/workspace/entity_model/content_objects/workspace_setting.py +0 -0
  94. {gooddata-sdk-1.15.1.dev2 → gooddata-sdk-1.15.1.dev3}/gooddata_sdk/catalog/workspace/entity_model/graph_objects/__init__.py +0 -0
  95. {gooddata-sdk-1.15.1.dev2 → gooddata-sdk-1.15.1.dev3}/gooddata_sdk/catalog/workspace/entity_model/graph_objects/graph.py +0 -0
  96. {gooddata-sdk-1.15.1.dev2 → gooddata-sdk-1.15.1.dev3}/gooddata_sdk/catalog/workspace/entity_model/user_data_filter.py +0 -0
  97. {gooddata-sdk-1.15.1.dev2 → gooddata-sdk-1.15.1.dev3}/gooddata_sdk/catalog/workspace/entity_model/workspace.py +0 -0
  98. {gooddata-sdk-1.15.1.dev2 → gooddata-sdk-1.15.1.dev3}/gooddata_sdk/catalog/workspace/model_container.py +0 -0
  99. {gooddata-sdk-1.15.1.dev2 → gooddata-sdk-1.15.1.dev3}/gooddata_sdk/catalog/workspace/service.py +0 -0
  100. {gooddata-sdk-1.15.1.dev2 → gooddata-sdk-1.15.1.dev3}/gooddata_sdk/compute/__init__.py +0 -0
  101. {gooddata-sdk-1.15.1.dev2 → gooddata-sdk-1.15.1.dev3}/gooddata_sdk/compute/model/__init__.py +0 -0
  102. {gooddata-sdk-1.15.1.dev2 → gooddata-sdk-1.15.1.dev3}/gooddata_sdk/compute/model/attribute.py +0 -0
  103. {gooddata-sdk-1.15.1.dev2 → gooddata-sdk-1.15.1.dev3}/gooddata_sdk/compute/model/base.py +0 -0
  104. {gooddata-sdk-1.15.1.dev2 → gooddata-sdk-1.15.1.dev3}/gooddata_sdk/compute/model/execution.py +0 -0
  105. {gooddata-sdk-1.15.1.dev2 → gooddata-sdk-1.15.1.dev3}/gooddata_sdk/compute/model/filter.py +0 -0
  106. {gooddata-sdk-1.15.1.dev2 → gooddata-sdk-1.15.1.dev3}/gooddata_sdk/compute/model/metric.py +0 -0
  107. {gooddata-sdk-1.15.1.dev2 → gooddata-sdk-1.15.1.dev3}/gooddata_sdk/compute/service.py +0 -0
  108. {gooddata-sdk-1.15.1.dev2 → gooddata-sdk-1.15.1.dev3}/gooddata_sdk/py.typed +0 -0
  109. {gooddata-sdk-1.15.1.dev2 → gooddata-sdk-1.15.1.dev3}/gooddata_sdk/sdk.py +0 -0
  110. {gooddata-sdk-1.15.1.dev2 → gooddata-sdk-1.15.1.dev3}/gooddata_sdk/support.py +0 -0
  111. {gooddata-sdk-1.15.1.dev2 → gooddata-sdk-1.15.1.dev3}/gooddata_sdk/table.py +140 -140
  112. {gooddata-sdk-1.15.1.dev2 → gooddata-sdk-1.15.1.dev3}/gooddata_sdk/type_converter.py +0 -0
  113. {gooddata-sdk-1.15.1.dev2 → gooddata-sdk-1.15.1.dev3}/gooddata_sdk/utils.py +0 -0
  114. {gooddata-sdk-1.15.1.dev2 → gooddata-sdk-1.15.1.dev3}/gooddata_sdk/visualization.py +0 -0
  115. {gooddata-sdk-1.15.1.dev2 → gooddata-sdk-1.15.1.dev3}/gooddata_sdk.egg-info/dependency_links.txt +0 -0
  116. {gooddata-sdk-1.15.1.dev2 → gooddata-sdk-1.15.1.dev3}/gooddata_sdk.egg-info/top_level.txt +0 -0
  117. {gooddata-sdk-1.15.1.dev2 → gooddata-sdk-1.15.1.dev3}/setup.cfg +0 -0
  118. {gooddata-sdk-1.15.1.dev2 → gooddata-sdk-1.15.1.dev3}/tests/test_client.py +0 -0
  119. {gooddata-sdk-1.15.1.dev2 → gooddata-sdk-1.15.1.dev3}/tests/test_type_converter.py +0 -0
@@ -1,11 +1,11 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: gooddata-sdk
3
- Version: 1.15.1.dev2
3
+ Version: 1.15.1.dev3
4
4
  Summary: GoodData Cloud Python SDK
5
5
  Author: GoodData
6
6
  Author-email: support@gooddata.com
7
7
  License: MIT
8
- Project-URL: Documentation, https://www.gooddata.com/docs/python-sdk/1.15.1.dev2
8
+ Project-URL: Documentation, https://www.gooddata.com/docs/python-sdk/1.15.1.dev3
9
9
  Project-URL: Source, https://github.com/gooddata/gooddata-python-sdk
10
10
  Keywords: gooddata,sdk,api,analytics,headless,business,intelligence,headless-bi,cloud,native,semantic,layer,sql,metrics
11
11
  Classifier: Development Status :: 5 - Production/Stable
@@ -22,7 +22,7 @@ Classifier: Typing :: Typed
22
22
  Requires-Python: >=3.8.0
23
23
  Description-Content-Type: text/markdown
24
24
  License-File: LICENSE.txt
25
- Requires-Dist: gooddata-api-client~=1.15.1.dev2
25
+ Requires-Dist: gooddata-api-client~=1.15.1.dev3
26
26
  Requires-Dist: python-dateutil>=2.5.3
27
27
  Requires-Dist: pyyaml>=5.1
28
28
  Requires-Dist: attrs<=23.2.0,>=21.4.0
@@ -44,7 +44,7 @@ At the moment the SDK provides services to inspect and interact with the Semanti
44
44
  * Compute Service
45
45
  * Table Service
46
46
 
47
- See [DOCUMENTATION](https://www.gooddata.com/docs/python-sdk/1.15.1.dev2) for more details.
47
+ See [DOCUMENTATION](https://www.gooddata.com/docs/python-sdk/1.15.1.dev3) for more details.
48
48
 
49
49
  ## Requirements
50
50
 
@@ -13,7 +13,7 @@ At the moment the SDK provides services to inspect and interact with the Semanti
13
13
  * Compute Service
14
14
  * Table Service
15
15
 
16
- See [DOCUMENTATION](https://www.gooddata.com/docs/python-sdk/1.15.1.dev2) for more details.
16
+ See [DOCUMENTATION](https://www.gooddata.com/docs/python-sdk/1.15.1.dev3) for more details.
17
17
 
18
18
  ## Requirements
19
19
 
@@ -114,6 +114,12 @@ from gooddata_sdk.catalog.user.declarative_model.user_group import (
114
114
  )
115
115
  from gooddata_sdk.catalog.user.entity_model.user import CatalogUser
116
116
  from gooddata_sdk.catalog.user.entity_model.user_group import CatalogUserGroup
117
+ from gooddata_sdk.catalog.user.management_model.management import (
118
+ CatalogDataSourcePermissionAssignment,
119
+ CatalogPermissionAssignments,
120
+ CatalogPermissionsAssignment,
121
+ CatalogWorkspacePermissionAssignment,
122
+ )
117
123
  from gooddata_sdk.catalog.validate_by_item import CatalogValidateByItem
118
124
  from gooddata_sdk.catalog.workspace.content_service import CatalogWorkspaceContent, CatalogWorkspaceContentService
119
125
  from gooddata_sdk.catalog.workspace.declarative_model.workspace.analytics_model.analytics_model import (
@@ -17,6 +17,7 @@ class CatalogServiceBase:
17
17
  self._entities_api: apis.EntitiesApi = api_client.entities_api
18
18
  self._layout_api: apis.LayoutApi = api_client.layout_api
19
19
  self._actions_api: apis.ActionsApi = api_client.actions_api
20
+ self._user_management_api: apis.UserManagementApi = api_client.user_management_api
20
21
 
21
22
  def get_organization(self) -> CatalogOrganization:
22
23
  # The generated client does work properly with redirecting APIs
@@ -71,6 +71,7 @@ class CatalogDeclarativeDataSource(Base):
71
71
  schema: str
72
72
  enable_caching: Optional[bool] = None
73
73
  cache_path: Optional[List[str]] = None
74
+ cache_strategy: Optional[str] = None
74
75
  username: Optional[str] = None
75
76
  parameters: Optional[List[CatalogParameter]] = None
76
77
  decoded_parameters: Optional[List[CatalogParameter]] = None
@@ -31,7 +31,16 @@ class CatalogDataSourceBase(Base):
31
31
  TokenCredentialsFromFile,
32
32
  ]
33
33
  _DELIMITER: ClassVar[str] = "&"
34
- _ATTRIBUTES: ClassVar[List[str]] = ["enable_caching", "cache_path", "url", "parameters", "name", "type", "schema"]
34
+ _ATTRIBUTES: ClassVar[List[str]] = [
35
+ "enable_caching",
36
+ "cache_path",
37
+ "cache_strategy",
38
+ "url",
39
+ "parameters",
40
+ "name",
41
+ "type",
42
+ "schema",
43
+ ]
35
44
 
36
45
  id: str
37
46
  name: str
@@ -40,6 +49,7 @@ class CatalogDataSourceBase(Base):
40
49
  url: Optional[str] = None
41
50
  enable_caching: Optional[bool] = None
42
51
  cache_path: Optional[List[str]] = None
52
+ cache_strategy: Optional[str] = None
43
53
  parameters: Optional[List[Dict[str, str]]] = None
44
54
  decoded_parameters: Optional[List[Dict[str, str]]] = None
45
55
  credentials: Credentials = attr.field(repr=False)
@@ -0,0 +1 @@
1
+ # (C) 2024 GoodData Corporation
@@ -0,0 +1,60 @@
1
+ # (C) 2024 GoodData Corporation
2
+
3
+ from typing import List, Optional, Type
4
+
5
+ import attrs
6
+
7
+ from gooddata_api_client.model.permissions_assignment import PermissionsAssignment
8
+ from gooddata_api_client.model.user_management_data_source_permission_assignment import (
9
+ UserManagementDataSourcePermissionAssignment,
10
+ )
11
+ from gooddata_api_client.model.user_management_permission_assignments import UserManagementPermissionAssignments
12
+ from gooddata_api_client.model.user_management_workspace_permission_assignment import (
13
+ UserManagementWorkspacePermissionAssignment,
14
+ )
15
+ from gooddata_sdk import CatalogAssigneeIdentifier
16
+ from gooddata_sdk.catalog.base import Base
17
+
18
+
19
+ @attrs.define(auto_attribs=True, kw_only=True)
20
+ class CatalogDataSourcePermissionAssignment(Base):
21
+ id: str
22
+ permissions: List[str] = attrs.field(factory=list)
23
+ name: Optional[str] = None
24
+
25
+ @staticmethod
26
+ def client_class() -> Type[UserManagementDataSourcePermissionAssignment]:
27
+ return UserManagementDataSourcePermissionAssignment
28
+
29
+
30
+ @attrs.define(auto_attribs=True, kw_only=True)
31
+ class CatalogWorkspacePermissionAssignment(Base):
32
+ id: str
33
+ permissions: List[str] = attrs.field(factory=list)
34
+ hierarchy_permissions: List[str] = attrs.field(factory=list)
35
+ name: Optional[str] = None
36
+
37
+ @staticmethod
38
+ def client_class() -> Type[UserManagementWorkspacePermissionAssignment]:
39
+ return UserManagementWorkspacePermissionAssignment
40
+
41
+
42
+ @attrs.define(auto_attribs=True, kw_only=True)
43
+ class CatalogPermissionAssignments(Base):
44
+ workspaces: List[CatalogWorkspacePermissionAssignment] = attrs.field(factory=list)
45
+ data_sources: List[CatalogDataSourcePermissionAssignment] = attrs.field(factory=list)
46
+
47
+ @staticmethod
48
+ def client_class() -> Type[UserManagementPermissionAssignments]:
49
+ return UserManagementPermissionAssignments
50
+
51
+
52
+ @attrs.define(auto_attribs=True, kw_only=True)
53
+ class CatalogPermissionsAssignment(Base):
54
+ assignees: List[CatalogAssigneeIdentifier]
55
+ workspaces: List[CatalogWorkspacePermissionAssignment] = attrs.field(factory=list)
56
+ data_sources: List[CatalogDataSourcePermissionAssignment] = attrs.field(factory=list)
57
+
58
+ @staticmethod
59
+ def client_class() -> Type[PermissionsAssignment]:
60
+ return PermissionsAssignment
@@ -12,6 +12,10 @@ from gooddata_sdk.catalog.user.declarative_model.user_and_user_groups import Cat
12
12
  from gooddata_sdk.catalog.user.declarative_model.user_group import CatalogDeclarativeUserGroups
13
13
  from gooddata_sdk.catalog.user.entity_model.user import CatalogUser, CatalogUserDocument
14
14
  from gooddata_sdk.catalog.user.entity_model.user_group import CatalogUserGroup, CatalogUserGroupDocument
15
+ from gooddata_sdk.catalog.user.management_model.management import (
16
+ CatalogPermissionAssignments,
17
+ CatalogPermissionsAssignment,
18
+ )
15
19
  from gooddata_sdk.utils import load_all_entities, load_all_entities_dict
16
20
 
17
21
 
@@ -363,3 +367,27 @@ class CatalogUserService(CatalogServiceBase):
363
367
  """
364
368
  declarative_users_user_groups = self.load_declarative_users_user_groups(layout_root_path)
365
369
  self.put_declarative_users_user_groups(declarative_users_user_groups)
370
+
371
+ # User management use case APIs
372
+
373
+ def get_user_permissions(self, user_id: str) -> CatalogPermissionAssignments:
374
+ return CatalogPermissionAssignments.from_api(self._user_management_api.list_permissions_for_user(user_id))
375
+
376
+ def manage_user_permissions(self, user_id: str, permission_assignments: CatalogPermissionAssignments) -> None:
377
+ self._user_management_api.manage_permissions_for_user(user_id, permission_assignments.to_api())
378
+
379
+ def get_user_group_permissions(self, user_group_id: str) -> CatalogPermissionAssignments:
380
+ return CatalogPermissionAssignments.from_api(
381
+ self._user_management_api.list_permissions_for_user_group(user_group_id)
382
+ )
383
+
384
+ def manage_user_group_permissions(
385
+ self, user_group_id: str, permission_assignments: CatalogPermissionAssignments
386
+ ) -> None:
387
+ self._user_management_api.manage_permissions_for_user_group(user_group_id, permission_assignments.to_api())
388
+
389
+ def assign_permissions_bulk(self, permissions_assignment: CatalogPermissionsAssignment) -> None:
390
+ self._user_management_api.assign_permissions(permissions_assignment.to_api())
391
+
392
+ def revoke_permissions_bulk(self, permissions_assignment: CatalogPermissionsAssignment) -> None:
393
+ self._user_management_api.revoke_permissions(permissions_assignment.to_api())
@@ -54,6 +54,7 @@ class GoodDataApiClient:
54
54
  self._entities_api = apis.EntitiesApi(self._api_client)
55
55
  self._layout_api = apis.LayoutApi(self._api_client)
56
56
  self._actions_api = apis.ActionsApi(self._api_client)
57
+ self._user_management_api = apis.UserManagementApi(self._api_client)
57
58
 
58
59
  @staticmethod
59
60
  def _set_default_headers(headers: dict) -> None:
@@ -75,3 +76,7 @@ class GoodDataApiClient:
75
76
  @property
76
77
  def actions_api(self) -> apis.ActionsApi:
77
78
  return self._actions_api
79
+
80
+ @property
81
+ def user_management_api(self) -> apis.UserManagementApi:
82
+ return self._user_management_api
@@ -1,11 +1,11 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: gooddata-sdk
3
- Version: 1.15.1.dev2
3
+ Version: 1.15.1.dev3
4
4
  Summary: GoodData Cloud Python SDK
5
5
  Author: GoodData
6
6
  Author-email: support@gooddata.com
7
7
  License: MIT
8
- Project-URL: Documentation, https://www.gooddata.com/docs/python-sdk/1.15.1.dev2
8
+ Project-URL: Documentation, https://www.gooddata.com/docs/python-sdk/1.15.1.dev3
9
9
  Project-URL: Source, https://github.com/gooddata/gooddata-python-sdk
10
10
  Keywords: gooddata,sdk,api,analytics,headless,business,intelligence,headless-bi,cloud,native,semantic,layer,sql,metrics
11
11
  Classifier: Development Status :: 5 - Production/Stable
@@ -22,7 +22,7 @@ Classifier: Typing :: Typed
22
22
  Requires-Python: >=3.8.0
23
23
  Description-Content-Type: text/markdown
24
24
  License-File: LICENSE.txt
25
- Requires-Dist: gooddata-api-client~=1.15.1.dev2
25
+ Requires-Dist: gooddata-api-client~=1.15.1.dev3
26
26
  Requires-Dist: python-dateutil>=2.5.3
27
27
  Requires-Dist: pyyaml>=5.1
28
28
  Requires-Dist: attrs<=23.2.0,>=21.4.0
@@ -44,7 +44,7 @@ At the moment the SDK provides services to inspect and interact with the Semanti
44
44
  * Compute Service
45
45
  * Table Service
46
46
 
47
- See [DOCUMENTATION](https://www.gooddata.com/docs/python-sdk/1.15.1.dev2) for more details.
47
+ See [DOCUMENTATION](https://www.gooddata.com/docs/python-sdk/1.15.1.dev3) for more details.
48
48
 
49
49
  ## Requirements
50
50
 
@@ -76,6 +76,8 @@ gooddata_sdk/catalog/user/declarative_model/user_group.py
76
76
  gooddata_sdk/catalog/user/entity_model/__init__.py
77
77
  gooddata_sdk/catalog/user/entity_model/user.py
78
78
  gooddata_sdk/catalog/user/entity_model/user_group.py
79
+ gooddata_sdk/catalog/user/management_model/__init__.py
80
+ gooddata_sdk/catalog/user/management_model/management.py
79
81
  gooddata_sdk/catalog/workspace/__init__.py
80
82
  gooddata_sdk/catalog/workspace/content_service.py
81
83
  gooddata_sdk/catalog/workspace/model_container.py
@@ -1,4 +1,4 @@
1
- gooddata-api-client~=1.15.1.dev2
1
+ gooddata-api-client~=1.15.1.dev3
2
2
  python-dateutil>=2.5.3
3
3
  pyyaml>=5.1
4
4
  attrs<=23.2.0,>=21.4.0
@@ -7,7 +7,7 @@ this_directory = Path(__file__).parent
7
7
  long_description = (this_directory / "README.md").read_text(encoding="utf-8")
8
8
 
9
9
  REQUIRES = [
10
- "gooddata-api-client~=1.15.1.dev2",
10
+ "gooddata-api-client~=1.15.1.dev3",
11
11
  "python-dateutil>=2.5.3",
12
12
  "pyyaml>=5.1",
13
13
  "attrs>=21.4.0,<=23.2.0",
@@ -20,7 +20,7 @@ setup(
20
20
  description="GoodData Cloud Python SDK",
21
21
  long_description=long_description,
22
22
  long_description_content_type="text/markdown",
23
- version="1.15.1.dev2",
23
+ version="1.15.1.dev3",
24
24
  author="GoodData",
25
25
  author_email="support@gooddata.com",
26
26
  license="MIT",
@@ -30,7 +30,7 @@ setup(
30
30
  packages=find_packages(exclude=["tests*"]),
31
31
  python_requires=">=3.8.0",
32
32
  project_urls={
33
- "Documentation": "https://www.gooddata.com/docs/python-sdk/1.15.1.dev2",
33
+ "Documentation": "https://www.gooddata.com/docs/python-sdk/1.15.1.dev3",
34
34
  "Source": "https://github.com/gooddata/gooddata-python-sdk",
35
35
  },
36
36
  classifiers=[
@@ -272,25 +272,6 @@ class TotalDefinitionOrdering:
272
272
  )
273
273
 
274
274
 
275
- def _create_dimension(bucket: VisualizationBucket, measures_item_identifier: Optional[str] = None) -> TableDimension:
276
- item_ids = [a.local_id for a in bucket.attributes]
277
- if measures_item_identifier is not None:
278
- item_ids.append(measures_item_identifier)
279
- return TableDimension(
280
- item_ids=item_ids,
281
- idx=_GET_DIM_INDEX_OF_BUCKET_TYPE[bucket.type],
282
- )
283
-
284
-
285
- def _get_dim_idx_for_predicate(
286
- dims: list[TableDimension], predicate: Callable[[TableDimension], bool]
287
- ) -> Optional[int]:
288
- for dim_idx, dim in enumerate(dims):
289
- if predicate(dim):
290
- return dim_idx
291
- return None
292
-
293
-
294
275
  @frozen
295
276
  class AttributeLocator:
296
277
  attribute_identifier: str
@@ -367,6 +348,73 @@ def _create_data_col_locators(locators: list[VisualizationSortLocator]) -> list[
367
348
  return converted_locators
368
349
 
369
350
 
351
+ def _get_dim_idx_for_predicate(
352
+ dims: list[TableDimension], predicate: Callable[[TableDimension], bool]
353
+ ) -> Optional[int]:
354
+ for dim_idx, dim in enumerate(dims):
355
+ if predicate(dim):
356
+ return dim_idx
357
+ return None
358
+
359
+
360
+ def _append_attribute_sort_key(
361
+ dims: list[TableDimension], sort_item: VisualizationSort, sorting: list[list[SortKey]]
362
+ ) -> None:
363
+ dim_idx = _get_dim_idx_for_predicate(dims, lambda x: sort_item.attribute_identifier in x.item_ids)
364
+ if dim_idx is None:
365
+ log_msg = (
366
+ f'attempting to sort by attribute with localId "{sort_item.attribute_identifier}" '
367
+ "but this attribute is not in any dimension."
368
+ )
369
+ logger.warning(log_msg)
370
+ return
371
+
372
+ sorting[dim_idx].append(
373
+ SortKeyAttribute(
374
+ sort_type=sort_item.type,
375
+ direction=sort_item.direction,
376
+ attribute_identifier=sort_item.attribute_identifier,
377
+ attribute_sort_type=sort_item.attribute_sort_type,
378
+ )
379
+ )
380
+
381
+
382
+ def _append_measure_sort_key(
383
+ measure_dim: Optional[TableDimension],
384
+ non_measure_dim_idx: Optional[int],
385
+ sort_item: VisualizationSort,
386
+ sorting: list[list[SortKey]],
387
+ ) -> None:
388
+ if non_measure_dim_idx is None:
389
+ logger.warning(
390
+ "Trying to use measure sort in an execution that only contains dimension with MeasureGroup. "
391
+ "This is not valid sort. Measure sort is used to sort the non-measure dimension by values "
392
+ "from measure dimension. Skipping."
393
+ )
394
+ return
395
+
396
+ if not measure_dim:
397
+ logger.warning("Trying to use measure sort in an execution that does not contain MeasureGroup. Skipping.")
398
+ return
399
+
400
+ sorting[non_measure_dim_idx].append(
401
+ SortKeyValue(
402
+ sort_type=sort_item.type,
403
+ direction=sort_item.direction,
404
+ measure_dim_identifier=f"dim_{measure_dim.idx}",
405
+ data_column_locators=_create_data_col_locators(sort_item.locators),
406
+ )
407
+ )
408
+
409
+
410
+ def _merge_dims_with_sorting(dims: list[TableDimension], sorting: list[list[SortKey]]) -> list[TableDimension]:
411
+ for dim in dims:
412
+ dim_sorting = sorting[dim.idx]
413
+ if dim_sorting:
414
+ dim.sorting = [ds.to_dict() for ds in dim_sorting]
415
+ return dims
416
+
417
+
370
418
  def _create_dims_with_sorts(dims: list[TableDimension], sorts: list[VisualizationSort]) -> list[TableDimension]:
371
419
  """
372
420
  Places sorting into dimensions. Returns the same dimensions objects but with modified sorting.
@@ -406,64 +454,6 @@ def _create_dims_with_sorts(dims: list[TableDimension], sorts: list[Visualizatio
406
454
  return _merge_dims_with_sorting(dims, sorting)
407
455
 
408
456
 
409
- def _merge_dims_with_sorting(dims: list[TableDimension], sorting: list[list[SortKey]]) -> list[TableDimension]:
410
- for dim in dims:
411
- dim_sorting = sorting[dim.idx]
412
- if dim_sorting:
413
- dim.sorting = [ds.to_dict() for ds in dim_sorting]
414
- return dims
415
-
416
-
417
- def _append_measure_sort_key(
418
- measure_dim: Optional[TableDimension],
419
- non_measure_dim_idx: Optional[int],
420
- sort_item: VisualizationSort,
421
- sorting: list[list[SortKey]],
422
- ) -> None:
423
- if non_measure_dim_idx is None:
424
- logger.warning(
425
- "Trying to use measure sort in an execution that only contains dimension with MeasureGroup. "
426
- "This is not valid sort. Measure sort is used to sort the non-measure dimension by values "
427
- "from measure dimension. Skipping."
428
- )
429
- return
430
-
431
- if not measure_dim:
432
- logger.warning("Trying to use measure sort in an execution that does not contain MeasureGroup. Skipping.")
433
- return
434
-
435
- sorting[non_measure_dim_idx].append(
436
- SortKeyValue(
437
- sort_type=sort_item.type,
438
- direction=sort_item.direction,
439
- measure_dim_identifier=f"dim_{measure_dim.idx}",
440
- data_column_locators=_create_data_col_locators(sort_item.locators),
441
- )
442
- )
443
-
444
-
445
- def _append_attribute_sort_key(
446
- dims: list[TableDimension], sort_item: VisualizationSort, sorting: list[list[SortKey]]
447
- ) -> None:
448
- dim_idx = _get_dim_idx_for_predicate(dims, lambda x: sort_item.attribute_identifier in x.item_ids)
449
- if dim_idx is None:
450
- log_msg = (
451
- f'attempting to sort by attribute with localId "{sort_item.attribute_identifier}" '
452
- "but this attribute is not in any dimension."
453
- )
454
- logger.warning(log_msg)
455
- return
456
-
457
- sorting[dim_idx].append(
458
- SortKeyAttribute(
459
- sort_type=sort_item.type,
460
- direction=sort_item.direction,
461
- attribute_identifier=sort_item.attribute_identifier,
462
- attribute_sort_type=sort_item.attribute_sort_type,
463
- )
464
- )
465
-
466
-
467
457
  def _vis_is_transposed(visualization: Visualization) -> bool:
468
458
  controls = visualization.properties.get("controls")
469
459
  if not controls:
@@ -471,6 +461,16 @@ def _vis_is_transposed(visualization: Visualization) -> bool:
471
461
  return controls.get("measureGroupDimension") == "rows"
472
462
 
473
463
 
464
+ def _create_dimension(bucket: VisualizationBucket, measures_item_identifier: Optional[str] = None) -> TableDimension:
465
+ item_ids = [a.local_id for a in bucket.attributes]
466
+ if measures_item_identifier is not None:
467
+ item_ids.append(measures_item_identifier)
468
+ return TableDimension(
469
+ item_ids=item_ids,
470
+ idx=_GET_DIM_INDEX_OF_BUCKET_TYPE[bucket.type],
471
+ )
472
+
473
+
474
474
  def _create_dimensions(visualization: Visualization) -> list[TableDimension]:
475
475
  measures_item_identifier = _MEASURE_GROUP_IDENTIFIER if visualization.metrics else None
476
476
  row_bucket = visualization.get_bucket_of_type(BucketType.ROWS)
@@ -567,59 +567,22 @@ class TotalsComputeInfo:
567
567
  self.column_subtotal_dimension_index = self.col_attr_ids.index(col_total_attr_id)
568
568
 
569
569
 
570
- def _get_additional_totals(visualization: Visualization, dimensions: list[TableDimension]) -> list[TotalDefinition]:
571
- """Construct special cases of pivot table totals.
572
-
573
- These special cases are -
574
- 1. Grand totals - is the value obtained from row and column totals.
575
- 2. Marginal totals - is the value obtained within the subgroups from row and column subtotals.
576
-
577
- For `Grand Totals`, in Tiger AFM, you specify that you want total of totals with total that have only
578
- "measureGroup" present.
579
- For `Marginal Total`, in Tiger AFM, would need to iterate through both dimensions and obtain the missing
580
- totalDimensions items based on the attribute and column identifiers order in buckets.
581
- """
582
- totals: list[TotalDefinition] = []
583
- row_bucket = visualization.get_bucket_of_type(BucketType.ROWS)
584
- col_bucket = visualization.get_bucket_of_type(BucketType.COLS)
585
-
586
- tci = TotalsComputeInfo(
587
- row_attr_ids=[a.local_id for a in row_bucket.attributes],
588
- col_attr_ids=[a.local_id for a in col_bucket.attributes],
589
- measure_group_rows=[_MEASURE_GROUP_IDENTIFIER] if _MEASURE_GROUP_IDENTIFIER in dimensions[0].item_ids else [],
590
- measure_group_cols=[_MEASURE_GROUP_IDENTIFIER] if _MEASURE_GROUP_IDENTIFIER in dimensions[1].item_ids else [],
591
- )
592
- for row_index, row_total in enumerate(row_bucket.totals):
593
- tci.reset_to_defaults()
594
- for col_index, col_total in enumerate(col_bucket.totals):
595
- # Check for totals from same measure and type
596
- if row_total.measure_id == col_total.measure_id and row_total.type == col_total.type:
597
- tci.update_compute_info(col_total.attribute_id, row_total.attribute_id)
598
-
599
- if tci.has_row_and_column_sub_totals:
600
- totals.append(_extend_marginal_totals(col_index, row_total, tci))
601
-
602
- if tci.has_row_subtotal_and_column_grand_total:
603
- totals.append(_extend_marginal_totals_of_rows(row_index, row_total, tci))
604
-
605
- if tci.has_column_subtotal_and_row_grand_total:
606
- totals.append(_extend_marginal_totals_of_cols(row_index, row_total, tci))
607
-
608
- if tci.has_row_and_column_grand_totals:
609
- totals.append(_extend_grand_totals(row_index, row_total, tci))
610
-
611
- return totals
612
-
613
-
614
- def _extend_grand_totals(row_index: int, row_total: VisualizationTotal, tci: TotalsComputeInfo) -> TotalDefinition:
615
- # Extend grand totals payload
616
- row_dim = [TotalDimension(idx=0, items=tci.measure_group_rows)] if tci.measure_group_rows else []
617
- col_dim = [TotalDimension(idx=1, items=tci.measure_group_cols)] if tci.measure_group_cols else []
570
+ def _extend_marginal_totals(col_index: int, row_total: VisualizationTotal, tci: TotalsComputeInfo) -> TotalDefinition:
571
+ # Extend marginal totals payload
618
572
  return TotalDefinition(
619
- local_id=_grand_total_local_identifier(row_total, row_index),
573
+ local_id=_marginal_total_local_identifier(row_total, col_index),
620
574
  aggregation=row_total.type,
621
575
  metric_local_id=row_total.measure_id,
622
- total_dims=row_dim + col_dim,
576
+ total_dims=[
577
+ TotalDimension(
578
+ idx=0,
579
+ items=tci.row_attr_ids[: tci.row_dimension_index] + tci.measure_group_rows,
580
+ ),
581
+ TotalDimension(
582
+ idx=1,
583
+ items=tci.col_attr_ids[: tci.column_dimension_index] + tci.measure_group_cols,
584
+ ),
585
+ ],
623
586
  )
624
587
 
625
588
 
@@ -663,23 +626,60 @@ def _extend_marginal_totals_of_rows(
663
626
  )
664
627
 
665
628
 
666
- def _extend_marginal_totals(col_index: int, row_total: VisualizationTotal, tci: TotalsComputeInfo) -> TotalDefinition:
667
- # Extend marginal totals payload
629
+ def _extend_grand_totals(row_index: int, row_total: VisualizationTotal, tci: TotalsComputeInfo) -> TotalDefinition:
630
+ # Extend grand totals payload
631
+ row_dim = [TotalDimension(idx=0, items=tci.measure_group_rows)] if tci.measure_group_rows else []
632
+ col_dim = [TotalDimension(idx=1, items=tci.measure_group_cols)] if tci.measure_group_cols else []
668
633
  return TotalDefinition(
669
- local_id=_marginal_total_local_identifier(row_total, col_index),
634
+ local_id=_grand_total_local_identifier(row_total, row_index),
670
635
  aggregation=row_total.type,
671
636
  metric_local_id=row_total.measure_id,
672
- total_dims=[
673
- TotalDimension(
674
- idx=0,
675
- items=tci.row_attr_ids[: tci.row_dimension_index] + tci.measure_group_rows,
676
- ),
677
- TotalDimension(
678
- idx=1,
679
- items=tci.col_attr_ids[: tci.column_dimension_index] + tci.measure_group_cols,
680
- ),
681
- ],
637
+ total_dims=row_dim + col_dim,
638
+ )
639
+
640
+
641
+ def _get_additional_totals(visualization: Visualization, dimensions: list[TableDimension]) -> list[TotalDefinition]:
642
+ """Construct special cases of pivot table totals.
643
+
644
+ These special cases are -
645
+ 1. Grand totals - is the value obtained from row and column totals.
646
+ 2. Marginal totals - is the value obtained within the subgroups from row and column subtotals.
647
+
648
+ For `Grand Totals`, in Tiger AFM, you specify that you want total of totals with total that have only
649
+ "measureGroup" present.
650
+ For `Marginal Total`, in Tiger AFM, would need to iterate through both dimensions and obtain the missing
651
+ totalDimensions items based on the attribute and column identifiers order in buckets.
652
+ """
653
+ totals: list[TotalDefinition] = []
654
+ row_bucket = visualization.get_bucket_of_type(BucketType.ROWS)
655
+ col_bucket = visualization.get_bucket_of_type(BucketType.COLS)
656
+
657
+ tci = TotalsComputeInfo(
658
+ row_attr_ids=[a.local_id for a in row_bucket.attributes],
659
+ col_attr_ids=[a.local_id for a in col_bucket.attributes],
660
+ measure_group_rows=[_MEASURE_GROUP_IDENTIFIER] if _MEASURE_GROUP_IDENTIFIER in dimensions[0].item_ids else [],
661
+ measure_group_cols=[_MEASURE_GROUP_IDENTIFIER] if _MEASURE_GROUP_IDENTIFIER in dimensions[1].item_ids else [],
682
662
  )
663
+ for row_index, row_total in enumerate(row_bucket.totals):
664
+ tci.reset_to_defaults()
665
+ for col_index, col_total in enumerate(col_bucket.totals):
666
+ # Check for totals from same measure and type
667
+ if row_total.measure_id == col_total.measure_id and row_total.type == col_total.type:
668
+ tci.update_compute_info(col_total.attribute_id, row_total.attribute_id)
669
+
670
+ if tci.has_row_and_column_sub_totals:
671
+ totals.append(_extend_marginal_totals(col_index, row_total, tci))
672
+
673
+ if tci.has_row_subtotal_and_column_grand_total:
674
+ totals.append(_extend_marginal_totals_of_rows(row_index, row_total, tci))
675
+
676
+ if tci.has_column_subtotal_and_row_grand_total:
677
+ totals.append(_extend_marginal_totals_of_cols(row_index, row_total, tci))
678
+
679
+ if tci.has_row_and_column_grand_totals:
680
+ totals.append(_extend_grand_totals(row_index, row_total, tci))
681
+
682
+ return totals
683
683
 
684
684
 
685
685
  def _get_computable_totals(visualization: Visualization, dimensions: list[TableDimension]) -> list[TotalDefinition]: