collibra-connector 1.0.7__py3-none-any.whl → 1.0.10a0__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.
@@ -1,27 +1,27 @@
1
1
  #
2
2
  #
3
- # ███
4
- # ██ ███ ███
5
- # ████ ███
6
- # ███ ████ ██ ███ ████
7
- # ███ ████ ███ ███ ████
8
- # ███ ███
9
- # ███ ███
10
- # ██████████████ ███ ███ ████████
11
- # ███████████ █ ███ ██████
3
+ # ███
4
+ # ██ ███ ███
5
+ # ████ ███
6
+ # ███ ████ ██ ███ ████
7
+ # ███ ████ ███ ███ ████
8
+ # ███ ███
9
+ # ███ ███
10
+ # ██████████████ ███ ███ ████████
11
+ # ███████████ █ ███ ██████
12
12
  #
13
- # ███ █████████████ ████████████ ███
14
- # ███ █████████████ ████████████ ███
13
+ # ███ █████████████ ████████████ ███
14
+ # ███ █████████████ ████████████ ███
15
15
  #
16
- # ██████ █ █████████████
17
- # ███████ ████ ███ ██████████████
18
- # ████ ███
19
- # ████ ███
20
- # ███ ████ ███ ███ ████
21
- # ██ ████ ██ ███ ██
22
- # ████ ███
23
- # ██ ███ ███
24
- # █
16
+ # ██████ █ █████████████
17
+ # ███████ ████ ███ ██████████████
18
+ # ████ ███
19
+ # ████ ███
20
+ # ███ ████ ███ ███ ████
21
+ # ██ ████ ██ ███ ██
22
+ # ████ ███
23
+ # ██ ███ ███
24
+ # █
25
25
  #
26
26
  #
27
27
  """
@@ -36,4 +36,4 @@ This library provides a simple interface to handle connection and URLs
36
36
 
37
37
  from .connector import CollibraConnector
38
38
 
39
- __version__ = "0.1.0"
39
+ __version__ = "0.1.0"
@@ -1,11 +1,13 @@
1
+ import uuid
1
2
  from .Base import BaseAPI
2
3
 
4
+
3
5
  class Asset(BaseAPI):
4
6
  def __init__(self, connector):
5
7
  super().__init__(connector)
6
8
  self.__base_api = connector.api + "/assets"
7
9
 
8
- def __get(self, url: str = None, params: dict = None, headers: dict = None):
10
+ def _get(self, url: str = None, params: dict = None, headers: dict = None):
9
11
  """
10
12
  Makes a GET request to the asset API.
11
13
  :param url: The URL to send the GET request to.
@@ -13,28 +15,16 @@ class Asset(BaseAPI):
13
15
  :param headers: Optional headers to include in the GET request.
14
16
  :return: The response from the GET request.
15
17
  """
16
- return super().__get(self.__base_api if not url else url, params, headers)
17
-
18
- def __post(self, url: str, data: dict):
19
- """
20
- Makes a POST request to the asset API.
21
- :param url: The URL to send the POST request to.
22
- :param data: The data to send in the POST request.
23
- :return: The response from the POST request.
24
- """
25
- return super().__post(url, data)
18
+ return super()._get(self.__base_api if not url else url, params, headers)
26
19
 
27
- def __handle_response(self, response):
28
- return super().__handle_response(response)
29
-
30
20
  def get_asset(self, asset_id):
31
21
  """
32
22
  Retrieves an asset by its ID.
33
23
  :param asset_id: The ID of the asset to retrieve.
34
24
  :return: Asset details.
35
25
  """
36
- response = self.__get(url=f"{self.__base_api}/{asset_id}")
37
- return self.__handle_response(response)
26
+ response = self._get(url=f"{self.__base_api}/{asset_id}")
27
+ return self._handle_response(response)
38
28
 
39
29
  def add_asset(
40
30
  self,
@@ -42,7 +32,7 @@ class Asset(BaseAPI):
42
32
  domain_id: str,
43
33
  display_name: str = None,
44
34
  type_id: str = None,
45
- id: str = None,
35
+ _id: str = None,
46
36
  status_id: str = None,
47
37
  excluded_from_auto_hyperlink: bool = False,
48
38
  type_public_id: str = None,
@@ -59,6 +49,30 @@ class Asset(BaseAPI):
59
49
  :param type_public_id: Optional public ID for the asset type.
60
50
  :return: Details of the created asset.
61
51
  """
