pyxecm 2.0.4__py3-none-any.whl → 3.0.0__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 (94) hide show
  1. pyxecm/coreshare.py +5 -3
  2. pyxecm/helper/data.py +4 -4
  3. pyxecm/helper/otel_config.py +26 -0
  4. pyxecm/helper/web.py +1 -2
  5. pyxecm/otca.py +1356 -16
  6. pyxecm/otcs.py +2354 -593
  7. pyxecm/otds.py +1 -1
  8. pyxecm/otmm.py +4 -5
  9. pyxecm/py.typed +0 -0
  10. pyxecm-3.0.0.dist-info/METADATA +48 -0
  11. pyxecm-3.0.0.dist-info/RECORD +96 -0
  12. {pyxecm-2.0.4.dist-info → pyxecm-3.0.0.dist-info}/WHEEL +1 -2
  13. pyxecm-3.0.0.dist-info/entry_points.txt +4 -0
  14. {pyxecm/customizer/api → pyxecm_api}/__main__.py +1 -1
  15. pyxecm_api/agents/__init__.py +7 -0
  16. pyxecm_api/agents/app.py +13 -0
  17. pyxecm_api/agents/functions.py +119 -0
  18. pyxecm_api/agents/models.py +10 -0
  19. pyxecm_api/agents/otcm_knowledgegraph/functions.py +85 -0
  20. pyxecm_api/agents/otcm_knowledgegraph/models.py +61 -0
  21. pyxecm_api/agents/otcm_knowledgegraph/router.py +74 -0
  22. pyxecm_api/agents/otcm_user_agent/models.py +20 -0
  23. pyxecm_api/agents/otcm_user_agent/router.py +65 -0
  24. pyxecm_api/agents/otcm_workspace_agent/models.py +40 -0
  25. pyxecm_api/agents/otcm_workspace_agent/router.py +200 -0
  26. pyxecm_api/app.py +221 -0
  27. {pyxecm/customizer/api → pyxecm_api}/auth/functions.py +10 -2
  28. {pyxecm/customizer/api → pyxecm_api}/auth/router.py +4 -3
  29. {pyxecm/customizer/api → pyxecm_api}/common/functions.py +39 -9
  30. {pyxecm/customizer/api → pyxecm_api}/common/metrics.py +1 -2
  31. {pyxecm/customizer/api → pyxecm_api}/common/router.py +7 -8
  32. {pyxecm/customizer/api → pyxecm_api}/settings.py +21 -6
  33. {pyxecm/customizer/api → pyxecm_api}/terminal/router.py +1 -1
  34. {pyxecm/customizer/api → pyxecm_api}/v1_csai/router.py +39 -10
  35. pyxecm_api/v1_csai/statics/bindings/utils.js +189 -0
  36. pyxecm_api/v1_csai/statics/tom-select/tom-select.complete.min.js +356 -0
  37. pyxecm_api/v1_csai/statics/tom-select/tom-select.css +334 -0
  38. pyxecm_api/v1_csai/statics/vis-9.1.2/vis-network.css +1 -0
  39. pyxecm_api/v1_csai/statics/vis-9.1.2/vis-network.min.js +27 -0
  40. pyxecm_api/v1_maintenance/__init__.py +1 -0
  41. {pyxecm/customizer/api → pyxecm_api}/v1_maintenance/functions.py +3 -3
  42. {pyxecm/customizer/api → pyxecm_api}/v1_maintenance/router.py +8 -8
  43. pyxecm_api/v1_otcs/__init__.py +1 -0
  44. {pyxecm/customizer/api → pyxecm_api}/v1_otcs/functions.py +7 -5
  45. {pyxecm/customizer/api → pyxecm_api}/v1_otcs/router.py +8 -7
  46. pyxecm_api/v1_payload/__init__.py +1 -0
  47. {pyxecm/customizer/api → pyxecm_api}/v1_payload/functions.py +10 -7
  48. {pyxecm/customizer/api → pyxecm_api}/v1_payload/router.py +11 -10
  49. {pyxecm/customizer → pyxecm_customizer}/__init__.py +8 -0
  50. {pyxecm/customizer → pyxecm_customizer}/__main__.py +15 -21
  51. {pyxecm/customizer → pyxecm_customizer}/browser_automation.py +414 -103
  52. {pyxecm/customizer → pyxecm_customizer}/customizer.py +178 -116
  53. {pyxecm/customizer → pyxecm_customizer}/guidewire.py +60 -20
  54. {pyxecm/customizer → pyxecm_customizer}/k8s.py +4 -4
  55. pyxecm_customizer/knowledge_graph.py +719 -0
  56. pyxecm_customizer/log.py +35 -0
  57. {pyxecm/customizer → pyxecm_customizer}/m365.py +41 -33
  58. {pyxecm/customizer → pyxecm_customizer}/payload.py +2265 -1933
  59. {pyxecm/customizer/api/common → pyxecm_customizer}/payload_list.py +18 -55
  60. {pyxecm/customizer → pyxecm_customizer}/salesforce.py +1 -1
  61. {pyxecm/customizer → pyxecm_customizer}/sap.py +6 -2
  62. {pyxecm/customizer → pyxecm_customizer}/servicenow.py +2 -4
  63. {pyxecm/customizer → pyxecm_customizer}/settings.py +7 -6
  64. {pyxecm/customizer → pyxecm_customizer}/successfactors.py +40 -28
  65. {pyxecm/customizer → pyxecm_customizer}/translate.py +1 -1
  66. {pyxecm/maintenance_page → pyxecm_maintenance_page}/__main__.py +1 -1
  67. {pyxecm/maintenance_page → pyxecm_maintenance_page}/app.py +14 -8
  68. pyxecm/customizer/api/app.py +0 -157
  69. pyxecm/customizer/log.py +0 -107
  70. pyxecm/customizer/nhc.py +0 -1169
  71. pyxecm/customizer/openapi.py +0 -258
  72. pyxecm/customizer/pht.py +0 -1357
  73. pyxecm-2.0.4.dist-info/METADATA +0 -119
  74. pyxecm-2.0.4.dist-info/RECORD +0 -78
  75. pyxecm-2.0.4.dist-info/licenses/LICENSE +0 -202
  76. pyxecm-2.0.4.dist-info/top_level.txt +0 -1
  77. {pyxecm/customizer/api → pyxecm_api}/__init__.py +0 -0
  78. {pyxecm/customizer/api/auth → pyxecm_api/agents/otcm_knowledgegraph}/__init__.py +0 -0
  79. {pyxecm/customizer/api/common → pyxecm_api/agents/otcm_user_agent}/__init__.py +0 -0
  80. {pyxecm/customizer/api/v1_csai → pyxecm_api/agents/otcm_workspace_agent}/__init__.py +0 -0
  81. {pyxecm/customizer/api/v1_maintenance → pyxecm_api/auth}/__init__.py +0 -0
  82. {pyxecm/customizer/api → pyxecm_api}/auth/models.py +0 -0
  83. {pyxecm/customizer/api/v1_otcs → pyxecm_api/common}/__init__.py +0 -0
  84. {pyxecm/customizer/api → pyxecm_api}/common/models.py +0 -0
  85. {pyxecm/customizer/api → pyxecm_api}/terminal/__init__.py +0 -0
  86. {pyxecm/customizer/api/v1_payload → pyxecm_api/v1_csai}/__init__.py +0 -0
  87. {pyxecm/customizer/api → pyxecm_api}/v1_csai/models.py +0 -0
  88. {pyxecm/customizer/api → pyxecm_api}/v1_maintenance/models.py +0 -0
  89. {pyxecm/customizer/api → pyxecm_api}/v1_payload/models.py +0 -0
  90. {pyxecm/customizer → pyxecm_customizer}/exceptions.py +0 -0
  91. {pyxecm/maintenance_page → pyxecm_maintenance_page}/__init__.py +0 -0
  92. {pyxecm/maintenance_page → pyxecm_maintenance_page}/settings.py +0 -0
  93. {pyxecm/maintenance_page → pyxecm_maintenance_page}/static/favicon.avif +0 -0
  94. {pyxecm/maintenance_page → pyxecm_maintenance_page}/templates/maintenance.html +0 -0
@@ -15,29 +15,19 @@ import pprint
15
15
  import threading
16
16
  import time
17
17
  import traceback
18
- from datetime import datetime, timezone
18
+ from datetime import UTC, datetime
19
19
 
20
+ import pandas as pd
20
21
  from pydantic import ValidationError
21
-
22
- from pyxecm.customizer.api.settings import api_settings
22
+ from pyxecm.helper.otel_config import trace, tracer
23
23
 
24
24
  # OpenText specific modules:
25
- from pyxecm.customizer.customizer import Customizer
26
- from pyxecm.customizer.exceptions import StopOnError
27
- from pyxecm.customizer.log import LogCountFilter, VictoriaLogsHandler
28
- from pyxecm.customizer.payload import load_payload
29
-
30
- default_logger = logging.getLogger("pyxecm.customizer.payload_list")
25
+ from pyxecm_customizer.customizer import Customizer
26
+ from pyxecm_customizer.exceptions import StopOnError
27
+ from pyxecm_customizer.log import LogCountFilter
28
+ from pyxecm_customizer.payload import load_payload
31
29
 
32
- try:
33
- import pandas as pd
34
-
35
- pandas_installed = True
36
- except ModuleNotFoundError:
37
- default_logger.warning(
38
- "Module pandas is not installed. Customizer will not support bulk workspace creation.",
39
- )
40
- pandas_installed = False
30
+ default_logger = logging.getLogger("pyxecm_customizer.payload_list")
41
31
 
