pyxecm 1.3.0__py3-none-any.whl → 1.5__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 +3 -0
- pyxecm/coreshare.py +2636 -0
- pyxecm/customizer/__init__.py +6 -0
- pyxecm/customizer/browser_automation.py +231 -56
- pyxecm/customizer/customizer.py +466 -235
- pyxecm/customizer/k8s.py +49 -27
- pyxecm/customizer/m365.py +1183 -263
- pyxecm/customizer/payload.py +13854 -5368
- pyxecm/customizer/pht.py +503 -0
- pyxecm/customizer/salesforce.py +1782 -0
- pyxecm/customizer/sap.py +5 -5
- pyxecm/customizer/servicenow.py +1221 -0
- pyxecm/customizer/successfactors.py +1056 -0
- pyxecm/customizer/translate.py +2 -2
- pyxecm/helper/__init__.py +2 -0
- pyxecm/helper/assoc.py +27 -7
- pyxecm/helper/data.py +1527 -0
- pyxecm/helper/web.py +189 -25
- pyxecm/helper/xml.py +244 -40
- pyxecm/otac.py +311 -25
- pyxecm/otcs.py +3866 -1103
- pyxecm/otds.py +397 -150
- pyxecm/otiv.py +1 -1
- pyxecm/otmm.py +808 -0
- pyxecm/otpd.py +17 -12
- {pyxecm-1.3.0.dist-info → pyxecm-1.5.dist-info}/METADATA +4 -1
- pyxecm-1.5.dist-info/RECORD +30 -0
- {pyxecm-1.3.0.dist-info → pyxecm-1.5.dist-info}/WHEEL +1 -1
- pyxecm-1.3.0.dist-info/RECORD +0 -23
- {pyxecm-1.3.0.dist-info → pyxecm-1.5.dist-info}/LICENSE +0 -0
- {pyxecm-1.3.0.dist-info → pyxecm-1.5.dist-info}/top_level.txt +0 -0
pyxecm/helper/web.py
CHANGED
|
@@ -11,14 +11,16 @@ http_request: make a HTTP request to a defined URL / endpoint (e.g. a Web Hook)
|
|
|
11
11
|
"""
|
|
12
12
|
|
|
13
13
|
__author__ = "Dr. Marc Diefenbruch"
|
|
14
|
-
__copyright__ = "Copyright
|
|
14
|
+
__copyright__ = "Copyright 2024, OpenText"
|
|
15
15
|
__credits__ = ["Kai-Philip Gatzweiler"]
|
|
16
16
|
__maintainer__ = "Dr. Marc Diefenbruch"
|
|
17
17
|
__email__ = "mdiefenb@opentext.com"
|
|
18
18
|
|
|
19
19
|
import logging
|
|
20
20
|
import socket
|
|
21
|
+
import time
|
|
21
22
|
import requests
|
|
23
|
+
from lxml import html
|
|
22
24
|
|
|
23
25
|
logger = logging.getLogger("pyxecm.web")
|
|
24
26
|
|
|
@@ -46,7 +48,7 @@ class HTTP(object):
|
|
|
46
48
|
bool: True is reachable, False otherwise
|
|
47
49
|
"""
|
|
48
50
|
|
|
49
|
-
logger.
|
|
51
|
+
logger.debug(
|
|
50
52
|
"Test if host -> %s is reachable on port -> %s ...", hostname, str(port)
|
|
51
53
|
)
|
|
52
54
|
try:
|
|
@@ -66,7 +68,7 @@ class HTTP(object):
|
|
|
66
68
|
)
|
|
67
69
|
return False
|
|
68
70
|
else:
|
|
69
|
-
logger.
|
|
71
|
+
logger.debug("Host is reachable at -> %s:%s", hostname, str(port))
|
|
70
72
|
return True
|
|
71
73
|
|
|
72
74
|
# end method definition
|
|
@@ -75,20 +77,28 @@ class HTTP(object):
|
|
|
75
77
|
self,
|
|
76
78
|
url: str,
|
|
77
79
|
method: str = "POST",
|
|
78
|
-
payload: dict =
|
|
79
|
-
headers: dict =
|
|
80
|
+
payload: dict | None = None,
|
|
81
|
+
headers: dict | None = None,
|
|
80
82
|
timeout: int = 60,
|
|
83
|
+
retries: int = 0,
|
|
84
|
+
wait_time: int = 0,
|
|
85
|
+
wait_on_status: list | None = None,
|
|
86
|
+
show_error: bool = True,
|
|
81
87
|
):
|
|
82
|
-
"""Issues an http request
|
|
88
|
+
"""Issues an http request to a given URL.
|
|
83
89
|
|
|
84
90
|
Args:
|
|
85
91
|
url (str): URL of the request
|
|
86
92
|
method (str, optional): Method of the request (POST, PUT, GET, ...). Defaults to "POST".
|
|
87
|
-
payload (dict, optional): Request payload. Defaults to
|
|
88
|
-
headers (dict, optional): Request header. Defaults to
|
|
93
|
+
payload (dict, optional): Request payload. Defaults to None.
|
|
94
|
+
headers (dict, optional): Request header. Defaults to None. If None then a default
|
|
89
95
|
value defined in "requestHeaders" is used.
|
|
90
|
-
timeout (int): timeout in seconds
|
|
91
|
-
|
|
96
|
+
timeout (int, optional): timeout in seconds
|
|
97
|
+
retries (int, optional): number of retries. If -1 then unlimited retries.
|
|
98
|
+
wait_time (int, optional): number of seconds to wait after each try
|
|
99
|
+
wait_on_status (list, optional): list of status codes we want to wait on. If None
|
|
100
|
+
or empty then we wait for all return codes if
|
|
101
|
+
wait_time > 0
|
|
92
102
|
Returns:
|
|
93
103
|
Response of call
|
|
94
104
|
"""
|
|
@@ -96,25 +106,179 @@ class HTTP(object):
|
|
|
96
106
|
if not headers:
|
|
97
107
|
headers = requestHeaders
|
|
98
108
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
url,
|
|
102
|
-
method,
|
|
103
|
-
str(payload),
|
|
109
|
+
message = "Make HTTP Request to URL -> {} using -> {} method".format(
|
|
110
|
+
url, method
|
|
104
111
|
)
|
|
112
|
+
if payload:
|
|
113
|
+
message += " with payload -> {}".format(payload)
|
|
114
|
+
if retries:
|
|
115
|
+
message += " (max number of retries -> {}, wait time between retries -> {})".format(
|
|
116
|
+
retries, wait_time
|
|
117
|
+
)
|
|
118
|
+
try:
|
|
119
|
+
retries = int(retries)
|
|
120
|
+
except ValueError:
|
|
121
|
+
logger.warning(
|
|
122
|
+
"HTTP request -> retries is not a valid integer value: %s, defaulting to 0 retries ",
|
|
123
|
+
retries,
|
|
124
|
+
)
|
|
125
|
+
retries = 0
|
|
126
|
+
|
|
127
|
+
logger.debug(message)
|
|
128
|
+
|
|
129
|
+
try_counter = 1
|
|
130
|
+
|
|
131
|
+
while True:
|
|
132
|
+
try:
|
|
133
|
+
response = requests.request(
|
|
134
|
+
method=method,
|
|
135
|
+
url=url,
|
|
136
|
+
data=payload,
|
|
137
|
+
headers=headers,
|
|
138
|
+
timeout=timeout,
|
|
139
|
+
)
|
|
140
|
+
logger.debug("%s", response.text)
|
|
141
|
+
except Exception as exc:
|
|
142
|
+
response = None
|
|
143
|
+
logger.warning(
|
|
144
|
+
"HTTP request -> %s to url -> %s failed failed (try %s); error -> %s",
|
|
145
|
+
method,
|
|
146
|
+
url,
|
|
147
|
+
try_counter,
|
|
148
|
+
exc,
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
# do we have an error and don't want to retry?
|
|
152
|
+
if response is not None:
|
|
153
|
+
# Do we have a successful result?
|
|
154
|
+
if response.ok:
|
|
155
|
+
logger.debug(
|
|
156
|
+
"HTTP request -> %s to url -> %s succeeded with status -> %s!",
|
|
157
|
+
method,
|
|
158
|
+
url,
|
|
159
|
+
response.status_code,
|
|
160
|
+
)
|
|
161
|
+
|
|
162
|
+
if wait_on_status and response.status_code in wait_on_status:
|
|
163
|
+
logger.debug(
|
|
164
|
+
"%s is in wait_on_status list: %s",
|
|
165
|
+
response.status_code,
|
|
166
|
+
wait_on_status,
|
|
167
|
+
)
|
|
168
|
+
else:
|
|
169
|
+
return response
|
|
170
|
+
|
|
171
|
+
elif not response.ok:
|
|
172
|
+
message = "HTTP request -> {} to url -> {} failed; status -> {}; error -> {}".format(
|
|
173
|
+
method,
|
|
174
|
+
url,
|
|
175
|
+
response.status_code,
|
|
176
|
+
(
|
|
177
|
+
response.text
|
|
178
|
+
if response.headers.get("content-type")
|
|
179
|
+
== "application/json"
|
|
180
|
+
else "see debug log"
|
|
181
|
+
),
|
|
182
|
+
)
|
|
183
|
+
if show_error and retries == 0:
|
|
184
|
+
logger.error(message)
|
|
185
|
+
else:
|
|
186
|
+
logger.warning(message)
|
|
187
|
+
|
|
188
|
+
# Check if another retry is allowed, if not return None
|
|
189
|
+
if retries == 0:
|
|
190
|
+
return None
|
|
191
|
+
|
|
192
|
+
if wait_time > 0:
|
|
193
|
+
logger.warning(
|
|
194
|
+
"Sleeping %s seconds and then trying once more...",
|
|
195
|
+
str(wait_time),
|
|
196
|
+
)
|
|
197
|
+
time.sleep(wait_time)
|
|
105
198
|
|
|
106
|
-
|
|
107
|
-
|
|
199
|
+
retries -= 1
|
|
200
|
+
try_counter += 1
|
|
201
|
+
|
|
202
|
+
# end method definition
|
|
203
|
+
|
|
204
|
+
def download_file(
|
|
205
|
+
self,
|
|
206
|
+
url: str,
|
|
207
|
+
filename: str,
|
|
208
|
+
timeout: int = 120,
|
|
209
|
+
retries: int = 0,
|
|
210
|
+
wait_time: int = 0,
|
|
211
|
+
wait_on_status: list | None = None,
|
|
212
|
+
show_error: bool = True,
|
|
213
|
+
) -> bool:
|
|
214
|
+
"""Download a file from a URL
|
|
215
|
+
|
|
216
|
+
Args:
|
|
217
|
+
url (str): URL
|
|
218
|
+
filename (str): filename to save
|
|
219
|
+
timeout (int, optional): timeout in seconds
|
|
220
|
+
retries (int, optional): number of retries. If -1 then unlimited retries.
|
|
221
|
+
wait_time (int, optional): number of seconds to wait after each try
|
|
222
|
+
wait_on_status (list, optional): list of status codes we want to wait on. If None
|
|
223
|
+
or empty then we wait for all return codes if
|
|
224
|
+
wait_time > 0
|
|
225
|
+
|
|
226
|
+
Returns:
|
|
227
|
+
bool: True if successful, False otherwise
|
|
228
|
+
"""
|
|
229
|
+
|
|
230
|
+
response = self.http_request(
|
|
231
|
+
url=url,
|
|
232
|
+
method="GET",
|
|
233
|
+
retries=retries,
|
|
234
|
+
timeout=timeout,
|
|
235
|
+
wait_time=wait_time,
|
|
236
|
+
wait_on_status=wait_on_status,
|
|
237
|
+
show_error=show_error,
|
|
108
238
|
)
|
|
109
239
|
|
|
110
|
-
if
|
|
111
|
-
|
|
112
|
-
"HTTP request -> %s to url -> %s failed; error -> %s",
|
|
113
|
-
method,
|
|
114
|
-
url,
|
|
115
|
-
response.text,
|
|
116
|
-
)
|
|
240
|
+
if response is None:
|
|
241
|
+
return False
|
|
117
242
|
|
|
118
|
-
|
|
243
|
+
if response.ok:
|
|
244
|
+
with open(filename, "wb") as f:
|
|
245
|
+
f.write(response.content)
|
|
246
|
+
logger.debug("File downloaded successfully as -> %s", filename)
|
|
247
|
+
return True
|
|
248
|
+
|
|
249
|
+
return False
|
|
119
250
|
|
|
120
251
|
# end method definition
|
|
252
|
+
|
|
253
|
+
def extract_content(self, url: str, xpath: str) -> str | None:
|
|
254
|
+
"""Extract a string from a response of a HTTP request
|
|
255
|
+
based on an XPath.
|
|
256
|
+
|
|
257
|
+
Args:
|
|
258
|
+
url (str): URL to open
|
|
259
|
+
xpath (str): XPath expression to apply to the result
|
|
260
|
+
|
|
261
|
+
Returns:
|
|
262
|
+
str | None: Extracted string or None in case of an error.
|
|
263
|
+
"""
|
|
264
|
+
|
|
265
|
+
# Send a GET request to the URL
|
|
266
|
+
response = requests.get(url, timeout=None)
|
|
267
|
+
|
|
268
|
+
# Check if request was successful
|
|
269
|
+
if response.status_code == 200:
|
|
270
|
+
# Parse the HTML content
|
|
271
|
+
tree = html.fromstring(response.content)
|
|
272
|
+
|
|
273
|
+
# Extract content using XPath
|
|
274
|
+
elements = tree.xpath(xpath)
|
|
275
|
+
|
|
276
|
+
# Get text content of all elements and join them
|
|
277
|
+
content = "\n".join([elem.text_content().strip() for elem in elements])
|
|
278
|
+
|
|
279
|
+
# Return the extracted content
|
|
280
|
+
return content
|
|
281
|
+
else:
|
|
282
|
+
# If request was not successful, print error message
|
|
283
|
+
logger.error(response.status_code)
|
|
284
|
+
return None
|