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/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 2023, OpenText"
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.info(
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.info("Host is reachable at -> %s:%s", hostname, str(port))
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 {}. If {} then a default
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
- logger.info(
100
- "Make HTTP Request to URL -> %s using -> %s method with payload -> %s",
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
- response = requests.request(
107
- method=method, url=url, data=payload, headers=headers, timeout=timeout
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 not response.ok:
111
- logger.error(
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
- return response
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