42
32
 
43
33
  class PayloadList:
@@ -93,7 +83,7 @@ class PayloadList:
93
83
 
94
84
  def calculate_duration(row: pd.Series) -> str:
95
85
  if row["status"] == "running":
96
- now = datetime.now(timezone.utc)
86
+ now = datetime.now(UTC)
97
87
  start_time = pd.to_datetime(row["start_time"])
98
88
 
99
89
  duration = now - start_time
@@ -650,10 +640,14 @@ class PayloadList:
650
640
 
651
641
  """
652
642
 
643
+ @tracer.start_as_current_span("run_and_complete_payload")
653
644
  def run_and_complete_payload(payload_item: pd.Series) -> None:
654
645
  """Run the payload's process_payload method and marks the status as completed afterward."""
655
646
 
656
- start_time = datetime.now(timezone.utc)
647
+ t = trace.get_current_span()
648
+ t.set_attributes({"payload_id": payload_item["index"], "payload_name": payload_item["name"]})
649
+
650
+ start_time = datetime.now(UTC)
657
651
  self.update_payload_item(payload_item["index"], {"start_time": start_time})
658
652
 
659
653
  # Create a logger with thread_id:
@@ -679,24 +673,6 @@ class PayloadList:
679
673
  handler.setFormatter(fmt=formatter)
680
674
  thread_logger.addHandler(hdlr=handler)
681
675
 
682
- # If hostname is set, configure log handler so forward logs
683
- if api_settings.victorialogs_host:
684
- handler_kwargs = {
685
- "host": api_settings.victorialogs_host,
686
- "port": api_settings.victorialogs_port,
687
- "app": "Customizer",
688
- "payload_item": payload_item["index"],
689
- "payload_file": payload_item["filename"],
690
- }
691
-
692
- # Read namespace if available and add as kwarg to loghandler
693
- file_path = "/var/run/secrets/kubernetes.io/serviceaccount/namespace"
694
- if os.path.isfile(file_path):
695
- with open(file_path) as file:
696
- handler_kwargs["namespace"] = file.read()
697
-
698
- thread_logger.addHandler(VictoriaLogsHandler(**handler_kwargs))
699
-
700
676
  if len(thread_logger.filters) == 0:
701
677
  thread_logger.debug("Adding log count filter to logger")
702
678
  thread_logger.addFilter(
@@ -749,12 +725,6 @@ class PayloadList:
749
725
  )
750
726
 
751
727
  if customizer_settings.get("profiling", False):
752
- from pyinstrument import Profiler
753
-
754
- profiler = Profiler()
755
- profiler.start()
756
-
757
- if customizer_settings.get("cprofiling", False):
758
728
  import cProfile
759
729
  import pstats
760
730
 
@@ -763,19 +733,16 @@ class PayloadList:
763
733
 
764
734
  success = local.customizer_thread_object.customization_run()
765
735
 
766
- if customizer_settings.get("cprofiling", False):
767
- cprofiler.disable()
768
-
769
736
  if customizer_settings.get("profiling", False):
770
- profiler.stop()
737
+ cprofiler.disable()
771
738
 
772
- now = datetime.now(timezone.utc)
739
+ now = datetime.now(UTC)
773
740
  log_path = os.path.dirname(payload_item.logfile)
