pyxecm 2.0.3__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 +76 -8
  2. pyxecm/helper/data.py +16 -24
  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 +4238 -758
  7. pyxecm/otds.py +4 -12
  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.3.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 +12 -11
  32. {pyxecm/customizer/api → pyxecm_api}/settings.py +30 -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 +24 -13
  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 +2359 -1991
  59. {pyxecm/customizer/api/common → pyxecm_customizer}/payload_list.py +57 -65
  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 +14 -10
  66. {pyxecm/maintenance_page → pyxecm_maintenance_page}/__main__.py +1 -1
  67. {pyxecm/maintenance_page → pyxecm_maintenance_page}/app.py +16 -6
  68. pyxecm/customizer/api/app.py +0 -163
  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.3.dist-info/METADATA +0 -119
  74. pyxecm-2.0.3.dist-info/RECORD +0 -78
  75. pyxecm-2.0.3.dist-info/licenses/LICENSE +0 -202
  76. pyxecm-2.0.3.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")
31
-
32
- try:
33
- import pandas as pd
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
34
29
 
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
@@ -609,7 +599,7 @@ class PayloadList:
609
599
 
610
600
  # Log each runnable item
611
601
  for _, row in runnable_df.iterrows():
612
- self.logger.info(
602
+ self.logger.debug(
613
603
  "Added payload file -> '%s' with index -> %s to runnable queue.",
614
604
  row["name"] if row["name"] else row["filename"],
615
605
  row["index"],
@@ -619,18 +609,45 @@ class PayloadList:
619
609
 
620
610
  # end method definition
621
611
 
622
- def process_payload_list(self) -> None:
612
+ def pick_running(self) -> int:
613
+ """Pick all PayloadItems with status "running".
614
+
615
+ Returns:
616
+ pd.DataFrame:
617
+ A list of running payload items.
618
+
619
+ """
620
+
621
+ if self.payload_items.empty:
622
+ return 0
623
+
624
+ all_status = self.payload_items["status"].value_counts().to_dict()
625
+
626
+ return all_status.get("running", 0)
627
+
628
+ # end method definition
629
+
630
+ def process_payload_list(self, concurrent: int | None = None) -> None:
623
631
  """Process runnable payloads.
624
632
 
633
+ Args:
634
+ concurrent (int | None, optional):
635
+ The maximum number of concurrent payloads to run at any given time.
636
+
625
637
  Continuously checks for runnable payload items and starts their
626
638
  "process_payload" method in separate threads.
627
639
  Runs as a daemon until the customizer ends.
640
+
628
641
  """
629
642
 
643
+ @tracer.start_as_current_span("run_and_complete_payload")
630
644
  def run_and_complete_payload(payload_item: pd.Series) -> None:
631
645
  """Run the payload's process_payload method and marks the status as completed afterward."""
632
646
 
633
- 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)
634
651
  self.update_payload_item(payload_item["index"], {"start_time": start_time})
635
652
 
636
653
  # Create a logger with thread_id:
@@ -656,24 +673,6 @@ class PayloadList:
656
673
  handler.setFormatter(fmt=formatter)
657
674
  thread_logger.addHandler(hdlr=handler)
658
675
 
659
- # If hostname is set, configure log handler so forward logs
660
- if api_settings.victorialogs_host:
661
- handler_kwargs = {
662
- "host": api_settings.victorialogs_host,
663
- "port": api_settings.victorialogs_port,
664
- "app": "Customizer",
665
- "payload_item": payload_item["index"],
666
- "payload_file": payload_item["filename"],
667
- }
668
-
669
- # Read namespace if available and add as kwarg to loghandler
670
- file_path = "/var/run/secrets/kubernetes.io/serviceaccount/namespace"
671
- if os.path.isfile(file_path):
672
- with open(file_path) as file:
673
- handler_kwargs["namespace"] = file.read()
674
-
675
- thread_logger.addHandler(VictoriaLogsHandler(**handler_kwargs))
676
-
677
676
  if len(thread_logger.filters) == 0:
678
677
  thread_logger.debug("Adding log count filter to logger")
679
678
  thread_logger.addFilter(
@@ -726,12 +725,6 @@ class PayloadList:
726
725
  )
727
726
 
728
727
  if customizer_settings.get("profiling", False):
729
- from pyinstrument import Profiler
730
-
731
- profiler = Profiler()
732
- profiler.start()
733
-
734
- if customizer_settings.get("cprofiling", False):
735
728
  import cProfile
736
729
  import pstats
737
730
 
@@ -740,19 +733,16 @@ class PayloadList:
740
733
 
741
734
  success = local.customizer_thread_object.customization_run()
742
735
 
743
- if customizer_settings.get("cprofiling", False):
744
- cprofiler.disable()
745
-
746
736
  if customizer_settings.get("profiling", False):
747
- profiler.stop()
737
+ cprofiler.disable()
748
738
 
749
- now = datetime.now(timezone.utc)
739
+ now = datetime.now(UTC)
750
740
  log_path = os.path.dirname(payload_item.logfile)
751
741
  profile_log_prefix = (
752
742
  f"{log_path}/{payload_item['index']}_{payload_item['name']}_{now.strftime('%Y-%m-%d_%H-%M-%S')}"
753
743
  )
754
744
 
755
- if customizer_settings.get("cprofiling", False):
745
+ if customizer_settings.get("profiling", False):
756
746
  import io
757
747
 
758
748
  s = io.StringIO()
@@ -762,10 +752,6 @@ class PayloadList:
762
752
  f.write(s.getvalue())
763
753
  stats.dump_stats(filename=f"{profile_log_prefix}.cprof")
764
754
 
765
- if customizer_settings.get("profiling", False):
766
- with open(f"{profile_log_prefix}.html", "w") as f:
767
- f.write(profiler.output_html())
768
-
769
755
  except ValidationError:
770
756
  thread_logger.error("Validation error!")
771
757
  success = False
@@ -795,7 +781,7 @@ class PayloadList:
795
781
  # Update the status to "completed" in the DataFrame after processing finishes
796
782
  self.update_payload_item(payload_item["index"], {"status": "completed"})
797
783
 
798
- stop_time = datetime.now(timezone.utc)
784
+ stop_time = datetime.now(UTC)
799
785
  duration = stop_time - start_time
800
786
 
801
787
  # Format duration in hh:mm:ss
@@ -817,6 +803,18 @@ class PayloadList:
817
803
  # Start a thread for each runnable item (item is a pd.Series)
818
804
  if runnable_items is not None:
819
805
  for _, item in runnable_items.iterrows():
806
+ if concurrent and self.pick_running() >= concurrent:
807
+ self.logger.debug(
808
+ "Reached concurrency limit of %s payloads. Waiting for one to finish.",
809
+ )
810
+ break
811
+
812
+ self.logger.info(
813
+ "Added payload file -> '%s' with index -> %s to runnable queue.",
814
+ item["name"] if item["name"] else item["filename"],
815
+ item["index"],
816
+ )
817
+
820
818
  # Update the status to "running" in the data frame to prevent re-processing
821
819
  self.payload_items.loc[
822
820
  self.payload_items["name"] == item["name"],
@@ -837,13 +835,14 @@ class PayloadList:
837
835
 
838
836
  # end method definition
839
837
 
840
- def run_payload_processing(self) -> None:
838
+ def run_payload_processing(self, concurrent: int | None = None) -> None:
841
839
  """Start the `process_payload_list` method in a daemon thread."""
842
840
 
843
841
  scheduler_thread = threading.Thread(
844
842
  target=self.process_payload_list,
845
843
  daemon=True,
846
844
  name="Scheduler",
845
+ kwargs={"concurrent": concurrent},
847
846
  )
848
847
 
849
848
  self.logger.info(
@@ -853,13 +852,6 @@ class PayloadList:
853
852
  self._stopped = False
854
853
  scheduler_thread.start()
855
854
 
856
- self.logger.info(
857
- "Waiting for thread -> '%s' to complete...",
858
- str(scheduler_thread.name),
859
- )
860
- scheduler_thread.join()
861
- self.logger.info("Thread -> '%s' has completed.", str(scheduler_thread.name))
862
-
863
855
  # end method definition
864
856
 
865
857
  def stop_payload_processing(self) -> None:
@@ -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
 
@@ -149,16 +149,20 @@ class Translator:
149
149
  request_header = self._headers
150
150
  request_url = self.config()["translateUrlV3"]
151
151
 
152
- response = requests.post(
153
- url=request_url,
154
- headers=request_header,
155
- json=data,
156
- timeout=REQUEST_TIMEOUT,
157
- )
152
+ try:
153
+ response = requests.post(
154
+ url=request_url,
155
+ headers=request_header,
156
+ json=data,
157
+ timeout=REQUEST_TIMEOUT,
158
+ )
158
159
 
159
- if response.status_code != 200:
160
- self.logger.error("Failed to translate text -> %s", response.content)
161
- return None
160
+ if response.status_code != 200:
161
+ self.logger.error("Failed to translate text -> %s", response.content)
162
+ return None
163
+
164
+ except Exception as error:
165
+ self.logger.error("Failed translation request; error -> %s", str(error))
162
166
 
163
167
  translated_text = response.json()["data"]["translations"][0]["translatedText"]
164
168
 
@@ -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,16 +1,19 @@
1
1
  """Maintenance Page that can be enabled by the customizer."""
2
2
 
3
+ import logging
3
4
  import os
4
- from datetime import datetime
5
+ import threading
6
+ from datetime import UTC, datetime
5
7
 
6
8
  import uvicorn
7
9
  from fastapi import FastAPI, HTTPException, Request
8
10
  from fastapi.responses import FileResponse
9
11
  from fastapi.templating import Jinja2Templates
10
- from pytz import UTC
11
12
  from starlette.exceptions import HTTPException as StarletteHTTPException
12
13
 
13
- from pyxecm.maintenance_page.settings import settings
14
+ from .settings import settings
15
+
16
+ logger = logging.getLogger("pyxecm_customizer.maintenance_page")
14
17
 
15
18
  app = FastAPI(openapi_url=None)
16
19
 
@@ -38,7 +41,7 @@ async def http_exception_handler(request: Request, exc: HTTPException) -> Jinja2
38
41
  "maint_text": settings.text,
39
42
  "maint_footer": settings.footer,
40
43
  "status_url": settings.status_url,
41
- "copyright_year": datetime.now(tz=UTC).year,
44
+ "copyright_year": datetime.now(UTC).year,
42
45
  },
43
46
  status_code=513,
44
47
  )
@@ -47,5 +50,12 @@ async def http_exception_handler(request: Request, exc: HTTPException) -> Jinja2
47
50
 
48
51
 
49
52
  def run_maintenance_page() -> None:
50
- """Start the FASTAPI Webserver."""
51
- uvicorn.run(app, host=settings.host, port=settings.port)
53
+ """Start the FASTAPI Webserver in a dedicated thread."""
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()