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