pyxecm 1.6__py3-none-any.whl → 2.0.0__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 pyxecm might be problematic. Click here for more details.
- pyxecm/__init__.py +6 -4
- pyxecm/avts.py +673 -246
- pyxecm/coreshare.py +686 -467
- pyxecm/customizer/__init__.py +16 -4
- pyxecm/customizer/__main__.py +58 -0
- pyxecm/customizer/api/__init__.py +5 -0
- pyxecm/customizer/api/__main__.py +6 -0
- pyxecm/customizer/api/app.py +914 -0
- pyxecm/customizer/api/auth.py +154 -0
- pyxecm/customizer/api/metrics.py +92 -0
- pyxecm/customizer/api/models.py +13 -0
- pyxecm/customizer/api/payload_list.py +865 -0
- pyxecm/customizer/api/settings.py +103 -0
- pyxecm/customizer/browser_automation.py +332 -139
- pyxecm/customizer/customizer.py +1007 -1130
- pyxecm/customizer/exceptions.py +35 -0
- pyxecm/customizer/guidewire.py +322 -0
- pyxecm/customizer/k8s.py +713 -378
- pyxecm/customizer/log.py +107 -0
- pyxecm/customizer/m365.py +2867 -909
- pyxecm/customizer/nhc.py +1169 -0
- pyxecm/customizer/openapi.py +258 -0
- pyxecm/customizer/payload.py +16817 -7467
- pyxecm/customizer/pht.py +699 -285
- pyxecm/customizer/salesforce.py +516 -342
- pyxecm/customizer/sap.py +58 -41
- pyxecm/customizer/servicenow.py +593 -371
- pyxecm/customizer/settings.py +442 -0
- pyxecm/customizer/successfactors.py +408 -346
- pyxecm/customizer/translate.py +83 -48
- pyxecm/helper/__init__.py +5 -2
- pyxecm/helper/assoc.py +83 -43
- pyxecm/helper/data.py +2406 -870
- pyxecm/helper/logadapter.py +27 -0
- pyxecm/helper/web.py +229 -101
- pyxecm/helper/xml.py +527 -171
- pyxecm/maintenance_page/__init__.py +5 -0
- pyxecm/maintenance_page/__main__.py +6 -0
- pyxecm/maintenance_page/app.py +51 -0
- pyxecm/maintenance_page/settings.py +28 -0
- pyxecm/maintenance_page/static/favicon.avif +0 -0
- pyxecm/maintenance_page/templates/maintenance.html +165 -0
- pyxecm/otac.py +234 -140
- pyxecm/otawp.py +1436 -557
- pyxecm/otcs.py +7716 -3161
- pyxecm/otds.py +2150 -919
- pyxecm/otiv.py +36 -21
- pyxecm/otmm.py +1272 -325
- pyxecm/otpd.py +231 -127
- pyxecm-2.0.0.dist-info/METADATA +145 -0
- pyxecm-2.0.0.dist-info/RECORD +54 -0
- {pyxecm-1.6.dist-info → pyxecm-2.0.0.dist-info}/WHEEL +1 -1
- pyxecm-1.6.dist-info/METADATA +0 -53
- pyxecm-1.6.dist-info/RECORD +0 -32
- {pyxecm-1.6.dist-info → pyxecm-2.0.0.dist-info/licenses}/LICENSE +0 -0
- {pyxecm-1.6.dist-info → pyxecm-2.0.0.dist-info}/top_level.txt +0 -0
pyxecm/customizer/pht.py
CHANGED
|
@@ -1,48 +1,16 @@
|
|
|
1
|
-
"""
|
|
2
|
-
PHT stands for Product Hierarchy Tracker and is an OpenText internal application aiming at creating a common naming reference for Engineering Products and
|
|
3
|
-
track all product-related data. It also provides an approved reporting hierarchy.
|
|
4
|
-
See: https://pht.opentext.com
|
|
5
|
-
|
|
6
|
-
Class: PHT
|
|
7
|
-
Methods:
|
|
8
|
-
|
|
9
|
-
__init__ : class initializer
|
|
10
|
-
config : Returns config data set
|
|
11
|
-
get_data: Get the Data object that holds all processed PHT products
|
|
12
|
-
request_header: Returns the request header for ServiceNow API calls
|
|
13
|
-
do_request: Call an PHT REST API in a safe way
|
|
14
|
-
parse_request_response: Parse the REST API responses and convert
|
|
15
|
-
them to Python dict in a safe way
|
|
16
|
-
|
|
17
|
-
authenticate : Authenticates at ServiceNow API
|
|
1
|
+
"""PHT stands for Product Hierarchy Tracker.
|
|
18
2
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
get_product_families: Get the list of PHT product families
|
|
22
|
-
|
|
23
|
-
get_products: Get the list of PHT products
|
|
24
|
-
get_products_filtered: Get a list of filtered PHT products
|
|
25
|
-
get_product: Get a specific product in PHT
|
|
26
|
-
search_products: Search products in PHT
|
|
27
|
-
|
|
28
|
-
get_master_products: Get the list of PHT master products
|
|
29
|
-
get_master_products_filtered: Get a list of filtered PHT master products
|
|
30
|
-
get_master_product: Get a specific product in PHT
|
|
31
|
-
|
|
32
|
-
get_teams: Get a list of all teams in PHT.
|
|
33
|
-
get_team: Get a specific team in PHT.
|
|
34
|
-
|
|
35
|
-
get_componets: Get a list of all components in PHT.
|
|
36
|
-
get_components_filtered: Get a list of filtered PHT components.
|
|
37
|
-
get_component: Get a specific component in PHT.
|
|
38
|
-
search_components: Search components in PHT
|
|
3
|
+
It is an OpenText internal application aiming at creating a common naming reference for Engineering Products and
|
|
4
|
+
track all product-related data. It also provides an approved reporting hierarchy.
|
|
39
5
|
|
|
40
|
-
|
|
6
|
+
See: https://pht.opentext.com
|
|
41
7
|
|
|
8
|
+
Request for User Access Token: https://confluence.opentext.com/display/RDOT/Request+a+User+Access+Token
|
|
9
|
+
PHT API Documentation: https://confluence.opentext.com/display/RDOT/PHT+API+Documentation
|
|
42
10
|
"""
|
|
43
11
|
|
|
44
12
|
__author__ = "Dr. Marc Diefenbruch"
|
|
45
|
-
__copyright__ = "Copyright 2024, OpenText"
|
|
13
|
+
__copyright__ = "Copyright (C) 2024-2025, OpenText"
|
|
46
14
|
__credits__ = ["Kai-Philip Gatzweiler"]
|
|
47
15
|
__maintainer__ = "Dr. Marc Diefenbruch"
|
|
48
16
|
__email__ = "mdiefenb@opentext.com"
|
|
@@ -53,9 +21,10 @@ import time
|
|
|
53
21
|
|
|
54
22
|
import requests
|
|
55
23
|
from requests.auth import HTTPBasicAuth
|
|
56
|
-
from pyxecm.helper.data import Data
|
|
57
24
|
|
|
58
|
-
|
|
25
|
+
from pyxecm.helper import Data
|
|
26
|
+
|
|
27
|
+
default_logger = logging.getLogger("pyxecm.customizer.pht")
|
|
59
28
|
|
|
60
29
|
REQUEST_HEADERS = {"Accept": "application/json", "Content-Type": "application/json"}
|
|
61
30
|
|
|
@@ -63,26 +32,75 @@ REQUEST_TIMEOUT = 60
|
|
|
63
32
|
REQUEST_RETRY_DELAY = 20
|
|
64
33
|
REQUEST_MAX_RETRIES = 2
|
|
65
34
|
|
|
66
|
-
|
|
67
|
-
|
|
35
|
+
|
|
36
|
+
class PHT:
|
|
37
|
+
"""Class PHT is used to retrieve data from OpenText PHT. It is a pure read-only access."""
|
|
38
|
+
|
|
39
|
+
logger: logging.Logger = (default_logger,)
|
|
68
40
|
|
|
69
41
|
_config: dict
|
|
70
42
|
_session = None
|
|
43
|
+
_business_unit_exclusions = None
|
|
44
|
+
_business_unit_inclusions = None
|
|
45
|
+
_product_exclusions = None
|
|
46
|
+
_product_inclusions = None
|
|
47
|
+
_product_category_exclusions = None
|
|
48
|
+
_product_category_inclusions = None
|
|
49
|
+
_product_status_exclusions = None
|
|
50
|
+
_product_status_inclusions = None
|
|
71
51
|
|
|
72
52
|
def __init__(
|
|
73
53
|
self,
|
|
74
54
|
base_url: str,
|
|
75
55
|
username: str,
|
|
76
56
|
password: str,
|
|
77
|
-
|
|
78
|
-
|
|
57
|
+
business_unit_exclusions: list | None = None,
|
|
58
|
+
business_unit_inclusions: list | None = None,
|
|
59
|
+
product_exclusions: list | None = None,
|
|
60
|
+
product_inclusions: list | None = None,
|
|
61
|
+
product_category_exclusions: list | None = None,
|
|
62
|
+
product_category_inclusions: list | None = None,
|
|
63
|
+
product_status_exclusions: list | None = None,
|
|
64
|
+
product_status_inclusions: list | None = None,
|
|
65
|
+
logger: logging.Logger = default_logger,
|
|
66
|
+
) -> None:
|
|
67
|
+
"""Initialize the PHT object.
|
|
79
68
|
|
|
80
69
|
Args:
|
|
81
|
-
base_url (str):
|
|
82
|
-
|
|
83
|
-
|
|
70
|
+
base_url (str):
|
|
71
|
+
The base URL of PHT.
|
|
72
|
+
username (str):
|
|
73
|
+
The user name to access PHT.
|
|
74
|
+
password (str):
|
|
75
|
+
The password of the user.
|
|
76
|
+
business_unit_exclusions (list | None, optional):
|
|
77
|
+
A black list for business units to exclude. Default = None.
|
|
78
|
+
business_unit_inclusions (list | None, optional):
|
|
79
|
+
A white list for business units to include. Default = None.
|
|
80
|
+
product_exclusions (list | None, optional):
|
|
81
|
+
A black list for products to exclude. Default = None.
|
|
82
|
+
product_inclusions (list | None, optional):
|
|
83
|
+
A white list for products to include. Default = None.
|
|
84
|
+
product_category_exclusions (list | None, optional):
|
|
85
|
+
A black list for product categories to exclude. Default = None.
|
|
86
|
+
product_category_inclusions (list | None, optional):
|
|
87
|
+
A white list for product categories to include. Default = None.
|
|
88
|
+
product_status_exclusions (list | None, optional):
|
|
89
|
+
A back list of product status to exclude. Only products with status NOT on
|
|
90
|
+
this list will be included. Default = None.
|
|
91
|
+
product_status_inclusions (list | None, optional):
|
|
92
|
+
A white list of product status to exclude. Only products with status on
|
|
93
|
+
this list will be included. Default = None.
|
|
94
|
+
logger (logging.Logger):
|
|
95
|
+
The logging object used for all log messages. Default = default_logger.
|
|
96
|
+
|
|
84
97
|
"""
|
|
85
98
|
|
|
99
|
+
if logger != default_logger:
|
|
100
|
+
self.logger = logger.getChild("pht")
|
|
101
|
+
for logfilter in logger.filters:
|
|
102
|
+
self.logger.addFilter(logfilter)
|
|
103
|
+
|
|
86
104
|
pht_config = {}
|
|
87
105
|
|
|
88
106
|
# Store the credentials and parameters in a config dictionary:
|
|
@@ -100,9 +118,7 @@ class PHT(object):
|
|
|
100
118
|
pht_config["productUsersUrl"] = pht_config["productUrl"] + "/users"
|
|
101
119
|
pht_config["teamUrl"] = pht_config["restUrl"] + "/team"
|
|
102
120
|
pht_config["masterProductUrl"] = pht_config["restUrl"] + "/master-product"
|
|
103
|
-
pht_config["masterProductFilteredUrl"] =
|
|
104
|
-
pht_config["masterProductUrl"] + "/filtered"
|
|
105
|
-
)
|
|
121
|
+
pht_config["masterProductFilteredUrl"] = pht_config["masterProductUrl"] + "/filtered"
|
|
106
122
|
pht_config["componentUrl"] = pht_config["restUrl"] + "/component"
|
|
107
123
|
pht_config["componentFilteredUrl"] = pht_config["componentUrl"] + "/filtered"
|
|
108
124
|
pht_config["componentSearchUrl"] = pht_config["componentUrl"] + "/search"
|
|
@@ -112,25 +128,38 @@ class PHT(object):
|
|
|
112
128
|
|
|
113
129
|
self._session = requests.Session()
|
|
114
130
|
|
|
115
|
-
self._data = Data()
|
|
131
|
+
self._data = Data(logger=self.logger)
|
|
132
|
+
|
|
133
|
+
self._business_unit_exclusions = business_unit_exclusions
|
|
134
|
+
self._business_unit_inclusions = business_unit_inclusions
|
|
135
|
+
self._product_exclusions = product_exclusions
|
|
136
|
+
self._product_inclusions = product_inclusions
|
|
137
|
+
self._product_category_exclusions = product_category_exclusions
|
|
138
|
+
self._product_category_inclusions = product_category_inclusions
|
|
139
|
+
self._product_status_exclusions = product_status_exclusions
|
|
140
|
+
self._product_status_inclusions = product_status_inclusions
|
|
116
141
|
|
|
117
142
|
# end method definition
|
|
118
143
|
|
|
119
144
|
def config(self) -> dict:
|
|
120
|
-
"""
|
|
145
|
+
"""Return the configuration dictionary.
|
|
121
146
|
|
|
122
147
|
Returns:
|
|
123
|
-
dict:
|
|
148
|
+
dict:
|
|
149
|
+
The configuration dictionary.
|
|
150
|
+
|
|
124
151
|
"""
|
|
125
152
|
return self._config
|
|
126
153
|
|
|
127
154
|
# end method definition
|
|
128
155
|
|
|
129
156
|
def get_data(self) -> Data:
|
|
130
|
-
"""Get the Data object that holds all processed PHT products
|
|
157
|
+
"""Get the Data object that holds all processed PHT products.
|
|
131
158
|
|
|
132
159
|
Returns:
|
|
133
|
-
Data:
|
|
160
|
+
Data:
|
|
161
|
+
Datastructure with all processed PHT product data.
|
|
162
|
+
|
|
134
163
|
"""
|
|
135
164
|
|
|
136
165
|
return self._data
|
|
@@ -138,13 +167,23 @@ class PHT(object):
|
|
|
138
167
|
# end method definition
|
|
139
168
|
|
|
140
169
|
def request_header(self, content_type: str = "") -> dict:
|
|
141
|
-
"""
|
|
142
|
-
|
|
170
|
+
"""Return the request header used for Application calls.
|
|
171
|
+
|
|
172
|
+
Consists of Bearer access token and Content Type
|
|
143
173
|
|
|
144
174
|
Args:
|
|
145
|
-
content_type (str, optional):
|
|
146
|
-
|
|
147
|
-
|
|
175
|
+
content_type (str, optional):
|
|
176
|
+
Custom content type for the request.
|
|
177
|
+
Typical values:
|
|
178
|
+
* application/json - Used for sending JSON-encoded data
|
|
179
|
+
* application/x-www-form-urlencoded - The default for HTML forms.
|
|
180
|
+
Data is sent as key-value pairs in the body of the request, similar to query parameters.
|
|
181
|
+
* multipart/form-data - Used for file uploads or when a form includes non-ASCII characters
|
|
182
|
+
|
|
183
|
+
Returns:
|
|
184
|
+
dict:
|
|
185
|
+
The request header values.
|
|
186
|
+
|
|
148
187
|
"""
|
|
149
188
|
|
|
150
189
|
request_header = {}
|
|
@@ -164,6 +203,7 @@ class PHT(object):
|
|
|
164
203
|
method: str = "GET",
|
|
165
204
|
headers: dict | None = None,
|
|
166
205
|
data: dict | None = None,
|
|
206
|
+
json_data: dict | None = None,
|
|
167
207
|
files: dict | None = None,
|
|
168
208
|
timeout: int | None = REQUEST_TIMEOUT,
|
|
169
209
|
show_error: bool = True,
|
|
@@ -172,24 +212,40 @@ class PHT(object):
|
|
|
172
212
|
max_retries: int = REQUEST_MAX_RETRIES,
|
|
173
213
|
retry_forever: bool = False,
|
|
174
214
|
) -> dict | None:
|
|
175
|
-
"""Call an PHT REST API in a safe way
|
|
215
|
+
"""Call an PHT REST API in a safe way.
|
|
176
216
|
|
|
177
217
|
Args:
|
|
178
|
-
url (str):
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
218
|
+
url (str):
|
|
219
|
+
The URL to send the request to.
|
|
220
|
+
method (str, optional):
|
|
221
|
+
HTTP method (GET, POST, etc.). Defaults to "GET".
|
|
222
|
+
headers (dict | None, optional):
|
|
223
|
+
Request Headers. Defaults to None.
|
|
224
|
+
data (dict | None, optional):
|
|
225
|
+
Request payload. Defaults to None.
|
|
226
|
+
json_data (dict | None, optional):
|
|
227
|
+
Request payload. Defaults to None.
|
|
228
|
+
files (dict | None, optional):
|
|
229
|
+
Dictionary of {"name": file-tuple} for multipart encoding upload.
|
|
230
|
+
The file-tuple can be a 2-tuple ("filename", fileobj) or a 3-tuple ("filename", fileobj, "content_type")
|
|
231
|
+
timeout (int | None, optional):
|
|
232
|
+
Timeout for the request in seconds. Defaults to REQUEST_TIMEOUT.
|
|
233
|
+
show_error (bool, optional):
|
|
234
|
+
Whether or not an error should be logged in case of a failed REST call.
|
|
235
|
+
If False, then only a warning is logged. Defaults to True.
|
|
236
|
+
failure_message (str, optional):
|
|
237
|
+
Specific error message. Defaults to "".
|
|
238
|
+
success_message (str, optional):
|
|
239
|
+
Specific success message. Defaults to "".
|
|
240
|
+
max_retries (int, optional):
|
|
241
|
+
How many retries on Connection errors? Default is REQUEST_MAX_RETRIES.
|
|
242
|
+
retry_forever (bool, optional):
|
|
243
|
+
Eventually wait forever - without timeout. Defaults to False.
|
|
190
244
|
|
|
191
245
|
Returns:
|
|
192
|
-
dict | None:
|
|
246
|
+
dict | None:
|
|
247
|
+
Response of PHT REST API or None in case of an error.
|
|
248
|
+
|
|
193
249
|
"""
|
|
194
250
|
|
|
195
251
|
retries = 0
|
|
@@ -198,7 +254,8 @@ class PHT(object):
|
|
|
198
254
|
response = self._session.request(
|
|
199
255
|
method=method,
|
|
200
256
|
url=url,
|
|
201
|
-
|
|
257
|
+
data=data,
|
|
258
|
+
json=json_data,
|
|
202
259
|
files=files,
|
|
203
260
|
headers=headers,
|
|
204
261
|
timeout=timeout,
|
|
@@ -206,30 +263,27 @@ class PHT(object):
|
|
|
206
263
|
|
|
207
264
|
if response.ok:
|
|
208
265
|
if success_message:
|
|
209
|
-
logger.debug(success_message)
|
|
266
|
+
self.logger.debug(success_message)
|
|
210
267
|
return self.parse_request_response(response)
|
|
211
268
|
# Check if Session has expired - then re-authenticate and try once more
|
|
212
269
|
elif response.status_code == 401 and retries == 0:
|
|
213
|
-
logger.debug("Session has expired - try to re-authenticate...")
|
|
270
|
+
self.logger.debug("Session has expired - try to re-authenticate...")
|
|
214
271
|
self.authenticate()
|
|
215
272
|
retries += 1
|
|
216
273
|
else:
|
|
217
274
|
# Handle plain HTML responses to not pollute the logs
|
|
218
275
|
content_type = response.headers.get("content-type", None)
|
|
219
|
-
if content_type == "text/html"
|
|
220
|
-
response_text = "HTML content (see debug log)"
|
|
221
|
-
else:
|
|
222
|
-
response_text = response.text
|
|
276
|
+
response_text = "HTML content (see debug log)" if content_type == "text/html" else response.text
|
|
223
277
|
|
|
224
278
|
if show_error:
|
|
225
|
-
logger.error(
|
|
279
|
+
self.logger.error(
|
|
226
280
|
"%s; status -> %s; error -> %s",
|
|
227
281
|
failure_message,
|
|
228
282
|
response.status_code,
|
|
229
283
|
response_text,
|
|
230
284
|
)
|
|
231
285
|
else:
|
|
232
|
-
logger.warning(
|
|
286
|
+
self.logger.warning(
|
|
233
287
|
"%s; status -> %s; warning -> %s",
|
|
234
288
|
failure_message,
|
|
235
289
|
response.status_code,
|
|
@@ -237,7 +291,7 @@ class PHT(object):
|
|
|
237
291
|
)
|
|
238
292
|
|
|
239
293
|
if content_type == "text/html":
|
|
240
|
-
logger.debug(
|
|
294
|
+
self.logger.debug(
|
|
241
295
|
"%s; status -> %s; warning -> %s",
|
|
242
296
|
failure_message,
|
|
243
297
|
response.status_code,
|
|
@@ -247,39 +301,39 @@ class PHT(object):
|
|
|
247
301
|
return None
|
|
248
302
|
except requests.exceptions.Timeout:
|
|
249
303
|
if retries <= max_retries:
|
|
250
|
-
logger.warning(
|
|
304
|
+
self.logger.warning(
|
|
251
305
|
"Request timed out. Retrying in %s seconds...",
|
|
252
306
|
str(REQUEST_RETRY_DELAY),
|
|
253
307
|
)
|
|
254
308
|
retries += 1
|
|
255
309
|
time.sleep(REQUEST_RETRY_DELAY) # Add a delay before retrying
|
|
256
310
|
else:
|
|
257
|
-
logger.error(
|
|
258
|
-
"%s; timeout error",
|
|
311
|
+
self.logger.error(
|
|
312
|
+
"%s; timeout error,",
|
|
259
313
|
failure_message,
|
|
260
314
|
)
|
|
261
315
|
if retry_forever:
|
|
262
316
|
# If it fails after REQUEST_MAX_RETRIES retries we let it wait forever
|
|
263
|
-
logger.warning("Turn timeouts off and wait forever...")
|
|
317
|
+
self.logger.warning("Turn timeouts off and wait forever...")
|
|
264
318
|
timeout = None
|
|
265
319
|
else:
|
|
266
320
|
return None
|
|
267
321
|
except requests.exceptions.ConnectionError:
|
|
268
322
|
if retries <= max_retries:
|
|
269
|
-
logger.warning(
|
|
323
|
+
self.logger.warning(
|
|
270
324
|
"Connection error. Retrying in %s seconds...",
|
|
271
325
|
str(REQUEST_RETRY_DELAY),
|
|
272
326
|
)
|
|
273
327
|
retries += 1
|
|
274
328
|
time.sleep(REQUEST_RETRY_DELAY) # Add a delay before retrying
|
|
275
329
|
else:
|
|
276
|
-
logger.error(
|
|
277
|
-
"%s; connection error",
|
|
330
|
+
self.logger.error(
|
|
331
|
+
"%s; connection error.",
|
|
278
332
|
failure_message,
|
|
279
333
|
)
|
|
280
334
|
if retry_forever:
|
|
281
335
|
# If it fails after REQUEST_MAX_RETRIES retries we let it wait forever
|
|
282
|
-
logger.warning("Turn timeouts off and wait forever...")
|
|
336
|
+
self.logger.warning("Turn timeouts off and wait forever...")
|
|
283
337
|
timeout = None
|
|
284
338
|
time.sleep(REQUEST_RETRY_DELAY) # Add a delay before retrying
|
|
285
339
|
else:
|
|
@@ -293,51 +347,59 @@ class PHT(object):
|
|
|
293
347
|
additional_error_message: str = "",
|
|
294
348
|
show_error: bool = True,
|
|
295
349
|
) -> list | None:
|
|
296
|
-
"""
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
350
|
+
"""Convert the request response (JSon) to a Python list in a safe way that also handles exceptions.
|
|
351
|
+
|
|
352
|
+
It first tries to load the response.text via json.loads() that produces
|
|
353
|
+
a dict output. Only if response.text is not set or is empty it just converts
|
|
354
|
+
the response_object to a dict using the vars() built-in method.
|
|
301
355
|
|
|
302
356
|
Args:
|
|
303
|
-
response_object (object):
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
357
|
+
response_object (object):
|
|
358
|
+
This is reponse object delivered by the request call.
|
|
359
|
+
additional_error_message (str, optional):
|
|
360
|
+
Used to provide a more specific error message.
|
|
361
|
+
show_error (bool):
|
|
362
|
+
If True, write an error to the log file.
|
|
363
|
+
If False, write a warning to the log file.
|
|
364
|
+
|
|
308
365
|
Returns:
|
|
309
366
|
list: response information or None in case of an error
|
|
367
|
+
|
|
310
368
|
"""
|
|
311
369
|
|
|
312
370
|
if not response_object:
|
|
313
371
|
return None
|
|
314
372
|
|
|
315
373
|
try:
|
|
316
|
-
if response_object.text
|
|
317
|
-
list_object = json.loads(response_object.text)
|
|
318
|
-
else:
|
|
319
|
-
list_object = vars(response_object)
|
|
374
|
+
list_object = json.loads(response_object.text) if response_object.text else vars(response_object)
|
|
320
375
|
except json.JSONDecodeError as exception:
|
|
321
376
|
if additional_error_message:
|
|
322
377
|
message = "Cannot decode response as JSON. {}; error -> {}".format(
|
|
323
|
-
additional_error_message,
|
|
378
|
+
additional_error_message,
|
|
379
|
+
exception,
|
|
324
380
|
)
|
|
325
381
|
else:
|
|
326
382
|
message = "Cannot decode response as JSON; error -> {}".format(
|
|
327
|
-
exception
|
|
383
|
+
exception,
|
|
328
384
|
)
|
|
329
385
|
if show_error:
|
|
330
|
-
logger.error(message)
|
|
386
|
+
self.logger.error(message)
|
|
331
387
|
else:
|
|
332
|
-
logger.warning(message)
|
|
388
|
+
self.logger.warning(message)
|
|
333
389
|
return None
|
|
334
390
|
else:
|
|
335
391
|
return list_object
|
|
336
392
|
|
|
337
393
|
# end method definition
|
|
338
394
|
|
|
339
|
-
def authenticate(self) ->
|
|
340
|
-
"""Authenticate at PHT with basic authentication.
|
|
395
|
+
def authenticate(self) -> HTTPBasicAuth | None:
|
|
396
|
+
"""Authenticate at PHT with basic authentication.
|
|
397
|
+
|
|
398
|
+
Returns:
|
|
399
|
+
str | None:
|
|
400
|
+
Session authorization string.
|
|
401
|
+
|
|
402
|
+
"""
|
|
341
403
|
|
|
342
404
|
self._session.headers.update(self.request_header())
|
|
343
405
|
|
|
@@ -352,18 +414,18 @@ class PHT(object):
|
|
|
352
414
|
# end method definition
|
|
353
415
|
|
|
354
416
|
def get_attributes(self) -> list | None:
|
|
355
|
-
"""Get a list of all product attributes (schema) of PHT
|
|
417
|
+
"""Get a list of all product attributes (schema) of PHT.
|
|
356
418
|
|
|
357
419
|
Returns:
|
|
358
420
|
list | None: list of product attributes
|
|
359
421
|
|
|
360
|
-
|
|
422
|
+
Example:
|
|
361
423
|
[
|
|
362
424
|
{
|
|
363
425
|
'id': 28,
|
|
364
426
|
'uuid': '43ba5852-eb83-11ed-a752-00505682262c',
|
|
365
427
|
'name': 'UBM SCM Migration JIRA/ValueEdge',
|
|
366
|
-
'description': 'Identifies the Issue to track work for the SCM migration for this project
|
|
428
|
+
'description': 'Identifies the Issue to track work for the SCM migration for this project.',
|
|
367
429
|
'type': 'TEXT',
|
|
368
430
|
'attributeCategory': {
|
|
369
431
|
'id': 2,
|
|
@@ -379,6 +441,7 @@ class PHT(object):
|
|
|
379
441
|
'allowedTeams': []
|
|
380
442
|
}
|
|
381
443
|
]
|
|
444
|
+
|
|
382
445
|
"""
|
|
383
446
|
|
|
384
447
|
request_header = self.request_header()
|
|
@@ -395,12 +458,13 @@ class PHT(object):
|
|
|
395
458
|
# end method definition
|
|
396
459
|
|
|
397
460
|
def get_business_units(self) -> list | None:
|
|
398
|
-
"""Get the list of PHT Business Units
|
|
461
|
+
"""Get the list of PHT Business Units.
|
|
399
462
|
|
|
400
463
|
Returns:
|
|
401
|
-
list | None:
|
|
464
|
+
list | None:
|
|
465
|
+
The list of the known business units.
|
|
402
466
|
|
|
403
|
-
|
|
467
|
+
Example:
|
|
404
468
|
[
|
|
405
469
|
{
|
|
406
470
|
'id': 1,
|
|
@@ -449,6 +513,7 @@ class PHT(object):
|
|
|
449
513
|
'sltOwnerDomain': 'jradko'
|
|
450
514
|
}
|
|
451
515
|
]
|
|
516
|
+
|
|
452
517
|
"""
|
|
453
518
|
|
|
454
519
|
request_header = self.request_header()
|
|
@@ -465,10 +530,12 @@ class PHT(object):
|
|
|
465
530
|
# end method definition
|
|
466
531
|
|
|
467
532
|
def get_product_families(self) -> list | None:
|
|
468
|
-
"""Get the list of PHT product families
|
|
533
|
+
"""Get the list of PHT product families (LoBs).
|
|
469
534
|
|
|
470
535
|
Returns:
|
|
471
|
-
list | None:
|
|
536
|
+
list | None:
|
|
537
|
+
A list of the known product families.
|
|
538
|
+
|
|
472
539
|
"""
|
|
473
540
|
|
|
474
541
|
request_header = self.request_header()
|
|
@@ -479,16 +546,18 @@ class PHT(object):
|
|
|
479
546
|
method="GET",
|
|
480
547
|
headers=request_header,
|
|
481
548
|
timeout=None,
|
|
482
|
-
failure_message="Failed to get PHT product families
|
|
549
|
+
failure_message="Failed to get PHT product families",
|
|
483
550
|
)
|
|
484
551
|
|
|
485
552
|
# end method definition
|
|
486
553
|
|
|
487
554
|
def get_products(self) -> list | None:
|
|
488
|
-
"""Get the list of PHT products
|
|
555
|
+
"""Get the list of PHT products.
|
|
489
556
|
|
|
490
557
|
Returns:
|
|
491
|
-
list | None:
|
|
558
|
+
list | None:
|
|
559
|
+
A list of the known products.
|
|
560
|
+
|
|
492
561
|
"""
|
|
493
562
|
|
|
494
563
|
request_header = self.request_header()
|
|
@@ -499,39 +568,44 @@ class PHT(object):
|
|
|
499
568
|
method="GET",
|
|
500
569
|
headers=request_header,
|
|
501
570
|
timeout=None,
|
|
502
|
-
failure_message="Failed to get PHT products
|
|
571
|
+
failure_message="Failed to get PHT products",
|
|
503
572
|
)
|
|
504
573
|
|
|
505
574
|
# end method definition
|
|
506
575
|
|
|
507
576
|
def get_products_filtered(
|
|
508
|
-
self,
|
|
577
|
+
self,
|
|
578
|
+
filter_definition: dict | None = None,
|
|
509
579
|
) -> list | None:
|
|
510
|
-
"""Get a list of filtered PHT products
|
|
580
|
+
"""Get a list of filtered PHT products.
|
|
511
581
|
|
|
512
582
|
Args:
|
|
513
|
-
filter_definition (dict
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
"
|
|
527
|
-
|
|
583
|
+
filter_definition (dict | None, optional):
|
|
584
|
+
A dictionary of filter conditions. Default is None (no filter).
|
|
585
|
+
Example filters:
|
|
586
|
+
{
|
|
587
|
+
businessUnitName: <String>,
|
|
588
|
+
productFamilyName: <String>,
|
|
589
|
+
productName: <String>,
|
|
590
|
+
productSyncId: <String>,
|
|
591
|
+
productStatus: ACTIVE | INACTIVE | MAINTENANCE,
|
|
592
|
+
productManager: <String>,
|
|
593
|
+
developmentManager: <String>,
|
|
594
|
+
attributeOperator: AND | OR,
|
|
595
|
+
attributes: {
|
|
596
|
+
"<AttributeName>": {
|
|
597
|
+
"compare": CONTAINS | EXISTS | DOES_NOT_EXISTS,
|
|
598
|
+
"values": List<String>
|
|
599
|
+
},
|
|
600
|
+
...
|
|
528
601
|
},
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
602
|
+
includeAttributes: true | false
|
|
603
|
+
statuses: ["ACTIVE"],
|
|
604
|
+
}
|
|
605
|
+
|
|
533
606
|
Returns:
|
|
534
607
|
list | None: list of matching products.
|
|
608
|
+
|
|
535
609
|
"""
|
|
536
610
|
|
|
537
611
|
if not filter_definition:
|
|
@@ -545,18 +619,22 @@ class PHT(object):
|
|
|
545
619
|
url=request_url,
|
|
546
620
|
method="POST",
|
|
547
621
|
headers=request_header,
|
|
548
|
-
|
|
622
|
+
json_data=request_data,
|
|
549
623
|
timeout=None,
|
|
550
|
-
failure_message="Failed to get filtered PHT products
|
|
624
|
+
failure_message="Failed to get filtered PHT products",
|
|
551
625
|
)
|
|
552
626
|
|
|
553
627
|
# end method definition
|
|
554
628
|
|
|
555
|
-
def get_product(self, sync_id:
|
|
629
|
+
def get_product(self, sync_id: str) -> dict | None:
|
|
556
630
|
"""Get a specific product in PHT.
|
|
557
631
|
|
|
632
|
+
Args:
|
|
633
|
+
sync_id (str): Unique ID of the PHT product.
|
|
634
|
+
|
|
558
635
|
Returns:
|
|
559
636
|
dict | None: product data matching the sync ID
|
|
637
|
+
|
|
560
638
|
"""
|
|
561
639
|
|
|
562
640
|
request_header = self.request_header()
|
|
@@ -568,19 +646,32 @@ class PHT(object):
|
|
|
568
646
|
headers=request_header,
|
|
569
647
|
timeout=None,
|
|
570
648
|
failure_message="Failed to retrieve PHT product with sync ID -> {}!".format(
|
|
571
|
-
sync_id
|
|
649
|
+
sync_id,
|
|
572
650
|
),
|
|
573
651
|
)
|
|
574
652
|
|
|
575
653
|
# end method definition
|
|
576
654
|
|
|
577
655
|
def search_products(
|
|
578
|
-
self,
|
|
656
|
+
self,
|
|
657
|
+
query: str,
|
|
658
|
+
business_unit: str | None = None,
|
|
659
|
+
family: str | None = None,
|
|
579
660
|
) -> list | None:
|
|
580
|
-
"""Search for specific product in PHT by the product name.
|
|
661
|
+
"""Search for specific product in PHT by the product name, business unit or product family (or a combination).
|
|
662
|
+
|
|
663
|
+
Args:
|
|
664
|
+
query (str):
|
|
665
|
+
Query to search for specific products.
|
|
666
|
+
business_unit (str | None, optional):
|
|
667
|
+
Used to focus the search on a specific Business Unit.
|
|
668
|
+
family (str | None, optional):
|
|
669
|
+
Used to focus the search on a specific product family (Line of Business)
|
|
581
670
|
|
|
582
671
|
Returns:
|
|
583
|
-
|
|
672
|
+
list | None:
|
|
673
|
+
Search term matches any part of the component name.
|
|
674
|
+
|
|
584
675
|
"""
|
|
585
676
|
|
|
586
677
|
request_header = self.request_header()
|
|
@@ -596,17 +687,19 @@ class PHT(object):
|
|
|
596
687
|
headers=request_header,
|
|
597
688
|
timeout=None,
|
|
598
689
|
failure_message="Failed to retrieve PHT components matching -> {}!".format(
|
|
599
|
-
query
|
|
690
|
+
query,
|
|
600
691
|
),
|
|
601
692
|
)
|
|
602
693
|
|
|
603
694
|
# end method definition
|
|
604
695
|
|
|
605
696
|
def get_master_products(self) -> list | None:
|
|
606
|
-
"""Get the list of PHT master products
|
|
697
|
+
"""Get the list of PHT master products.
|
|
607
698
|
|
|
608
699
|
Returns:
|
|
609
|
-
list | None:
|
|
700
|
+
list | None:
|
|
701
|
+
A list of the known master products.
|
|
702
|
+
|
|
610
703
|
"""
|
|
611
704
|
|
|
612
705
|
request_header = self.request_header()
|
|
@@ -617,39 +710,43 @@ class PHT(object):
|
|
|
617
710
|
method="GET",
|
|
618
711
|
headers=request_header,
|
|
619
712
|
timeout=None,
|
|
620
|
-
failure_message="Failed to get PHT master products
|
|
713
|
+
failure_message="Failed to get PHT master products",
|
|
621
714
|
)
|
|
622
715
|
|
|
623
716
|
# end method definition
|
|
624
717
|
|
|
625
718
|
def get_master_products_filtered(
|
|
626
|
-
self,
|
|
719
|
+
self,
|
|
720
|
+
filter_definition: dict | None = None,
|
|
627
721
|
) -> list | None:
|
|
628
|
-
"""Get a list of filtered PHT master products
|
|
722
|
+
"""Get a list of filtered PHT master products.
|
|
629
723
|
|
|
630
724
|
Args:
|
|
631
|
-
filter_definition (dict
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
"
|
|
644
|
-
|
|
725
|
+
filter_definition (dict | None, optional):
|
|
726
|
+
A dictionary of filter conditions.
|
|
727
|
+
Example filters:
|
|
728
|
+
{
|
|
729
|
+
businessUnitName: <String>,
|
|
730
|
+
productFamilyName: <String>,
|
|
731
|
+
masterproductName: <String>,
|
|
732
|
+
masterproductSyncId: <String>,
|
|
733
|
+
masterproductStatus: ACTIVE | INACTIVE | MAINTENANCE,
|
|
734
|
+
productManagerDomain: <String>,
|
|
735
|
+
attributeOperator: AND | OR,
|
|
736
|
+
attributes: {
|
|
737
|
+
"<AttributeName>": {
|
|
738
|
+
"compare": CONTAINS | EXISTS | DOES_NOT_EXISTS,
|
|
739
|
+
"values": List<String>
|
|
740
|
+
},
|
|
741
|
+
...
|
|
645
742
|
},
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
}
|
|
743
|
+
includeAttributes: true | false
|
|
744
|
+
includeLinkedProducts: true | false
|
|
745
|
+
}
|
|
746
|
+
|
|
651
747
|
Returns:
|
|
652
|
-
list | None:
|
|
748
|
+
list | None: List of matching products.
|
|
749
|
+
|
|
653
750
|
"""
|
|
654
751
|
|
|
655
752
|
if not filter_definition:
|
|
@@ -663,18 +760,22 @@ class PHT(object):
|
|
|
663
760
|
url=request_url,
|
|
664
761
|
method="POST",
|
|
665
762
|
headers=request_header,
|
|
666
|
-
|
|
763
|
+
json_data=request_data,
|
|
667
764
|
timeout=None,
|
|
668
|
-
failure_message="Failed to get filtered PHT master products
|
|
765
|
+
failure_message="Failed to get filtered PHT master products",
|
|
669
766
|
)
|
|
670
767
|
|
|
671
768
|
# end method definition
|
|
672
769
|
|
|
673
|
-
def get_master_product(self, sync_id:
|
|
770
|
+
def get_master_product(self, sync_id: str) -> dict | None:
|
|
674
771
|
"""Get a specific product in PHT.
|
|
675
772
|
|
|
773
|
+
Args:
|
|
774
|
+
sync_id (str): Unique PHT ID of the master product.
|
|
775
|
+
|
|
676
776
|
Returns:
|
|
677
777
|
dict | None: product data matching the sync ID
|
|
778
|
+
|
|
678
779
|
"""
|
|
679
780
|
|
|
680
781
|
request_header = self.request_header()
|
|
@@ -685,18 +786,19 @@ class PHT(object):
|
|
|
685
786
|
method="GET",
|
|
686
787
|
headers=request_header,
|
|
687
788
|
timeout=None,
|
|
688
|
-
failure_message="Failed to retrieve PHT product with sync ID -> {}
|
|
689
|
-
sync_id
|
|
789
|
+
failure_message="Failed to retrieve PHT product with sync ID -> {}".format(
|
|
790
|
+
sync_id,
|
|
690
791
|
),
|
|
691
792
|
)
|
|
692
793
|
|
|
693
794
|
# end method definition
|
|
694
795
|
|
|
695
|
-
def get_teams(self) -> list:
|
|
796
|
+
def get_teams(self) -> list | None:
|
|
696
797
|
"""Get a list of all teams in PHT.
|
|
697
798
|
|
|
698
799
|
Returns:
|
|
699
|
-
list: list of PHT teams
|
|
800
|
+
list | None: list of PHT teams
|
|
801
|
+
|
|
700
802
|
"""
|
|
701
803
|
|
|
702
804
|
request_header = self.request_header()
|
|
@@ -707,16 +809,20 @@ class PHT(object):
|
|
|
707
809
|
method="GET",
|
|
708
810
|
headers=request_header,
|
|
709
811
|
timeout=None,
|
|
710
|
-
failure_message="Failed to retrieve PHT teams
|
|
812
|
+
failure_message="Failed to retrieve PHT teams",
|
|
711
813
|
)
|
|
712
814
|
|
|
713
815
|
# end method definition
|
|
714
816
|
|
|
715
|
-
def get_team(self, team_id:
|
|
817
|
+
def get_team(self, team_id: str) -> dict | None:
|
|
716
818
|
"""Get a specific team in PHT.
|
|
717
819
|
|
|
820
|
+
Args:
|
|
821
|
+
team_id (str): Unique PHT ID of the team.
|
|
822
|
+
|
|
718
823
|
Returns:
|
|
719
|
-
dict | None:
|
|
824
|
+
dict | None: Details of the PHT team.
|
|
825
|
+
|
|
720
826
|
"""
|
|
721
827
|
|
|
722
828
|
request_header = self.request_header()
|
|
@@ -727,7 +833,7 @@ class PHT(object):
|
|
|
727
833
|
method="GET",
|
|
728
834
|
headers=request_header,
|
|
729
835
|
timeout=None,
|
|
730
|
-
failure_message="Failed to retrieve PHT
|
|
836
|
+
failure_message="Failed to retrieve PHT team with ID -> {}".format(team_id),
|
|
731
837
|
)
|
|
732
838
|
|
|
733
839
|
# end method definition
|
|
@@ -738,67 +844,68 @@ class PHT(object):
|
|
|
738
844
|
Returns:
|
|
739
845
|
list: list of PHT components
|
|
740
846
|
|
|
741
|
-
Example
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
},
|
|
758
|
-
'componentCategory': {
|
|
759
|
-
'id': 2,
|
|
760
|
-
'name': 'Testing scripts',
|
|
761
|
-
'shortName': 'Testing scripts'
|
|
762
|
-
},
|
|
763
|
-
'comment': 'Test Framework maintained and used by Core Archive Team',
|
|
764
|
-
'status': 'MAINTENANCE',
|
|
765
|
-
'attributes': [
|
|
766
|
-
{
|
|
767
|
-
'id': 409,
|
|
768
|
-
'attribute': {
|
|
769
|
-
'id': 4,
|
|
770
|
-
'uuid': '03e228b5-9eae-11ea-96ab-00505682bce9',
|
|
771
|
-
'name': 'Build Advocate',
|
|
772
|
-
'description': 'Primary contact for build items.',
|
|
773
|
-
'type': 'USER'
|
|
774
|
-
},
|
|
775
|
-
'value': 'burkhard',
|
|
776
|
-
'textAttributeValue': None,
|
|
777
|
-
'userAttributeValue': {
|
|
778
|
-
'id': 414,
|
|
779
|
-
'domain': 'burkhard',
|
|
780
|
-
'email': 'burkhard.meier@opentext.com',
|
|
781
|
-
'name': 'Burkhard Meier',
|
|
782
|
-
'role': None,
|
|
783
|
-
'status': 'ACTIVE',
|
|
784
|
-
'location': 'Virtual, DEU',
|
|
785
|
-
'title': 'Principal Software Engineer',
|
|
786
|
-
'type': 'DEV'
|
|
787
|
-
},
|
|
788
|
-
'listAttributeValue': None
|
|
847
|
+
Example:
|
|
848
|
+
[
|
|
849
|
+
{
|
|
850
|
+
'id': 468,
|
|
851
|
+
'syncId': '6380c3da-8ded-40cd-8071-61f6721956f5',
|
|
852
|
+
'name': 'XOTE',
|
|
853
|
+
'developmentManager': {
|
|
854
|
+
'id': 237,
|
|
855
|
+
'domain': 'kurt',
|
|
856
|
+
'email': 'kurt.junker@opentext.com',
|
|
857
|
+
'name': 'Kurt Junker',
|
|
858
|
+
'role': None,
|
|
859
|
+
'status': 'ACTIVE',
|
|
860
|
+
'location': 'Grasbrunn, DEU',
|
|
861
|
+
'title': 'Sr. Manager, Software Engineering',
|
|
862
|
+
'type': 'OTHERS'
|
|
789
863
|
},
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
864
|
+
'componentCategory': {
|
|
865
|
+
'id': 2,
|
|
866
|
+
'name': 'Testing scripts',
|
|
867
|
+
'shortName': 'Testing scripts'
|
|
868
|
+
},
|
|
869
|
+
'comment': 'Test Framework maintained and used by Core Archive Team',
|
|
870
|
+
'status': 'MAINTENANCE',
|
|
871
|
+
'attributes': [
|
|
872
|
+
{
|
|
873
|
+
'id': 409,
|
|
874
|
+
'attribute': {
|
|
875
|
+
'id': 4,
|
|
876
|
+
'uuid': '03e228b5-9eae-11ea-96ab-00505682bce9',
|
|
877
|
+
'name': 'Build Advocate',
|
|
878
|
+
'description': 'Primary contact for build items.',
|
|
879
|
+
'type': 'USER'
|
|
880
|
+
},
|
|
881
|
+
'value': 'burkhard',
|
|
882
|
+
'textAttributeValue': None,
|
|
883
|
+
'userAttributeValue': {
|
|
884
|
+
'id': 414,
|
|
885
|
+
'domain': 'burkhard',
|
|
886
|
+
'email': 'burkhard.meier@opentext.com',
|
|
887
|
+
'name': 'Burkhard Meier',
|
|
888
|
+
'role': None,
|
|
889
|
+
'status': 'ACTIVE',
|
|
890
|
+
'location': 'Virtual, DEU',
|
|
891
|
+
'title': 'Principal Software Engineer',
|
|
892
|
+
'type': 'DEV'
|
|
893
|
+
},
|
|
894
|
+
'listAttributeValue': None
|
|
895
|
+
},
|
|
896
|
+
...
|
|
897
|
+
],
|
|
898
|
+
'sourceRepos': [],
|
|
899
|
+
'artifacts': [],
|
|
900
|
+
'products': [],
|
|
901
|
+
'teams': [],
|
|
902
|
+
'users': [],
|
|
903
|
+
'guestTeams': [],
|
|
904
|
+
'guestUsers': [],
|
|
905
|
+
'relatedLOBS': []
|
|
906
|
+
}
|
|
907
|
+
]
|
|
908
|
+
|
|
802
909
|
"""
|
|
803
910
|
|
|
804
911
|
request_header = self.request_header()
|
|
@@ -809,36 +916,40 @@ class PHT(object):
|
|
|
809
916
|
method="GET",
|
|
810
917
|
headers=request_header,
|
|
811
918
|
timeout=None,
|
|
812
|
-
failure_message="Failed to retrieve PHT components
|
|
919
|
+
failure_message="Failed to retrieve PHT components",
|
|
813
920
|
)
|
|
814
921
|
|
|
815
922
|
# end method definition
|
|
816
923
|
|
|
817
924
|
def get_components_filtered(
|
|
818
|
-
self,
|
|
925
|
+
self,
|
|
926
|
+
filter_definition: dict | None = None,
|
|
819
927
|
) -> list | None:
|
|
820
|
-
"""Get a list of filtered PHT components
|
|
928
|
+
"""Get a list of filtered PHT components.
|
|
821
929
|
|
|
822
930
|
Args:
|
|
823
|
-
filter_definition (dict
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
"
|
|
834
|
-
|
|
931
|
+
filter_definition (dict | None, optional):
|
|
932
|
+
A dictionary of filter conditions.
|
|
933
|
+
Example filters:
|
|
934
|
+
{
|
|
935
|
+
componentName: <String>,
|
|
936
|
+
componentSyncId: <String>,
|
|
937
|
+
componentStatus: ACTIVE | INACTIVE | MAINTENANCE,
|
|
938
|
+
developmentManager: <String>,
|
|
939
|
+
attributeOperator: AND | OR,
|
|
940
|
+
attributes: {
|
|
941
|
+
"<AttributeName>": {
|
|
942
|
+
"compare": CONTAINS | EXISTS | DOES_NOT_EXISTS,
|
|
943
|
+
"values": List<String>
|
|
944
|
+
},
|
|
945
|
+
...
|
|
835
946
|
},
|
|
836
|
-
|
|
837
|
-
}
|
|
838
|
-
|
|
839
|
-
}
|
|
947
|
+
includeAttributes: true | false
|
|
948
|
+
}
|
|
949
|
+
|
|
840
950
|
Returns:
|
|
841
951
|
list | None: list of matching components.
|
|
952
|
+
|
|
842
953
|
"""
|
|
843
954
|
|
|
844
955
|
if not filter_definition:
|
|
@@ -852,18 +963,24 @@ class PHT(object):
|
|
|
852
963
|
url=request_url,
|
|
853
964
|
method="POST",
|
|
854
965
|
headers=request_header,
|
|
855
|
-
|
|
966
|
+
json_data=request_data,
|
|
856
967
|
timeout=None,
|
|
857
|
-
failure_message="Failed to get filtered PHT
|
|
968
|
+
failure_message="Failed to get filtered PHT components",
|
|
858
969
|
)
|
|
859
970
|
|
|
860
971
|
# end method definition
|
|
861
972
|
|
|
862
|
-
def get_component(self, sync_id:
|
|
973
|
+
def get_component(self, sync_id: str) -> dict | None:
|
|
863
974
|
"""Get a specific component in PHT.
|
|
864
975
|
|
|
976
|
+
Args:
|
|
977
|
+
sync_id (str):
|
|
978
|
+
Unique PHT ID of the component.
|
|
979
|
+
|
|
865
980
|
Returns:
|
|
866
|
-
dict | None:
|
|
981
|
+
dict | None:
|
|
982
|
+
Details of the PHT component. None in case of an error.
|
|
983
|
+
|
|
867
984
|
"""
|
|
868
985
|
|
|
869
986
|
request_header = self.request_header()
|
|
@@ -875,7 +992,7 @@ class PHT(object):
|
|
|
875
992
|
headers=request_header,
|
|
876
993
|
timeout=None,
|
|
877
994
|
failure_message="Failed to retrieve PHT component with sync ID -> {}!".format(
|
|
878
|
-
sync_id
|
|
995
|
+
sync_id,
|
|
879
996
|
),
|
|
880
997
|
)
|
|
881
998
|
|
|
@@ -884,8 +1001,12 @@ class PHT(object):
|
|
|
884
1001
|
def search_components(self, query: str) -> list | None:
|
|
885
1002
|
"""Search for specific components in PHT by the component name.
|
|
886
1003
|
|
|
1004
|
+
Args:
|
|
1005
|
+
query (str): Search term to match any part of the component name.
|
|
1006
|
+
|
|
887
1007
|
Returns:
|
|
888
|
-
|
|
1008
|
+
list | None: List of matching components.
|
|
1009
|
+
|
|
889
1010
|
"""
|
|
890
1011
|
|
|
891
1012
|
request_header = self.request_header()
|
|
@@ -896,31 +1017,324 @@ class PHT(object):
|
|
|
896
1017
|
method="GET",
|
|
897
1018
|
headers=request_header,
|
|
898
1019
|
timeout=None,
|
|
899
|
-
failure_message="Failed to retrieve PHT components matching -> {}
|
|
900
|
-
query
|
|
1020
|
+
failure_message="Failed to retrieve PHT components matching -> {}".format(
|
|
1021
|
+
query,
|
|
901
1022
|
),
|
|
902
1023
|
)
|
|
903
1024
|
|
|
904
1025
|
# end method definition
|
|
905
1026
|
|
|
906
|
-
def
|
|
907
|
-
"""Load
|
|
1027
|
+
def load_business_units(self, business_unit_list: list | None = None) -> bool:
|
|
1028
|
+
"""Load business units into a data frame in the self._data object.
|
|
908
1029
|
|
|
909
1030
|
Args:
|
|
910
|
-
|
|
1031
|
+
business_unit_list (list, optional):
|
|
1032
|
+
List of business units - if already avaiable. Defaults to None.
|
|
1033
|
+
If None, then the list of all business units is created on-the-fly.
|
|
911
1034
|
|
|
912
1035
|
Returns:
|
|
913
|
-
bool:
|
|
1036
|
+
bool:
|
|
1037
|
+
True if successful, False otherwise.
|
|
1038
|
+
|
|
1039
|
+
"""
|
|
1040
|
+
|
|
1041
|
+
if not business_unit_list:
|
|
1042
|
+
self.logger.info("Load PHT business unit list...")
|
|
1043
|
+
# First, get the list of all products:
|
|
1044
|
+
business_unit_list = self.get_business_units()
|
|
1045
|
+
if business_unit_list:
|
|
1046
|
+
self.logger.info(
|
|
1047
|
+
"Completed loading of -> %s PHT business units",
|
|
1048
|
+
str(len(business_unit_list)),
|
|
1049
|
+
)
|
|
1050
|
+
else:
|
|
1051
|
+
self.logger.error("Failed to load PHT business units!")
|
|
1052
|
+
return False
|
|
1053
|
+
|
|
1054
|
+
# Put the business unit list in an initial data frame.
|
|
1055
|
+
# This makes it easy to filter with powerful Pandas capabilities:
|
|
1056
|
+
self._data = Data(business_unit_list, logger=self.logger)
|
|
1057
|
+
|
|
1058
|
+
# Filter based on black list for business units:
|
|
1059
|
+
if self._business_unit_exclusions:
|
|
1060
|
+
self.logger.info("Found PHT business unit exclusions...")
|
|
1061
|
+
condition = [
|
|
1062
|
+
{
|
|
1063
|
+
"field": "name",
|
|
1064
|
+
"value": self._business_unit_exclusions,
|
|
1065
|
+
"equal": False,
|
|
1066
|
+
},
|
|
1067
|
+
]
|
|
1068
|
+
self._data.filter(conditions=condition)
|
|
1069
|
+
|
|
1070
|
+
# Filter based on white list for business units:
|
|
1071
|
+
if self._business_unit_inclusions:
|
|
1072
|
+
self.logger.info("Found PHT business unit inclusions...")
|
|
1073
|
+
condition = [
|
|
1074
|
+
{"field": "name", "value": self._business_unit_inclusions},
|
|
1075
|
+
]
|
|
1076
|
+
self._data.filter(conditions=condition)
|
|
1077
|
+
|
|
1078
|
+
return bool(self._data)
|
|
1079
|
+
|
|
1080
|
+
# end method definition
|
|
1081
|
+
|
|
1082
|
+
def load_product_families(self, product_family_list: list | None = None, append: bool = False) -> bool:
|
|
1083
|
+
"""Load product families (LoBs) into a data frame in the self._data object.
|
|
1084
|
+
|
|
1085
|
+
Args:
|
|
1086
|
+
product_family_list (list, optional):
|
|
1087
|
+
List of product families (LoBs) - if already avaiable. Defaults to None.
|
|
1088
|
+
If None, then the list of all product families is created on-the-fly.
|
|
1089
|
+
append (bool):
|
|
1090
|
+
Whether or not the product families should be added to an existing data frame
|
|
1091
|
+
or if the data frame should be reset with the product family data only.
|
|
1092
|
+
Default is False (drop existing data rows).
|
|
1093
|
+
|
|
1094
|
+
Returns:
|
|
1095
|
+
bool:
|
|
1096
|
+
True if successful, False otherwise.
|
|
1097
|
+
|
|
1098
|
+
"""
|
|
1099
|
+
|
|
1100
|
+
if not product_family_list:
|
|
1101
|
+
self.logger.info("Load PHT product family (LoB) list...")
|
|
1102
|
+
# First, get the list of all products:
|
|
1103
|
+
product_family_list = self.get_product_families()
|
|
1104
|
+
if product_family_list:
|
|
1105
|
+
self.logger.info(
|
|
1106
|
+
"Completed loading of -> %s PHT product families (LoBs)",
|
|
1107
|
+
str(len(product_family_list)),
|
|
1108
|
+
)
|
|
1109
|
+
else:
|
|
1110
|
+
self.logger.error("Failed to load PHT product families!")
|
|
1111
|
+
return False
|
|
1112
|
+
|
|
1113
|
+
# Put the product family (LoB) list in an initial data frame.
|
|
1114
|
+
# This makes it easy to filter with powerful Pandas capabilities:
|
|
1115
|
+
data = Data(product_family_list, logger=self.logger)
|
|
1116
|
+
|
|
1117
|
+
# Filter based on black list for business units:
|
|
1118
|
+
if self._business_unit_exclusions:
|
|
1119
|
+
self.logger.info("Found PHT business unit exclusions...")
|
|
1120
|
+
condition = [
|
|
1121
|
+
{
|
|
1122
|
+
"field": "businessUnit.name",
|
|
1123
|
+
"value": self._business_unit_exclusions,
|
|
1124
|
+
"equal": False,
|
|
1125
|
+
},
|
|
1126
|
+
]
|
|
1127
|
+
data.filter(conditions=condition)
|
|
1128
|
+
|
|
1129
|
+
# Filter based on white list for business units:
|
|
1130
|
+
if self._business_unit_inclusions:
|
|
1131
|
+
self.logger.info("Found PHT business unit inclusions...")
|
|
1132
|
+
condition = [
|
|
1133
|
+
{"field": "businessUnit.name", "value": self._business_unit_inclusions},
|
|
1134
|
+
]
|
|
1135
|
+
data.filter(conditions=condition)
|
|
1136
|
+
|
|
1137
|
+
if self.get_data() and not data.get_data_frame().empty and append:
|
|
1138
|
+
self.get_data().append(add_data=data)
|
|
1139
|
+
else:
|
|
1140
|
+
self._data = data
|
|
1141
|
+
|
|
1142
|
+
return bool(self._data)
|
|
1143
|
+
|
|
1144
|
+
# end method definition
|
|
1145
|
+
|
|
1146
|
+
def load_products(
|
|
1147
|
+
self,
|
|
1148
|
+
product_list: list | None = None,
|
|
1149
|
+
append: bool = False,
|
|
1150
|
+
attributes_to_extract: list | None = None,
|
|
1151
|
+
) -> bool:
|
|
1152
|
+
"""Load products into a data frame in the self._data object.
|
|
1153
|
+
|
|
1154
|
+
The data frame has these columns:
|
|
1155
|
+
"syncId"
|
|
1156
|
+
"id"
|
|
1157
|
+
"name"
|
|
1158
|
+
"shortCode"
|
|
1159
|
+
"family"
|
|
1160
|
+
"businessUnit"
|
|
1161
|
+
"familySyncId"
|
|
1162
|
+
"businessUnitSyncId"
|
|
1163
|
+
"manager"
|
|
1164
|
+
"developmentManager"
|
|
1165
|
+
"status"
|
|
1166
|
+
"category"
|
|
1167
|
+
"attributes"
|
|
1168
|
+
|
|
1169
|
+
Args:
|
|
1170
|
+
product_list (list, optional):
|
|
1171
|
+
List of products - if already avaiable. Defaults to None.
|
|
1172
|
+
If None, then the list of all products is created on-the-fly.
|
|
1173
|
+
append (bool):
|
|
1174
|
+
Whether or not the products should be added to an existing data frame
|
|
1175
|
+
or if the data frame should be reset with the product data only.
|
|
1176
|
+
Default is False (drop existing data rows).
|
|
1177
|
+
attributes_to_extract (list):
|
|
1178
|
+
A list of attributes names that should be extracted for the PHT
|
|
1179
|
+
"attributes" data structure inside product.
|
|
1180
|
+
|
|
1181
|
+
Returns:
|
|
1182
|
+
bool:
|
|
1183
|
+
True if successful, False otherwise.
|
|
1184
|
+
|
|
914
1185
|
"""
|
|
915
1186
|
|
|
916
1187
|
if not product_list:
|
|
1188
|
+
self.logger.info("Load PHT product list...")
|
|
1189
|
+
# First, get the list of all products:
|
|
917
1190
|
product_list = self.get_products()
|
|
1191
|
+
if product_list:
|
|
1192
|
+
self.logger.info(
|
|
1193
|
+
"Completed loading of -> %s PHT products",
|
|
1194
|
+
str(len(product_list)),
|
|
1195
|
+
)
|
|
1196
|
+
else:
|
|
1197
|
+
self.logger.error("Failed to load PHT products!")
|
|
1198
|
+
return False
|
|
1199
|
+
|
|
1200
|
+
attribute_columns = []
|
|
1201
|
+
|
|
1202
|
+
for product in product_list:
|
|
1203
|
+
product_family = product["productFamily"]
|
|
1204
|
+
business_unit = product_family["businessUnit"]
|
|
1205
|
+
category = product.get("productCategory")
|
|
1206
|
+
|
|
1207
|
+
product["businessUnitSyncId"] = business_unit["syncId"]
|
|
1208
|
+
product["familySyncId"] = product_family["syncId"]
|
|
1209
|
+
if category:
|
|
1210
|
+
product["category"] = category["name"]
|
|
1211
|
+
|
|
1212
|
+
attributes = product.get("attributes")
|
|
1213
|
+
# Does this product have attributes and do we want to extract any?
|
|
1214
|
+
if attributes and attributes_to_extract:
|
|
1215
|
+
for attribute in attributes:
|
|
1216
|
+
if attribute.get("name") in attributes_to_extract:
|
|
1217
|
+
# We fist check if there's a text value
|
|
1218
|
+
value = None
|
|
1219
|
+
value = attribute.get("textAttributeValue")
|
|
1220
|
+
# If we don't have a text value we try to get a list value:
|
|
1221
|
+
if not value and attribute.get("listAttributeValue"):
|
|
1222
|
+
value = attribute.get("listAttributeValue")["name"]
|
|
1223
|
+
# Create a new key / value pait with the extracted attribute and its value:
|
|
1224
|
+
product[attribute.get("name")] = value
|
|
1225
|
+
# We keep the attribute name as a column below:
|
|
1226
|
+
if attribute.get("name") not in attribute_columns:
|
|
1227
|
+
attribute_columns.append(attribute.get("name"))
|
|
1228
|
+
|
|
1229
|
+
# Put the product list in an initial data frame.
|
|
1230
|
+
# This makes it easy to filter with powerful Pandas capabilities:
|
|
1231
|
+
data = Data(product_list, logger=self.logger)
|
|
1232
|
+
|
|
1233
|
+
data.keep_columns(
|
|
1234
|
+
column_names=[
|
|
1235
|
+
"syncId",
|
|
1236
|
+
"id",
|
|
1237
|
+
"name",
|
|
1238
|
+
"shortCode",
|
|
1239
|
+
"family",
|
|
1240
|
+
"businessUnit",
|
|
1241
|
+
"familySyncId",
|
|
1242
|
+
"businessUnitSyncId",
|
|
1243
|
+
"manager",
|
|
1244
|
+
"developmentManager",
|
|
1245
|
+
"status",
|
|
1246
|
+
"category",
|
|
1247
|
+
"attributes",
|
|
1248
|
+
"comment",
|
|
1249
|
+
]
|
|
1250
|
+
+ attribute_columns,
|
|
1251
|
+
)
|
|
1252
|
+
# Filter based on black list for Business Units:
|
|
1253
|
+
if self._business_unit_exclusions:
|
|
1254
|
+
self.logger.info("Found PHT business unit exclusions...")
|
|
1255
|
+
condition = [
|
|
1256
|
+
{
|
|
1257
|
+
"field": "businessUnit",
|
|
1258
|
+
"value": self._business_unit_exclusions,
|
|
1259
|
+
"equal": False,
|
|
1260
|
+
},
|
|
1261
|
+
]
|
|
1262
|
+
data.filter(conditions=condition)
|
|
918
1263
|
|
|
919
|
-
|
|
1264
|
+
# Filter based on white list for Business Units:
|
|
1265
|
+
if self._business_unit_inclusions:
|
|
1266
|
+
self.logger.info("Found PHT business unit inclusions...")
|
|
1267
|
+
condition = [
|
|
1268
|
+
{"field": "businessUnit", "value": self._business_unit_inclusions},
|
|
1269
|
+
]
|
|
1270
|
+
data.filter(conditions=condition)
|
|
920
1271
|
|
|
921
|
-
|
|
922
|
-
|
|
1272
|
+
# Filter based on black list for products:
|
|
1273
|
+
if self._product_exclusions:
|
|
1274
|
+
self.logger.info("Found PHT product exclusions...")
|
|
1275
|
+
condition = [
|
|
1276
|
+
{"field": "name", "value": self._product_exclusions, "equal": False},
|
|
1277
|
+
]
|
|
1278
|
+
data.filter(conditions=condition)
|
|
1279
|
+
|
|
1280
|
+
# Filter based on white list for products:
|
|
1281
|
+
if self._product_inclusions:
|
|
1282
|
+
self.logger.info("Found PHT product inclusions...")
|
|
1283
|
+
condition = [{"field": "name", "value": self._product_inclusions}]
|
|
1284
|
+
data.filter(conditions=condition)
|
|
1285
|
+
|
|
1286
|
+
# Filter based on black list for product categories:
|
|
1287
|
+
if self._product_category_exclusions:
|
|
1288
|
+
self.logger.info("Found PHT product category exclusions...")
|
|
1289
|
+
condition = [
|
|
1290
|
+
{
|
|
1291
|
+
"field": "category",
|
|
1292
|
+
"value": self._product_category_exclusions,
|
|
1293
|
+
"equal": False,
|
|
1294
|
+
},
|
|
1295
|
+
]
|
|
1296
|
+
data.filter(conditions=condition)
|
|
1297
|
+
|
|
1298
|
+
# Filter based on white list for product categories:
|
|
1299
|
+
if self._product_category_inclusions:
|
|
1300
|
+
self.logger.info("Found PHT product category inclusions...")
|
|
1301
|
+
condition = [
|
|
1302
|
+
{
|
|
1303
|
+
"field": "category",
|
|
1304
|
+
"value": self._product_category_inclusions,
|
|
1305
|
+
},
|
|
1306
|
+
]
|
|
1307
|
+
data.filter(conditions=condition)
|
|
1308
|
+
|
|
1309
|
+
# Filter based on product status exclusions:
|
|
1310
|
+
if self._product_status_exclusions:
|
|
1311
|
+
self.logger.info("Found PHT product status exclusions...")
|
|
1312
|
+
condition = [
|
|
1313
|
+
{
|
|
1314
|
+
"field": "status",
|
|
1315
|
+
"value": self._product_status_exclusions,
|
|
1316
|
+
"equal": False,
|
|
1317
|
+
},
|
|
1318
|
+
]
|
|
1319
|
+
data.filter(conditions=condition)
|
|
1320
|
+
|
|
1321
|
+
# Filter based on product status inclusions:
|
|
1322
|
+
if self._product_status_inclusions:
|
|
1323
|
+
self.logger.info("Found PHT product status inclusions...")
|
|
1324
|
+
condition = [
|
|
1325
|
+
{
|
|
1326
|
+
"field": "status",
|
|
1327
|
+
"value": self._product_status_inclusions,
|
|
1328
|
+
"equal": True,
|
|
1329
|
+
},
|
|
1330
|
+
]
|
|
1331
|
+
data.filter(conditions=condition)
|
|
1332
|
+
|
|
1333
|
+
if self.get_data() and not data.get_data_frame().empty and append:
|
|
1334
|
+
self.get_data().append(data)
|
|
1335
|
+
else:
|
|
1336
|
+
self._data = data
|
|
923
1337
|
|
|
924
|
-
return
|
|
1338
|
+
return bool(self._data)
|
|
925
1339
|
|
|
926
1340
|
# end method definition
|