pyxecm 1.6__py3-none-any.whl → 2.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 (56) hide show
  1. pyxecm/__init__.py +6 -4
  2. pyxecm/avts.py +673 -246
  3. pyxecm/coreshare.py +686 -467
  4. pyxecm/customizer/__init__.py +16 -4
  5. pyxecm/customizer/__main__.py +58 -0
  6. pyxecm/customizer/api/__init__.py +5 -0
  7. pyxecm/customizer/api/__main__.py +6 -0
  8. pyxecm/customizer/api/app.py +914 -0
  9. pyxecm/customizer/api/auth.py +154 -0
  10. pyxecm/customizer/api/metrics.py +92 -0
  11. pyxecm/customizer/api/models.py +13 -0
  12. pyxecm/customizer/api/payload_list.py +865 -0
  13. pyxecm/customizer/api/settings.py +103 -0
  14. pyxecm/customizer/browser_automation.py +332 -139
  15. pyxecm/customizer/customizer.py +1007 -1130
  16. pyxecm/customizer/exceptions.py +35 -0
  17. pyxecm/customizer/guidewire.py +322 -0
  18. pyxecm/customizer/k8s.py +713 -378
  19. pyxecm/customizer/log.py +107 -0
  20. pyxecm/customizer/m365.py +2867 -909
  21. pyxecm/customizer/nhc.py +1169 -0
  22. pyxecm/customizer/openapi.py +258 -0
  23. pyxecm/customizer/payload.py +16817 -7467
  24. pyxecm/customizer/pht.py +699 -285
  25. pyxecm/customizer/salesforce.py +516 -342
  26. pyxecm/customizer/sap.py +58 -41
  27. pyxecm/customizer/servicenow.py +593 -371
  28. pyxecm/customizer/settings.py +442 -0
  29. pyxecm/customizer/successfactors.py +408 -346
  30. pyxecm/customizer/translate.py +83 -48
  31. pyxecm/helper/__init__.py +5 -2
  32. pyxecm/helper/assoc.py +83 -43
  33. pyxecm/helper/data.py +2406 -870
  34. pyxecm/helper/logadapter.py +27 -0
  35. pyxecm/helper/web.py +229 -101
  36. pyxecm/helper/xml.py +527 -171
  37. pyxecm/maintenance_page/__init__.py +5 -0
  38. pyxecm/maintenance_page/__main__.py +6 -0
  39. pyxecm/maintenance_page/app.py +51 -0
  40. pyxecm/maintenance_page/settings.py +28 -0
  41. pyxecm/maintenance_page/static/favicon.avif +0 -0
  42. pyxecm/maintenance_page/templates/maintenance.html +165 -0
  43. pyxecm/otac.py +234 -140
  44. pyxecm/otawp.py +1436 -557
  45. pyxecm/otcs.py +7716 -3161
  46. pyxecm/otds.py +2150 -919
  47. pyxecm/otiv.py +36 -21
  48. pyxecm/otmm.py +1272 -325
  49. pyxecm/otpd.py +231 -127
  50. pyxecm-2.0.0.dist-info/METADATA +145 -0
  51. pyxecm-2.0.0.dist-info/RECORD +54 -0
  52. {pyxecm-1.6.dist-info → pyxecm-2.0.0.dist-info}/WHEEL +1 -1
  53. pyxecm-1.6.dist-info/METADATA +0 -53
  54. pyxecm-1.6.dist-info/RECORD +0 -32
  55. {pyxecm-1.6.dist-info → pyxecm-2.0.0.dist-info/licenses}/LICENSE +0 -0
  56. {pyxecm-1.6.dist-info → pyxecm-2.0.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,154 @@
1
+ """Utility library to handle the authentication with OTDS."""
2
+
3
+ import json
4
+ from typing import Annotated
5
+
6
+ import requests
7
+ from fastapi import APIRouter, Depends, HTTPException, status
8
+ from fastapi.responses import JSONResponse
9
+ from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm
10
+
11
+ from pyxecm.customizer.api.models import User
12
+ from pyxecm.customizer.api.settings import api_settings
13
+
14
+ router = APIRouter()
15
+
16
+ oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
17
+
18
+
19
+ def get_groups(response: dict, token: str) -> list:
20
+ """Get the groups of the user.
21
+
22
+ Args:
23
+ response (_type_): _description_
24
+ token (_type_): _description_
25
+
26
+ Returns:
27
+ list: _description_
28
+
29
+ """
30
+
31
+ headers = {
32
+ "Accept": "application/json",
33
+ "otdsticket": token,
34
+ }
35
+ url = api_settings.otds_url + "/otdsws/rest/users/" + response["user"]["id"] + "/memberof"
36
+
37
+ response = requests.request("GET", url, headers=headers, timeout=5)
38
+ if response.ok:
39
+ response = json.loads(response.text)
40
+ return [group["id"] for group in response.get("groups", [])]
41
+
42
+ # Retur empty list if request wasn't successful
43
+ return []
44
+
45
+
46
+ async def get_current_user(token: Annotated[str, Depends(oauth2_scheme)]) -> User:
47
+ """Get the current user from OTDS and verify it."""
48
+
49
+ if api_settings.api_token is not None and token == api_settings.api_token:
50
+ return User(
51
+ id="api",
52
+ full_name="API Token",
53
+ groups=["otadmins@otds.admin"],
54
+ is_admin=True,
55
+ is_sysadmin=True,
56
+ )
57
+
58
+ url = api_settings.otds_url + "/otdsws/rest/currentuser"
59
+ headers = {
60
+ "Accept": "application/json",
61
+ "otdsticket": token,
62
+ }
63
+ response = requests.request("GET", url, headers=headers, timeout=2)
64
+
65
+ if response.ok:
66
+ response = json.loads(response.text)
67
+
68
+ user = User(
69
+ id=response["user"]["id"],
70
+ full_name=response["user"]["name"],
71
+ groups=get_groups(response, token),
72
+ is_admin=response["isAdmin"],
73
+ is_sysadmin=response["isSysAdmin"],
74
+ )
75
+
76
+ return user
77
+ else:
78
+ raise HTTPException(
79
+ status_code=status.HTTP_401_UNAUTHORIZED,
80
+ detail="Invalid authentication credentials",
81
+ headers={"WWW-Authenticate": "Bearer"},
82
+ )
83
+
84
+
85
+ async def get_authorized_user(current_user: Annotated[User, Depends(get_current_user)]) -> User:
86
+ """Check if the user is authorized (member of the Group otadmin@otds.admin)."""
87
+
88
+ if "otadmins@otds.admin" not in current_user.groups:
89
+ raise HTTPException(
90
+ status_code=status.HTTP_403_FORBIDDEN,
91
+ detail=f"User {current_user.id} is not authorized",
92
+ )
93
+ return current_user
94
+
95
+
96
+ @router.post("/token", tags=["auth"])
97
+ async def login(form_data: Annotated[OAuth2PasswordRequestForm, Depends()]) -> JSONResponse:
98
+ """Login using OTDS and return a token."""
99
+
100
+ url = api_settings.otds_url + "/otdsws/rest/authentication/credentials"
101
+
102
+ payload = json.dumps(
103
+ {"userName": form_data.username, "password": form_data.password},
104
+ )
105
+ headers = {
106
+ "Content-Type": "application/json",
107
+ "Accept": "application/json",
108
+ }
109
+
110
+ try:
111
+ response = requests.request(
112
+ "POST",
113
+ url,
114
+ headers=headers,
115
+ data=payload,
116
+ timeout=10,
117
+ )
118
+ except requests.exceptions.ConnectionError as exc:
119
+ raise HTTPException(
120
+ status_code=500,
121
+ detail=f"{exc.request.url} cannot be reached",
122
+ ) from exc
123
+
124
+ if response.ok:
125
+ response = json.loads(response.text)
126
+ else:
127
+ raise HTTPException(status_code=400, detail="Incorrect username or password")
128
+
129
+ return JSONResponse(
130
+ {
131
+ "access_token": response["ticket"],
132
+ "token_type": "bearer",
133
+ "userId": response["userId"],
134
+ },
135
+ )
136
+
137
+
138
+ @router.get("/users/me", tags=["auth"])
139
+ async def read_users_me(current_user: Annotated[User, Depends(get_current_user)]) -> JSONResponse:
140
+ """Get the current user.
141
+
142
+ current_user:
143
+ type: User
144
+ description: The current user.
145
+
146
+ """
147
+
148
+ if "otadmins@otds.admin" in current_user.groups:
149
+ return JSONResponse(current_user.model_dump())
150
+ else:
151
+ raise HTTPException(
152
+ status_code=403,
153
+ detail=f"User {current_user.id} is not authorized",
154
+ )
@@ -0,0 +1,92 @@
1
+ """Metics for payload logs."""
2
+
3
+ from collections.abc import Callable
4
+
5
+ from prometheus_client import Gauge
6
+ from prometheus_fastapi_instrumentator.metrics import Info
7
+
8
+ from pyxecm.customizer.api.payload_list import PayloadList
9
+
10
+
11
+ ## By Payload
12
+ def payload_logs_by_payload(payload_list: PayloadList) -> Callable[[Info], None]:
13
+ """Metrics for payload logs by payload."""
14
+
15
+ metrics_error = Gauge(
16
+ "payload_error",
17
+ "Number of ERROR log messages for by payload",
18
+ labelnames=("index", "name", "logfile"),
19
+ )
20
+
21
+ metrics_warning = Gauge(
22
+ "payload_warning",
23
+ "Number of WARNING log messages for by payload",
24
+ labelnames=("index", "name", "logfile"),
25
+ )
26
+
27
+ metrics_info = Gauge(
28
+ "payload_info",
29
+ "Number of INFO log messages for by payload",
30
+ labelnames=("index", "name", "logfile"),
31
+ )
32
+
33
+ metrics_debug = Gauge(
34
+ "payload_debug",
35
+ "Number of DEBUG log messages for by payload",
36
+ labelnames=("index", "name", "logfile"),
37
+ )
38
+
39
+ def instrumentation(info: Info) -> None: # noqa: ARG001
40
+ df = payload_list.get_payload_items()
41
+ data = [{"index": idx, **row} for idx, row in df.iterrows()]
42
+
43
+ for item in data:
44
+ metrics_error.labels(item["index"], item["name"], item["logfile"]).set(
45
+ item["log_error"],
46
+ )
47
+ metrics_warning.labels(item["index"], item["name"], item["logfile"]).set(
48
+ item["log_warning"],
49
+ )
50
+ metrics_info.labels(item["index"], item["name"], item["logfile"]).set(
51
+ item["log_info"],
52
+ )
53
+ metrics_debug.labels(item["index"], item["name"], item["logfile"]).set(
54
+ item["log_debug"],
55
+ )
56
+
57
+ return instrumentation
58
+
59
+
60
+ ## Total
61
+ def payload_logs_total(payload_list: PayloadList) -> Callable[[Info], None]:
62
+ """Metrics for total payload logs messages."""
63
+
64
+ metrics_error = Gauge(
65
+ "payload_error_total",
66
+ "Total number of ERROR log messages",
67
+ )
68
+
69
+ metrics_warning = Gauge(
70
+ "payload_warning_total",
71
+ "Total number of WARNING log messages",
72
+ )
73
+
74
+ metrics_info = Gauge(
75
+ "payload_info_total",
76
+ "Total number of INFO log messages",
77
+ )
78
+
79
+ metrics_debug = Gauge(
80
+ "payload_debug_total",
81
+ "Total number of DEBUG log messages",
82
+ )
83
+
84
+ def instrumentation(info: Info) -> None: # noqa: ARG001
85
+ df = payload_list.get_payload_items()
86
+
87
+ metrics_error.set(df["log_error"].sum())
88
+ metrics_warning.set(df["log_warning"].sum())
89
+ metrics_info.set(df["log_info"].sum())
90
+ metrics_debug.set(df["log_debug"].sum())
91
+
92
+ return instrumentation
@@ -0,0 +1,13 @@
1
+ """Models for FastAPI."""
2
+
3
+ from pydantic import BaseModel
4
+
5
+
6
+ class User(BaseModel):
7
+ """Model for users authenticated by OTDS."""
8
+
9
+ id: str
10
+ full_name: str | None = None
11
+ groups: list[str] | None = None
12
+ is_admin: bool = False
13
+ is_sysadmin: bool = False