pyxecm 2.0.2__py3-none-any.whl → 2.0.4__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.

@@ -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(websocket: WebSocket, pod: str = Query(...), command: str = Query(...)) -> None:
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
- process = ["bash"] if pod == "customizer" else ["kubectl", "exec", "-it", pod, "--", command]
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
- os.write(fd, data.encode())
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
- # Launch read/write tasks
72
- read_task = asyncio.create_task(read_from_pty())
73
- write_task = asyncio.create_task(write_to_pty())
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
- done, pending = await asyncio.wait([read_task, write_task], return_when=asyncio.FIRST_COMPLETED)
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
- # Cancel other task
78
- for task in pending:
79
- task.cancel()
105
+ with contextlib.suppress(Exception):
106
+ process.terminate()
80
107
 
81
- try: # noqa: SIM105
82
- os.kill(pid, signal.SIGKILL)
83
- except ProcessLookupError:
84
- pass # Already exited
108
+ with contextlib.suppress(Exception):
109
+ os.kill(pid, signal.SIGKILL)
85
110
 
86
- with contextlib.suppress(Exception):
87
- os.close(fd)
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)],
@@ -35,10 +35,15 @@ async def put_otcs_logs(
35
35
 
36
36
  if "all" in hosts:
37
37
  hosts = []
38
- for sts in ["otcs-admin", "otcs-frontend", "otcs-backend-search"]:
38
+ for sts in ["otcs-admin", "otcs-frontend", "otcs-backend-search", "otcs-da"]:
39
39
  try:
40
- sts_replicas = k8s_object.get_stateful_set_scale(sts).status.replicas
41
- hosts.extend([f"{sts}-{i}" for i in range(sts_replicas)])
40
+ sts_replicas = k8s_object.get_stateful_set_scale(sts)
41
+
42
+ if sts_replicas is None:
43
+ logger.debug("Cannot get statefulset {sts}")
44
+ continue
45
+
46
+ hosts.extend([f"{sts}-{i}" for i in range(sts_replicas.status.replicas)])
42
47
  except Exception as e:
43
48
  raise HTTPException(status_code=HTTPStatus.INTERNAL_SERVER_ERROR) from e
44
49
 
@@ -118,10 +123,15 @@ async def get_otcs_log_files(
118
123
  response = {"status": {host: bool(otcs_logs_lock[host].locked()) for host in otcs_logs_lock}, "files": files}
119
124
 
120
125
  # Extend response with all hosts
121
- for sts in ["otcs-admin", "otcs-frontend", "otcs-backend-search"]:
126
+ for sts in ["otcs-admin", "otcs-frontend", "otcs-backend-search", "otcs-da"]:
122
127
  try:
123
- sts_replicas = k8s_object.get_stateful_set_scale(sts).status.replicas
124
- for i in range(sts_replicas):
128
+ sts_replicas = k8s_object.get_stateful_set_scale(sts)
129
+
130
+ if sts_replicas is None:
131
+ logger.debug("Cannot get statefulset {sts}")
132
+ continue
133
+
134
+ for i in range(sts_replicas.status.replicas):
125
135
  host = f"{sts}-{i}"
126
136
 
127
137
  if host in otcs_logs_lock:
@@ -129,16 +129,22 @@ def import_payload(
129
129
  payload_dir = payload
130
130
 
131
131
  if payload_dir is None:
132
- import_payload_file(payload, enabled, dependencies)
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("Payload import failed")
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: