gooddata-pipelines 1.50.0__py3-none-any.whl → 1.50.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.

Potentially problematic release.


This version of gooddata-pipelines might be problematic. Click here for more details.

@@ -11,6 +11,7 @@ from gooddata_sdk.catalog.user.entity_model.user_group import CatalogUserGroup
11
11
  from gooddata_pipelines.provisioning.entities.users.models.users import (
12
12
  UserFullLoad,
13
13
  UserIncrementalLoad,
14
+ UserProfile,
14
15
  )
15
16
  from gooddata_pipelines.provisioning.provisioning import Provisioning
16
17
  from gooddata_pipelines.provisioning.utils.context_objects import UserContext
@@ -30,6 +31,8 @@ class UserProvisioner(Provisioning[UserFullLoad, UserIncrementalLoad]):
30
31
  source_group_incremental: list[UserIncrementalLoad]
31
32
  source_group_full: list[UserFullLoad]
32
33
 
34
+ current_user_id: str
35
+
33
36
  FULL_LOAD_TYPE: type[UserFullLoad] = UserFullLoad
34
37
  INCREMENTAL_LOAD_TYPE: type[UserIncrementalLoad] = UserIncrementalLoad
35
38
 
@@ -37,6 +40,19 @@ class UserProvisioner(Provisioning[UserFullLoad, UserIncrementalLoad]):
37
40
  super().__init__(host, token)
38
41
  self.upstream_user_cache: dict[UserId, UserModel] = {}
39
42
 
43
+ def _get_current_user_id(self) -> str:
44
+ """Gets the current user ID."""
45
+
46
+ profile_response = self._api.get_profile()
47
+
48
+ if not profile_response.ok:
49
+ raise Exception("Failed to get current user profile")
50
+
51
+ profile_json = profile_response.json()
52
+ profile = UserProfile.model_validate(profile_json)
53
+
54
+ return profile.user_id
55
+
40
56
  def _try_get_user(
41
57
  self, user: UserModel, model: type[UserModel]
42
58
  ) -> UserModel | None:
@@ -99,6 +115,14 @@ class UserProvisioner(Provisioning[UserFullLoad, UserIncrementalLoad]):
99
115
  for its existence and create it if needed.
100
116
 