52
+ # Parameter type validation
53
+ if not name or not domain_id:
54
+ raise ValueError("Name and domain_id are required parameters.")
55
+ if not isinstance(excluded_from_auto_hyperlink, bool):
56
+ raise ValueError("excluded_from_auto_hyperlink must be a boolean value.")
57
+ if type_id and not isinstance(type_id, str):
58
+ raise ValueError("type_id must be a string if provided.")
59
+ if _id and not isinstance(_id, str):
60
+ raise ValueError("_id must be a string if provided.")
61
+ if status_id and not isinstance(status_id, str):
62
+ raise ValueError("status_id must be a string if provided.")
63
+ if type_public_id and not isinstance(type_public_id, str):
64
+ raise ValueError("type_public_id must be a string if provided.")
65
+
66
+ # Check Ids are UUIDS
67
+ if _id and self._uuid_validation(_id) is False:
68
+ raise ValueError("id must be a valid UUID.")
69
+ if domain_id and self._uuid_validation(domain_id) is False:
70
+ raise ValueError("domain_id must be a valid UUID.")
71
+ if type_id and self._uuid_validation(type_id) is False:
72
+ raise ValueError("type_id must be a valid UUID.")
73
+ if status_id and self._uuid_validation(status_id) is False:
74
+ raise ValueError("status_id must be a valid UUID.")
75
+
62
76
  data = {
63
77
  "name": name,
64
78
  "domainId": domain_id,
@@ -69,5 +83,190 @@ class Asset(BaseAPI):
69
83
  "excludedFromAutoHyperlink": excluded_from_auto_hyperlink,
70
84
  "typePublicId": type_public_id
71
85
  }
