pyxecm 1.4__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.

@@ -0,0 +1,926 @@
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
18
+
19
+ get_attributes: Get a list of all product attributes (schema) of PHT
20
+ get_business_units: Get the list of PHT Business Units
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
39
+
40
+ load_products: Load products into a data frame.
41
+
42
+ """
43
+
44
+ __author__ = "Dr. Marc Diefenbruch"
45
+ __copyright__ = "Copyright 2024, OpenText"
46
+ __credits__ = ["Kai-Philip Gatzweiler"]
47
+ __maintainer__ = "Dr. Marc Diefenbruch"
48
+ __email__ = "mdiefenb@opentext.com"
49
+
50
+ import json
51
+ import logging
52
+ import time
53
+
54
+ import requests
55
+ from requests.auth import HTTPBasicAuth
56
+ from pyxecm.helper.data import Data
57
+
58
+ logger = logging.getLogger("pyxecm.customizer.pht")
59
+
60
+ REQUEST_HEADERS = {"Accept": "application/json", "Content-Type": "application/json"}
61
+
62
+ REQUEST_TIMEOUT = 60
63
+ REQUEST_RETRY_DELAY = 20
64
+ REQUEST_MAX_RETRIES = 2
65
+
66
+ class PHT(object):
67
+ """Used to retrieve data from OpenText PHT."""
68
+
69
+ _config: dict
70
+ _session = None
71
+
72
+ def __init__(
73
+ self,
74
+ base_url: str,
75
+ username: str,
76
+ password: str,
77
+ ):
78
+ """Initialize the PHT object
79
+
80
+ Args:
81
+ base_url (str): base URL of the ServiceNow tenant
82
+ username (str): user name in Saleforce
83
+ password (str): password of the user
84
+ """
85
+
86
+ pht_config = {}
87
+
88
+ # Store the credentials and parameters in a config dictionary:
89
+ pht_config["baseUrl"] = base_url
90
+ pht_config["username"] = username
91
+ pht_config["password"] = password
92
+
93
+ pht_config["restUrl"] = pht_config["baseUrl"] + "/api"
94
+ pht_config["attributeUrl"] = pht_config["restUrl"] + "/attribute"
95
+ pht_config["businessUnitUrl"] = pht_config["restUrl"] + "/business-unit"
96
+ pht_config["productFamilyUrl"] = pht_config["restUrl"] + "/product-family"
97
+ pht_config["productUrl"] = pht_config["restUrl"] + "/product"
98
+ pht_config["productFilteredUrl"] = pht_config["productUrl"] + "/filtered"
99
+ pht_config["productSearchUrl"] = pht_config["productUrl"] + "/search"
100
+ pht_config["productUsersUrl"] = pht_config["productUrl"] + "/users"
101
+ pht_config["teamUrl"] = pht_config["restUrl"] + "/team"
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"
110
+
111
+ self._config = pht_config
112
+
113
+ self._session = requests.Session()
114
+
115
+ self._data = Data()
116
+
117
+ # end method definition
118
+
119
+ def config(self) -> dict:
120
+ """Returns the configuration dictionary
121
+
122
+ Returns:
123
+ dict: Configuration dictionary
124
+ """
125
+ return self._config
126
+
127
+ # end method definition
128
+
129
+ def get_data(self) -> Data:
130
+ """Get the Data object that holds all processed PHT products
131
+
132
+ Returns:
133
+ Data: Datastructure with all processed PHT product data.
134
+ """
135
+
136
+ return self._data
137
+
138
+ # end method definition
139
+
140
+ def request_header(self, content_type: str = "") -> dict:
141
+ """Returns the request header used for Application calls.
142
+ Consists of Bearer access token and Content Type
143
+
144
+ Args:
145
+ content_type (str, optional): custom content type for the request
146
+ Return:
147
+ dict: request header values
148
+ """
149
+
150
+ request_header = {}
151
+
152
+ request_header = REQUEST_HEADERS
153
+
154
+ if content_type:
155
+ request_header["Content-Type"] = content_type
156
+
157
+ return request_header
158
+
159
+ # end method definition
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
+
290
+ def parse_request_response(
291
+ self,
292
+ response_object: requests.Response,
293
+ additional_error_message: str = "",
294
+ show_error: bool = True,
295
+ ) -> list | None:
296
+ """Converts the request response (JSon) to a Python list in a safe way
297
+ that also handles exceptions. It first tries to load the response.text
298
+ via json.loads() that produces a dict output. Only if response.text is
299
+ not set or is empty it just converts the response_object to a dict using
300
+ the vars() built-in method.
301
+
302
+ Args:
303
+ response_object (object): this is reponse object delivered by the request call
304
+ additional_error_message (str, optional): use a more specific error message
305
+ in case of an error
306
+ show_error (bool): True: write an error to the log file
307
+ False: write a warning to the log file
308
+ Returns:
309
+ list: response information or None in case of an error
310
+ """
311
+
312
+ if not response_object:
313
+ return None
314
+
315
+ try:
316
+ if response_object.text:
317
+ list_object = json.loads(response_object.text)
318
+ else:
319
+ list_object = vars(response_object)
320
+ except json.JSONDecodeError as exception:
321
+ if additional_error_message:
322
+ message = "Cannot decode response as JSON. {}; error -> {}".format(
323
+ additional_error_message, exception
324
+ )
325
+ else:
326
+ message = "Cannot decode response as JSON; error -> {}".format(
327
+ exception
328
+ )
329
+ if show_error:
330
+ logger.error(message)
331
+ else:
332
+ logger.warning(message)
333
+ return None
334
+ else:
335
+ return list_object
336
+
337
+ # end method definition
338
+
339
+ def authenticate(self) -> str | None:
340
+ """Authenticate at PHT with basic authentication."""
341
+
342
+ self._session.headers.update(self.request_header())
343
+
344
+ username = self.config()["username"]
345
+ password = self.config()["password"]
346
+ if not self._session:
347
+ self._session = requests.Session()
348
+ self._session.auth = HTTPBasicAuth(username, password)
349
+
350
+ return self._session.auth
351
+
352
+ # end method definition
353
+
354
+ def get_attributes(self) -> list | None:
355
+ """Get a list of all product attributes (schema) of PHT
356
+
357
+ Returns:
358
+ list | None: list of product attributes
359
+
360
+ Example:
361
+ [
362
+ {
363
+ 'id': 28,
364
+ 'uuid': '43ba5852-eb83-11ed-a752-00505682262c',
365
+ 'name': 'UBM SCM Migration JIRA/ValueEdge',
366
+ 'description': 'Identifies the Issue to track work for the SCM migration for this project.\nIts a free text field and no validation with JIRA/ValueEdge will take place',
367
+ 'type': 'TEXT',
368
+ 'attributeCategory': {
369
+ 'id': 2,
370
+ 'name': 'Auxiliary assignment'
371
+ },
372
+ 'showDefault': False,
373
+ 'restricted': True,
374
+ 'allowScopeChain': True,
375
+ 'visibleToAll': False,
376
+ 'deleted': False,
377
+ 'attributeOptions': [],
378
+ 'attributeScopes': [],
379
+ 'allowedTeams': []
380
+ }
381
+ ]
382
+ """
383
+
384
+ request_header = self.request_header()
385
+ request_url = self.config()["attributeUrl"]
386
+
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
+ )
394
+
395
+ # end method definition
396
+
397
+ def get_business_units(self) -> list | None:
398
+ """Get the list of PHT Business Units
399
+
400
+ Returns:
401
+ list | None: list of the known business units.
402
+
403
+ Example:
404
+ [
405
+ {
406
+ 'id': 1,
407
+ 'name': 'Content Services',
408
+ 'leaderModel': {
409
+ 'id': 219,
410
+ 'domain': 'mcybala',
411
+ 'email': 'mcybala@opentext.com',
412
+ 'name': 'Michael Cybala',
413
+ 'role': None,
414
+ 'status': 'ACTIVE',
415
+ 'location': 'Kempten, DEU',
416
+ 'title': 'VP, Software Engineering',
417
+ 'type': 'OTHERS'
418
+ },
419
+ 'pmLeaderModel': {
420
+ 'id': 350,
421
+ 'domain': 'mdiefenb',
422
+ 'email': 'mdiefenb@opentext.com',
423
+ 'name': 'Marc Diefenbruch',
424
+ 'role': None,
425
+ 'status': 'ACTIVE',
426
+ 'location': 'Virtual, DEU',
427
+ 'title': 'VP, Product Management',
428
+ 'type': 'OTHERS'
429
+ },
430
+ 'sltOwnerModel': {
431
+ 'id': 450,
432
+ 'domain': 'jradko',
433
+ 'email': 'jradko@opentext.com',
434
+ 'name': 'John Radko',
435
+ 'role': None,
436
+ 'status': 'ACTIVE',
437
+ 'location': 'Gaithersburg, MD, USA',
438
+ 'title': 'SVP, Software Engineering',
439
+ 'type': 'OTHERS'
440
+ },
441
+ 'status': 'ACTIVE',
442
+ 'engineering': True,
443
+ 'attributes': [{...}, {...}, {...}, {...}, {...}, {...}, {...}, {...}, {...}],
444
+ 'leader': 'Michael Cybala',
445
+ 'leaderDomain': 'mcybala',
446
+ 'pmLeader': 'Marc Diefenbruch',
447
+ 'pmLeaderDomain': 'mdiefenb',
448
+ 'sltOwner': 'John Radko',
449
+ 'sltOwnerDomain': 'jradko'
450
+ }
451
+ ]
452
+ """
453
+
454
+ request_header = self.request_header()
455
+ request_url = self.config()["businessUnitUrl"]
456
+
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
+ )
464
+
465
+ # end method definition
466
+
467
+ def get_product_families(self) -> list | None:
468
+ """Get the list of PHT product families
469
+
470
+ Returns:
471
+ list | None: list of the known product families.
472
+ """
473
+
474
+ request_header = self.request_header()
475
+ request_url = self.config()["productFamilyUrl"]
476
+
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
+ )
484
+
485
+ # end method definition
486
+
487
+ def get_products(self) -> list | None:
488
+ """Get the list of PHT products
489
+
490
+ Returns:
491
+ list | None: list of the known products.
492
+ """
493
+
494
+ request_header = self.request_header()
495
+ request_url = self.config()["productUrl"]
496
+
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
+ )
504
+
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
+ )
602
+
603
+ # end method definition
604
+
605
+ def get_master_products(self) -> list | None:
606
+ """Get the list of PHT master products
607
+
608
+ Returns:
609
+ list | None: list of the known master products.
610
+ """
611
+
612
+ request_header = self.request_header()
613
+ request_url = self.config()["masterProductUrl"]
614
+
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
+ )
622
+
623
+ # end method definition
624
+
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
629
+
630
+ Args:
631
+ filter_definition (dict): a dictionary of filter conditions.
632
+ Example filters:
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,
641
+ attributes: {
642
+ "<AttributeName>": {
643
+ "compare": CONTAINS | EXISTS | DOES_NOT_EXISTS,
644
+ "values": List<String>
645
+ },
646
+ ...
647
+ },
648
+ includeAttributes: true | false
649
+ includeLinkedProducts: true | false
650
+ }
651
+ Returns:
652
+ list | None: list of matching products.
653
+ """
654
+
655
+ if not filter_definition:
656
+ return self.get_products()
657
+
658
+ request_header = self.request_header()
659
+ request_url = self.config()["masterProductFilteredUrl"]
660
+ request_data = filter_definition
661
+
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
+ )
670
+
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
+ )
903
+
904
+ # end method definition
905
+
906
+ def load_products(self, product_list: list = None) -> bool:
907
+ """Load products into a data frame in the self._data object
908
+
909
+ Args:
910
+ product_list (list, optional): listn of products - if already avaiable. Defaults to None.
911
+
912
+ Returns:
913
+ bool: True if successful, False otherwise.
914
+ """
915
+
916
+ if not product_list:
917
+ product_list = self.get_products()
918
+
919
+ self._data = Data(product_list)
920
+
921
+ if self._data:
922
+ return True
923
+
924
+ return False
925
+
926
+ # end method definition