774
741
  profile_log_prefix = (
775
742
  f"{log_path}/{payload_item['index']}_{payload_item['name']}_{now.strftime('%Y-%m-%d_%H-%M-%S')}"
776
743
  )
777
744
 
778
- if customizer_settings.get("cprofiling", False):
745
+ if customizer_settings.get("profiling", False):
779
746
  import io
780
747
 
781
748
  s = io.StringIO()
@@ -785,10 +752,6 @@ class PayloadList:
785
752
  f.write(s.getvalue())
786
753
  stats.dump_stats(filename=f"{profile_log_prefix}.cprof")
787
754
 
788
- if customizer_settings.get("profiling", False):
789
- with open(f"{profile_log_prefix}.html", "w") as f:
790
- f.write(profiler.output_html())
791
-
792
755
  except ValidationError:
793
756
  thread_logger.error("Validation error!")
794
757
  success = False
@@ -818,7 +781,7 @@ class PayloadList:
818
781
  # Update the status to "completed" in the DataFrame after processing finishes
819
782
  self.update_payload_item(payload_item["index"], {"status": "completed"})
820
783
 
821
- stop_time = datetime.now(timezone.utc)
784
+ stop_time = datetime.now(UTC)
822
785
  duration = stop_time - start_time
823
786
 
824
787
  # Format duration in hh:mm:ss
@@ -17,7 +17,7 @@ from http import HTTPStatus
17
17
 
18
18
  import requests
19
19
 
20
- default_logger = logging.getLogger("pyxecm.customizer.salesforce")
20
+ default_logger = logging.getLogger("pyxecm_customizer.salesforce")
21
21
 
