pyxecm 1.6__py3-none-any.whl → 2.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.

Files changed (78) hide show
  1. pyxecm/__init__.py +7 -4
  2. pyxecm/avts.py +727 -254
  3. pyxecm/coreshare.py +686 -467
  4. pyxecm/customizer/__init__.py +16 -4
  5. pyxecm/customizer/__main__.py +58 -0
  6. pyxecm/customizer/api/__init__.py +5 -0
  7. pyxecm/customizer/api/__main__.py +6 -0
  8. pyxecm/customizer/api/app.py +163 -0
  9. pyxecm/customizer/api/auth/__init__.py +1 -0
  10. pyxecm/customizer/api/auth/functions.py +92 -0
  11. pyxecm/customizer/api/auth/models.py +13 -0
  12. pyxecm/customizer/api/auth/router.py +78 -0
  13. pyxecm/customizer/api/common/__init__.py +1 -0
  14. pyxecm/customizer/api/common/functions.py +47 -0
  15. pyxecm/customizer/api/common/metrics.py +92 -0
  16. pyxecm/customizer/api/common/models.py +21 -0
  17. pyxecm/customizer/api/common/payload_list.py +870 -0
  18. pyxecm/customizer/api/common/router.py +72 -0
  19. pyxecm/customizer/api/settings.py +128 -0
  20. pyxecm/customizer/api/terminal/__init__.py +1 -0
  21. pyxecm/customizer/api/terminal/router.py +87 -0
  22. pyxecm/customizer/api/v1_csai/__init__.py +1 -0
  23. pyxecm/customizer/api/v1_csai/router.py +87 -0
  24. pyxecm/customizer/api/v1_maintenance/__init__.py +1 -0
  25. pyxecm/customizer/api/v1_maintenance/functions.py +100 -0
  26. pyxecm/customizer/api/v1_maintenance/models.py +12 -0
  27. pyxecm/customizer/api/v1_maintenance/router.py +76 -0
  28. pyxecm/customizer/api/v1_otcs/__init__.py +1 -0
  29. pyxecm/customizer/api/v1_otcs/functions.py +61 -0
  30. pyxecm/customizer/api/v1_otcs/router.py +179 -0
  31. pyxecm/customizer/api/v1_payload/__init__.py +1 -0
  32. pyxecm/customizer/api/v1_payload/functions.py +179 -0
  33. pyxecm/customizer/api/v1_payload/models.py +51 -0
  34. pyxecm/customizer/api/v1_payload/router.py +499 -0
  35. pyxecm/customizer/browser_automation.py +721 -286
  36. pyxecm/customizer/customizer.py +1076 -1425
  37. pyxecm/customizer/exceptions.py +35 -0
  38. pyxecm/customizer/guidewire.py +1186 -0
  39. pyxecm/customizer/k8s.py +901 -379
  40. pyxecm/customizer/log.py +107 -0
  41. pyxecm/customizer/m365.py +2967 -920
  42. pyxecm/customizer/nhc.py +1169 -0
  43. pyxecm/customizer/openapi.py +258 -0
  44. pyxecm/customizer/payload.py +18228 -7820
  45. pyxecm/customizer/pht.py +717 -286
  46. pyxecm/customizer/salesforce.py +516 -342
  47. pyxecm/customizer/sap.py +58 -41
  48. pyxecm/customizer/servicenow.py +611 -372
  49. pyxecm/customizer/settings.py +445 -0
  50. pyxecm/customizer/successfactors.py +408 -346
  51. pyxecm/customizer/translate.py +83 -48
  52. pyxecm/helper/__init__.py +5 -2
  53. pyxecm/helper/assoc.py +83 -43
  54. pyxecm/helper/data.py +2406 -870
  55. pyxecm/helper/logadapter.py +27 -0
  56. pyxecm/helper/web.py +229 -101
  57. pyxecm/helper/xml.py +596 -171
  58. pyxecm/maintenance_page/__init__.py +5 -0
  59. pyxecm/maintenance_page/__main__.py +6 -0
  60. pyxecm/maintenance_page/app.py +51 -0
  61. pyxecm/maintenance_page/settings.py +28 -0
  62. pyxecm/maintenance_page/static/favicon.avif +0 -0
  63. pyxecm/maintenance_page/templates/maintenance.html +165 -0
  64. pyxecm/otac.py +235 -141
  65. pyxecm/otawp.py +2668 -1220
  66. pyxecm/otca.py +569 -0
  67. pyxecm/otcs.py +7956 -3237
  68. pyxecm/otds.py +2178 -925
  69. pyxecm/otiv.py +36 -21
  70. pyxecm/otmm.py +1272 -325
  71. pyxecm/otpd.py +231 -127
  72. pyxecm-2.0.1.dist-info/METADATA +122 -0
  73. pyxecm-2.0.1.dist-info/RECORD +76 -0
  74. {pyxecm-1.6.dist-info → pyxecm-2.0.1.dist-info}/WHEEL +1 -1
  75. pyxecm-1.6.dist-info/METADATA +0 -53
  76. pyxecm-1.6.dist-info/RECORD +0 -32
  77. {pyxecm-1.6.dist-info → pyxecm-2.0.1.dist-info/licenses}/LICENSE +0 -0
  78. {pyxecm-1.6.dist-info → pyxecm-2.0.1.dist-info}/top_level.txt +0 -0
