pyxecm 1.5__py3-none-any.whl → 1.6__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 +2 -0
- pyxecm/avts.py +1065 -0
- pyxecm/coreshare.py +467 -571
- pyxecm/customizer/customizer.py +160 -19
- pyxecm/customizer/k8s.py +139 -25
- pyxecm/customizer/m365.py +694 -1498
- pyxecm/customizer/payload.py +2306 -485
- pyxecm/customizer/pht.py +547 -124
- pyxecm/customizer/salesforce.py +378 -443
- pyxecm/customizer/servicenow.py +379 -133
- pyxecm/helper/assoc.py +20 -0
- pyxecm/helper/data.py +237 -33
- pyxecm/helper/xml.py +1 -1
- pyxecm/otawp.py +1810 -0
- pyxecm/otcs.py +3180 -2938
- pyxecm/otds.py +1591 -1875
- pyxecm/otmm.py +131 -11
- {pyxecm-1.5.dist-info → pyxecm-1.6.dist-info}/METADATA +3 -1
- pyxecm-1.6.dist-info/RECORD +32 -0
- {pyxecm-1.5.dist-info → pyxecm-1.6.dist-info}/WHEEL +1 -1
- pyxecm-1.5.dist-info/RECORD +0 -30
- {pyxecm-1.5.dist-info → pyxecm-1.6.dist-info}/LICENSE +0 -0
- {pyxecm-1.5.dist-info → pyxecm-1.6.dist-info}/top_level.txt +0 -0
pyxecm/customizer/pht.py
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
"""
|
|
2
|
-
PHT is an OpenText internal application aiming at creating a common naming reference for Engineering Products and
|
|
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
3
|
track all product-related data. It also provides an approved reporting hierarchy.
|
|
4
4
|
See: https://pht.opentext.com
|
|
5
5
|
|
|
@@ -10,6 +10,7 @@ __init__ : class initializer
|
|
|
10
10
|
config : Returns config data set
|
|
11
11
|
get_data: Get the Data object that holds all processed PHT products
|
|
12
12
|
request_header: Returns the request header for ServiceNow API calls
|
|
13
|
+
do_request: Call an PHT REST API in a safe way
|
|
13
14
|
parse_request_response: Parse the REST API responses and convert
|
|
14
15
|
them to Python dict in a safe way
|
|
15
16
|
|
|
@@ -18,9 +19,24 @@ authenticate : Authenticates at ServiceNow API
|
|
|
18
19
|
get_attributes: Get a list of all product attributes (schema) of PHT
|
|
19
20
|
get_business_units: Get the list of PHT Business Units
|
|
20
21
|
get_product_families: Get the list of PHT product families
|
|
22
|
+
|
|
21
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
|
+
|
|
22
28
|
get_master_products: Get the list of PHT master products
|
|
23
|
-
|
|
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
|
|
39
|
+
|
|
24
40
|
load_products: Load products into a data frame.
|
|
25
41
|
|
|
26
42
|
"""
|
|
@@ -33,6 +49,7 @@ __email__ = "mdiefenb@opentext.com"
|
|
|
33
49
|
|
|
34
50
|
import json
|
|
35
51
|
import logging
|
|
52
|
+
import time
|
|
36
53
|
|
|
37
54
|
import requests
|
|
38
55
|
from requests.auth import HTTPBasicAuth
|
|
@@ -43,7 +60,8 @@ logger = logging.getLogger("pyxecm.customizer.pht")
|
|
|
43
60
|
REQUEST_HEADERS = {"Accept": "application/json", "Content-Type": "application/json"}
|
|
44
61
|
|
|
45
62
|
REQUEST_TIMEOUT = 60
|
|
46
|
-
|
|
63
|
+
REQUEST_RETRY_DELAY = 20
|
|
64
|
+
REQUEST_MAX_RETRIES = 2
|
|
47
65
|
|
|
48
66
|
class PHT(object):
|
|
49
67
|
"""Used to retrieve data from OpenText PHT."""
|
|
@@ -77,10 +95,18 @@ class PHT(object):
|
|
|
77
95
|
pht_config["businessUnitUrl"] = pht_config["restUrl"] + "/business-unit"
|
|
78
96
|
pht_config["productFamilyUrl"] = pht_config["restUrl"] + "/product-family"
|
|
79
97
|
pht_config["productUrl"] = pht_config["restUrl"] + "/product"
|
|
80
|
-
pht_config["
|
|
98
|
+
pht_config["productFilteredUrl"] = pht_config["productUrl"] + "/filtered"
|
|
99
|
+
pht_config["productSearchUrl"] = pht_config["productUrl"] + "/search"
|
|
100
|
+
pht_config["productUsersUrl"] = pht_config["productUrl"] + "/users"
|
|
81
101
|
pht_config["teamUrl"] = pht_config["restUrl"] + "/team"
|
|
82
|
-
pht_config["componentUrl"] = pht_config["restUrl"] + "/component"
|
|
83
102
|
pht_config["masterProductUrl"] = pht_config["restUrl"] + "/master-product"
|
|
103
|
+
pht_config["masterProductFilteredUrl"] = (
|
|
104
|
+
pht_config["masterProductUrl"] + "/filtered"
|
|
105
|
+
)
|
|
106
|
+
pht_config["componentUrl"] = pht_config["restUrl"] + "/component"
|
|
107
|
+
pht_config["componentFilteredUrl"] = pht_config["componentUrl"] + "/filtered"
|
|
108
|
+
pht_config["componentSearchUrl"] = pht_config["componentUrl"] + "/search"
|
|
109
|
+
pht_config["componentUsersUrl"] = pht_config["componentUrl"] + "/users"
|
|
84
110
|
|
|
85
111
|
self._config = pht_config
|
|
86
112
|
|
|
@@ -132,6 +158,135 @@ class PHT(object):
|
|
|
132
158
|
|
|
133
159
|
# end method definition
|
|
134
160
|
|
|
161
|
+
def do_request(
|
|
162
|
+
self,
|
|
163
|
+
url: str,
|
|
164
|
+
method: str = "GET",
|
|
165
|
+
headers: dict | None = None,
|
|
166
|
+
data: dict | None = None,
|
|
167
|
+
files: dict | None = None,
|
|
168
|
+
timeout: int | None = REQUEST_TIMEOUT,
|
|
169
|
+
show_error: bool = True,
|
|
170
|
+
failure_message: str = "",
|
|
171
|
+
success_message: str = "",
|
|
172
|
+
max_retries: int = REQUEST_MAX_RETRIES,
|
|
173
|
+
retry_forever: bool = False,
|
|
174
|
+
) -> dict | None:
|
|
175
|
+
"""Call an PHT REST API in a safe way
|
|
176
|
+
|
|
177
|
+
Args:
|
|
178
|
+
url (str): URL to send the request to.
|
|
179
|
+
method (str, optional): HTTP method (GET, POST, etc.). Defaults to "GET".
|
|
180
|
+
headers (dict | None, optional): Request Headers. Defaults to None.
|
|
181
|
+
json (dict | None, optional): Request payload. Defaults to None.
|
|
182
|
+
files (dict | None, optional): Dictionary of {"name": file-tuple} for multipart encoding upload.
|
|
183
|
+
file-tuple can be a 2-tuple ("filename", fileobj) or a 3-tuple ("filename", fileobj, "content_type")
|
|
184
|
+
timeout (int | None, optional): Timeout for the request in seconds. Defaults to REQUEST_TIMEOUT.
|
|
185
|
+
show_error (bool, optional): Whether or not an error should be logged in case of a failed REST call.
|
|
186
|
+
If False, then only a warning is logged. Defaults to True.
|
|
187
|
+
failure_message (str, optional): Specific error message. Defaults to "".
|
|
188
|
+
max_retries (int, optional): How many retries on Connection errors? Default is REQUEST_MAX_RETRIES.
|
|
189
|
+
retry_forever (bool, optional): Eventually wait forever - without timeout. Defaults to False.
|
|
190
|
+
|
|
191
|
+
Returns:
|
|
192
|
+
dict | None: Response of PHT REST API or None in case of an error.
|
|
193
|
+
"""
|
|
194
|
+
|
|
195
|
+
retries = 0
|
|
196
|
+
while True:
|
|
197
|
+
try:
|
|
198
|
+
response = self._session.request(
|
|
199
|
+
method=method,
|
|
200
|
+
url=url,
|
|
201
|
+
json=data,
|
|
202
|
+
files=files,
|
|
203
|
+
headers=headers,
|
|
204
|
+
timeout=timeout,
|
|
205
|
+
)
|
|
206
|
+
|
|
207
|
+
if response.ok:
|
|
208
|
+
if success_message:
|
|
209
|
+
logger.debug(success_message)
|
|
210
|
+
return self.parse_request_response(response)
|
|
211
|
+
# Check if Session has expired - then re-authenticate and try once more
|
|
212
|
+
elif response.status_code == 401 and retries == 0:
|
|
213
|
+
logger.debug("Session has expired - try to re-authenticate...")
|
|
214
|
+
self.authenticate()
|
|
215
|
+
retries += 1
|
|
216
|
+
else:
|
|
217
|
+
# Handle plain HTML responses to not pollute the logs
|
|
218
|
+
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
|
|
223
|
+
|
|
224
|
+
if show_error:
|
|
225
|
+
logger.error(
|
|
226
|
+
"%s; status -> %s; error -> %s",
|
|
227
|
+
failure_message,
|
|
228
|
+
response.status_code,
|
|
229
|
+
response_text,
|
|
230
|
+
)
|
|
231
|
+
else:
|
|
232
|
+
logger.warning(
|
|
233
|
+
"%s; status -> %s; warning -> %s",
|
|
234
|
+
failure_message,
|
|
235
|
+
response.status_code,
|
|
236
|
+
response_text,
|
|
237
|
+
)
|
|
238
|
+
|
|
239
|
+
if content_type == "text/html":
|
|
240
|
+
logger.debug(
|
|
241
|
+
"%s; status -> %s; warning -> %s",
|
|
242
|
+
failure_message,
|
|
243
|
+
response.status_code,
|
|
244
|
+
response.text,
|
|
245
|
+
)
|
|
246
|
+
|
|
247
|
+
return None
|
|
248
|
+
except requests.exceptions.Timeout:
|
|
249
|
+
if retries <= max_retries:
|
|
250
|
+
logger.warning(
|
|
251
|
+
"Request timed out. Retrying in %s seconds...",
|
|
252
|
+
str(REQUEST_RETRY_DELAY),
|
|
253
|
+
)
|
|
254
|
+
retries += 1
|
|
255
|
+
time.sleep(REQUEST_RETRY_DELAY) # Add a delay before retrying
|
|
256
|
+
else:
|
|
257
|
+
logger.error(
|
|
258
|
+
"%s; timeout error",
|
|
259
|
+
failure_message,
|
|
260
|
+
)
|
|
261
|
+
if retry_forever:
|
|
262
|
+
# If it fails after REQUEST_MAX_RETRIES retries we let it wait forever
|
|
263
|
+
logger.warning("Turn timeouts off and wait forever...")
|
|
264
|
+
timeout = None
|
|
265
|
+
else:
|
|
266
|
+
return None
|
|
267
|
+
except requests.exceptions.ConnectionError:
|
|
268
|
+
if retries <= max_retries:
|
|
269
|
+
logger.warning(
|
|
270
|
+
"Connection error. Retrying in %s seconds...",
|
|
271
|
+
str(REQUEST_RETRY_DELAY),
|
|
272
|
+
)
|
|
273
|
+
retries += 1
|
|
274
|
+
time.sleep(REQUEST_RETRY_DELAY) # Add a delay before retrying
|
|
275
|
+
else:
|
|
276
|
+
logger.error(
|
|
277
|
+
"%s; connection error",
|
|
278
|
+
failure_message,
|
|
279
|
+
)
|
|
280
|
+
if retry_forever:
|
|
281
|
+
# If it fails after REQUEST_MAX_RETRIES retries we let it wait forever
|
|
282
|
+
logger.warning("Turn timeouts off and wait forever...")
|
|
283
|
+
timeout = None
|
|
284
|
+
time.sleep(REQUEST_RETRY_DELAY) # Add a delay before retrying
|
|
285
|
+
else:
|
|
286
|
+
return None
|
|
287
|
+
|
|
288
|
+
# end method definition
|
|
289
|
+
|
|
135
290
|
def parse_request_response(
|
|
136
291
|
self,
|
|
137
292
|
response_object: requests.Response,
|
|
@@ -229,24 +384,13 @@ class PHT(object):
|
|
|
229
384
|
request_header = self.request_header()
|
|
230
385
|
request_url = self.config()["attributeUrl"]
|
|
231
386
|
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
elif response.status_code == 401 and retries == 0:
|
|
240
|
-
logger.debug("Session has expired - try to re-authenticate...")
|
|
241
|
-
self.authenticate()
|
|
242
|
-
retries += 1
|
|
243
|
-
else:
|
|
244
|
-
logger.error(
|
|
245
|
-
"Failed to get PHT attributes; error -> %s (%s)",
|
|
246
|
-
response.text,
|
|
247
|
-
response.status_code,
|
|
248
|
-
)
|
|
249
|
-
return None
|
|
387
|
+
return self.do_request(
|
|
388
|
+
url=request_url,
|
|
389
|
+
method="GET",
|
|
390
|
+
headers=request_header,
|
|
391
|
+
timeout=None,
|
|
392
|
+
failure_message="Failed to get PHT attributes!",
|
|
393
|
+
)
|
|
250
394
|
|
|
251
395
|
# end method definition
|
|
252
396
|
|
|
@@ -310,24 +454,13 @@ class PHT(object):
|
|
|
310
454
|
request_header = self.request_header()
|
|
311
455
|
request_url = self.config()["businessUnitUrl"]
|
|
312
456
|
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
elif response.status_code == 401 and retries == 0:
|
|
321
|
-
logger.debug("Session has expired - try to re-authenticate...")
|
|
322
|
-
self.authenticate()
|
|
323
|
-
retries += 1
|
|
324
|
-
else:
|
|
325
|
-
logger.error(
|
|
326
|
-
"Failed to get PHT business units; error -> %s (%s)",
|
|
327
|
-
response.text,
|
|
328
|
-
response.status_code,
|
|
329
|
-
)
|
|
330
|
-
return None
|
|
457
|
+
return self.do_request(
|
|
458
|
+
url=request_url,
|
|
459
|
+
method="GET",
|
|
460
|
+
headers=request_header,
|
|
461
|
+
timeout=None,
|
|
462
|
+
failure_message="Failed to get PHT business units!",
|
|
463
|
+
)
|
|
331
464
|
|
|
332
465
|
# end method definition
|
|
333
466
|
|
|
@@ -341,24 +474,13 @@ class PHT(object):
|
|
|
341
474
|
request_header = self.request_header()
|
|
342
475
|
request_url = self.config()["productFamilyUrl"]
|
|
343
476
|
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
elif response.status_code == 401 and retries == 0:
|
|
352
|
-
logger.debug("Session has expired - try to re-authenticate...")
|
|
353
|
-
self.authenticate()
|
|
354
|
-
retries += 1
|
|
355
|
-
else:
|
|
356
|
-
logger.error(
|
|
357
|
-
"Failed to get PHT product families; error -> %s (%s)",
|
|
358
|
-
response.text,
|
|
359
|
-
response.status_code,
|
|
360
|
-
)
|
|
361
|
-
return None
|
|
477
|
+
return self.do_request(
|
|
478
|
+
url=request_url,
|
|
479
|
+
method="GET",
|
|
480
|
+
headers=request_header,
|
|
481
|
+
timeout=None,
|
|
482
|
+
failure_message="Failed to get PHT product families!",
|
|
483
|
+
)
|
|
362
484
|
|
|
363
485
|
# end method definition
|
|
364
486
|
|
|
@@ -372,24 +494,111 @@ class PHT(object):
|
|
|
372
494
|
request_header = self.request_header()
|
|
373
495
|
request_url = self.config()["productUrl"]
|
|
374
496
|
|
|
375
|
-
|
|
497
|
+
return self.do_request(
|
|
498
|
+
url=request_url,
|
|
499
|
+
method="GET",
|
|
500
|
+
headers=request_header,
|
|
501
|
+
timeout=None,
|
|
502
|
+
failure_message="Failed to get PHT products!",
|
|
503
|
+
)
|
|
376
504
|
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
505
|
+
# end method definition
|
|
506
|
+
|
|
507
|
+
def get_products_filtered(
|
|
508
|
+
self, filter_definition: dict | None = None
|
|
509
|
+
) -> list | None:
|
|
510
|
+
"""Get a list of filtered PHT products
|
|
511
|
+
|
|
512
|
+
Args:
|
|
513
|
+
filter_definition (dict): a dictionary of filter conditions.
|
|
514
|
+
Example filters:
|
|
515
|
+
{
|
|
516
|
+
businessUnitName: <String>,
|
|
517
|
+
productFamilyName: <String>,
|
|
518
|
+
productName: <String>,
|
|
519
|
+
productSyncId: <String>,
|
|
520
|
+
productStatus: ACTIVE | INACTIVE | MAINTENANCE,
|
|
521
|
+
productManager: <String>,
|
|
522
|
+
developmentManager: <String>,
|
|
523
|
+
attributeOperator: AND | OR,
|
|
524
|
+
attributes: {
|
|
525
|
+
"<AttributeName>": {
|
|
526
|
+
"compare": CONTAINS | EXISTS | DOES_NOT_EXISTS,
|
|
527
|
+
"values": List<String>
|
|
528
|
+
},
|
|
529
|
+
...
|
|
530
|
+
},
|
|
531
|
+
includeAttributes: true | false
|
|
532
|
+
}
|
|
533
|
+
Returns:
|
|
534
|
+
list | None: list of matching products.
|
|
535
|
+
"""
|
|
536
|
+
|
|
537
|
+
if not filter_definition:
|
|
538
|
+
return self.get_products()
|
|
539
|
+
|
|
540
|
+
request_header = self.request_header()
|
|
541
|
+
request_url = self.config()["productFilteredUrl"]
|
|
542
|
+
request_data = filter_definition
|
|
543
|
+
|
|
544
|
+
return self.do_request(
|
|
545
|
+
url=request_url,
|
|
546
|
+
method="POST",
|
|
547
|
+
headers=request_header,
|
|
548
|
+
data=request_data,
|
|
549
|
+
timeout=None,
|
|
550
|
+
failure_message="Failed to get filtered PHT products!",
|
|
551
|
+
)
|
|
552
|
+
|
|
553
|
+
# end method definition
|
|
554
|
+
|
|
555
|
+
def get_product(self, sync_id: int) -> dict | None:
|
|
556
|
+
"""Get a specific product in PHT.
|
|
557
|
+
|
|
558
|
+
Returns:
|
|
559
|
+
dict | None: product data matching the sync ID
|
|
560
|
+
"""
|
|
561
|
+
|
|
562
|
+
request_header = self.request_header()
|
|
563
|
+
request_url = self.config()["productUrl"] + "/" + str(sync_id)
|
|
564
|
+
|
|
565
|
+
return self.do_request(
|
|
566
|
+
url=request_url,
|
|
567
|
+
method="GET",
|
|
568
|
+
headers=request_header,
|
|
569
|
+
timeout=None,
|
|
570
|
+
failure_message="Failed to retrieve PHT product with sync ID -> {}!".format(
|
|
571
|
+
sync_id
|
|
572
|
+
),
|
|
573
|
+
)
|
|
574
|
+
|
|
575
|
+
# end method definition
|
|
576
|
+
|
|
577
|
+
def search_products(
|
|
578
|
+
self, query: str, business_unit: str | None = None, family: str | None = None
|
|
579
|
+
) -> list | None:
|
|
580
|
+
"""Search for specific product in PHT by the product name.
|
|
581
|
+
|
|
582
|
+
Returns:
|
|
583
|
+
str: search term matches any part of the component name
|
|
584
|
+
"""
|
|
585
|
+
|
|
586
|
+
request_header = self.request_header()
|
|
587
|
+
request_url = self.config()["componentSearchUrl"] + "?q=" + query
|
|
588
|
+
if business_unit:
|
|
589
|
+
request_url += "&businessUnit=" + business_unit
|
|
590
|
+
if family:
|
|
591
|
+
request_url += "&family=" + family
|
|
592
|
+
|
|
593
|
+
return self.do_request(
|
|
594
|
+
url=request_url,
|
|
595
|
+
method="GET",
|
|
596
|
+
headers=request_header,
|
|
597
|
+
timeout=None,
|
|
598
|
+
failure_message="Failed to retrieve PHT components matching -> {}!".format(
|
|
599
|
+
query
|
|
600
|
+
),
|
|
601
|
+
)
|
|
393
602
|
|
|
394
603
|
# end method definition
|
|
395
604
|
|
|
@@ -403,41 +612,32 @@ class PHT(object):
|
|
|
403
612
|
request_header = self.request_header()
|
|
404
613
|
request_url = self.config()["masterProductUrl"]
|
|
405
614
|
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
elif response.status_code == 401 and retries == 0:
|
|
414
|
-
logger.debug("Session has expired - try to re-authenticate...")
|
|
415
|
-
self.authenticate()
|
|
416
|
-
retries += 1
|
|
417
|
-
else:
|
|
418
|
-
logger.error(
|
|
419
|
-
"Failed to get PHT master products; error -> %s (%s)",
|
|
420
|
-
response.text,
|
|
421
|
-
response.status_code,
|
|
422
|
-
)
|
|
423
|
-
return None
|
|
615
|
+
return self.do_request(
|
|
616
|
+
url=request_url,
|
|
617
|
+
method="GET",
|
|
618
|
+
headers=request_header,
|
|
619
|
+
timeout=None,
|
|
620
|
+
failure_message="Failed to get PHT master products!",
|
|
621
|
+
)
|
|
424
622
|
|
|
425
623
|
# end method definition
|
|
426
624
|
|
|
427
|
-
def
|
|
428
|
-
|
|
625
|
+
def get_master_products_filtered(
|
|
626
|
+
self, filter_definition: dict | None = None
|
|
627
|
+
) -> list | None:
|
|
628
|
+
"""Get a list of filtered PHT master products
|
|
429
629
|
|
|
430
630
|
Args:
|
|
431
631
|
filter_definition (dict): a dictionary of filter conditions.
|
|
432
632
|
Example filters:
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
attributeOperator: AND | OR
|
|
633
|
+
{
|
|
634
|
+
businessUnitName: <String>,
|
|
635
|
+
productFamilyName: <String>,
|
|
636
|
+
masterproductName: <String>,
|
|
637
|
+
masterproductSyncId: <String>,
|
|
638
|
+
masterproductStatus: ACTIVE | INACTIVE | MAINTENANCE,
|
|
639
|
+
productManagerDomain: <String>,
|
|
640
|
+
attributeOperator: AND | OR,
|
|
441
641
|
attributes: {
|
|
442
642
|
"<AttributeName>": {
|
|
443
643
|
"compare": CONTAINS | EXISTS | DOES_NOT_EXISTS,
|
|
@@ -446,6 +646,8 @@ class PHT(object):
|
|
|
446
646
|
...
|
|
447
647
|
},
|
|
448
648
|
includeAttributes: true | false
|
|
649
|
+
includeLinkedProducts: true | false
|
|
650
|
+
}
|
|
449
651
|
Returns:
|
|
450
652
|
list | None: list of matching products.
|
|
451
653
|
"""
|
|
@@ -454,29 +656,250 @@ class PHT(object):
|
|
|
454
656
|
return self.get_products()
|
|
455
657
|
|
|
456
658
|
request_header = self.request_header()
|
|
457
|
-
request_url = self.config()["
|
|
659
|
+
request_url = self.config()["masterProductFilteredUrl"]
|
|
458
660
|
request_data = filter_definition
|
|
459
661
|
|
|
460
|
-
|
|
662
|
+
return self.do_request(
|
|
663
|
+
url=request_url,
|
|
664
|
+
method="POST",
|
|
665
|
+
headers=request_header,
|
|
666
|
+
data=request_data,
|
|
667
|
+
timeout=None,
|
|
668
|
+
failure_message="Failed to get filtered PHT master products!",
|
|
669
|
+
)
|
|
461
670
|
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
671
|
+
# end method definition
|
|
672
|
+
|
|
673
|
+
def get_master_product(self, sync_id: int) -> dict | None:
|
|
674
|
+
"""Get a specific product in PHT.
|
|
675
|
+
|
|
676
|
+
Returns:
|
|
677
|
+
dict | None: product data matching the sync ID
|
|
678
|
+
"""
|
|
679
|
+
|
|
680
|
+
request_header = self.request_header()
|
|
681
|
+
request_url = self.config()["productUrl"] + "/" + str(sync_id)
|
|
682
|
+
|
|
683
|
+
return self.do_request(
|
|
684
|
+
url=request_url,
|
|
685
|
+
method="GET",
|
|
686
|
+
headers=request_header,
|
|
687
|
+
timeout=None,
|
|
688
|
+
failure_message="Failed to retrieve PHT product with sync ID -> {}!".format(
|
|
689
|
+
sync_id
|
|
690
|
+
),
|
|
691
|
+
)
|
|
692
|
+
|
|
693
|
+
# end method definition
|
|
694
|
+
|
|
695
|
+
def get_teams(self) -> list:
|
|
696
|
+
"""Get a list of all teams in PHT.
|
|
697
|
+
|
|
698
|
+
Returns:
|
|
699
|
+
list: list of PHT teams
|
|
700
|
+
"""
|
|
701
|
+
|
|
702
|
+
request_header = self.request_header()
|
|
703
|
+
request_url = self.config()["teamUrl"]
|
|
704
|
+
|
|
705
|
+
return self.do_request(
|
|
706
|
+
url=request_url,
|
|
707
|
+
method="GET",
|
|
708
|
+
headers=request_header,
|
|
709
|
+
timeout=None,
|
|
710
|
+
failure_message="Failed to retrieve PHT teams!",
|
|
711
|
+
)
|
|
712
|
+
|
|
713
|
+
# end method definition
|
|
714
|
+
|
|
715
|
+
def get_team(self, team_id: int) -> dict | None:
|
|
716
|
+
"""Get a specific team in PHT.
|
|
717
|
+
|
|
718
|
+
Returns:
|
|
719
|
+
dict | None: dict of the PHT team
|
|
720
|
+
"""
|
|
721
|
+
|
|
722
|
+
request_header = self.request_header()
|
|
723
|
+
request_url = self.config()["teamUrl"] + "/" + str(team_id)
|
|
724
|
+
|
|
725
|
+
return self.do_request(
|
|
726
|
+
url=request_url,
|
|
727
|
+
method="GET",
|
|
728
|
+
headers=request_header,
|
|
729
|
+
timeout=None,
|
|
730
|
+
failure_message="Failed to retrieve PHT teams!",
|
|
731
|
+
)
|
|
732
|
+
|
|
733
|
+
# end method definition
|
|
734
|
+
|
|
735
|
+
def get_components(self) -> list:
|
|
736
|
+
"""Get a list of all components in PHT.
|
|
737
|
+
|
|
738
|
+
Returns:
|
|
739
|
+
list: list of PHT components
|
|
740
|
+
|
|
741
|
+
Example result:
|
|
742
|
+
[
|
|
743
|
+
{
|
|
744
|
+
'id': 468,
|
|
745
|
+
'syncId': '6380c3da-8ded-40cd-8071-61f6721956f5',
|
|
746
|
+
'name': 'XOTE',
|
|
747
|
+
'developmentManager': {
|
|
748
|
+
'id': 237,
|
|
749
|
+
'domain': 'kurt',
|
|
750
|
+
'email': 'kurt.junker@opentext.com',
|
|
751
|
+
'name': 'Kurt Junker',
|
|
752
|
+
'role': None,
|
|
753
|
+
'status': 'ACTIVE',
|
|
754
|
+
'location': 'Grasbrunn, DEU',
|
|
755
|
+
'title': 'Sr. Manager, Software Engineering',
|
|
756
|
+
'type': 'OTHERS'
|
|
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
|
|
789
|
+
},
|
|
790
|
+
...
|
|
791
|
+
],
|
|
792
|
+
'sourceRepos': [],
|
|
793
|
+
'artifacts': [],
|
|
794
|
+
'products': [],
|
|
795
|
+
'teams': [],
|
|
796
|
+
'users': [],
|
|
797
|
+
'guestTeams': [],
|
|
798
|
+
'guestUsers': [],
|
|
799
|
+
'relatedLOBS': []
|
|
800
|
+
}
|
|
801
|
+
]
|
|
802
|
+
"""
|
|
803
|
+
|
|
804
|
+
request_header = self.request_header()
|
|
805
|
+
request_url = self.config()["componentUrl"]
|
|
806
|
+
|
|
807
|
+
return self.do_request(
|
|
808
|
+
url=request_url,
|
|
809
|
+
method="GET",
|
|
810
|
+
headers=request_header,
|
|
811
|
+
timeout=None,
|
|
812
|
+
failure_message="Failed to retrieve PHT components!",
|
|
813
|
+
)
|
|
814
|
+
|
|
815
|
+
# end method definition
|
|
816
|
+
|
|
817
|
+
def get_components_filtered(
|
|
818
|
+
self, filter_definition: dict | None = None
|
|
819
|
+
) -> list | None:
|
|
820
|
+
"""Get a list of filtered PHT components
|
|
821
|
+
|
|
822
|
+
Args:
|
|
823
|
+
filter_definition (dict): a dictionary of filter conditions.
|
|
824
|
+
Example filters:
|
|
825
|
+
{
|
|
826
|
+
componentName: <String>,
|
|
827
|
+
componentSyncId: <String>,
|
|
828
|
+
componentStatus: ACTIVE | INACTIVE | MAINTENANCE,
|
|
829
|
+
developmentManager: <String>,
|
|
830
|
+
attributeOperator: AND | OR,
|
|
831
|
+
attributes: {
|
|
832
|
+
"<AttributeName>": {
|
|
833
|
+
"compare": CONTAINS | EXISTS | DOES_NOT_EXISTS,
|
|
834
|
+
"values": List<String>
|
|
835
|
+
},
|
|
836
|
+
...
|
|
837
|
+
},
|
|
838
|
+
includeAttributes: true | false
|
|
839
|
+
}
|
|
840
|
+
Returns:
|
|
841
|
+
list | None: list of matching components.
|
|
842
|
+
"""
|
|
843
|
+
|
|
844
|
+
if not filter_definition:
|
|
845
|
+
return self.get_products()
|
|
846
|
+
|
|
847
|
+
request_header = self.request_header()
|
|
848
|
+
request_url = self.config()["masterProductFilteredUrl"]
|
|
849
|
+
request_data = filter_definition
|
|
850
|
+
|
|
851
|
+
return self.do_request(
|
|
852
|
+
url=request_url,
|
|
853
|
+
method="POST",
|
|
854
|
+
headers=request_header,
|
|
855
|
+
data=request_data,
|
|
856
|
+
timeout=None,
|
|
857
|
+
failure_message="Failed to get filtered PHT master products!",
|
|
858
|
+
)
|
|
859
|
+
|
|
860
|
+
# end method definition
|
|
861
|
+
|
|
862
|
+
def get_component(self, sync_id: int) -> dict | None:
|
|
863
|
+
"""Get a specific component in PHT.
|
|
864
|
+
|
|
865
|
+
Returns:
|
|
866
|
+
dict | None: PHT component
|
|
867
|
+
"""
|
|
868
|
+
|
|
869
|
+
request_header = self.request_header()
|
|
870
|
+
request_url = self.config()["componentUrl"] + "/" + str(sync_id)
|
|
871
|
+
|
|
872
|
+
return self.do_request(
|
|
873
|
+
url=request_url,
|
|
874
|
+
method="GET",
|
|
875
|
+
headers=request_header,
|
|
876
|
+
timeout=None,
|
|
877
|
+
failure_message="Failed to retrieve PHT component with sync ID -> {}!".format(
|
|
878
|
+
sync_id
|
|
879
|
+
),
|
|
880
|
+
)
|
|
881
|
+
|
|
882
|
+
# end method definition
|
|
883
|
+
|
|
884
|
+
def search_components(self, query: str) -> list | None:
|
|
885
|
+
"""Search for specific components in PHT by the component name.
|
|
886
|
+
|
|
887
|
+
Returns:
|
|
888
|
+
str: search term matches any part of the component name
|
|
889
|
+
"""
|
|
890
|
+
|
|
891
|
+
request_header = self.request_header()
|
|
892
|
+
request_url = self.config()["componentSearchUrl"] + "?q=" + query
|
|
893
|
+
|
|
894
|
+
return self.do_request(
|
|
895
|
+
url=request_url,
|
|
896
|
+
method="GET",
|
|
897
|
+
headers=request_header,
|
|
898
|
+
timeout=None,
|
|
899
|
+
failure_message="Failed to retrieve PHT components matching -> {}!".format(
|
|
900
|
+
query
|
|
901
|
+
),
|
|
902
|
+
)
|
|
480
903
|
|
|
481
904
|
# end method definition
|
|
482
905
|
|