22
22
  REQUEST_LOGIN_HEADERS = {
23
23
  "Content-Type": "application/x-www-form-urlencoded",
@@ -32,7 +32,7 @@ __email__ = "mdiefenb@opentext.com"
32
32
 
33
33
  import logging
34
34
 
35
- default_logger = logging.getLogger("pyxecm.customizer.sap")
35
+ default_logger = logging.getLogger("pyxecm_customizer.sap")
36
36
 
37
37
  try:
38
38
  import pyrfc
@@ -79,7 +79,11 @@ class SAP:
79
79
  self.logger.addFilter(logfilter)
80
80
 
81
81
  self.logger.info("Initializing SAP object...")
82
- self.logger.info("Using PyRFC version -> %s", pyrfc.__version__)
82
+
83
+ if _has_pyrfc:
84
+ self.logger.info("Using PyRFC version -> %s", pyrfc.__version__)
85
+ else:
86
+ self.logger.warning("PyRFC not installed. Cannot talk to SAP!")
83
87
 
84
88
  # Set up connection parameters
85
89
  self._connection_parameters = {
@@ -24,11 +24,10 @@ from importlib.metadata import version
24
24
  from typing import Any
25
25
 
26
26
  import requests
27
+ from pyxecm.helper import Data
27
28
  from requests.auth import HTTPBasicAuth
28
29
  from requests.exceptions import HTTPError, RequestException
29
30
 
30
- from pyxecm.helper import Data
31
-
32
31
  APP_NAME = "pyxecm"
33
32
  APP_VERSION = version("pyxecm")
34
33
  MODULE_NAME = APP_NAME + ".customizer.servicenow"
@@ -1120,8 +1119,7 @@ class ServiceNow:
1120
1119
 
1121
1120
  # Read and write the attachment file in chunks:
1122
1121
  with open(file_path, "wb") as attachment_file:
1123
- for chunk in attachment_response.iter_content(chunk_size=8192):
1124
- attachment_file.write(chunk)
1122
+ attachment_file.writelines(attachment_response.iter_content(chunk_size=8192))
1125
1123
 
1126
1124
  # We build a list of filenames and IDs.
1127
1125
  # The IDs we want to use as nicknames later on.
@@ -265,6 +265,8 @@ class CustomizerSettingsK8S(BaseModel):
265
265
  sts_otcs_frontend_replicas: int = Field(None)
266
266
  sts_otcs_admin: str = Field(default="otcs-admin", description="Name of the OTCS-ADMIN statefulset")
267
267
  sts_otcs_admin_replicas: int = Field(default=None)
268
+ sts_otcs_da: str = Field(default="otcs-da", description="Name of the OTCS-DA statefulset")
269
+ sts_otcs_da_replicas: int = Field(default=None)
268
270
  ingress_otxecm: str = Field(default="otxecm-ingress", description="Name of the otxecm ingress")
269
271
 
270
272
  maintenance_service_name: str = Field(default="customizer")
@@ -348,10 +350,13 @@ class CustomizerSettingsAviator(BaseModel):
348
350
  oauth_client: str = Field(default="", description="OAuth Client ID for Content Aviator")
349
351
  oauth_secret: str = Field(default="", description="OAuth Client Secret for Content Aviator")
350
352
  chat_svc_url: HttpUrl = Field(
351
- default="http://csai-chat-svc:3000", description="Chat Service URL for Content Aviator"
353
+ default=HttpUrl("http://csai-chat-svc:3000"), description="Chat Service URL for Content Aviator"
352
354
  )
353
355
  embed_svc_url: HttpUrl = Field(
354
- default="http://csai-embed-svc:3000", description="Embed Service URL for Content Aviator"
356
+ default=HttpUrl("http://csai-embed-svc:3000"), description="Embed Service URL for Content Aviator"
357
+ )
358
+ studio_url: HttpUrl = Field(
359
+ default=HttpUrl("http://csai-aviator-studio"), description="Service URL for Aviator Studio"
355
360
  )
356
361
 
357
362
 
@@ -416,10 +421,6 @@ class Settings(BaseSettings):
416
421
  placeholder_values: dict = Field(default={})
417
422
 
418
423
  profiling: bool = Field(
419
- default=False,
420
- description="Profiling can only be enabled when using the CustomizerAPI. Switch to enable python profiling using pyinstrument. Result is a html file showing the execution of payload broken down into functions and their duration. The files are located in the logdir. Profiling is disabled by default.",
421
- )
422
- cprofiling: bool = Field(
423
424
  default=False,
424
425
  description="Profiling can only be enabled when using the CustomizerAPI. Switch to enable python profiling using cProfile. Result is a log file with the cProfile results, as well as a dump of the profiling session. The files are located in the logdir. The files are located in the logdir. Profilig is disabled by default.",
425
426
  )
@@ -19,7 +19,7 @@ import urllib.parse
19
19
  import requests
20
20
  import xmltodict
21
21
 
22
- default_logger = logging.getLogger("pyxecm.customizer.sucessfactors")
22
+ default_logger = logging.getLogger("pyxecm_customizer.sucessfactors")
23
23
 
24
24
  request_login_headers = {
25
25
  "Content-Type": "application/x-www-form-urlencoded", # "application/json",
@@ -63,11 +63,11 @@ class SuccessFactors:
63
63
  The SuccessFactors Client ID.
64
64
  client_secret (str):
65
65
  The SuccessFactors Client Secret.
66
- username (str):
66
+ username (str, optional):
67
67
  The user name in SuccessFactors.
68
- password (str):
68
+ password (str, optional):
69
69
  The password of the SuccessFactors user.
70
- company_id (str):
70
+ company_id (str, optional):
71
71
  The SuccessFactors company ID.
72
72
  authorization_url (str, optional):
73
73
  URL for SuccessFactors login.
@@ -254,11 +254,16 @@ class SuccessFactors:
254
254
  """Check existence of key / value pair in the response properties of an SuccessFactors API call.
255
255
 
256
256
  Args:
257
- response (dict): REST response from an SuccessFactors API call
258
- key (str): property name (key)
259
- value (str): value to find in the item with the matching key
257
+ response (dict):
258
+ REST response from an SuccessFactors API call
259
+ key (str):
260
+ The property name (key).
261
+ value (str):
262
+ The value to find in the item with the matching key.
263
+
260
264
  Returns:
261
- bool: True if the value was found, False otherwise
265
+ bool:
266
+ True if the value was found, False otherwise
262
267
 
263
268
  """
264
269
 
@@ -290,13 +295,17 @@ class SuccessFactors:
290
295
  """Get value of a result property with a given key of an SuccessFactors API call.
291
296
 
292
297
  Args:
293
- response (dict): REST response from an SuccessFactors REST Call
294
- key (str): property name (key)
295
- index (int, optional): Index to use (1st element has index 0).
296
- Defaults to 0.
298
+ response (dict):
299
+ REST response from an SuccessFactors REST Call
300
+ key (str):
301
+ The property name (key).
302
+ index (int, optional):
303
+ Index to use (1st element has index 0).
304
+ Defaults to 0.
297
305
 
298
306
  Returns:
299
- str: value for the key, None otherwise
307
+ str | None:
308
+ The value for the key, None if not found.
300
309
 
301
310
  """
302
311
 
@@ -342,7 +351,9 @@ class SuccessFactors:
342
351
  Args:
343
352
  None
344
353
  Returns:
345
- str: Assertion. Also stores access token in self._assertion. None in case of error
354
+ str:
355
+ The SAML assertion. Also stores access token in self._assertion.
356
+ Returns None in case of an error.
346
357
 
347
358
  """
348
359
 
@@ -460,7 +471,7 @@ class SuccessFactors:
460
471
  """Get information for a country / countries.
461
472
 
462
473
  Args:
463
- code (str):
474
+ code (str, optional):
464
475
  3 character code for contry selection, like "USA"
465
476
 
466
477
  Returns:
@@ -545,13 +556,14 @@ class SuccessFactors:
545
556
  The field name of the filter.
546
557
  field_value (str):
547
558
  The filter value to compare the field with.
548
- field_operation (str):
559
+ field_operation (str, optional):
549
560
  The operation of the filter. Like "in".
550
- max_results (int):
561
+ max_results (int, optional):
551
562
  The maximum number of results to return. Default is 1.
552
563
 
553
564
  Returns:
554
- dict | None: User Account details
565
+ dict | None:
566
+ User Account details
555
567
 
556
568
  Example return data in "d" dictionary:
557
569
 
@@ -817,14 +829,14 @@ class SuccessFactors:
817
829
  entity (str, optional):
818
830
  Entity type to query. Examples are "PerPerson" (default),
819
831
  "PerPersonal", "PerEmail", "PersonKey", ...
820
- field_name (str):
832
+ field_name (str, optional):
821
833
  Field to search in. E.g. personIdExternal, firstName, lastName,
822
834
  fullName, email, dateOfBirth, gender, nationality, maritalStatus,
823
835
  employeeId.
824
- field_value (str):
836
+ field_value (str, optional):
825
837
  Value to match in the Field
826
- field_operation (str):
827
- The operation to apply for the filter.
838
+ field_operation (str, optional):
839
+ The operation to apply for the filter. Default is 'eq' (equal).
828
840
  max_results (int):
829
841
  The maximum number of results to return. Default is 1.
830
842
 
@@ -972,7 +984,7 @@ class SuccessFactors:
972
984
  The metadata response supports only application/xml type.
973
985
 
974
986
  Args:
975
- entities (list):
987
+ entities (list | None, optional):
976
988
  A list of entities to deliver metadata for.
977
989
 
978
990
  Returns:
@@ -1055,8 +1067,8 @@ class SuccessFactors:
1055
1067
  self,
1056
1068
  user_id: str, # this is NOT the username but really an ID like 106020
1057
1069
  email_address: str,
1058
- email_type: int = 8448, # 8448
1059
- ) -> dict:
1070
+ email_type: int = 8448,
1071
+ ) -> dict | None:
1060
1072
  """Update user email.
1061
1073
 
1062
1074
  See: https://help.sap.com/docs/SAP_SUCCESSFACTORS_PLATFORM/d599f15995d348a1b45ba5603e2aba9b/7b3daeb3d77d491bb401345eede34bb5.html?locale=en-US
@@ -1066,11 +1078,11 @@ class SuccessFactors:
1066
1078
  The ID of the user (e.g. 106020).
1067
1079
  email_address (str):
1068
1080
  The new email address of user.
1069
- email_type (int):
1070
- Type of the email. 8448 = Business
1081
+ email_type (int, optional):
1082
+ Type of the email. 8448 = Business.
1071
1083
 
1072
1084
  Returns:
1073
- dict:
1085
+ dict | None:
1074
1086
  Request response or None if an error occured.
1075
1087
 
1076
1088
  """
@@ -10,7 +10,7 @@ import logging
10
10
 
11
11
  import requests
12
12
 
13
- default_logger = logging.getLogger("pyxecm.customizer.translate")
13
+ default_logger = logging.getLogger("pyxecm_customizer.translate")
14
14
 
15
15
  REQUEST_TIMEOUT = 60
16
16
 
@@ -1,6 +1,6 @@
1
1
  """Wrapper to start the Maintenance Page."""
2
2
 
3
- from pyxecm.maintenance_page import run_maintenance_page
3
+ from pyxecm_maintenance_page import run_maintenance_page
4
4
 
5
5
  if __name__ == "__main__":
6
6
  run_maintenance_page()
@@ -1,17 +1,19 @@
1
1
  """Maintenance Page that can be enabled by the customizer."""
2
2
 
3
+ import logging
3
4
  import os
4
5
  import threading
5
- from datetime import datetime
6
+ from datetime import UTC, datetime
6
7
 
7
8
  import uvicorn
8
9
  from fastapi import FastAPI, HTTPException, Request
9
10
  from fastapi.responses import FileResponse
10
11
  from fastapi.templating import Jinja2Templates
11
- from pytz import UTC
12
12
  from starlette.exceptions import HTTPException as StarletteHTTPException
13
13
 
14
- from pyxecm.maintenance_page.settings import settings
14
+ from .settings import settings
15
+
16
+ logger = logging.getLogger("pyxecm_customizer.maintenance_page")
15
17
 
16
18
  app = FastAPI(openapi_url=None)
17
19
 
@@ -39,7 +41,7 @@ async def http_exception_handler(request: Request, exc: HTTPException) -> Jinja2
39
41
  "maint_text": settings.text,
40
42
  "maint_footer": settings.footer,
41
43
  "status_url": settings.status_url,
42
- "copyright_year": datetime.now(tz=UTC).year,
44
+ "copyright_year": datetime.now(UTC).year,
43
45
  },