72
- response = self.__post(url=self.__base_api, data=data)
73
- return self.__handle_response(response)
86
+ response = self._post(url=self.__base_api, data=data)
87
+ return self._handle_response(response)
88
+
89
+ def update_asset_properties(
90
+ self,
91
+ asset_id: str,
92
+ name: str = None,
93
+ display_name: str = None,
94
+ type_id: str = None,
95
+ status_id: str = None,
96
+ domain_id: str = None,
97
+ excluded_from_auto_hyperlinking: bool = None,
98
+ type_public_id: str = None
99
+ ):
100
+ """
101
+ Update asset properties.
102
+ :param asset_id: The ID of the asset to update.
103
+ :param name: Optional new name for the asset.
104
+ :param display_name: Optional new display name.
105
+ :param type_id: Optional new type ID.
106
+ :param status_id: Optional new status ID.
107
+ :param domain_id: Optional new domain ID.
108
+ :param excluded_from_auto_hyperlinking: Optional auto-hyperlinking setting.
109
+ :param type_public_id: Optional new type public ID.
110
+ :return: Updated asset details.
111
+ """
112
+ if not asset_id:
113
+ raise ValueError("asset_id is required")
114
+ if not isinstance(asset_id, str):
115
+ raise ValueError("asset_id must be a string")
116
+
117
+ try:
118
+ uuid.UUID(asset_id)
119
+ except ValueError as exc:
120
+ raise ValueError("asset_id must be a valid UUID") from exc
121
+
122
+ # Validate UUID fields if provided
123
+ uuid_fields = [
124
+ ("type_id", type_id),
125
+ ("status_id", status_id),
126
+ ("domain_id", domain_id)
127
+ ]
128
+
129
+ for field_name, field_value in uuid_fields:
130
+ if field_value:
131
+ if not isinstance(field_value, str):
132
+ raise ValueError(f"{field_name} must be a string")
133
+ try:
134
+ uuid.UUID(field_value)
135
+ except ValueError as exc:
136
+ raise ValueError(f"{field_name} must be a valid UUID") from exc
137
+
138
+ data = {
139
+ "id": asset_id,
140
+ "name": name,
141
+ "displayName": display_name,
142
+ "typeId": type_id,
143
+ "statusId": status_id,
144
+ "domainId": domain_id,
145
+ "excludedFromAutoHyperlinking": excluded_from_auto_hyperlinking,
146
+ "typePublicId": type_public_id,
147
+ }
148
+
149
+ # Use PATCH method through requests directly since BaseAPI doesn't have _patch
150
+ import requests
151
+ response = requests.patch(
152
+ f"{self.__base_api}/{asset_id}",
153
+ auth=self.__connector.auth,
154
+ json=data,
155
+ headers=self.__header,
156
+ timeout=30
157
+ )
158
+
159
+ return self._handle_response(response)
160
+
161
+ def update_asset_attribute(self, asset_id: str, attribute_id: str, value):
162
+ """
163
+ Update an asset attribute.
164
+ :param asset_id: The ID of the asset.
165
+ :param attribute_id: The ID of the attribute type.
166
+ :param value: The new value for the attribute.
167
+ :return: The response from updating the attribute.
168
+ """
169
+ if not all([asset_id, attribute_id]):
170
+ raise ValueError("asset_id and attribute_id are required")
171
+
172
+ for param_name, param_value in [("asset_id", asset_id), ("attribute_id", attribute_id)]:
173
+ if not isinstance(param_value, str):
174
+ raise ValueError(f"{param_name} must be a string")
175
+ try:
176
+ uuid.UUID(param_value)
177
+ except ValueError as exc:
178
+ raise ValueError(f"{param_name} must be a valid UUID") from exc
179
+
180
+ data = {
181
+ "values": [value],
182
+ "typeId": attribute_id
183
+ }
184
+
185
+ # Use PUT method through requests directly since BaseAPI doesn't have _put
186
+ import requests
187
+ response = requests.put(
188
+ f"{self.__base_api}/{asset_id}/attributes",
189
+ auth=self.__connector.auth,
190
+ json=data,
191
+ headers=self.__header,
192
+ timeout=30
193
+ )
194
+
195
+ return self._handle_response(response)
196
+
197
+ def find_assets(
198
+ self,
199
+ community_id: str = None,
200
+ asset_type_ids: list = None,
201
+ domain_id: str = None,
202
+ limit: int = 1000
203
+ ):
204
+ """
205
+ Find assets with optional filters.
206
+ :param community_id: Optional community ID to filter by.
207
+ :param asset_type_ids: Optional list of asset type IDs to filter by.
208
+ :param domain_id: Optional domain ID to filter by.
209
+ :param limit: Maximum number of results per page.
210
+ :return: List of assets matching the criteria.
211
+ """
212
+ params = {"limit": limit}
213
+
214
+ if community_id:
215
+ if not isinstance(community_id, str):
216
+ raise ValueError("community_id must be a string")
217
+ try:
218
+ uuid.UUID(community_id)
219
+ except ValueError as exc:
220
+ raise ValueError("community_id must be a valid UUID") from exc
221
+ params["communityId"] = community_id
222
+
223
+ if asset_type_ids:
224
+ if not isinstance(asset_type_ids, list):
225
+ raise ValueError("asset_type_ids must be a list")
226
+ for type_id in asset_type_ids:
227
+ if not isinstance(type_id, str):
228
+ raise ValueError("All asset_type_ids must be strings")
229
+ try:
230
+ uuid.UUID(type_id)
231
+ except ValueError as exc:
232
+ raise ValueError("All asset_type_ids must be valid UUIDs") from exc
233
+ params["typeIds"] = asset_type_ids
234
+
235
+ if domain_id:
236
+ if not isinstance(domain_id, str):
237
+ raise ValueError("domain_id must be a string")
238
+ try:
239
+ uuid.UUID(domain_id)
240
+ except ValueError as exc:
241
+ raise ValueError("domain_id must be a valid UUID") from exc
242
+ params["domainId"] = domain_id
243
+
244
+ response = self._get(params=params)
245
+ return self._handle_response(response)
246
+
247
+ def get_asset_activities(self, asset_id: str, limit: int = 50):
248
+ """
249
+ Get activities for a specific asset.
250
+ :param asset_id: The ID of the asset.
251
+ :param limit: Maximum number of activities to retrieve.
252
+ :return: List of activities for the asset.
253
+ """
254
+ if not asset_id:
255
+ raise ValueError("asset_id is required")
256
+ if not isinstance(asset_id, str):
257
+ raise ValueError("asset_id must be a string")
258
+
259
+ try:
260
+ uuid.UUID(asset_id)
261
+ except ValueError as exc:
262
+ raise ValueError("asset_id must be a valid UUID") from exc
263
+
264
+ params = {
265
+ "contextId": asset_id,
266
+ "resourceTypes": ["Asset"],
267
+ "limit": limit
268
+ }
269
+
270
+ response = self._get(url=f"{self.__base_api}/activities", params=params)
271
+ result = self._handle_response(response)
272
+ return result.get("results", [])
@@ -1,3 +1,4 @@
1
+ import re
1
2
  import requests
