ayon-python-api 1.2.0__tar.gz → 1.2.1__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 (45) hide show
  1. {ayon-python-api-1.2.0 → ayon-python-api-1.2.1}/PKG-INFO +1 -1
  2. ayon-python-api-1.2.1/ayon_api/_api_helpers/__init__.py +42 -0
  3. ayon-python-api-1.2.1/ayon_api/_api_helpers/actions.py +303 -0
  4. ayon-python-api-1.2.1/ayon_api/_api_helpers/activities.py +295 -0
  5. ayon-python-api-1.2.1/ayon_api/_api_helpers/attributes.py +159 -0
  6. ayon-python-api-1.2.1/ayon_api/_api_helpers/base.py +130 -0
  7. ayon-python-api-1.2.1/ayon_api/_api_helpers/bundles_addons.py +885 -0
  8. ayon-python-api-1.2.1/ayon_api/_api_helpers/dependency_packages.py +236 -0
  9. ayon-python-api-1.2.1/ayon_api/_api_helpers/events.py +440 -0
  10. ayon-python-api-1.2.1/ayon_api/_api_helpers/folders.py +638 -0
  11. ayon-python-api-1.2.1/ayon_api/_api_helpers/installers.py +174 -0
  12. ayon-python-api-1.2.1/ayon_api/_api_helpers/links.py +661 -0
  13. ayon-python-api-1.2.1/ayon_api/_api_helpers/lists.py +417 -0
  14. ayon-python-api-1.2.1/ayon_api/_api_helpers/products.py +504 -0
  15. ayon-python-api-1.2.1/ayon_api/_api_helpers/projects.py +737 -0
  16. ayon-python-api-1.2.1/ayon_api/_api_helpers/representations.py +747 -0
  17. ayon-python-api-1.2.1/ayon_api/_api_helpers/secrets.py +81 -0
  18. ayon-python-api-1.2.1/ayon_api/_api_helpers/tasks.py +515 -0
  19. ayon-python-api-1.2.1/ayon_api/_api_helpers/thumbnails.py +307 -0
  20. ayon-python-api-1.2.1/ayon_api/_api_helpers/versions.py +640 -0
  21. ayon-python-api-1.2.1/ayon_api/_api_helpers/workfiles.py +265 -0
  22. {ayon-python-api-1.2.0 → ayon-python-api-1.2.1}/ayon_api/version.py +1 -1
  23. {ayon-python-api-1.2.0 → ayon-python-api-1.2.1}/ayon_python_api.egg-info/PKG-INFO +1 -1
  24. ayon-python-api-1.2.1/ayon_python_api.egg-info/SOURCES.txt +42 -0
  25. {ayon-python-api-1.2.0 → ayon-python-api-1.2.1}/pyproject.toml +4 -3
  26. {ayon-python-api-1.2.0 → ayon-python-api-1.2.1}/setup.py +1 -1
  27. ayon-python-api-1.2.0/ayon_python_api.egg-info/SOURCES.txt +0 -22
  28. {ayon-python-api-1.2.0 → ayon-python-api-1.2.1}/LICENSE +0 -0
  29. {ayon-python-api-1.2.0 → ayon-python-api-1.2.1}/README.md +0 -0
  30. {ayon-python-api-1.2.0 → ayon-python-api-1.2.1}/ayon_api/__init__.py +0 -0
  31. {ayon-python-api-1.2.0 → ayon-python-api-1.2.1}/ayon_api/_api.py +0 -0
  32. {ayon-python-api-1.2.0 → ayon-python-api-1.2.1}/ayon_api/constants.py +0 -0
  33. {ayon-python-api-1.2.0 → ayon-python-api-1.2.1}/ayon_api/entity_hub.py +0 -0
  34. {ayon-python-api-1.2.0 → ayon-python-api-1.2.1}/ayon_api/events.py +0 -0
  35. {ayon-python-api-1.2.0 → ayon-python-api-1.2.1}/ayon_api/exceptions.py +0 -0
  36. {ayon-python-api-1.2.0 → ayon-python-api-1.2.1}/ayon_api/graphql.py +0 -0
  37. {ayon-python-api-1.2.0 → ayon-python-api-1.2.1}/ayon_api/graphql_queries.py +0 -0
  38. {ayon-python-api-1.2.0 → ayon-python-api-1.2.1}/ayon_api/operations.py +0 -0
  39. {ayon-python-api-1.2.0 → ayon-python-api-1.2.1}/ayon_api/server_api.py +0 -0
  40. {ayon-python-api-1.2.0 → ayon-python-api-1.2.1}/ayon_api/typing.py +0 -0
  41. {ayon-python-api-1.2.0 → ayon-python-api-1.2.1}/ayon_api/utils.py +0 -0
  42. {ayon-python-api-1.2.0 → ayon-python-api-1.2.1}/ayon_python_api.egg-info/dependency_links.txt +0 -0
  43. {ayon-python-api-1.2.0 → ayon-python-api-1.2.1}/ayon_python_api.egg-info/requires.txt +0 -0
  44. {ayon-python-api-1.2.0 → ayon-python-api-1.2.1}/ayon_python_api.egg-info/top_level.txt +0 -0
  45. {ayon-python-api-1.2.0 → ayon-python-api-1.2.1}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: ayon-python-api