101
117
  """
118
+
119
+ if user.user_id == self.current_user_id:
120
+ self.logger.warning(
121
+ f"Skipping creation/update of current user: {user.user_id}. "
122
+ + "Current user should not be modified.",
123
+ )
124
+ return
125
+
102
126
  user_context = UserContext(
103
127
  user_id=user.user_id,
104
128
  user_groups=user.user_groups,
@@ -118,6 +142,13 @@ class UserProvisioner(Provisioning[UserFullLoad, UserIncrementalLoad]):
118
142
 
119
143
  def _delete_user(self, user_id: str) -> None:
120
144
  """Deletes user from the project."""
145
+ if user_id == self.current_user_id:
146
+ self.logger.warning(
147
+ f"Skipping deletion of current user: {user_id}."
148
+ + " Current user should not be deleted.",
149
+ )
150
+ return
151
+
121
152
  try:
122
153
  self._api._sdk.catalog_user.get_user(user_id)
123
154
  except NotFoundException:
@@ -135,6 +166,9 @@ class UserProvisioner(Provisioning[UserFullLoad, UserIncrementalLoad]):
135
166
 
136
167
  def _provision_incremental_load(self) -> None:
137
168
  """Runs the incremental provisioning logic."""
169
+ # Set the current user ID
170
+ self.current_user_id = self._get_current_user_id()
171
+
138
172
  for user in self.source_group_incremental:
139
173
  # Attempt to process each user. On failure, log the error and continue
140
174
  try:
@@ -146,6 +180,10 @@ class UserProvisioner(Provisioning[UserFullLoad, UserIncrementalLoad]):
146
180
 
147
181
  def _provision_full_load(self) -> None:
148
182
  """Runs the full load provisioning logic."""
183
+
184
+ # Set the current user ID
185
+ self.current_user_id = self._get_current_user_id()
186
+
149
187
  # Get all upstream users
150
188
  catalog_upstream_users: list[CatalogUser] = self._api.list_users()
151
189
 
@@ -50,7 +50,7 @@ class Provisioning(Generic[TFullLoadSourceData, TIncrementalSourceData]):
50
50
  ) -> TProvisioning:
51
51
  """Creates a provisioner instance using a GoodData profile file."""
52
52
  content = profile_content(profile, profiles_path)
53
- return cls(**content)
53
+ return cls(host=content["host"], token=content["token"])
54
54
 
55
55
  @staticmethod
56
56
  def _validate_credentials(host: str, token: str) -> None:
@@ -165,5 +165,4 @@ class Provisioning(Generic[TFullLoadSourceData, TIncrementalSourceData]):
165
165
 
166
166
  self.logger.error(exception_message)
167
167
 
168
- if not self.logger.subscribers:
169
- raise Exception(exception_message)
168
+ raise Exception(exception_message)
@@ -0,0 +1,9 @@
1
+ # (C) 2025 GoodData Corporation
2
+
3
+ """
4
+ Utility modules for gooddata-pipelines package.
5
+ """
6
+
7
+ from .rate_limiter import RateLimiter
8
+
9
+ __all__ = ["RateLimiter"]
@@ -0,0 +1,64 @@
1
+ # (C) 2025 GoodData Corporation
2
+
3
+ import time
4
+ import threading
5
+ import functools
6
+ from typing import Callable, Any, Literal
7
+
8
+
9
+ class RateLimiter:
10
+ """
11
+ Rate limiter usable as a decorator and as a context manager.
12
+ - Shared instance decorator: limiter = RateLimiter(); @limiter
13
+ - Per-function decorator: @RateLimiter(calls_per_second=2)
14
+ - Context manager: with RateLimiter(2): ...
15
+ """
16
+
17
+ def __init__(self, calls_per_second: float = 1.0) -> None:
18
+ if calls_per_second <= 0:
19
+ raise ValueError("calls_per_second must be greater than 0")
20
+
21
+ self.calls_per_second = calls_per_second
22
+ self.min_interval = 1.0 / calls_per_second
23
+
24
+ self._lock = threading.Lock()
25
+ self._last_call_time = 0.0
26
+
27
+ def wait_if_needed(self) -> float:
28
+ """Sleep if needed to maintain the rate limit, return actual sleep time."""
29
+ with self._lock:
30
+ now = time.monotonic()
31
+ since_last = now - self._last_call_time
32
+
33
+ if since_last < self.min_interval:
34
+ sleep_time = self.min_interval - since_last
35
+ time.sleep(sleep_time)
36
+ self._last_call_time = time.monotonic()
37
+ return sleep_time
38
+ else:
39
+ self._last_call_time = now
40
+ return 0.0
41
+
42
+ # Decorator support
43
+ def __call__(self, func: Callable[..., Any]) -> Callable[..., Any]:
44
+ @functools.wraps(func)
45
+ def wrapper(*args: Any, **kwargs: Any) -> Any:
46
+ self.wait_if_needed()
47
+ return func(*args, **kwargs)
48
+
49
+ return wrapper
50
+
51
+ # Context manager support
52
+ def __enter__(self) -> "RateLimiter":
53
+ self.wait_if_needed()
54
+ return self
55
+
56
+ def __exit__(
57
+ self, exc_type: Any, exc_val: Any, exc_tb: Any
58
+ ) -> Literal[False]:
59
+ return False
60
+
61
+ def reset(self) -> None:
62
+ """Reset the limiter (useful in tests)."""
63
+ with self._lock:
64
+ self._last_call_time = 0.0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: gooddata-pipelines
3
- Version: 1.50.0
3
+ Version: 1.50.1.dev2
4
4
  Summary: GoodData Cloud lifecycle automation pipelines
5
5
  Author-email: GoodData <support@gooddata.com>
6
6
  License: MIT
@@ -8,7 +8,7 @@ License-File: LICENSE.txt
8
8
  Requires-Python: >=3.10
9
9
  Requires-Dist: boto3-stubs<2.0.0,>=1.39.3
10
10
  Requires-Dist: boto3<2.0.0,>=1.39.3
11
- Requires-Dist: gooddata-sdk~=1.50.0
11
+ Requires-Dist: gooddata-sdk~=1.50.1.dev2
12
12
  Requires-Dist: pydantic<3.0.0,>=2.11.3
13
13
  Requires-Dist: requests<3.0.0,>=2.32.3
14
14
  Requires-Dist: types-pyyaml<7.0.0,>=6.0.12.20250326
@@ -74,4 +74,12 @@ full_load_data: list[UserFullLoad] = UserFullLoad.from_list_of_dicts(
74
74
  provisioner.full_load(full_load_data)
75
75
  ```
76
76
 
77
- Ready-made scripts covering the basic use cases can be found here in the [GoodData Productivity Tools](https://github.com/gooddata/gooddata-productivity-tools) repository
77
+ ## Bugs & Requests
78
+
79
+ Please use the [GitHub issue tracker](https://github.com/gooddata/gooddata-python-sdk/issues) to submit bugs
80
+ or request features.
81
+
82
+ ## Changelog
83
+
84
+ See [Github releases](https://github.com/gooddata/gooddata-python-sdk/releases) for released versions
85
+ and a list of changes.
@@ -1,29 +1,37 @@
1
- gooddata_pipelines/__init__.py,sha256=AEKIRuGBPMA_RkL14RF-recw9hS4dGV8cVqgDM3XmrA,1931
1
+ gooddata_pipelines/__init__.py,sha256=UyE19wWfPh2R_5O0KSAS4XLllP3km3iGkDzRQFBd7jQ,2415
2
2
  gooddata_pipelines/_version.py,sha256=Zi8Ht5ofjFeSYGG5USixQtJNB1po6okh0Rez8VyAsFM,200
3
3
  gooddata_pipelines/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
4
4
  gooddata_pipelines/api/__init__.py,sha256=0WaBI2XMdkkZgnUsQ9kqipNzh2l2zamZvUt_qjp8xCk,106
5
5
  gooddata_pipelines/api/exceptions.py,sha256=rddQXfv8Ktckz7RONKBnKfm53M7dzPCh50Dl1k-8hqs,1545
6
- gooddata_pipelines/api/gooddata_api.py,sha256=ALuxTgu3KOK5S2b0C5HpDyvmT_UNfGeF-eqbvxXhDQM,8667
6
+ gooddata_pipelines/api/gooddata_api.py,sha256=8AZ5-mGGvo_4pPFjaf_DxkzQQqp2dRtiRPTM2sIdfYs,10934
7
7
  gooddata_pipelines/api/gooddata_api_wrapper.py,sha256=t7dFrXJ6X4yXS9XDthOmvd2CyzdnDDNPeIngTEW72YU,1152
8
8
  gooddata_pipelines/api/gooddata_sdk.py,sha256=wd5O4e9BQLWUawt6odrs5a51nqFGthBkvqh9WOiW36Q,13734
9
9
  gooddata_pipelines/api/utils.py,sha256=3QY_aYH17I9THoCINE3l-n5oj52k-gNeT1wv6Z_VxN8,1433
10
10
  gooddata_pipelines/backup_and_restore/__init__.py,sha256=-BG28PGDbalLyZGQjpFG0pjdIvtf25ut0r8ZwZVbi4s,32
11
11
  gooddata_pipelines/backup_and_restore/backup_input_processor.py,sha256=ex1tGwETdHDDBRJ_DGKZsZbH6uoRuOrbGbKOC976H5s,7940
12
- gooddata_pipelines/backup_and_restore/backup_manager.py,sha256=rfNMn6VLul2OjnLmMyy7AL3qaOVuGapyelORvoTOjGA,16012
13
- gooddata_pipelines/backup_and_restore/constants.py,sha256=TYw4hU5hhzDVTLJa0gWseaiSs_VboWsYwW7QsqtJ1hA,939
12
+ gooddata_pipelines/backup_and_restore/backup_manager.py,sha256=kWxhxe5K8_oK3tz2e1RBMpyHHv18_UA_QVlXQeb7UUk,15227
13
+ gooddata_pipelines/backup_and_restore/constants.py,sha256=m8wAYhVGlRlfAgiC_54wJr6N8HDEAT7hIfrH1N2UrQY,884
14
14
  gooddata_pipelines/backup_and_restore/csv_reader.py,sha256=0Kw7mJT7REj3Gjqfsc6YT9MbhcqfCGNB_SKBwzTI1rk,1268
15
15
  gooddata_pipelines/backup_and_restore/models/__init__.py,sha256=-BG28PGDbalLyZGQjpFG0pjdIvtf25ut0r8ZwZVbi4s,32
16
16
  gooddata_pipelines/backup_and_restore/models/input_type.py,sha256=CBKJigKdmZ-NJD9MSfNhq89bo86W0AqCMMoyonbd1QA,239
17
- gooddata_pipelines/backup_and_restore/models/storage.py,sha256=GToCc1M2OlqZJd9NcrIZKsZH_FCD_P_XjdHB4QPtAvo,2791
17
+ gooddata_pipelines/backup_and_restore/models/storage.py,sha256=BcgOGIk4u3EaH0u0gArDHQpDyIPjx_c3fmoc-i_Ptj4,2795
18
18
  gooddata_pipelines/backup_and_restore/models/workspace_response.py,sha256=eQbYLgRQc17IRG0yPTAJVrD-Xs05SzuwtzoNrPT2DoY,833
19
19
  gooddata_pipelines/backup_and_restore/storage/__init__.py,sha256=-BG28PGDbalLyZGQjpFG0pjdIvtf25ut0r8ZwZVbi4s,32
20
20
  gooddata_pipelines/backup_and_restore/storage/base_storage.py,sha256=67wdItlG3neExeb_eCUDQhswdUB62X5Nyj9sOImB_Hg,487
21
21
  gooddata_pipelines/backup_and_restore/storage/local_storage.py,sha256=NvhPRzRAvuSpc5qCDyPqZaMB0i1jeZOZczaSwjUSGEg,1155
22
22
  gooddata_pipelines/backup_and_restore/storage/s3_storage.py,sha256=ZAysu4sPMAvdWs3RUroHHp2XZLHeU_LhJ5qBHlBQ7n4,3732
23
+ gooddata_pipelines/ldm_extension/__init__.py,sha256=-BG28PGDbalLyZGQjpFG0pjdIvtf25ut0r8ZwZVbi4s,32
24
+ gooddata_pipelines/ldm_extension/input_processor.py,sha256=lNIx6YfU4OJpSLyAitCoPwwf6eFIT6OyivRnqYX5O-o,11678
25
+ gooddata_pipelines/ldm_extension/input_validator.py,sha256=sAl-tixrS69G_lP19U9CjKHiWZinXOcjeAqwiydVctQ,7459
26
+ gooddata_pipelines/ldm_extension/ldm_extension_manager.py,sha256=XHNBMAaiUvIzBib3zz9mYcmGk6YOkIhqrxYfQaV9s9Q,11483
27
+ gooddata_pipelines/ldm_extension/models/__init__.py,sha256=-BG28PGDbalLyZGQjpFG0pjdIvtf25ut0r8ZwZVbi4s,32
28
+ gooddata_pipelines/ldm_extension/models/aliases.py,sha256=vmac3fGhTjGQqclW3Be42kE-ooC3ZBtYS8JqpXmBy_g,231
29
+ gooddata_pipelines/ldm_extension/models/analytical_object.py,sha256=biWgRdczuF-IRz7zQNWrWAWmc-r7_OpSdDJA7klI7ME,913
30
+ gooddata_pipelines/ldm_extension/models/custom_data_object.py,sha256=wH2ZrgjKiuFCDB2BTUntyGbEw-oFuwtaepYKdtSwgHY,2771
23
31
  gooddata_pipelines/logger/__init__.py,sha256=W-fJvMStnsDUY52AYFhx_LnS2cSCFNf3bB47Iew2j04,129
24
32
  gooddata_pipelines/logger/logger.py,sha256=yIMdvqsmOSGQLI4U_tQwxX5E2q_FXUu0Ko7Hv39slFM,3549
25
33
  gooddata_pipelines/provisioning/__init__.py,sha256=RZDEiv8nla4Jwa2TZXUdp1NSxg2_-lLqz4h7k2c4v5Y,854
26
- gooddata_pipelines/provisioning/provisioning.py,sha256=Mibf1-ZwPfHzmoAjgIRuYvtakY7LqerDTF36FgPg990,6175
34
+ gooddata_pipelines/provisioning/provisioning.py,sha256=UUHClT0q6O1XDAgiR2M23eFgtU3uEFBp87-b13-m97I,6166
27
35
  gooddata_pipelines/provisioning/assets/wdf_setting.json,sha256=nxOLGZkEQiMdARcUDER5ygqr3Zu-MQlLlUyXVhPUq64,280
28
36
  gooddata_pipelines/provisioning/entities/__init__.py,sha256=-BG28PGDbalLyZGQjpFG0pjdIvtf25ut0r8ZwZVbi4s,32
29
37
  gooddata_pipelines/provisioning/entities/user_data_filters/__init__.py,sha256=-BG28PGDbalLyZGQjpFG0pjdIvtf25ut0r8ZwZVbi4s,32
@@ -33,11 +41,11 @@ gooddata_pipelines/provisioning/entities/user_data_filters/models/udf_models.py,
33
41
  gooddata_pipelines/provisioning/entities/users/__init__.py,sha256=-BG28PGDbalLyZGQjpFG0pjdIvtf25ut0r8ZwZVbi4s,32
34
42
  gooddata_pipelines/provisioning/entities/users/permissions.py,sha256=2k3oPI7WyABcD2TMmLPsMUDrAjnKM7Vw56kz_RWhcmI,7135
35
43
  gooddata_pipelines/provisioning/entities/users/user_groups.py,sha256=-2Nca01ZMjXmnAGDUuKP5G7mqFyn4MnsgZsnS2oy7vg,8511
36
- gooddata_pipelines/provisioning/entities/users/users.py,sha256=TVfOp3fqQYmzA4K03IBGNYJrqGQAzWH_oay0qsvR8Xo,6633
44
+ gooddata_pipelines/provisioning/entities/users/users.py,sha256=BPTbE0-lvwkgoTVwLUbMqmlq7L597nwRCSK5FaM8F4I,7730
37
45
  gooddata_pipelines/provisioning/entities/users/models/__init__.py,sha256=-BG28PGDbalLyZGQjpFG0pjdIvtf25ut0r8ZwZVbi4s,32
38
46
  gooddata_pipelines/provisioning/entities/users/models/permissions.py,sha256=buyNtDShvAJL4mFZSV-UqK_9JAL_2-AaIlGYCHibhHo,7244
39
47
  gooddata_pipelines/provisioning/entities/users/models/user_groups.py,sha256=Odp4yZoK2vC40jgh7FBKmaIINpwffl62uoaT8Xxr-14,1160
40
- gooddata_pipelines/provisioning/entities/users/models/users.py,sha256=lwb8Q-slBELs_0882KOumkMgKiFKCL3ZABONsoT5Nw0,2234
48
+ gooddata_pipelines/provisioning/entities/users/models/users.py,sha256=hR5on68NEpw3KAPooR3Z1TRUzV5nbp0jrrOLUDW8P24,2424
41
49
  gooddata_pipelines/provisioning/entities/workspaces/__init__.py,sha256=-BG28PGDbalLyZGQjpFG0pjdIvtf25ut0r8ZwZVbi4s,32
42
50
  gooddata_pipelines/provisioning/entities/workspaces/models.py,sha256=-ehte9HLNos3l6yLip4mZU6wBcmY_Yzwq0t0m0fhwPI,2031
43
51
  gooddata_pipelines/provisioning/entities/workspaces/workspace.py,sha256=jngaEKNlMfhjRr4rQ2ECQDoh0gk7KaZTMuTazPLECnM,11505
@@ -48,7 +56,9 @@ gooddata_pipelines/provisioning/utils/__init__.py,sha256=-BG28PGDbalLyZGQjpFG0pj
48
56
  gooddata_pipelines/provisioning/utils/context_objects.py,sha256=HJoeumH_gXwM6X-GO3HkC4w-6RYozz6-aqQOhDnu7no,879
49
57
  gooddata_pipelines/provisioning/utils/exceptions.py,sha256=1WnAOlPhqOf0xRcvn70lxAlLb8Oo6m6WCYS4hj9uzDU,3630
50
58
  gooddata_pipelines/provisioning/utils/utils.py,sha256=uF3k5hmoM5d6UoWWfPGCQgT_861zcU-ACyaQHHOOncY,2434
51
- gooddata_pipelines-1.50.0.dist-info/METADATA,sha256=CeJDooBpPypFs18Un0bz0MjSUYhsFJWGddlkwaOpD98,3512
52
- gooddata_pipelines-1.50.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
53
- gooddata_pipelines-1.50.0.dist-info/licenses/LICENSE.txt,sha256=PNC7WXGIo6OKkNoPLRxlVrw6jaLcjSTUsSxy9Xcu9Jo,560365
54
- gooddata_pipelines-1.50.0.dist-info/RECORD,,
59
+ gooddata_pipelines/utils/__init__.py,sha256=s9TtSjKqo1gSGWOVoGrXaGi1TsbRowjRDYKtjmKy7BY,155
60
+ gooddata_pipelines/utils/rate_limiter.py,sha256=owbcEZhUxlTnE7rRHiWQ8XBC-vML2fVPbt41EeGEM7o,2002
61
+ gooddata_pipelines-1.50.1.dev2.dist-info/METADATA,sha256=7ikqYG7cNfxQm5dBgWO1PcPvGqANtyYj6MoX2BQQsVA,3642
62
+ gooddata_pipelines-1.50.1.dev2.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
63
+ gooddata_pipelines-1.50.1.dev2.dist-info/licenses/LICENSE.txt,sha256=PNC7WXGIo6OKkNoPLRxlVrw6jaLcjSTUsSxy9Xcu9Jo,560365
64
+ gooddata_pipelines-1.50.1.dev2.dist-info/RECORD,,