2
3
  from .Exceptions import (
3
4
  UnauthorizedError,
@@ -6,7 +7,9 @@ from .Exceptions import (
6
7
  ServerError
7
8
  )
8
9
 
10
+
9
11
  class BaseAPI:
12
+
10
13
  def __init__(self, connector):
11
14
  self.__connector = connector
12
15
  self.__base_api = connector.api
@@ -16,7 +19,7 @@ class BaseAPI:
16
19
  }
17
20
  self.__params = None
18
21
 
19
- def __get(self, url: str = None, params: dict = None, headers: dict = None):
22
+ def _get(self, url: str = None, params: dict = None, headers: dict = None):
20
23
  """
21
24
  Makes a GET request to the specified URL.
22
25
  :param url: The URL to send the GET request to.
@@ -31,10 +34,11 @@ class BaseAPI:
31
34
  url,
32
35
  auth=self.__connector.auth,
33
36
  params=params,
34
- headers=headers
37
+ headers=headers,
38
+ timeout=self.__connector.timeout
35
39
  )
36
40
 
37
- def __post(self, url: str, data: dict, headers: dict = None):
41
+ def _post(self, url: str, data: dict, headers: dict = None):
38
42
  """
39
43
  Makes a POST request to the specified URL with the given data.
40
44
  :param url: The URL to send the POST request to.
@@ -51,22 +55,99 @@ class BaseAPI:
51
55
  url,
52
56
  auth=self.__connector.auth,
53
57
  json=data,
54
- headers=self.__header
58
+ headers=headers,
59
+ timeout=self.__connector.timeout
60
+ )
61
+
62
+ def _put(self, url: str, data: dict, headers: dict = None):
63
+ """
64
+ Makes a PUT request to the specified URL with the given data.
65
+ :param url: The URL to send the PUT request to.
66
+ :param data: The data to send in the PUT request.
67
+ :return: The response from the PUT request.
68
+ """
69
+ url = self.__base_api if not url else url
70
+ headers = self.__header if not headers else headers
71
+ if not isinstance(data, dict):
72
+ raise ValueError("Data must be a dictionary")
73
+ if not data:
74
+ raise ValueError("Data cannot be empty")
75
+ return requests.put(
76
+ url,
77
+ auth=self.__connector.auth,
78
+ json=data,
79
+ headers=headers,
80
+ timeout=self.__connector.timeout
55
81
  )
56
82
 
