pyxecm 2.0.4__py3-none-any.whl → 3.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.
- pyxecm/coreshare.py +5 -3
- pyxecm/helper/data.py +4 -4
- pyxecm/helper/otel_config.py +26 -0
- pyxecm/helper/web.py +1 -2
- pyxecm/otca.py +1356 -16
- pyxecm/otcs.py +2354 -593
- pyxecm/otds.py +1 -1
- pyxecm/otmm.py +4 -5
- pyxecm/py.typed +0 -0
- pyxecm-3.0.1.dist-info/METADATA +126 -0
- pyxecm-3.0.1.dist-info/RECORD +96 -0
- {pyxecm-2.0.4.dist-info → pyxecm-3.0.1.dist-info}/WHEEL +1 -2
- pyxecm-3.0.1.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 +7 -8
- {pyxecm/customizer/api → pyxecm_api}/settings.py +21 -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 +8 -7
- 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 +2265 -1933
- {pyxecm/customizer/api/common → pyxecm_customizer}/payload_list.py +18 -55
- {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 +1 -1
- {pyxecm/maintenance_page → pyxecm_maintenance_page}/__main__.py +1 -1
- {pyxecm/maintenance_page → pyxecm_maintenance_page}/app.py +14 -8
- pyxecm/customizer/api/app.py +0 -157
- 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.4.dist-info/METADATA +0 -119
- pyxecm-2.0.4.dist-info/RECORD +0 -78
- pyxecm-2.0.4.dist-info/licenses/LICENSE +0 -202
- pyxecm-2.0.4.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")
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
737
|
+
cprofiler.disable()
|
|
771
738
|
|
|
772
|
-
now = datetime.now(
|
|
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("
|
|
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(
|
|
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("
|
|
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
|
"""
|
|
@@ -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
|
|
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(
|
|
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
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
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()
|
pyxecm/customizer/api/app.py
DELETED
|
@@ -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
|
-
)
|