44
46
  status_code=513,
45
47
  )
@@ -49,7 +51,11 @@ async def http_exception_handler(request: Request, exc: HTTPException) -> Jinja2
49
51
 
50
52
  def run_maintenance_page() -> None:
51
53
  """Start the FASTAPI Webserver in a dedicated thread."""
52
- maint_thread = threading.Thread(
53
- target=uvicorn.run, name="MaintenancePage", kwargs={"app": app, "host": settings.host, "port": settings.port}
54
- )
55
- maint_thread.start()
54
+
55
+ def start_server() -> None:
56
+ try:
57
+ uvicorn.run(app, host=settings.host, port=settings.port)
58
+ except Exception:
59
+ logger.error("Could not start the maintenance page.")
60
+
61
+ threading.Thread(target=start_server, name="MaintenancePage").start()
@@ -1,157 +0,0 @@
1
- """API Implemenation for the Customizer to start and control the payload processing."""
2
-
3
- __author__ = "Dr. Marc Diefenbruch"
4
- __copyright__ = "Copyright (C) 2024-2025, OpenText"
5
- __credits__ = ["Kai-Philip Gatzweiler"]
6
- __maintainer__ = "Dr. Marc Diefenbruch"
7
- __email__ = "mdiefenb@opentext.com"
8
-
9
- import logging
10
- import os
11
- import sys
12
- from collections.abc import AsyncGenerator
13
- from contextlib import asynccontextmanager
14
- from datetime import datetime, timezone
15
- from importlib.metadata import version
16
-
17
- import uvicorn
18
- from fastapi import FastAPI
19
- from fastapi.middleware.cors import CORSMiddleware
20
- from prometheus_fastapi_instrumentator import Instrumentator
21
-
22
- from pyxecm.customizer.api.auth.router import router as auth_router
23
- from pyxecm.customizer.api.common.functions import PAYLOAD_LIST
24
- from pyxecm.customizer.api.common.metrics import payload_logs_by_payload, payload_logs_total
25
- from pyxecm.customizer.api.common.router import router as common_router
26
- from pyxecm.customizer.api.settings import api_settings
27
- from pyxecm.customizer.api.terminal.router import router as terminal_router
28
- from pyxecm.customizer.api.v1_csai.router import router as v1_csai_router
29
- from pyxecm.customizer.api.v1_maintenance.router import router as v1_maintenance_router
30
- from pyxecm.customizer.api.v1_otcs.router import router as v1_otcs_router
31
- from pyxecm.customizer.api.v1_payload.functions import import_payload
32
- from pyxecm.customizer.api.v1_payload.router import router as v1_payload_router
33
- from pyxecm.maintenance_page import run_maintenance_page
34
-
35
- # Check if Temp dir exists
36
- if not os.path.exists(api_settings.temp_dir):
37
- os.makedirs(api_settings.temp_dir)
38
-
39
- # Check if Logfile and folder exists and is unique
40
- if os.path.isfile(os.path.join(api_settings.logfolder, api_settings.logfile)):
41
- customizer_start_time = datetime.now(timezone.utc).strftime(
42
- "%Y-%m-%d_%H-%M",
43
- )
44
- api_settings.logfile = f"customizer_{customizer_start_time}.log"
45
- elif not os.path.exists(api_settings.logfolder):
46
- os.makedirs(api_settings.logfolder)
47
-
48
- handlers = [
49
- logging.FileHandler(os.path.join(api_settings.logfolder, api_settings.logfile)),
50
- logging.StreamHandler(sys.stdout),
51
- ]
52
-
53
- logging.basicConfig(
54
- format="%(asctime)s %(levelname)s [%(name)s] [%(threadName)s] %(message)s",
55
- datefmt="%d-%b-%Y %H:%M:%S",
56
- level=api_settings.loglevel,
57
- handlers=handlers,
58
- )
59
-
60
-
61
- @asynccontextmanager
62
- async def lifespan(
63
- app: FastAPI, # noqa: ARG001
64
- ) -> AsyncGenerator:
65
- """Lifespan Method for FASTAPI to handle the startup and shutdown process.
66
-
67
- Args:
68
- app (FastAPI):
69
- The application.
70
-
71
- """
72
-
73
- logger.debug("Settings -> %s", api_settings)
74
-
75
- if api_settings.import_payload:
76
- logger.info("Importing filesystem payloads...")
77
-
78
- # Base Payload
79
- import_payload(payload=api_settings.payload)
80
-
81
- # External Payload
82
- import_payload(payload_dir=api_settings.payload_dir, dependencies=True)
83
-
84
- # Optional Payload
85
- import_payload(payload_dir=api_settings.payload_dir_optional)
86
-
87
- logger.info("Starting maintenance_page thread...")
88
- run_maintenance_page()
89
-
90
- logger.info("Starting processing thread...")
91
- PAYLOAD_LIST.run_payload_processing(concurrent=api_settings.concurrent_payloads)
92
-
93
- yield
94
- logger.info("Shutdown")
95
- PAYLOAD_LIST.stop_payload_processing()
96
-
97
-
98
- app = FastAPI(
99
- docs_url="/api",
100
- title=api_settings.title,
101
- description=api_settings.description,
102
- openapi_url=api_settings.openapi_url,
103
- root_path=api_settings.root_path,
104
- lifespan=lifespan,
105
- version=version("pyxecm"),
106
- openapi_tags=[
107
- {
108
- "name": "auth",
109
- "description": "Authentication Endpoint - Users are authenticated against Opentext Directory Services",
110
- },
111
- {
112
- "name": "payload",
113
- "description": "Get status and manipulate payload objects ",
114
- },
115
- {
116
- "name": "maintenance",
117
- "description": "Enable, disable or alter the maintenance mode.",
118
- },
119
- ],
120
- )
121
-
122
- ## Add all Routers
123
- app.include_router(router=common_router)
124
- app.include_router(router=auth_router)
125
- app.include_router(router=v1_maintenance_router)
126
- app.include_router(router=v1_otcs_router)
127
- app.include_router(router=v1_payload_router)
128
- if api_settings.ws_terminal:
129
- app.include_router(router=terminal_router)
130
- if api_settings.csai:
131
- app.include_router(router=v1_csai_router)
132
-
133
- logger = logging.getLogger("CustomizerAPI")
134
- app.add_middleware(
135
- CORSMiddleware,
136
- allow_origins=api_settings.trusted_origins,
137
- allow_credentials=True,
138
- allow_methods=["*"],
139
- allow_headers=["*"],
140
- )
141
-
142
- if api_settings.metrics:
143
- # Add Prometheus Instrumentator for /metrics
144
- instrumentator = Instrumentator().instrument(app).expose(app)
145
- instrumentator.add(payload_logs_by_payload(PAYLOAD_LIST))
146
- instrumentator.add(payload_logs_total(PAYLOAD_LIST))
147
-
148
-
149
- def run_api() -> None:
150
- """Start the FASTAPI Webserver."""
151
-
152
- uvicorn.run(
153
- "pyxecm.customizer.api:app",
154
- host=api_settings.bind_address,
155
- port=api_settings.bind_port,
156
- workers=api_settings.workers,
157
- )