57
- def __handle_response(self, response):
83
+ def _delete(self, url: str, headers: dict = None):
84
+ """
85
+ Makes a DELETE request to the specified URL.
86
+ :param url: The URL to send the DELETE request to.
87
+ :return: The response from the DELETE request.
88
+ """
89
+ url = self.__base_api if not url else url
90
+ headers = self.__header if not headers else headers
91
+ return requests.delete(
92
+ url,
93
+ auth=self.__connector.auth,
94
+ headers=headers,
95
+ timeout=self.__connector.timeout
96
+ )
97
+
98
+ def _patch(self, url: str, data: dict, headers: dict = None):
99
+ """
100
+ Makes a PATCH request to the specified URL with the given data.
101
+ :param url: The URL to send the PATCH request to.
102
+ :param data: The data to send in the PATCH request.
103
+ :return: The response from the PATCH request.
104
+ """
105
+ url = self.__base_api if not url else url
106
+ headers = self.__header if not headers else headers
107
+ if not isinstance(data, dict):
108
+ raise ValueError("Data must be a dictionary")
109
+ if not data:
110
+ raise ValueError("Data cannot be empty")
111
+ return requests.patch(
112
+ url,
113
+ auth=self.__connector.auth,
114
+ json=data,
115
+ headers=headers,
116
+ timeout=self.__connector.timeout
117
+ )
118
+
119
+ def _handle_response(self, response):
58
120
  """
59
121
  Handles the response from the API.
60
122
  :param response: The response object from the API request.
61
123
  :return: The JSON content of the response if successful, otherwise raises an error.
62
124
  """
63
- if response.status_code == 200 or response.status_code == 201:
64
- return response.json()
125
+ if response.status_code in [200, 201]:
126
+ # Check if response has content before trying to parse JSON
127
+ if response.text.strip():
128
+ try:
129
+ return response.json()
130
+ except ValueError as e:
131
+ raise ValueError(f"Invalid JSON response: {e}") from e
132
+ return {}
65
133
  elif response.status_code == 401:
66
- raise UnauthorizedError("Unauthorized access - invalid credentials")
134
+ raise UnauthorizedError("Unauthorized access - invalid credentials: " + response.text)
67
135
  elif response.status_code == 403:
68
- raise ForbiddenError("Forbidden access - insufficient permissions")
136
+ raise ForbiddenError("Forbidden access - insufficient permissions: " + response.text)
69
137
  elif response.status_code == 404:
70
- raise NotFoundError("The specified resource was not found")
138
+ raise NotFoundError("The specified resource was not found: " + response.text)
71
139
  elif response.status_code >= 500:
