pyxecm 2.0.2__py3-none-any.whl → 2.0.3__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/__init__.py +3 -2
- pyxecm/avts.py +3 -1
- pyxecm/customizer/api/app.py +2 -2
- pyxecm/customizer/api/auth/functions.py +37 -30
- pyxecm/customizer/api/common/functions.py +54 -0
- pyxecm/customizer/api/common/router.py +50 -3
- pyxecm/customizer/api/settings.py +5 -3
- pyxecm/customizer/api/terminal/router.py +43 -18
- pyxecm/customizer/api/v1_csai/models.py +18 -0
- pyxecm/customizer/api/v1_csai/router.py +26 -1
- pyxecm/customizer/api/v1_payload/functions.py +9 -3
- pyxecm/customizer/browser_automation.py +506 -199
- pyxecm/customizer/customizer.py +123 -22
- pyxecm/customizer/guidewire.py +170 -37
- pyxecm/customizer/payload.py +614 -257
- pyxecm/customizer/settings.py +21 -3
- pyxecm/helper/xml.py +1 -1
- pyxecm/otawp.py +10 -6
- pyxecm/otca.py +187 -21
- pyxecm/otcs.py +495 -205
- pyxecm/otds.py +1 -0
- pyxecm/otkd.py +1369 -0
- pyxecm/otmm.py +190 -66
- {pyxecm-2.0.2.dist-info → pyxecm-2.0.3.dist-info}/METADATA +2 -2
- {pyxecm-2.0.2.dist-info → pyxecm-2.0.3.dist-info}/RECORD +28 -26
- {pyxecm-2.0.2.dist-info → pyxecm-2.0.3.dist-info}/WHEEL +1 -1
- {pyxecm-2.0.2.dist-info → pyxecm-2.0.3.dist-info}/licenses/LICENSE +0 -0
- {pyxecm-2.0.2.dist-info → pyxecm-2.0.3.dist-info}/top_level.txt +0 -0
pyxecm/__init__.py
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
"""pyxecm - A python library to interact with Opentext
|
|
1
|
+
"""pyxecm - A python library to interact with Opentext REST APIs."""
|
|
2
2
|
|
|
3
3
|
from .avts import AVTS
|
|
4
4
|
from .coreshare import CoreShare
|
|
@@ -8,7 +8,8 @@ from .otca import OTCA
|
|
|
8
8
|
from .otcs import OTCS
|
|
9
9
|
from .otds import OTDS
|
|
10
10
|
from .otiv import OTIV
|
|
11
|
+
from .otkd import OTKD
|
|
11
12
|
from .otmm import OTMM
|
|
12
13
|
from .otpd import OTPD
|
|
13
14
|
|
|
14
|
-
__all__ = ["AVTS", "OTAC", "OTAWP", "OTCA", "OTCS", "OTDS", "OTIV", "OTMM", "OTPD", "CoreShare"]
|
|
15
|
+
__all__ = ["AVTS", "OTAC", "OTAWP", "OTCA", "OTCS", "OTDS", "OTIV", "OTKD", "OTMM", "OTPD", "CoreShare"]
|
pyxecm/avts.py
CHANGED
pyxecm/customizer/api/app.py
CHANGED
|
@@ -105,7 +105,8 @@ async def lifespan(
|
|
|
105
105
|
app = FastAPI(
|
|
106
106
|
docs_url="/api",
|
|
107
107
|
title="Customizer API",
|
|
108
|
-
openapi_url=
|
|
108
|
+
openapi_url=api_settings.openapi_url,
|
|
109
|
+
root_path=api_settings.root_path,
|
|
109
110
|
lifespan=lifespan,
|
|
110
111
|
version=version("pyxecm"),
|
|
111
112
|
openapi_tags=[
|
|
@@ -135,7 +136,6 @@ if api_settings.ws_terminal:
|
|
|
135
136
|
if api_settings.csai:
|
|
136
137
|
app.include_router(router=v1_csai_router)
|
|
137
138
|
|
|
138
|
-
|
|
139
139
|
logger = logging.getLogger("CustomizerAPI")
|
|
140
140
|
app.add_middleware(
|
|
141
141
|
CORSMiddleware,
|
|
@@ -5,14 +5,20 @@ from typing import Annotated
|
|
|
5
5
|
|
|
6
6
|
import requests
|
|
7
7
|
from fastapi import APIRouter, Depends, HTTPException, status
|
|
8
|
-
from fastapi.security import OAuth2PasswordBearer
|
|
8
|
+
from fastapi.security import APIKeyHeader, OAuth2PasswordBearer
|
|
9
9
|
|
|
10
10
|
from pyxecm.customizer.api.auth.models import User
|
|
11
11
|
from pyxecm.customizer.api.settings import api_settings
|
|
12
12
|
|
|
13
13
|
router = APIRouter()
|
|
14
14
|
|
|
15
|
-
oauth2_scheme = OAuth2PasswordBearer(
|
|
15
|
+
oauth2_scheme = OAuth2PasswordBearer(
|
|
16
|
+
tokenUrl="token",
|
|
17
|
+
auto_error=False,
|
|
18
|
+
description="Authenticate with OTDS, user needs to be member of 'otadmins@otds.admin' group",
|
|
19
|
+
scheme_name="OTDS Authentication",
|
|
20
|
+
)
|
|
21
|
+
apikey_header = APIKeyHeader(name="x-api-key", auto_error=False, scheme_name="APIKey")
|
|
16
22
|
|
|
17
23
|
|
|
18
24
|
def get_groups(response: dict, token: str) -> list:
|
|
@@ -42,43 +48,44 @@ def get_groups(response: dict, token: str) -> list:
|
|
|
42
48
|
return []
|
|
43
49
|
|
|
44
50
|
|
|
45
|
-
async def get_current_user(
|
|
51
|
+
async def get_current_user(
|
|
52
|
+
token: Annotated[str, Depends(oauth2_scheme)], api_key: Annotated[str, Depends(apikey_header)]
|
|
53
|
+
) -> User:
|
|
46
54
|
"""Get the current user from OTDS and verify it."""
|
|
47
55
|
|
|
48
|
-
if api_settings.
|
|
56
|
+
if api_settings.api_key is not None and api_key == api_settings.api_key:
|
|
49
57
|
return User(
|
|
50
58
|
id="api",
|
|
51
|
-
full_name="API
|
|
59
|
+
full_name="API Key",
|
|
52
60
|
groups=["otadmins@otds.admin"],
|
|
53
61
|
is_admin=True,
|
|
54
62
|
is_sysadmin=True,
|
|
55
63
|
)
|
|
56
64
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
)
|
|
65
|
+
if token and token.startswith("*OTDSSSO*"):
|
|
66
|
+
url = api_settings.otds_url + "/otdsws/rest/currentuser"
|
|
67
|
+
headers = {
|
|
68
|
+
"Accept": "application/json",
|
|
69
|
+
"otdsticket": token,
|
|
70
|
+
}
|
|
71
|
+
response = requests.request("GET", url, headers=headers, timeout=2)
|
|
72
|
+
|
|
73
|
+
if response.ok:
|
|
74
|
+
response = json.loads(response.text)
|
|
75
|
+
|
|
76
|
+
return User(
|
|
77
|
+
id=response["user"]["id"],
|
|
78
|
+
full_name=response["user"]["name"],
|
|
79
|
+
groups=get_groups(response, token),
|
|
80
|
+
is_admin=response["isAdmin"],
|
|
81
|
+
is_sysadmin=response["isSysAdmin"],
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
raise HTTPException(
|
|
85
|
+
status_code=status.HTTP_401_UNAUTHORIZED,
|
|
86
|
+
detail="Invalid authentication credentials",
|
|
87
|
+
headers={"WWW-Authenticate": "Bearer"},
|
|
88
|
+
)
|
|
82
89
|
|
|
83
90
|
|
|
84
91
|
async def get_authorized_user(current_user: Annotated[User, Depends(get_current_user)]) -> User:
|
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
"""Define common functions."""
|
|
2
2
|
|
|
3
3
|
import logging
|
|
4
|
+
import os
|
|
4
5
|
|
|
5
6
|
from pyxecm.customizer.api.common.payload_list import PayloadList
|
|
6
7
|
from pyxecm.customizer.api.settings import CustomizerAPISettings, api_settings
|
|
7
8
|
from pyxecm.customizer.k8s import K8s
|
|
9
|
+
from pyxecm.customizer.settings import Settings
|
|
10
|
+
from pyxecm.otcs import OTCS
|
|
8
11
|
|
|
9
12
|
logger = logging.getLogger("pyxecm.customizer.api")
|
|
10
13
|
|
|
@@ -25,6 +28,36 @@ def get_k8s_object() -> K8s:
|
|
|
25
28
|
return K8s(logger=logger, namespace=api_settings.namespace)
|
|
26
29
|
|
|
27
30
|
|
|
31
|
+
def get_otcs_object() -> OTCS:
|
|
32
|
+
"""Get an instance of a K8s object.
|
|
33
|
+
|
|
34
|
+
Returns:
|
|
35
|
+
K8s: Return a K8s object
|
|
36
|
+
|
|
37
|
+
"""
|
|
38
|
+
settings = Settings()
|
|
39
|
+
|
|
40
|
+
cs = OTCS(
|
|
41
|
+
protocol=settings.otcs.url_backend.scheme,
|
|
42
|
+
hostname=settings.otcs.url_backend.host,
|
|
43
|
+
port=settings.otcs.url_backend.port,
|
|
44
|
+
public_url=str(settings.otcs.url),
|
|
45
|
+
username=settings.otcs.username,
|
|
46
|
+
password=settings.otcs.password.get_secret_value(),
|
|
47
|
+
user_partition=settings.otcs.partition,
|
|
48
|
+
resource_name=settings.otcs.resource_name,
|
|
49
|
+
base_path=settings.otcs.base_path,
|
|
50
|
+
support_path=settings.otcs.support_path,
|
|
51
|
+
download_dir=settings.otcs.download_dir,
|
|
52
|
+
feme_uri=settings.otcs.feme_uri,
|
|
53
|
+
logger=logger,
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
cs.authenticate()
|
|
57
|
+
|
|
58
|
+
return cs
|
|
59
|
+
|
|
60
|
+
|
|
28
61
|
def get_settings() -> CustomizerAPISettings:
|
|
29
62
|
"""Get the API Settings object.
|
|
30
63
|
|
|
@@ -45,3 +78,24 @@ def get_otcs_logs_lock() -> dict:
|
|
|
45
78
|
"""
|
|
46
79
|
|
|
47
80
|
return LOGS_LOCK
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def list_files_in_directory(directory: str) -> dict:
|
|
84
|
+
"""Recursively list files in a directory and return a nested JSON structure with URLs."""
|
|
85
|
+
result = {}
|
|
86
|
+
for root, dirs, files in os.walk(directory):
|
|
87
|
+
# Sort directories and files alphabetically
|
|
88
|
+
dirs.sort()
|
|
89
|
+
files.sort()
|
|
90
|
+
|
|
91
|
+
current_level = result
|
|
92
|
+
path_parts = root.split(os.sep)
|
|
93
|
+
relative_path = os.path.relpath(root, directory)
|
|
94
|
+
for part in path_parts[len(directory.split(os.sep)) :]:
|
|
95
|
+
if part not in current_level:
|
|
96
|
+
current_level[part] = {}
|
|
97
|
+
current_level = current_level[part]
|
|
98
|
+
for file in files:
|
|
99
|
+
file_path = os.path.join(relative_path, file)
|
|
100
|
+
current_level[file] = file_path
|
|
101
|
+
return result
|
|
@@ -1,17 +1,19 @@
|
|
|
1
1
|
"""API Implemenation for the Customizer to start and control the payload processing."""
|
|
2
2
|
|
|
3
3
|
import logging
|
|
4
|
+
import mimetypes
|
|
4
5
|
import os
|
|
5
6
|
import signal
|
|
7
|
+
import tempfile
|
|
6
8
|
from http import HTTPStatus
|
|
7
9
|
from typing import Annotated
|
|
8
10
|
|
|
9
|
-
from fastapi import APIRouter, Depends, HTTPException
|
|
10
|
-
from fastapi.responses import JSONResponse, RedirectResponse
|
|
11
|
+
from fastapi import APIRouter, Depends, HTTPException, Query, Response
|
|
12
|
+
from fastapi.responses import FileResponse, JSONResponse, RedirectResponse
|
|
11
13
|
|
|
12
14
|
from pyxecm.customizer.api.auth.functions import get_authorized_user
|
|
13
15
|
from pyxecm.customizer.api.auth.models import User
|
|
14
|
-
from pyxecm.customizer.api.common.functions import PAYLOAD_LIST
|
|
16
|
+
from pyxecm.customizer.api.common.functions import PAYLOAD_LIST, list_files_in_directory
|
|
15
17
|
from pyxecm.customizer.api.common.models import CustomizerStatus
|
|
16
18
|
|
|
17
19
|
router = APIRouter(tags=["default"])
|
|
@@ -70,3 +72,48 @@ def shutdown(user: Annotated[User, Depends(get_authorized_user)]) -> JSONRespons
|
|
|
70
72
|
os.kill(os.getpid(), signal.SIGTERM)
|
|
71
73
|
|
|
72
74
|
return JSONResponse({"status": "shutdown"}, status_code=HTTPStatus.ACCEPTED)
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
@router.get(path="/browser_automations/assets")
|
|
78
|
+
def list_browser_automation_files(
|
|
79
|
+
user: Annotated[User, Depends(get_authorized_user)], # noqa: ARG001
|
|
80
|
+
) -> JSONResponse:
|
|
81
|
+
"""List all browser automation files."""
|
|
82
|
+
|
|
83
|
+
result = list_files_in_directory(
|
|
84
|
+
os.path.join(
|
|
85
|
+
tempfile.gettempdir(),
|
|
86
|
+
"browser_automations",
|
|
87
|
+
)
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
return JSONResponse(result)
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
@router.get(path="/browser_automations/download")
|
|
94
|
+
def get_browser_automation_file(
|
|
95
|
+
user: Annotated[User, Depends(get_authorized_user)], # noqa: ARG001
|
|
96
|
+
file: Annotated[str, Query(description="File name")],
|
|
97
|
+
) -> FileResponse:
|
|
98
|
+
"""Download the logfile for a specific payload."""
|
|
99
|
+
|
|
100
|
+
filename = os.path.join(tempfile.gettempdir(), "browser_automations", file)
|
|
101
|
+
|
|
102
|
+
if not os.path.isfile(filename):
|
|
103
|
+
raise HTTPException(
|
|
104
|
+
status_code=HTTPStatus.NOT_FOUND,
|
|
105
|
+
detail="File -> '{}' not found".format(filename),
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
media_type, _ = mimetypes.guess_type(filename)
|
|
109
|
+
|
|
110
|
+
with open(filename, "rb") as f:
|
|
111
|
+
content = f.read()
|
|
112
|
+
|
|
113
|
+
return Response(
|
|
114
|
+
content,
|
|
115
|
+
media_type=media_type,
|
|
116
|
+
headers={
|
|
117
|
+
"Content-Disposition": f'attachment; filename="{os.path.basename(filename)}"',
|
|
118
|
+
},
|
|
119
|
+
)
|
|
@@ -16,13 +16,15 @@ from pydantic_settings import (
|
|
|
16
16
|
class CustomizerAPISettings(BaseSettings):
|
|
17
17
|
"""Settings for the Customizer API."""
|
|
18
18
|
|
|
19
|
-
|
|
19
|
+
api_key: str | None = Field(
|
|
20
20
|
default=None,
|
|
21
|
-
description="Optional
|
|
21
|
+
description="Optional API KEY that can be specified that has access to the Customizer API, bypassing the OTDS authentication.",
|
|
22
22
|
)
|
|
23
23
|
bind_address: str = Field(default="0.0.0.0", description="Interface to bind the Customizer API.") # noqa: S104
|
|
24
24
|
bind_port: int = Field(default=8000, description="Port to bind the Customizer API to")
|
|
25
25
|
workers: int = Field(default=1, description="Number of workers to use for the API BackgroundTasks")
|
|
26
|
+
root_path: str = Field(default="/", description="Root path for the Customizer API")
|
|
27
|
+
openapi_url: str = Field(default="/api/openapi.json", description="OpenAPI URL")
|
|
26
28
|
|
|
27
29
|
import_payload: bool = Field(default=False)
|
|
28
30
|
payload: str = Field(
|
|
@@ -58,7 +60,7 @@ class CustomizerAPISettings(BaseSettings):
|
|
|
58
60
|
description="Namespace to use for otxecm resource lookups",
|
|
59
61
|
)
|
|
60
62
|
maintenance_mode: bool = Field(
|
|
61
|
-
default=
|
|
63
|
+
default=False,
|
|
62
64
|
description="Automatically enable and disable the maintenance mode during payload deployments.",
|
|
63
65
|
)
|
|
64
66
|
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import asyncio
|
|
4
4
|
import contextlib
|
|
5
|
+
import json
|
|
5
6
|
import os
|
|
6
7
|
import pty
|
|
7
8
|
import signal
|
|
@@ -15,13 +16,16 @@ router = APIRouter(tags=["terminal"])
|
|
|
15
16
|
|
|
16
17
|
|
|
17
18
|
@router.websocket("/ws/terminal")
|
|
18
|
-
async def ws_terminal(
|
|
19
|
+
async def ws_terminal(
|
|
20
|
+
websocket: WebSocket, pod: str = Query(...), command: str = Query(...), container: str = Query(None)
|
|
21
|
+
) -> None:
|
|
19
22
|
"""Websocket to connect to a shell session in a pod.
|
|
20
23
|
|
|
21
24
|
Args:
|
|
22
25
|
websocket (WebSocket): WebSocket to connect to the shell session.
|
|
23
26
|
pod (str): pod name to connect to.
|
|
24
27
|
command (str): command to be executed.
|
|
28
|
+
container (str, optional): container name to connect to.
|
|
25
29
|
|
|
26
30
|
"""
|
|
27
31
|
await websocket.accept()
|
|
@@ -30,7 +34,7 @@ async def ws_terminal(websocket: WebSocket, pod: str = Query(...), command: str
|
|
|
30
34
|
# Wait for the first message to be the token
|
|
31
35
|
token = await websocket.receive_text()
|
|
32
36
|
|
|
33
|
-
user = await get_current_user(token)
|
|
37
|
+
user = await get_current_user(token=token, api_key=token)
|
|
34
38
|
authrorized = await get_authorized_user(user)
|
|
35
39
|
|
|
36
40
|
if not authrorized:
|
|
@@ -45,11 +49,17 @@ async def ws_terminal(websocket: WebSocket, pod: str = Query(...), command: str
|
|
|
45
49
|
except WebSocketDisconnect:
|
|
46
50
|
return
|
|
47
51
|
|
|
48
|
-
|
|
52
|
+
container = ["-c", container] if container else []
|
|
53
|
+
|
|
54
|
+
process = [command] if pod == "customizer" else ["kubectl", "exec", "-it", pod, *container, "--", command]
|
|
49
55
|
|
|
50
56
|
pid, fd = pty.fork()
|
|
57
|
+
import fcntl
|
|
58
|
+
import struct
|
|
59
|
+
import termios
|
|
60
|
+
|
|
51
61
|
if pid == 0:
|
|
52
|
-
subprocess.run(process, check=False) # noqa: ASYNC221
|
|
62
|
+
process = subprocess.run(process, check=False) # noqa: ASYNC221
|
|
53
63
|
|
|
54
64
|
async def read_from_pty() -> None:
|
|
55
65
|
loop = asyncio.get_event_loop()
|
|
@@ -64,24 +74,39 @@ async def ws_terminal(websocket: WebSocket, pod: str = Query(...), command: str
|
|
|
64
74
|
try:
|
|
65
75
|
while True:
|
|
66
76
|
data = await websocket.receive_text()
|
|
67
|
-
|
|
77
|
+
if data.startswith("{") and data.endswith("}"):
|
|
78
|
+
message = json.loads(data)
|
|
79
|
+
if message.get("type") == "resize":
|
|
80
|
+
rows = message.get("rows")
|
|
81
|
+
cols = message.get("cols")
|
|
82
|
+
|
|
83
|
+
packed_size = struct.pack("HHHH", rows, cols, 0, 0)
|
|
84
|
+
fcntl.ioctl(fd, termios.TIOCSWINSZ, packed_size)
|
|
85
|
+
|
|
86
|
+
else:
|
|
87
|
+
os.write(fd, data.encode())
|
|
68
88
|
except Exception: # noqa: S110
|
|
69
89
|
pass
|
|
70
90
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
91
|
+
try:
|
|
92
|
+
# Launch read/write tasks
|
|
93
|
+
read_task = asyncio.create_task(read_from_pty())
|
|
94
|
+
write_task = asyncio.create_task(write_to_pty())
|
|
95
|
+
|
|
96
|
+
done, pending = await asyncio.wait([read_task, write_task], return_when=asyncio.FIRST_COMPLETED)
|
|
74
97
|
|
|
75
|
-
|
|
98
|
+
# Cancel other task
|
|
99
|
+
for task in pending:
|
|
100
|
+
task.cancel()
|
|
101
|
+
finally:
|
|
102
|
+
with contextlib.suppress(Exception):
|
|
103
|
+
await websocket.close(code=status.WS_1008_POLICY_VIOLATION)
|
|
76
104
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
task.cancel()
|
|
105
|
+
with contextlib.suppress(Exception):
|
|
106
|
+
process.terminate()
|
|
80
107
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
except ProcessLookupError:
|
|
84
|
-
pass # Already exited
|
|
108
|
+
with contextlib.suppress(Exception):
|
|
109
|
+
os.kill(pid, signal.SIGKILL)
|
|
85
110
|
|
|
86
|
-
|
|
87
|
-
|
|
111
|
+
with contextlib.suppress(Exception):
|
|
112
|
+
os.close(fd)
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"""Define Models for Payload."""
|
|
2
|
+
|
|
3
|
+
from pydantic import BaseModel
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class CSAIEmbedMetadata(BaseModel):
|
|
7
|
+
"""Defines Data Model for embeding metadata for documents and workspaces."""
|
|
8
|
+
|
|
9
|
+
node_id: int = None
|
|
10
|
+
crawl: bool = False
|
|
11
|
+
wait_for_completion: bool = False
|
|
12
|
+
message_override: dict = None
|
|
13
|
+
timeout: float = 30.0
|
|
14
|
+
document_metadata: bool = False
|
|
15
|
+
images: bool = False
|
|
16
|
+
image_prompt: str = ""
|
|
17
|
+
workspace_metadata: bool = True
|
|
18
|
+
remove_existing: bool = False
|
|
@@ -9,15 +9,40 @@ from fastapi.responses import JSONResponse
|
|
|
9
9
|
|
|
10
10
|
from pyxecm.customizer.api.auth.functions import get_authorized_user
|
|
11
11
|
from pyxecm.customizer.api.auth.models import User
|
|
12
|
-
from pyxecm.customizer.api.common.functions import get_k8s_object, get_settings
|
|
12
|
+
from pyxecm.customizer.api.common.functions import get_k8s_object, get_otcs_object, get_settings
|
|
13
13
|
from pyxecm.customizer.api.settings import CustomizerAPISettings
|
|
14
|
+
from pyxecm.customizer.api.v1_csai.models import CSAIEmbedMetadata
|
|
14
15
|
from pyxecm.customizer.k8s import K8s
|
|
16
|
+
from pyxecm.otcs import OTCS
|
|
15
17
|
|
|
16
18
|
router = APIRouter(prefix="/api/v1/csai", tags=["csai"])
|
|
17
19
|
|
|
18
20
|
logger = logging.getLogger("pyxecm.customizer.api.v1_csai")
|
|
19
21
|
|
|
20
22
|
|
|
23
|
+
@router.post("/metadata")
|
|
24
|
+
def embed_metadata(
|
|
25
|
+
user: Annotated[User, Depends(get_authorized_user)], # noqa: ARG001
|
|
26
|
+
otcs_object: Annotated[OTCS, Depends(get_otcs_object)],
|
|
27
|
+
body: Annotated[CSAIEmbedMetadata, Body()],
|
|
28
|
+
) -> JSONResponse:
|
|
29
|
+
"""Embed the Metadata of the given objects.
|
|
30
|
+
|
|
31
|
+
Args:
|
|
32
|
+
user (Annotated[User, Depends): User required for authentication
|
|
33
|
+
otcs_object (Annotated[OTCS, Depends(get_otcs_object)]): OTCS object to interact with OTCS
|
|
34
|
+
body (Annotated[CSAIEmbedMetadata, Body): Request body
|
|
35
|
+
|
|
36
|
+
Returns:
|
|
37
|
+
JSONResponse: JSONResponse with success=true/false
|
|
38
|
+
|
|
39
|
+
"""
|
|
40
|
+
|
|
41
|
+
success = otcs_object.aviator_embed_metadata(**body.model_dump())
|
|
42
|
+
|
|
43
|
+
return JSONResponse({"success": success})
|
|
44
|
+
|
|
45
|
+
|
|
21
46
|
@router.get("")
|
|
22
47
|
def get_csai_config_data(
|
|
23
48
|
user: Annotated[User, Depends(get_authorized_user)],
|
|
@@ -129,16 +129,22 @@ def import_payload(
|
|
|
129
129
|
payload_dir = payload
|
|
130
130
|
|
|
131
131
|
if payload_dir is None:
|
|
132
|
-
|
|
132
|
+
try:
|
|
133
|
+
import_payload_file(payload, enabled, dependencies)
|
|
134
|
+
except PayloadImportError as exc:
|
|
135
|
+
logger.error(exc)
|
|
136
|
+
logger.debug(exc, exc_info=True)
|
|
133
137
|
return
|
|
138
|
+
|
|
134
139
|
elif not os.path.isdir(payload_dir):
|
|
135
140
|
return
|
|
136
141
|
|
|
137
142
|
for filename in sorted(os.listdir(payload_dir)):
|
|
138
143
|
try:
|
|
139
144
|
import_payload_file(os.path.join(payload_dir, filename), enabled, dependencies)
|
|
140
|
-
except PayloadImportError:
|
|
141
|
-
logger.error(
|
|
145
|
+
except PayloadImportError as exc:
|
|
146
|
+
logger.error(exc)
|
|
147
|
+
logger.debug(exc, exc_info=True)
|
|
142
148
|
|
|
143
149
|
|
|
144
150
|
def prepare_dependencies(dependencies: list) -> list | None:
|