3
- Version: 1.2.0
3
+ Version: 1.2.1
4
4
  Summary: AYON Python API
5
5
  Home-page: https://github.com/ynput/ayon-python-api
6
6
  Author: ynput.io
@@ -0,0 +1,42 @@
1
+ from .base import BaseServerAPI
2
+ from .installers import InstallersAPI
3
+ from .dependency_packages import DependencyPackagesAPI
4
+ from .secrets import SecretsAPI
5
+ from .bundles_addons import BundlesAddonsAPI
6
+ from .events import EventsAPI
7
+ from .attributes import AttributesAPI
8
+ from .projects import ProjectsAPI
9
+ from .folders import FoldersAPI
10
+ from .tasks import TasksAPI
11
+ from .products import ProductsAPI
12
+ from .versions import VersionsAPI
13
+ from .representations import RepresentationsAPI
14
+ from .workfiles import WorkfilesAPI
15
+ from .thumbnails import ThumbnailsAPI
16
+ from .activities import ActivitiesAPI
17
+ from .actions import ActionsAPI
18
+ from .links import LinksAPI
19
+ from .lists import ListsAPI
20
+
21
+
22
+ __all__ = (
23
+ "BaseServerAPI",
24
+ "InstallersAPI",
25
+ "DependencyPackagesAPI",
26
+ "SecretsAPI",
27
+ "BundlesAddonsAPI",
28
+ "EventsAPI",
29
+ "AttributesAPI",
30
+ "ProjectsAPI",
31
+ "FoldersAPI",
32
+ "TasksAPI",
33
+ "ProductsAPI",
34
+ "VersionsAPI",
35
+ "RepresentationsAPI",
36
+ "WorkfilesAPI",
37
+ "ThumbnailsAPI",
38
+ "ActivitiesAPI",
39
+ "ActionsAPI",
40
+ "LinksAPI",
41
+ "ListsAPI",
42
+ )
@@ -0,0 +1,303 @@
1
+ from __future__ import annotations
2
+
3
+ import typing
4
+ from typing import Optional, Any
5
+
6
+ from ayon_api.utils import prepare_query_string
7
+
8
+ from .base import BaseServerAPI
9
+
10
+ if typing.TYPE_CHECKING:
11
+ from ayon_api.typing import (
12
+ ActionEntityTypes,
13
+ ActionManifestDict,
14
+ ActionTriggerResponse,
15
+ ActionTakeResponse,
16
+ ActionConfigResponse,
17
+ ActionModeType,
18
+ )
19
+
20
+
21
+ class ActionsAPI(BaseServerAPI):
22
+ """Implementation of actions API for ServerAPI."""
23
+ def get_actions(
24
+ self,
25
+ project_name: Optional[str] = None,
26
+ entity_type: Optional[ActionEntityTypes] = None,
27
+ entity_ids: Optional[list[str]] = None,
28
+ entity_subtypes: Optional[list[str]] = None,
29
+ form_data: Optional[dict[str, Any]] = None,
30
+ *,
31
+ variant: Optional[str] = None,
32
+ mode: Optional[ActionModeType] = None,
33
+ ) -> list[ActionManifestDict]:
34
+ """Get actions for a context.
35
+
36
+ Args:
37
+ project_name (Optional[str]): Name of the project. None for global
38
+ actions.
39
+ entity_type (Optional[ActionEntityTypes]): Entity type where the
40
+ action is triggered. None for global actions.
41
+ entity_ids (Optional[list[str]]): list of entity ids where the
42
+ action is triggered. None for global actions.
43
+ entity_subtypes (Optional[list[str]]): list of entity subtypes
44
+ folder types for folder ids, task types for tasks ids.
45
+ form_data (Optional[dict[str, Any]]): Form data of the action.
46
+ variant (Optional[str]): Settings variant.
47
+ mode (Optional[ActionModeType]): Action modes.
48
+
49
+ Returns:
50
+ list[ActionManifestDict]: list of action manifests.
51
+
52
+ """
53
+ if variant is None:
54
+ variant = self.get_default_settings_variant()
55
+ query_data = {"variant": variant}
56
+ if mode:
57
+ query_data["mode"] = mode
58
+ query = prepare_query_string(query_data)
59
+ kwargs = {
60
+ key: value
61
+ for key, value in (
62
+ ("projectName", project_name),
63
+ ("entityType", entity_type),
64
+ ("entityIds", entity_ids),
65
+ ("entitySubtypes", entity_subtypes),
66
+ ("formData", form_data),
67
+ )
68
+ if value is not None
69
+ }
70
+ response = self.post(f"actions/list{query}", **kwargs)
71
+ response.raise_for_status()
72
+ return response.data["actions"]
73
+
74
+ def trigger_action(
75
+ self,
76
+ identifier: str,
77
+ addon_name: str,
78
+ addon_version: str,
79
+ project_name: Optional[str] = None,
80
+ entity_type: Optional[ActionEntityTypes] = None,
81
+ entity_ids: Optional[list[str]] = None,
82
+ entity_subtypes: Optional[list[str]] = None,
83
+ form_data: Optional[dict[str, Any]] = None,
84
+ *,
85
+ variant: Optional[str] = None,
86
+ ) -> ActionTriggerResponse:
87
+ """Trigger action.
88
+
89
+ Args:
90
+ identifier (str): Identifier of the action.
91
+ addon_name (str): Name of the addon.
92
+ addon_version (str): Version of the addon.
93
+ project_name (Optional[str]): Name of the project. None for global
94
+ actions.
95
+ entity_type (Optional[ActionEntityTypes]): Entity type where the
96
+ action is triggered. None for global actions.
97
+ entity_ids (Optional[list[str]]): list of entity ids where the
98
+ action is triggered. None for global actions.
99
+ entity_subtypes (Optional[list[str]]): list of entity subtypes
100
+ folder types for folder ids, task types for tasks ids.
101
+ form_data (Optional[dict[str, Any]]): Form data of the action.
102
+ variant (Optional[str]): Settings variant.
103
+
104
+ """
105
+ if variant is None:
106
+ variant = self.get_default_settings_variant()
107
+ query_data = {
108
+ "addonName": addon_name,
109
+ "addonVersion": addon_version,
110
+ "identifier": identifier,
111
+ "variant": variant,
112
+ }
113
+ query = prepare_query_string(query_data)
114
+
115
+ kwargs = {
116
+ key: value
117
+ for key, value in (
118
+ ("projectName", project_name),
119
+ ("entityType", entity_type),
120
+ ("entityIds", entity_ids),
121
+ ("entitySubtypes", entity_subtypes),
122
+ ("formData", form_data),
123
+ )
124
+ if value is not None
125
+ }
126
+
127
+ response = self.post(f"actions/execute{query}", **kwargs)
128
+ response.raise_for_status()
129
+ return response.data
130
+
131
+ def get_action_config(
132
+ self,
133
+ identifier: str,
134
+ addon_name: str,
135
+ addon_version: str,
136
+ project_name: Optional[str] = None,
137
+ entity_type: Optional[ActionEntityTypes] = None,
138
+ entity_ids: Optional[list[str]] = None,
139
+ entity_subtypes: Optional[list[str]] = None,
140
+ form_data: Optional[dict[str, Any]] = None,
141
+ *,
142
+ variant: Optional[str] = None,
143
+ ) -> ActionConfigResponse:
144
+ """Get action configuration.
145
+
146
+ Args:
147
+ identifier (str): Identifier of the action.
148
+ addon_name (str): Name of the addon.
149
+ addon_version (str): Version of the addon.
150
+ project_name (Optional[str]): Name of the project. None for global
151
+ actions.
152
+ entity_type (Optional[ActionEntityTypes]): Entity type where the
153
+ action is triggered. None for global actions.
154
+ entity_ids (Optional[list[str]]): list of entity ids where the
155
+ action is triggered. None for global actions.
156
+ entity_subtypes (Optional[list[str]]): list of entity subtypes
157
+ folder types for folder ids, task types for tasks ids.
158
+ form_data (Optional[dict[str, Any]]): Form data of the action.
159
+ variant (Optional[str]): Settings variant.
160
+
161
+ Returns:
162
+ ActionConfigResponse: Action configuration data.
163
+
164
+ """
165
+ return self._send_config_request(
166
+ identifier,
167
+ addon_name,
168
+ addon_version,
169
+ None,
170
+ project_name,
171
+ entity_type,
172
+ entity_ids,
173
+ entity_subtypes,
174
+ form_data,
175
+ variant,
176
+ )
177
+
178
+ def set_action_config(
179
+ self,
180
+ identifier: str,
181
+ addon_name: str,
182
+ addon_version: str,
183
+ value: dict[str, Any],
184
+ project_name: Optional[str] = None,
185
+ entity_type: Optional[ActionEntityTypes] = None,
186
+ entity_ids: Optional[list[str]] = None,
187
+ entity_subtypes: Optional[list[str]] = None,
188
+ form_data: Optional[dict[str, Any]] = None,
189
+ *,
190
+ variant: Optional[str] = None,
191
+ ) -> ActionConfigResponse:
192
+ """Set action configuration.
193
+
194
+ Args:
195
+ identifier (str): Identifier of the action.
196
+ addon_name (str): Name of the addon.
197
+ addon_version (str): Version of the addon.
198
+ value (Optional[dict[str, Any]]): Value of the action
199
+ configuration.
200
+ project_name (Optional[str]): Name of the project. None for global
201
+ actions.
202
+ entity_type (Optional[ActionEntityTypes]): Entity type where the
203
+ action is triggered. None for global actions.
204
+ entity_ids (Optional[list[str]]): list of entity ids where the
205
+ action is triggered. None for global actions.
206
+ entity_subtypes (Optional[list[str]]): list of entity subtypes
207
+ folder types for folder ids, task types for tasks ids.
208
+ form_data (Optional[dict[str, Any]]): Form data of the action.
209
+ variant (Optional[str]): Settings variant.
210
+
211
+ Returns:
212
+ ActionConfigResponse: New action configuration data.
213
+
214
+ """
215
+ return self._send_config_request(
216
+ identifier,
217
+ addon_name,
218
+ addon_version,
219
+ value,
220
+ project_name,
221
+ entity_type,
222
+ entity_ids,
223
+ entity_subtypes,
224
+ form_data,
225
+ variant,
226
+ )
227
+
228
+ def take_action(self, action_token: str) -> ActionTakeResponse:
229
+ """Take action metadata using an action token.
230
+
231
+ Args:
232
+ action_token (str): AYON launcher action token.
233
+
234
+ Returns:
235
+ ActionTakeResponse: Action metadata describing how to launch
236
+ action.
237
+
238
+ """
239
+ response = self.get(f"actions/abort/{action_token}")
240
+ response.raise_for_status()
241
+ return response.data
242
+
243
+ def abort_action(
244
+ self,
245
+ action_token: str,
246
+ message: Optional[str] = None,
247
+ ) -> None:
248
+ """Abort action using an action token.
249
+
250
+ Args:
251
+ action_token (str): AYON launcher action token.
252
+ message (Optional[str]): Message to display in the UI.
253
+
254
+ """
255
+ if message is None:
256
+ message = "Action aborted"
257
+ response = self.post(
258
+ f"actions/abort/{action_token}",
259
+ message=message,
260
+ )
261
+ response.raise_for_status()
262
+
263
+ def _send_config_request(
264
+ self,
265
+ identifier: str,
266
+ addon_name: str,
267
+ addon_version: str,
268
+ value: Optional[dict[str, Any]],
269
+ project_name: Optional[str],
270
+ entity_type: Optional[ActionEntityTypes],
271
+ entity_ids: Optional[list[str]],
272
+ entity_subtypes: Optional[list[str]],
273
+ form_data: Optional[dict[str, Any]],
274
+ variant: Optional[str],
275
+ ) -> ActionConfigResponse:
276
+ """Set and get action configuration."""
277
+ if variant is None:
278
+ variant = self.get_default_settings_variant()
279
+ query_data = {
280
+ "addonName": addon_name,
281
+ "addonVersion": addon_version,
282
+ "identifier": identifier,
283
+ "variant": variant,
284
+ }
285
+ query = prepare_query_string(query_data)
286
+
287
+ kwargs = {
288
+ query_key: query_value
289
+ for query_key, query_value in (
290
+ ("projectName", project_name),
291
+ ("entityType", entity_type),
292
+ ("entityIds", entity_ids),
293
+ ("entitySubtypes", entity_subtypes),
294
+ ("formData", form_data),
295
+ )
296
+ if query_value is not None
297
+ }
298
+ if value is not None:
299
+ kwargs["value"] = value
300
+
301
+ response = self.post(f"actions/config{query}", **kwargs)
302
+ response.raise_for_status()
303
+ return response.data
@@ -0,0 +1,295 @@
1
+ from __future__ import annotations
2
+
3
+ import json
4
+ import typing
5
+ from typing import Optional, Iterable, Generator, Any
6
+
7
+ from ayon_api.utils import (
8
+ SortOrder,
9
+ prepare_list_filters,
10
+ )
11
+ from ayon_api.graphql_queries import activities_graphql_query
12
+
13
+ from .base import BaseServerAPI
14
+
15
+ if typing.TYPE_CHECKING:
16
+ from ayon_api.typing import (
17
+ ActivityType,
18
+ ActivityReferenceType,
19
+ )
20
+
21
+
22
+ class ActivitiesAPI(BaseServerAPI):
23
+ def get_activities(
24
+ self,
25
+ project_name: str,
26
+ activity_ids: Optional[Iterable[str]] = None,
27
+ activity_types: Optional[Iterable[ActivityType]] = None,
28
+ entity_ids: Optional[Iterable[str]] = None,
29
+ entity_names: Optional[Iterable[str]] = None,
30
+ entity_type: Optional[str] = None,
31
+ changed_after: Optional[str] = None,
32
+ changed_before: Optional[str] = None,
33
+ reference_types: Optional[Iterable[ActivityReferenceType]] = None,
34
+ fields: Optional[Iterable[str]] = None,
35
+ limit: Optional[int] = None,
36
+ order: Optional[SortOrder] = None,
37
+ ) -> Generator[dict[str, Any], None, None]:
38
+ """Get activities from server with filtering options.
39
+
40
+ Args:
41
+ project_name (str): Project on which activities happened.
42
+ activity_ids (Optional[Iterable[str]]): Activity ids.
43
+ activity_types (Optional[Iterable[ActivityType]]): Activity types.
44
+ entity_ids (Optional[Iterable[str]]): Entity ids.
45
+ entity_names (Optional[Iterable[str]]): Entity names.
46
+ entity_type (Optional[str]): Entity type.
47
+ changed_after (Optional[str]): Return only activities changed
48
+ after given iso datetime string.
49
+ changed_before (Optional[str]): Return only activities changed
50
+ before given iso datetime string.
51
+ reference_types (Optional[Iterable[ActivityReferenceType]]):
52
+ Reference types filter. Defaults to `['origin']`.
53
+ fields (Optional[Iterable[str]]): Fields that should be received
54
+ for each activity.
55
+ limit (Optional[int]): Limit number of activities to be fetched.
56
+ order (Optional[SortOrder]): Order activities in ascending
57
+ or descending order. It is recommended to set 'limit'
58
+ when used descending.
59
+
60
+ Returns:
61
+ Generator[dict[str, Any]]: Available activities matching filters.
62
+
63
+ """
64
+ if not project_name:
65
+ return
66
+ filters = {
67
+ "projectName": project_name,
68
+ }
69
+ if reference_types is None:
70
+ reference_types = {"origin"}
71
+
72
+ if not prepare_list_filters(
73
+ filters,
74
+ ("activityIds", activity_ids),
75
+ ("activityTypes", activity_types),
76
+ ("entityIds", entity_ids),
77
+ ("entityNames", entity_names),
78
+ ("referenceTypes", reference_types),
79
+ ):
80
+ return
81
+
82
+ for filter_key, filter_value in (
83
+ ("entityType", entity_type),
84
+ ("changedAfter", changed_after),
85
+ ("changedBefore", changed_before),
86
+ ):
87
+ if filter_value is not None:
88
+ filters[filter_key] = filter_value
89
+
90
+ if not fields:
91
+ fields = self.get_default_fields_for_type("activity")
92
+
93
+ query = activities_graphql_query(set(fields), order)
94
+ for attr, filter_value in filters.items():
95
+ query.set_variable_value(attr, filter_value)
96
+
97
+ if limit:
98
+ activities_field = query.get_field_by_path("activities")
99
+ activities_field.set_limit(limit)
100
+
101
+ for parsed_data in query.continuous_query(self):
102
+ for activity in parsed_data["project"]["activities"]:
103
+ activity_data = activity.get("activityData")
104
+ if isinstance(activity_data, str):
105
+ activity["activityData"] = json.loads(activity_data)
106
+ yield activity
107
+
108
+ def get_activity_by_id(
109
+ self,
110
+ project_name: str,
111
+ activity_id: str,
112
+ reference_types: Optional[Iterable[ActivityReferenceType]] = None,
113
+ fields: Optional[Iterable[str]] = None,
114
+ ) -> Optional[dict[str, Any]]:
115
+ """Get activity by id.
116
+
117
+ Args:
118
+ project_name (str): Project on which activity happened.
119
+ activity_id (str): Activity id.
120
+ reference_types: Optional[Iterable[ActivityReferenceType]]: Filter
121
+ by reference types.
122
+ fields (Optional[Iterable[str]]): Fields that should be received
123
+ for each activity.
124
+
125
+ Returns:
126
+ Optional[dict[str, Any]]: Activity data or None if activity is not
127
+ found.
128
+
129
+ """
130
+ for activity in self.get_activities(
131
+ project_name=project_name,
132
+ activity_ids={activity_id},
133
+ reference_types=reference_types,
134
+ fields=fields,
135
+ ):
136
+ return activity
137
+ return None
138
+
139
+ def create_activity(
140
+ self,
141
+ project_name: str,
142
+ entity_id: str,
143
+ entity_type: str,
144
+ activity_type: ActivityType,
145
+ activity_id: Optional[str] = None,
146
+ body: Optional[str] = None,
147
+ file_ids: Optional[list[str]] = None,
148
+ timestamp: Optional[str] = None,
149
+ data: Optional[dict[str, Any]] = None,
150
+ ) -> str:
151
+ """Create activity on a project.
152
+
153
+ Args:
154
+ project_name (str): Project on which activity happened.
155
+ entity_id (str): Entity id.
156
+ entity_type (str): Entity type.
157
+ activity_type (ActivityType): Activity type.
158
+ activity_id (Optional[str]): Activity id.
159
+ body (Optional[str]): Activity body.
160
+ file_ids (Optional[list[str]]): List of file ids attached
161
+ to activity.
162
+ timestamp (Optional[str]): Activity timestamp.
163
+ data (Optional[dict[str, Any]]): Additional data.
164
+
165
+ Returns:
166
+ str: Activity id.
167
+
168
+ """
169
+ post_data = {
170
+ "activityType": activity_type,
171
+ }
172
+ for key, value in (
173
+ ("id", activity_id),
174
+ ("body", body),
175
+ ("files", file_ids),
176
+ ("timestamp", timestamp),
177
+ ("data", data),
178
+ ):
179
+ if value is not None:
180
+ post_data[key] = value
181
+
182
+ response = self.post(
183
+ f"projects/{project_name}/{entity_type}/{entity_id}/activities",
184
+ **post_data
185
+ )
186
+ response.raise_for_status()
187
+ return response.data["id"]
188
+
189
+ def update_activity(
190
+ self,
191
+ project_name: str,
192
+ activity_id: str,
193
+ body: Optional[str] = None,
194
+ file_ids: Optional[list[str]] = None,
195
+ append_file_ids: Optional[bool] = False,
196
+ data: Optional[dict[str, Any]] = None,
197
+ ) -> None:
198
+ """Update activity by id.
199
+
200
+ Args:
201
+ project_name (str): Project on which activity happened.
202
+ activity_id (str): Activity id.
203
+ body (str): Activity body.
204
+ file_ids (Optional[list[str]]): List of file ids attached
205
+ to activity.
206
+ append_file_ids (Optional[bool]): Append file ids to existing
207
+ list of file ids.
208
+ data (Optional[dict[str, Any]]): Update data in activity.
209
+
210
+ """
211
+ update_data = {}
212
+ major, minor, patch, _, _ = self.get_server_version_tuple()
213
+ new_patch_model = (major, minor, patch) > (1, 5, 6)
214
+ if body is None and not new_patch_model:
215
+ raise ValueError(
216
+ "Update without 'body' is supported"
217
+ " after server version 1.5.6."
218
+ )
219
+
220
+ if body is not None:
221
+ update_data["body"] = body
222
+
223
+ if file_ids is not None:
224
+ update_data["files"] = file_ids
225
+ if new_patch_model:
226
+ update_data["appendFiles"] = append_file_ids
227
+ elif append_file_ids:
228
+ raise ValueError(
229
+ "Append file ids is supported after server version 1.5.6."
230
+ )
231
+
232
+ if data is not None:
233
+ if not new_patch_model:
234
+ raise ValueError(
235
+ "Update of data is supported after server version 1.5.6."
236
+ )
237
+ update_data["data"] = data
238
+
239
+ response = self.patch(
240
+ f"projects/{project_name}/activities/{activity_id}",
241
+ **update_data
242
+ )
243
+ response.raise_for_status()
244
+
245
+ def delete_activity(self, project_name: str, activity_id: str) -> None:
246
+ """Delete activity by id.
247
+
248
+ Args:
249
+ project_name (str): Project on which activity happened.
250
+ activity_id (str): Activity id to remove.
251
+
252
+ """
253
+ response = self.delete(
254
+ f"projects/{project_name}/activities/{activity_id}"
255
+ )
256
+ response.raise_for_status()
257
+
258
+ def send_activities_batch_operations(
259
+ self,
260
+ project_name: str,
261
+ operations: list[dict[str, Any]],
262
+ can_fail: bool = False,
263
+ raise_on_fail: bool = True
264
+ ) -> list[dict[str, Any]]:
265
+ """Post multiple CRUD activities operations to server.
266
+
267
+ When multiple changes should be made on server side this is the best
268
+ way to go. It is possible to pass multiple operations to process on a
269
+ server side and do the changes in a transaction.
270
+
271
+ Args:
272
+ project_name (str): On which project should be operations
273
+ processed.
274
+ operations (list[dict[str, Any]]): Operations to be processed.
275
+ can_fail (Optional[bool]): Server will try to process all
276
+ operations even if one of them fails.
277
+ raise_on_fail (Optional[bool]): Raise exception if an operation
278
+ fails. You can handle failed operations on your own
279
+ when set to 'False'.
280
+
281
+ Raises:
282
+ ValueError: Operations can't be converted to json string.
283
+ FailedOperations: When output does not contain server operations
284
+ or 'raise_on_fail' is enabled and any operation fails.
285
+
286
+ Returns:
287
+ list[dict[str, Any]]: Operations result with process details.
288
+
289
+ """
290
+ return self._send_batch_operations(
291
+ f"projects/{project_name}/operations/activities",
292
+ operations,
293
+ can_fail,
294
+ raise_on_fail,
295
+ )