pyxecm/otpd.py CHANGED
@@ -1,43 +1,22 @@
1
- """
2
- OTPD Module to implement functions to read / write PowerDocs objects
3
-
4
- Class: OTPD
5
- Methods:
6
-
7
- __init__ : class initializer
8
- config : returns config data set
9
- credentials: Get credentials (username and password)
10
- set_credentials: Set new credentials
11
- hostname: Get the configured PowerDocs hostname
12
- set_hostname: Set the hostname of PowerDocs
13
- base_url : Get PowerDocs base URL
14
- rest_url : Get PowerDocs REST base URL
15
-
16
- parse_request_response: Converts the text property of a request
17
- response object to a Python dict in a safe way
18
-
19
- authenticate : Authenticates at PowerDocs and retrieve OTCS Ticket.
20
-
21
- import_database: imports the PowerDocs database from a zip file
22
- apply_setting: apply a setting to a PowerDocs tenant
23
-
24
- """
1
+ """OTPD Module to implement functions to read / write PowerDocs objects."""
25
2
 
26
3
  __author__ = "Dr. Marc Diefenbruch"
27
- __copyright__ = "Copyright 2024, OpenText"
4
+ __copyright__ = "Copyright (C) 2024-2025, OpenText"
28
5
  __credits__ = ["Kai-Philip Gatzweiler"]
29
6
  __maintainer__ = "Dr. Marc Diefenbruch"
30
7
  __email__ = "mdiefenb@opentext.com"
31
8
 
32
9
  import json
33
10
  import logging
11
+ import os
12
+
34
13
  import requests
35
14
  from requests.auth import HTTPBasicAuth
36
15
  from requests_toolbelt.multipart.encoder import MultipartEncoder
37
16
 
38
- logger = logging.getLogger("pyxecm.otpd")
17
+ default_logger = logging.getLogger("pyxecm.otpd")
39
18
 