72
- raise ServerError("Internal server error - something went wrong on the server")
140
+ raise ServerError("Internal server error - something went wrong on the server: " + response.text)
141
+ else:
142
+ raise Exception(f"Unexpected status code {response.status_code}: {response.text}")
143
+
144
+ def _uuid_validation(self, id: str):
145
+ """
146
+ Validates if the provided ID is a valid UUID.
147
+ :param id: The ID to validate.
148
+ :return: True if the ID is a valid UUID, False otherwise.
149
+ """
150
+ if not id or not isinstance(id, str):
151
+ return False
152
+ pattern = re.compile(r"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$")
153
+ return bool(pattern.match(id))
@@ -0,0 +1,125 @@
1
+ import uuid
2
+ from .Base import BaseAPI
3
+
4
+
5
+ class Comment(BaseAPI):
6
+ def __init__(self, connector):
7
+ super().__init__(connector)
8
+ self.__base_api = connector.api + "/comments"
9
+
10
+ def add_comment(self, asset_id: str, content: str):
11
+ """
12
+ Add a comment to an asset.
13
+ :param asset_id: The ID of the asset to comment on.
14
+ :param content: The content of the comment.
15
+ :return: The created comment details.
16
+ """
17
+ if not asset_id or not content:
18
+ raise ValueError("asset_id and content are required parameters")
19
+ if not isinstance(asset_id, str) or not isinstance(content, str):
20
+ raise ValueError("asset_id and content must be strings")
21
+
22
+ try:
23
+ uuid.UUID(asset_id)
24
+ except ValueError as exc:
25
+ raise ValueError("asset_id must be a valid UUID") from exc
26
+
27
+ data = {
28
+ "content": content,
29
+ "baseResource": {
30
+ "id": asset_id,
31
+ "resourceType": "Asset"
32
+ },
33
+ }
34
+
35
+ response = self._post(url=self.__base_api, data=data)
36
+ return self._handle_response(response)
37
+
38
+ def find_comments(
39
+ self,
40
+ base_resource_id: str = None,
41
+ count_limit: int = -1,
42
+ limit: int = 0,
43
+ offset: int = 0,
44
+ parent_id: str = None,
45
+ resolved: bool = None,
46
+ root_comment: bool = None,
47
+ sort_order: str = "DESC",
48
+ user_id: str = None,
49
+ user_threads: bool = False
50
+ ):
51
+ """
52
+ Find comments matching the given search criteria.
53
+ :param base_resource_id: The ID of the resource which the searched comments refer to.
54
+ :param count_limit: Limit the number of elements counted. -1 counts everything, 0 skips count.
55
+ :param limit: Maximum number of results to retrieve. 0 uses default, max 1000.
56
+ :param offset: First result to retrieve. 0 starts from beginning.
57
+ :param parent_id: ID of the comment for which reply comments should be searched.
58
+ :param resolved: Whether the searched comments should be resolved.
59
+ :param root_comment: Whether the searched comments should be root comments.
60
+ :param sort_order: Sort order on creation date. 'ASC' or 'DESC'.
61
+ :param user_id: ID of the user to filter comments by.
62
+ :param user_threads: Whether to search for root comments created by or mentioning the user.
63
+ :return: List of comments matching the criteria.
64
+ """
65
+ # Validate base_resource_id if provided
66
+ if base_resource_id is not None:
67
+ if not isinstance(base_resource_id, str):
68
+ raise ValueError("base_resource_id must be a string")
69
+ try:
70
+ uuid.UUID(base_resource_id)
71
+ except ValueError as exc:
72
+ raise ValueError("base_resource_id must be a valid UUID") from exc
73
+
74
+ # Validate parent_id if provided
75
+ if parent_id is not None:
76
+ if not isinstance(parent_id, str):
77
+ raise ValueError("parent_id must be a string")
78
+ try:
79
+ uuid.UUID(parent_id)
80
+ except ValueError as exc:
81
+ raise ValueError("parent_id must be a valid UUID") from exc
82
+
83
+ # Validate user_id if provided
84
+ if user_id is not None:
85
+ if not isinstance(user_id, str):
86
+ raise ValueError("user_id must be a string")
87
+ try:
88
+ uuid.UUID(user_id)
89
+ except ValueError as exc:
90
+ raise ValueError("user_id must be a valid UUID") from exc
91
+
92
+ # Validate sort_order
93
+ if sort_order not in ["ASC", "DESC"]:
94
+ raise ValueError("sort_order must be 'ASC' or 'DESC'")
95
+
96
+ # Validate limit
97
+ if limit < 0 or limit > 1000:
98
+ raise ValueError("limit must be between 0 and 1000")
99
+
100
+ # Build parameters - only include non-None values
101
+ params = {}
102
+
103
+ if base_resource_id is not None:
104
+ params["baseResourceId"] = base_resource_id
105
+ if count_limit != -1: # Only add if different from default
106
+ params["countLimit"] = count_limit
107
+ if limit != 0: # Only add if different from default
108
+ params["limit"] = limit
109
+ if offset != 0: # Only add if different from default
110
+ params["offset"] = offset
111
+ if parent_id is not None:
112
+ params["parentId"] = parent_id
113
+ if resolved is not None:
114
+ params["resolved"] = resolved
115
+ if root_comment is not None:
116
+ params["rootComment"] = root_comment
117
+ if sort_order != "DESC": # Only add if different from default
118
+ params["sortOrder"] = sort_order
119
+ if user_id is not None:
120
+ params["userId"] = user_id
121
+ if user_threads is not False: # Only add if different from default
122
+ params["userThreads"] = user_threads
123
+
124
+ response = self._get(params=params)
125
+ return self._handle_response(response)