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.
- pyxecm/coreshare.py +76 -8
- pyxecm/helper/data.py +16 -24
- pyxecm/helper/otel_config.py +26 -0
- pyxecm/helper/web.py +1 -2
- pyxecm/otca.py +1356 -16
- pyxecm/otcs.py +4238 -758
- pyxecm/otds.py +4 -12
- pyxecm/otmm.py +4 -5
- pyxecm/py.typed +0 -0
- pyxecm-3.0.0.dist-info/METADATA +48 -0
- pyxecm-3.0.0.dist-info/RECORD +96 -0
- {pyxecm-2.0.3.dist-info → pyxecm-3.0.0.dist-info}/WHEEL +1 -2
- pyxecm-3.0.0.dist-info/entry_points.txt +4 -0
- {pyxecm/customizer/api → pyxecm_api}/__main__.py +1 -1
- pyxecm_api/agents/__init__.py +7 -0
- pyxecm_api/agents/app.py +13 -0
- pyxecm_api/agents/functions.py +119 -0
- pyxecm_api/agents/models.py +10 -0
- pyxecm_api/agents/otcm_knowledgegraph/functions.py +85 -0
- pyxecm_api/agents/otcm_knowledgegraph/models.py +61 -0
- pyxecm_api/agents/otcm_knowledgegraph/router.py +74 -0
- pyxecm_api/agents/otcm_user_agent/models.py +20 -0
- pyxecm_api/agents/otcm_user_agent/router.py +65 -0
- pyxecm_api/agents/otcm_workspace_agent/models.py +40 -0
- pyxecm_api/agents/otcm_workspace_agent/router.py +200 -0
- pyxecm_api/app.py +221 -0
- {pyxecm/customizer/api → pyxecm_api}/auth/functions.py +10 -2
- {pyxecm/customizer/api → pyxecm_api}/auth/router.py +4 -3
- {pyxecm/customizer/api → pyxecm_api}/common/functions.py +39 -9
- {pyxecm/customizer/api → pyxecm_api}/common/metrics.py +1 -2
- {pyxecm/customizer/api → pyxecm_api}/common/router.py +12 -11
- {pyxecm/customizer/api → pyxecm_api}/settings.py +30 -6
- {pyxecm/customizer/api → pyxecm_api}/terminal/router.py +1 -1
- {pyxecm/customizer/api → pyxecm_api}/v1_csai/router.py +39 -10
- pyxecm_api/v1_csai/statics/bindings/utils.js +189 -0
- pyxecm_api/v1_csai/statics/tom-select/tom-select.complete.min.js +356 -0
- pyxecm_api/v1_csai/statics/tom-select/tom-select.css +334 -0
- pyxecm_api/v1_csai/statics/vis-9.1.2/vis-network.css +1 -0
- pyxecm_api/v1_csai/statics/vis-9.1.2/vis-network.min.js +27 -0
- pyxecm_api/v1_maintenance/__init__.py +1 -0
- {pyxecm/customizer/api → pyxecm_api}/v1_maintenance/functions.py +3 -3
- {pyxecm/customizer/api → pyxecm_api}/v1_maintenance/router.py +8 -8
- pyxecm_api/v1_otcs/__init__.py +1 -0
- {pyxecm/customizer/api → pyxecm_api}/v1_otcs/functions.py +7 -5
- {pyxecm/customizer/api → pyxecm_api}/v1_otcs/router.py +24 -13
- pyxecm_api/v1_payload/__init__.py +1 -0
- {pyxecm/customizer/api → pyxecm_api}/v1_payload/functions.py +10 -7
- {pyxecm/customizer/api → pyxecm_api}/v1_payload/router.py +11 -10
- {pyxecm/customizer → pyxecm_customizer}/__init__.py +8 -0
- {pyxecm/customizer → pyxecm_customizer}/__main__.py +15 -21
- {pyxecm/customizer → pyxecm_customizer}/browser_automation.py +414 -103
- {pyxecm/customizer → pyxecm_customizer}/customizer.py +178 -116
- {pyxecm/customizer → pyxecm_customizer}/guidewire.py +60 -20
- {pyxecm/customizer → pyxecm_customizer}/k8s.py +4 -4
- pyxecm_customizer/knowledge_graph.py +719 -0
- pyxecm_customizer/log.py +35 -0
- {pyxecm/customizer → pyxecm_customizer}/m365.py +41 -33
- {pyxecm/customizer → pyxecm_customizer}/payload.py +2359 -1991
- {pyxecm/customizer/api/common → pyxecm_customizer}/payload_list.py +57 -65
- {pyxecm/customizer → pyxecm_customizer}/salesforce.py +1 -1
- {pyxecm/customizer → pyxecm_customizer}/sap.py +6 -2
- {pyxecm/customizer → pyxecm_customizer}/servicenow.py +2 -4
- {pyxecm/customizer → pyxecm_customizer}/settings.py +7 -6
- {pyxecm/customizer → pyxecm_customizer}/successfactors.py +40 -28
- {pyxecm/customizer → pyxecm_customizer}/translate.py +14 -10
- {pyxecm/maintenance_page → pyxecm_maintenance_page}/__main__.py +1 -1
- {pyxecm/maintenance_page → pyxecm_maintenance_page}/app.py +16 -6
- pyxecm/customizer/api/app.py +0 -163
- pyxecm/customizer/log.py +0 -107
- pyxecm/customizer/nhc.py +0 -1169
- pyxecm/customizer/openapi.py +0 -258
- pyxecm/customizer/pht.py +0 -1357
- pyxecm-2.0.3.dist-info/METADATA +0 -119
- pyxecm-2.0.3.dist-info/RECORD +0 -78
- pyxecm-2.0.3.dist-info/licenses/LICENSE +0 -202
- pyxecm-2.0.3.dist-info/top_level.txt +0 -1
- {pyxecm/customizer/api → pyxecm_api}/__init__.py +0 -0
- {pyxecm/customizer/api/auth → pyxecm_api/agents/otcm_knowledgegraph}/__init__.py +0 -0
- {pyxecm/customizer/api/common → pyxecm_api/agents/otcm_user_agent}/__init__.py +0 -0
- {pyxecm/customizer/api/v1_csai → pyxecm_api/agents/otcm_workspace_agent}/__init__.py +0 -0
- {pyxecm/customizer/api/v1_maintenance → pyxecm_api/auth}/__init__.py +0 -0
- {pyxecm/customizer/api → pyxecm_api}/auth/models.py +0 -0
- {pyxecm/customizer/api/v1_otcs → pyxecm_api/common}/__init__.py +0 -0
- {pyxecm/customizer/api → pyxecm_api}/common/models.py +0 -0
- {pyxecm/customizer/api → pyxecm_api}/terminal/__init__.py +0 -0
- {pyxecm/customizer/api/v1_payload → pyxecm_api/v1_csai}/__init__.py +0 -0
- {pyxecm/customizer/api → pyxecm_api}/v1_csai/models.py +0 -0
- {pyxecm/customizer/api → pyxecm_api}/v1_maintenance/models.py +0 -0
- {pyxecm/customizer/api → pyxecm_api}/v1_payload/models.py +0 -0
- {pyxecm/customizer → pyxecm_customizer}/exceptions.py +0 -0
- {pyxecm/maintenance_page → pyxecm_maintenance_page}/__init__.py +0 -0
- {pyxecm/maintenance_page → pyxecm_maintenance_page}/settings.py +0 -0
- {pyxecm/maintenance_page → pyxecm_maintenance_page}/static/favicon.avif +0 -0
- {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
|
|
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
|
|
26
|
-
from
|
|
27
|
-
from
|
|
28
|
-
from
|
|
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
|
-
|
|
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(
|
|
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.
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
737
|
+
cprofiler.disable()
|
|
748
738
|
|
|
749
|
-
now = datetime.now(
|
|
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("
|
|
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(
|
|
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("
|
|
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("
|
|
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
|
-
|
|
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
|
-
|
|
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("
|
|
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):
|
|
258
|
-
|
|
259
|
-
|
|
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:
|
|
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):
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
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
|
|
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:
|
|
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:
|
|
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,
|
|
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("
|
|
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
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
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
|
-
|
|
160
|
-
|
|
161
|
-
|
|
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,16 +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
|
|
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
|
|
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(
|
|
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
|
-
|
|
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()
|