40
- requestHeaders = {
19
+ request_headers = {
41
20
  "accept": "application/json;charset=utf-8",
42
21
  "Connection": "keep-alive",
43
22
  "Content-Type": "application/json",
@@ -45,7 +24,9 @@ requestHeaders = {
45
24
 
46
25
 
47
26
  class OTPD:
48
- """Used to automate stettings in OpenText Extended ECM PowerDocs."""
27
+ """Class OTPD is used to automate stettings in OpenText Extended ECM PowerDocs."""
28
+
29
+ logger: logging.Logger = default_logger
49
30
 
50
31
  _config = None
51
32
  _jsessionid = None
@@ -57,17 +38,31 @@ class OTPD:
57
38
  port: int,
58
39
  username: str,
59
40
  password: str,
60
- ):
61
- """Initialize the OTPD object
41
+ logger: logging.Logger = default_logger,
42
+ ) -> None:
43
+ """Initialize the OTPD object.
62
44
 
63
45
  Args:
64
- protocol (str): Either http or https.
65
- hostname (str): The hostname of the PowerDocs Server Manager to communicate with.
66
- port (int): The port number used to talk to the PowerDocs Server Manager.
67
- username (str): The admin user name of PowerDocs Server Manager.
68
- password (str): The admin password of PowerDocs Server Manager.
46
+ protocol (str):
47
+ Either http or https.
48
+ hostname (str):
49
+ The hostname of the PowerDocs Server Manager to communicate with.
50
+ port (int):
51
+ The port number used to talk to the PowerDocs Server Manager.
52
+ username (str):
53
+ The admin user name of PowerDocs Server Manager.
54
+ password (str):
55
+ The admin password of PowerDocs Server Manager.
56
+ logger (logging.logger):
57
+ The logger object to use. Defaults to "default_logger".
58
+
69
59
  """
70
60
 
61
+ if logger != default_logger:
62
+ self.logger = logger.getChild("otpd")
63
+ for logfilter in logger.filters:
64
+ self.logger.addFilter(logfilter)
65
+
71
66
  otpd_config = {}
72
67
 
73
68
  if hostname:
@@ -110,82 +105,121 @@ class OTPD:
110
105
 
111
106
  self._config = otpd_config
112
107
 
108
+ # end method definition
109
+
113
110
  def config(self) -> dict:
114
- """Returns the configuration dictionary
111
+ """Return the configuration dictionary.
115
112
 
116
113
  Returns:
117
114
  dict: Configuration dictionary
115
+
118
116
  """
117
+
119
118
  return self._config
120
119
 
120
+ # end method definition
121
+
121
122
  def credentials(self) -> dict:
122
- """Get credentials (username + password)
123
+ """Get credentials (username + password).
123
124
 
124
125
  Returns:
125
126
  dict: dictionary with username and password
127
+
126
128
  """
127
129
  return {
128
130
  "username": self.config()["username"],
129
131
  "password": self.config()["password"],
130
132
  }
131
133
 
132
- def set_credentials(self, username: str = "admin", password: str = ""):
134
+ # end method definition
135
+
136
+ def set_credentials(self, username: str = "admin", password: str = "") -> None:
133
137
  """Set the credentials for PowerDocs for the based on user name and password.
134
138
 
135
139
  Args:
136
- username (str, optional): Username. Defaults to "admin".
137
- password (str, optional): Password of the user. Defaults to "".
140
+ username (str, optional):
141
+ The username. Defaults to "admin".
142
+ password (str, optional):
143
+ The password of the user. Defaults to "".
144
+
138
145
  """
146
+
139
147
  self.config()["username"] = username
140
148
  self.config()["password"] = password
141
149
 
150
+ # end method definition
151
+
142
152
  def hostname(self) -> str:
143
- """Returns the hostname of PowerDocs (e.g. "otpd")
153
+ """Return the hostname of PowerDocs (e.g. "otpd").
144
154
 
145
155
  Returns:
146
156
  string: hostname
157
+
147
158
  """
159
+
148
160
  return self.config()["hostname"]
149
161
 
150
- def set_hostname(self, hostname: str):
151
- """Sets the hostname of PowerDocs
162
+ # end method definition
163
+
164
+ def set_hostname(self, hostname: str) -> None:
165
+ """Set the hostname of PowerDocs.
152
166
 
153
167
  Args:
154
- hostname (str): new hostname
168
+ hostname (str):
169
+ The new hostname.
170
+
155
171
  """
172
+
156
173
  self.config()["hostname"] = hostname
157
174
 
158
- def base_url(self):
159
- """Returns the base URL of PowerDocs
175
+ # end method definition
176
+
177
+ def base_url(self) -> str:
178
+ """Return the base URL of PowerDocs.
160
179
 
161
180
  Returns:
162
- string: base URL
181
+ string:
182
+ The base URL.
183
+
163
184
  """
185
+
164
186
  return self.config()["baseUrl"]
165
187
 
166
- def rest_url(self):
167
- """Returns the REST URL of PowerDocs
188
+ # end method definition
189
+
190
+ def rest_url(self) -> str:
191
+ """Return the REST URL of PowerDocs.
168
192
 
169
193
  Returns:
170
- string: REST URL
194
+ string:
195
+ The REST URL.
196
+
171
197
  """
198
+
172
199
  return self.config()["restUrl"]
173
200
 
201
+ # end method definition
202
+
174
203
  def parse_request_response(
175
204
  self,
176
205
  response_object: object,
177
206
  additional_error_message: str = "",
178
207
  show_error: bool = True,
179
208
  ) -> dict | None:
180
- """Converts the request response to a Python dict in a safe way
181
- that also handles exceptions.
209
+ """Convert the request response to a dict in a safe way that handles exceptions.
182
210
 
183
211
  Args:
184
- response_object (object): this is reponse object delivered by the request call
185
- additional_error_message (str): print a custom error message
186
- show_error (bool): if True log an error, if False log a warning
212
+ response_object (object):
213
+ Reponse object delivered by the request call.
214
+ additional_error_message (str, optional):
215
+ If provided, print a custom error message.
216
+ show_error (bool, optional):
217
+ If True, log an error, if False log a warning.
218
+
187
219
  Returns:
188
- dict: a python dict object or None in case of an error
220
+ dict | None:
221
+ A python dict object or None in case of an error.
222
+
189
223
  """
190
224
 
191
225
  if not response_object:
@@ -196,16 +230,17 @@ class OTPD:
196
230
  except json.JSONDecodeError as exception:
197
231
  if additional_error_message:
198
232
  message = "Cannot decode response as JSon. {}; error -> {}".format(
199
- additional_error_message, exception
233
+ additional_error_message,
234
+ exception,
200
235
  )
201
236
  else:
202
237
  message = "Cannot decode response as JSon; error -> {}".format(
203
- exception
238
+ exception,
204
239
  )
205
240
  if show_error:
206
- logger.error(message)
241
+ self.logger.error(message)
207
242
  else:
208
- logger.warning(message)
243
+ self.logger.warning(message)
209
244
  return None
210
245
  else:
211
246
  return dict_object
@@ -216,14 +251,18 @@ class OTPD:
216
251
  # It cannot handle the Request - ServerManager returns an
217
252
  # error stating that JavaScript is not enabled...
218
253
  def authenticate(self, revalidate: bool = False) -> dict:
219
- """Authenticates at PowerDocs and retrieve session ID.
254
+ """Authenticate at PowerDocs and retrieve session ID.
220
255
 
221
256
  Args:
222
- revalidate (bool): determinse if a re-athentication is enforced
223
- (e.g. if session has timed out with 401 error)
257
+ revalidate (bool):
258
+ Determine, if a re-athentication is enforced
259
+ (e.g. if session has timed out with 401 error).
260
+
224
261
  Returns:
225
- dict: Cookie information of None in case of an error.
226
- Also stores cookie information in self._cookie
262
+ dict:
263
+ Cookie information of None in case of an error.
264
+ Also stores cookie information in self._cookie
265
+
227
266
  """
228
267
 
229
268
  # Already authenticated and session still valid?
@@ -243,108 +282,172 @@ class OTPD:
243
282
 
244
283
  request_url = self.config()["settingsUrl"]
245
284
 
246
- ##Fetching session id will be three step process
285
+ # Fetching session id will be three step process:
247
286
  # Step1: intiate a dummy request to tomcat
248
- # Step2: fetch session id from the response, and hit j_security_check with proper authentication
249
- # Step3: get session id from the response, add to self. It can be used for other transactions
287
+ # Step2: fetch session id from the response,
288
+ # and hit j_security_check with proper authentication
289
+ # Step3: get session id from the response, add to self.
290
+ # It can be used for other transactions
250
291
  session = requests.Session()
251
- logger.debug("Initiating dummy rest call to Tomcat to get initial session id")
292
+ self.logger.debug(
293
+ "Initiating dummy rest call to Tomcat to get initial session ID.",
294
+ )
252
295
  response = session.put(request_url, json=payload)
253
- logger.info(response.text)
296
+ self.logger.info(response.text)
254
297
  if response.ok:
255
- logger.debug("Url to authenticate Tomcat for Session id -> %s", auth_url)
298
+ self.logger.debug(
299
+ "Url to authenticate Tomcat for Session id -> %s",
300
+ auth_url,
301
+ )
256
302
  session_response = session.post(auth_url)
257
303
  if session_response.ok:
258
- logger.debug(
259
- "Response for -> %s is -> %s", auth_url, str(session_response)
304
+ self.logger.debug(
305
+ "Response for -> %s is -> %s",
306
+ auth_url,
307
+ str(session_response),
260
308
  )
261
309
  session_dict = session.cookies.get_dict()
262
- logger.debug(
310
+ self.logger.debug(
263
311
  "Session id to perform Rest API calls to Tomcat -> %s",
264
312
  session_dict["JSESSIONID"],
265
313
  )
266
- # store session ID an write it into the global requestHeaders variable:
314
+ # store session ID an write it into the global request_headers variable:
267
315
  self._jsessionid = session_dict["JSESSIONID"]
268
- requestHeaders["Cookie"] = "JSESSIONID=" + self._jsessionid
316
+ request_headers["Cookie"] = "JSESSIONID=" + self._jsessionid
269
317
  return session_response
270
318
  else:
271
- logger.error(
272
- "Fetching session id from -> %s failed with j_security_check. Response -> %s",
319
+ self.logger.error(
320
+ "Fetching session id from -> %s failed! Response -> %s",
273
321
  auth_url,
274
322
  session_response.text,
275
323
  )
276
324
  return None
277
325
  else:
278
- logger.error(
279
- "Fetching session id from -> %s failed. Response -> %s",
326
+ self.logger.error(
327
+ "Fetching session id from -> %s failed! Response -> %s",
280
328
  request_url,
281
329
  response.text,
282
330
  )
283
- return None
331
+ return None
284
332
 
285
333
  # end method definition
286
334
 
287
- def import_database(self, filename: str):
288
- """Import PowerDocs database backup from a zip file"""
335
+ def import_database(self, file_path: str) -> dict | None:
336
+ """Import PowerDocs database backup from a zip file.
289
337
 
290
- file = filename.split("/")[-1]
291
- file_tup = (file, open(filename, "rb"), "application/zip")
292
-
293
- # fields attribute is set according to the other party's interface description
294
- m = MultipartEncoder(fields={"name": file, "zipfile": file_tup})
338
+ Args:
339
+ file_path (str):
340
+ The path to the database file to import.
295
341
 
296
- request_url = self.config()["otpdImportDatabaseUrl"]
342
+ Returns:
343
+ dict | None:
344
+ The request response or None in case of an error.
297
345
 
298
- logger.info(
299
- "Importing PowerDocs database backup -> %s, into PowerDocs ServerManager on -> %s",
300
- filename,
301
- request_url,
302
- )
303
- response = requests.post(
304
- request_url, data=m, headers={"content-type": m.content_type}, timeout=60
305
- )
346
+ """
306
347
 
307
- if response.ok:
308
- return response
309
- else:
310
- logger.error(
311
- "Failed to import PowerDocs database backup -> %s into -> %s; error -> %s",
312
- filename,
313
- request_url,
314
- response.text,
348
+ if not file_path or not os.path.isfile(file_path):
349
+ self.logger.error(
350
+ "Cannot import PowerDocs database from non-existent file -> %s",
351
+ file_path,
315
352
  )
316
353
  return None
317
354
 
355
+ try:
356
+ # Extract the filename
357
+ file_name = os.path.basename(file_path)
358
+
359
+ # Open the file safely
360
+ with open(file_path, "rb") as file:
361
+ file_tuple = (file_name, file, "application/zip")
362
+
363
+ # Prepare the multipart encoder
364
+ multipart = MultipartEncoder(
365
+ fields={"name": file_name, "zipfile": file_tuple},
366
+ )
367
+
368
+ # Retrieve the request URL
369
+ request_url = self.config().get("otpdImportDatabaseUrl")
370
+
371
+ if not request_url:
372
+ self.logger.error("Import database URL is not configured.")
373
+ return None
374
+
375
+ self.logger.info(
376
+ "Importing PowerDocs database backup from -> %s; calling -> %s",
377
+ file_path,
378
+ request_url,
379
+ )
380
+
381
+ # Send the request
382
+ response = requests.post(
383
+ url=request_url,
384
+ data=multipart,
385
+ headers={"content-type": multipart.content_type},
386
+ timeout=60,
387
+ )
388
+
389
+ # Handle the response
390
+ if response.ok:
391
+ self.logger.info("Database backup imported successfully.")
392
+ return response.json()
393
+ else:
394
+ self.logger.error(
395
+ "Failed to import PowerDocs database backup from -> %s into -> %s; error -> %s",
396
+ file_path,
397
+ request_url,
398
+ response.text,
399
+ )
400
+ return None
401
+
402
+ except FileNotFoundError:
403
+ self.logger.error("File -> '%s' not found!", file_path)
404
+ except requests.RequestException:
405
+ self.logger.error("HTTP request to -> '%s' failed", request_url)
406
+ except Exception:
407
+ self.logger.error("An unexpected error occurred!")
408
+
409
+ return None
410
+
318
411
  # end method definition
319
412
 
320
413
  def apply_setting(
321
- self, setting_name: str, setting_value: str, tenant_name: str = ""
414
+ self,
415
+ setting_name: str,
416
+ setting_value: str,
417
+ tenant_name: str = "",
322
418
  ) -> dict | None:
323
- """Appy a setting to the PowerDocs Server Manager
419
+ """Apply a setting to the PowerDocs Server Manager.
324
420
 
325
421
  Args:
326
- setting_name (str): name of the setting
327
- setting_value (str): new value of the setting
328
- tenant_name (str): name of the PowerDocs tenant - this is optional as some settings are not tenant-specific!
329
- Return:
330
- dict: Request response or None if the REST call fails.
422
+ setting_name (str):
423
+ The name of the setting.
424
+ setting_value (str):
425
+ The new value of the setting.
426
+ tenant_name (str):
427
+ The name of the PowerDocs tenant.
428
+ The tenant name is optional as some settings are not tenant-specific!
429
+
430
+ Returns:
431
+ dict | None:
432
+ Request response or None if the REST call fails.
433
+
331
434
  """
332
435
 
333
- settingsPutBody = {
436
+ settings_put_body = {
334
437
  "settingname": setting_name,
335
438
  "settingvalue": setting_value,
336
439
  }
337
440
 
338
441
  if tenant_name:
339
- settingsPutBody["tenantName"] = tenant_name
442
+ settings_put_body["tenantName"] = tenant_name
340
443
 
341
444
  request_url = self.config()["settingsUrl"]
342
445
 
343
- logger.debug(
344
- "Update PowerDocs setting -> %s with value -> %s (tenant -> %s); calling -> %s",
446
+ self.logger.debug(
447
+ "Update PowerDocs setting -> '%s' with value -> '%s'%s; calling -> %s",
345
448
  setting_name,
346
449
  setting_value,
347
- tenant_name,
450
+ " (tenant -> '%s')" if tenant_name else "",
348
451
  request_url,
349
452
  )
350
453
 
@@ -352,27 +455,28 @@ class OTPD:
352
455
  while True:
353
456
  response = requests.put(
354
457
  url=request_url,
355
- json=settingsPutBody,
356
- headers=requestHeaders,
458
+ json=settings_put_body,
459
+ headers=request_headers,
357
460
  auth=HTTPBasicAuth(
358
- self.config()["username"], self.config()["password"]
461
+ self.config()["username"],
462
+ self.config()["password"],
359
463
  ),
360
- verify=False, # for localhost deployments this will fail otherwise
464
+ verify=False, # for localhost deployments this will fail otherwis e# noqa: S501
361
465
  timeout=None,
362
466
  )
363
467
  if response.ok:
364
468
  return self.parse_request_response(response)
365
469
  # Check if Session has expired - then re-authenticate and try once more
366
- elif response.status_code == 401 and retries == 0:
367
- logger.debug("Session has expired - try to re-authenticate...")
368
- self.authenticate(True)
470
+ if response.status_code == 401 and retries == 0:
471
+ self.logger.debug("Session has expired - try to re-authenticate...")
472
+ self.authenticate(revalidate=True)
369
473
  retries += 1
370
474
  else:
371
- logger.error(
372
- "Failed to update PowerDocs setting -> %s with value -> %s (tenant -> %s); error -> %s",
475
+ self.logger.error(
476
+ "Failed to update PowerDocs setting -> '%s' with value -> '%s'%s; error -> %s",
373
477
  setting_name,
374
478
  setting_value,
375
- tenant_name,
479
+ " (tenant -> '%s')" if tenant_name else "",
376
480
  response.text,
377
481
  )
378
482
  return None
@@ -0,0 +1,122 @@
1
+ Metadata-Version: 2.4
2
+ Name: pyxecm
3
+ Version: 2.0.1
4
+ Summary: A Python library to interact with Opentext Extended ECM REST API
5
+ Author-email: Kai Gatzweiler <kgatzweiler@opentext.com>, "Dr. Marc Diefenbruch" <mdiefenb@opentext.com>
6
+ Project-URL: Homepage, https://github.com/opentext/pyxecm
7
+ Keywords: opentext,extendedecm,contentserver,otds,appworks,archivecenter
8
+ Classifier: Development Status :: 4 - Beta
9
+ Classifier: Programming Language :: Python :: 3
10
+ Classifier: License :: OSI Approved :: Apache Software License
11
+ Classifier: Operating System :: OS Independent
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content :: Content Management System
14
+ Requires-Python: >=3.10
15
+ Description-Content-Type: text/markdown
16
+ License-File: LICENSE
17
+ Requires-Dist: requests<3
18
+ Requires-Dist: requests_toolbelt
19
+ Requires-Dist: setuptools
20
+ Requires-Dist: kubernetes
21
+ Requires-Dist: zipfile36
22
+ Requires-Dist: suds
23
+ Requires-Dist: python-hcl2
24
+ Requires-Dist: xmltodict
25
+ Requires-Dist: lxml
26
+ Requires-Dist: openpyxl
27
+ Requires-Dist: pandas
28
+ Requires-Dist: python-magic
29
+ Requires-Dist: websockets
30
+ Requires-Dist: pydantic-settings
31
+ Requires-Dist: fastapi>=0.115.12
32
+ Requires-Dist: uvicorn
33
+ Requires-Dist: python-multipart
34
+ Requires-Dist: aiofiles
35
+ Requires-Dist: asyncio
36
+ Requires-Dist: jinja2
37
+ Requires-Dist: prometheus-fastapi-instrumentator
38
+ Requires-Dist: psycopg[binary,pool]>=3.2.6
39
+ Provides-Extra: browserautomation
40
+ Requires-Dist: chromedriver_autoinstaller; extra == "browserautomation"
41
+ Requires-Dist: playwright>=1.52.0; extra == "browserautomation"
42
+ Provides-Extra: dataloader
43
+ Requires-Dist: pandas; extra == "dataloader"
44
+ Requires-Dist: pyyaml; extra == "dataloader"
45
+ Requires-Dist: python-hcl2; extra == "dataloader"
46
+ Requires-Dist: tropycal; extra == "dataloader"
47
+ Requires-Dist: shapely; extra == "dataloader"
48
+ Requires-Dist: cartopy; extra == "dataloader"
49
+ Requires-Dist: psycopg; extra == "dataloader"
50
+ Provides-Extra: sap
51
+ Requires-Dist: pyrfc==2.8.3; extra == "sap"
52
+ Provides-Extra: profiling
53
+ Requires-Dist: pyinstrument; extra == "profiling"
54
+ Dynamic: license-file
55
+
56
+ # PYXECM
57
+
58
+ A python library to interact with Opentext Extended ECM REST API.
59
+ The product API documentation is available on [OpenText Developer](https://developer.opentext.com/ce/products/extendedecm)
60
+ Detailed documentation of this package is available [here](https://opentext.github.io/pyxecm/).
61
+
62
+ # Quick start
63
+
64
+ Install pyxecm with the desired extras into your python environment, extra options are:
65
+
66
+ - browserautomation
67
+ - dataloader
68
+ - sap
69
+
70
+ ```bash
71
+ pip install pyxecm
72
+ ```
73
+
74
+ ## Start using the Customizer
75
+
76
+ Create an `.env` file as described here: [sample-environment-variables](customizerapisettings/#sample-environment-variables)
77
+
78
+ ```bash
79
+ python -m pyxecm.customizer.api
80
+ ```
81
+
82
+
83
+ Access the Customizer API at [http://localhost:8000/api](http://localhost:8000/api)
84
+
85
+
86
+ ## Start using the package libraries
87
+ ```python
88
+ from pyxecm import OTCS
89
+
90
+ otcs_object = OTCS(
91
+ protocol="https",
92
+ hostname="otcs.domain.tld",
93
+ port="443",
94
+ public_url="otcs.domain.tld",
95
+ username="admin",
96
+ password="********",
97
+ base_path="/cs/llisapi.dll",
98
+ )
99
+
100
+ otcs_object.authenticate()
101
+
102
+ nodes = otcs_object.get_subnodes(2000)
103
+
104
+ for node in nodes["results"]:
105
+ print(node["data"]["properties"]["id"], node["data"]["properties"]["name"])
106
+ ```
107
+
108
+
109
+ # Disclaimer
110
+
111
+ !!! quote ""
112
+ Copyright © 2025 Open Text Corporation, All Rights Reserved.
113
+ The above copyright notice and this permission notice shall be included in all
114
+ copies or substantial portions of the Software.
115
+
116
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
117
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
118
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
119
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
120
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
121
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
122
+ SOFTWARE.