pyxecm 2.0.4__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.

Files changed (94) hide show
  1. pyxecm/coreshare.py +5 -3
  2. pyxecm/helper/data.py +4 -4
  3. pyxecm/helper/otel_config.py +26 -0
  4. pyxecm/helper/web.py +1 -2
  5. pyxecm/otca.py +1356 -16
  6. pyxecm/otcs.py +2354 -593
  7. pyxecm/otds.py +1 -1
  8. pyxecm/otmm.py +4 -5
  9. pyxecm/py.typed +0 -0
  10. pyxecm-3.0.0.dist-info/METADATA +48 -0
  11. pyxecm-3.0.0.dist-info/RECORD +96 -0
  12. {pyxecm-2.0.4.dist-info → pyxecm-3.0.0.dist-info}/WHEEL +1 -2
  13. pyxecm-3.0.0.dist-info/entry_points.txt +4 -0
  14. {pyxecm/customizer/api → pyxecm_api}/__main__.py +1 -1
  15. pyxecm_api/agents/__init__.py +7 -0
  16. pyxecm_api/agents/app.py +13 -0
  17. pyxecm_api/agents/functions.py +119 -0
  18. pyxecm_api/agents/models.py +10 -0
  19. pyxecm_api/agents/otcm_knowledgegraph/functions.py +85 -0
  20. pyxecm_api/agents/otcm_knowledgegraph/models.py +61 -0
  21. pyxecm_api/agents/otcm_knowledgegraph/router.py +74 -0
  22. pyxecm_api/agents/otcm_user_agent/models.py +20 -0
  23. pyxecm_api/agents/otcm_user_agent/router.py +65 -0
  24. pyxecm_api/agents/otcm_workspace_agent/models.py +40 -0
  25. pyxecm_api/agents/otcm_workspace_agent/router.py +200 -0
  26. pyxecm_api/app.py +221 -0
  27. {pyxecm/customizer/api → pyxecm_api}/auth/functions.py +10 -2
  28. {pyxecm/customizer/api → pyxecm_api}/auth/router.py +4 -3
  29. {pyxecm/customizer/api → pyxecm_api}/common/functions.py +39 -9
  30. {pyxecm/customizer/api → pyxecm_api}/common/metrics.py +1 -2
  31. {pyxecm/customizer/api → pyxecm_api}/common/router.py +7 -8
  32. {pyxecm/customizer/api → pyxecm_api}/settings.py +21 -6
  33. {pyxecm/customizer/api → pyxecm_api}/terminal/router.py +1 -1
  34. {pyxecm/customizer/api → pyxecm_api}/v1_csai/router.py +39 -10
  35. pyxecm_api/v1_csai/statics/bindings/utils.js +189 -0
  36. pyxecm_api/v1_csai/statics/tom-select/tom-select.complete.min.js +356 -0
  37. pyxecm_api/v1_csai/statics/tom-select/tom-select.css +334 -0
  38. pyxecm_api/v1_csai/statics/vis-9.1.2/vis-network.css +1 -0
  39. pyxecm_api/v1_csai/statics/vis-9.1.2/vis-network.min.js +27 -0
  40. pyxecm_api/v1_maintenance/__init__.py +1 -0
  41. {pyxecm/customizer/api → pyxecm_api}/v1_maintenance/functions.py +3 -3
  42. {pyxecm/customizer/api → pyxecm_api}/v1_maintenance/router.py +8 -8
  43. pyxecm_api/v1_otcs/__init__.py +1 -0
  44. {pyxecm/customizer/api → pyxecm_api}/v1_otcs/functions.py +7 -5
  45. {pyxecm/customizer/api → pyxecm_api}/v1_otcs/router.py +8 -7
  46. pyxecm_api/v1_payload/__init__.py +1 -0
  47. {pyxecm/customizer/api → pyxecm_api}/v1_payload/functions.py +10 -7
  48. {pyxecm/customizer/api → pyxecm_api}/v1_payload/router.py +11 -10
  49. {pyxecm/customizer → pyxecm_customizer}/__init__.py +8 -0
  50. {pyxecm/customizer → pyxecm_customizer}/__main__.py +15 -21
  51. {pyxecm/customizer → pyxecm_customizer}/browser_automation.py +414 -103
  52. {pyxecm/customizer → pyxecm_customizer}/customizer.py +178 -116
  53. {pyxecm/customizer → pyxecm_customizer}/guidewire.py +60 -20
  54. {pyxecm/customizer → pyxecm_customizer}/k8s.py +4 -4
  55. pyxecm_customizer/knowledge_graph.py +719 -0
  56. pyxecm_customizer/log.py +35 -0
  57. {pyxecm/customizer → pyxecm_customizer}/m365.py +41 -33
  58. {pyxecm/customizer → pyxecm_customizer}/payload.py +2265 -1933
  59. {pyxecm/customizer/api/common → pyxecm_customizer}/payload_list.py +18 -55
  60. {pyxecm/customizer → pyxecm_customizer}/salesforce.py +1 -1
  61. {pyxecm/customizer → pyxecm_customizer}/sap.py +6 -2
  62. {pyxecm/customizer → pyxecm_customizer}/servicenow.py +2 -4
  63. {pyxecm/customizer → pyxecm_customizer}/settings.py +7 -6
  64. {pyxecm/customizer → pyxecm_customizer}/successfactors.py +40 -28
  65. {pyxecm/customizer → pyxecm_customizer}/translate.py +1 -1
  66. {pyxecm/maintenance_page → pyxecm_maintenance_page}/__main__.py +1 -1
  67. {pyxecm/maintenance_page → pyxecm_maintenance_page}/app.py +14 -8
  68. pyxecm/customizer/api/app.py +0 -157
  69. pyxecm/customizer/log.py +0 -107
  70. pyxecm/customizer/nhc.py +0 -1169
  71. pyxecm/customizer/openapi.py +0 -258
  72. pyxecm/customizer/pht.py +0 -1357
  73. pyxecm-2.0.4.dist-info/METADATA +0 -119
  74. pyxecm-2.0.4.dist-info/RECORD +0 -78
  75. pyxecm-2.0.4.dist-info/licenses/LICENSE +0 -202
  76. pyxecm-2.0.4.dist-info/top_level.txt +0 -1
  77. {pyxecm/customizer/api → pyxecm_api}/__init__.py +0 -0
  78. {pyxecm/customizer/api/auth → pyxecm_api/agents/otcm_knowledgegraph}/__init__.py +0 -0
  79. {pyxecm/customizer/api/common → pyxecm_api/agents/otcm_user_agent}/__init__.py +0 -0
  80. {pyxecm/customizer/api/v1_csai → pyxecm_api/agents/otcm_workspace_agent}/__init__.py +0 -0
  81. {pyxecm/customizer/api/v1_maintenance → pyxecm_api/auth}/__init__.py +0 -0
  82. {pyxecm/customizer/api → pyxecm_api}/auth/models.py +0 -0
  83. {pyxecm/customizer/api/v1_otcs → pyxecm_api/common}/__init__.py +0 -0
  84. {pyxecm/customizer/api → pyxecm_api}/common/models.py +0 -0
  85. {pyxecm/customizer/api → pyxecm_api}/terminal/__init__.py +0 -0
  86. {pyxecm/customizer/api/v1_payload → pyxecm_api/v1_csai}/__init__.py +0 -0
  87. {pyxecm/customizer/api → pyxecm_api}/v1_csai/models.py +0 -0
  88. {pyxecm/customizer/api → pyxecm_api}/v1_maintenance/models.py +0 -0
  89. {pyxecm/customizer/api → pyxecm_api}/v1_payload/models.py +0 -0
  90. {pyxecm/customizer → pyxecm_customizer}/exceptions.py +0 -0
  91. {pyxecm/maintenance_page → pyxecm_maintenance_page}/__init__.py +0 -0
  92. {pyxecm/maintenance_page → pyxecm_maintenance_page}/settings.py +0 -0
  93. {pyxecm/maintenance_page → pyxecm_maintenance_page}/static/favicon.avif +0 -0
  94. {pyxecm/maintenance_page → pyxecm_maintenance_page}/templates/maintenance.html +0 -0
@@ -0,0 +1,65 @@
1
+ """Define router for User endpoints."""
2
+
3
+ import logging
4
+ from typing import Annotated
5
+
6
+ from fastapi import APIRouter, Body, Depends, HTTPException, status
7
+ from pyxecm.otcs import OTCS
8
+
9
+ from pyxecm_api.common.functions import get_otcs_object_from_otcsticket
10
+
11
+ from .models import UserModel
12
+
13
+ router = APIRouter(prefix="/otcm_user_agent", tags=["csai agents"])
14
+
15
+ logger = logging.getLogger("pyxecm_api.agents.otcm_user_agent")
16
+
17
+
18
+ @router.post(
19
+ "/user",
20
+ summary="Find a user by name or user attributes",
21
+ responses={
22
+ 200: {"description": "User found"},
23
+ 403: {"description": "Invalid credentials"},
24
+ 400: {"description": "User not found"},
25
+ },
26
+ response_model=UserModel,
27
+ response_description="Details about a user. Best presented as a table with each user property in a row.",
28
+ )
29
+ def otcm_user_agent_find_user(
30
+ otcs: Annotated[OTCS, Depends(get_otcs_object_from_otcsticket)],
31
+ user: Annotated[UserModel, Body()],
32
+ ) -> UserModel | None:
33
+ """Find a user by by its name or other attributes.
34
+
35
+ The user ID, the user name (login), user first name, user last name and user email will be returned.
36
+ When a user is returned, display the user data as a markdown table.
37
+ """
38
+
39
+ response = otcs.get_users(
40
+ where_name=user.login,
41
+ where_first_name=user.first_name,
42
+ where_last_name=user.last_name,
43
+ where_business_email=user.email,
44
+ )
45
+
46
+ if not response or not response["results"]:
47
+ raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="User not found")
48
+
49
+ try:
50
+ department_id = otcs.get_result_value(response=response, key="group_id")
51
+ department = otcs.get_group(group_id=department_id) if department_id else None
52
+ department_name = otcs.get_result_value(response=department, key="name") if department else None
53
+ return UserModel(
54
+ user_id=otcs.get_result_value(response=response, key="id"),
55
+ login=otcs.get_result_value(response=response, key="name"),
56
+ first_name=otcs.get_result_value(response=response, key="first_name"),
57
+ last_name=otcs.get_result_value(response=response, key="last_name"),
58
+ email=otcs.get_result_value(response=response, key="business_email"),
59
+ department=department_name,
60
+ )
61
+ except Exception as e:
62
+ raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="User not found") from e
63
+
64
+
65
+ # end function definition
@@ -0,0 +1,40 @@
1
+ """Define Models for workspaces."""
2
+
3
+ from typing import Annotated, Any
4
+
5
+ from pydantic import BaseModel, Field
6
+
7
+
8
+ class WorkspaceAttributeModel(BaseModel):
9
+ """Model for a workspace attribute."""
10
+
11
+ category: Annotated[str, Field(description="Category for the workspace attributes")]
12
+ attribute: Annotated[str, Field(description="Name of the attribute")]
13
+ value: Annotated[str, Field(description="Value of the attribute")]
14
+ set: Annotated[str, Field(description="Optional attribute group or set name")]
15
+
16
+
17
+ class WorkspaceModel(BaseModel):
18
+ """Defines Model for describing workspaces in OTCM (Opentext Content Management).
19
+
20
+ To display an instance of this model, please display the link.
21
+ """
22
+
23
+ id: Annotated[int, Field(description="ID of the workspace")] = None
24
+ name: Annotated[str, Field(description="Name of the workspace")] = None
25
+ type: Annotated[str, Field(description="Name of the workspace type")] = None
26
+ description: Annotated[str, Field(description="Description of the workspace")] = None
27
+
28
+ link: Annotated[str, Field(description="Link to the workspace, should be used when the instance is displayed")] = (
29
+ None
30
+ )
31
+
32
+ # attributes: Annotated[
33
+ # list[WorkspaceAttributeModel] | None,
34
+ # Body(description="List of custom attributes associated with the workspace"),
35
+ # ] = None
36
+
37
+ def model_post_init(self, context: Any) -> None: # noqa: ARG002, ANN401
38
+ """Post model initialization."""
39
+ # this could also be done with `default_factory`:
40
+ self.link = f"[{self.name}](/cs/cs/app/nodes/{self.id})"
@@ -0,0 +1,200 @@
1
+ """Define router for workspace endpoints."""
2
+
3
+ import logging
4
+ from typing import Annotated
5
+
6
+ from fastapi import APIRouter, Body, Depends, HTTPException, status
7
+ from pyxecm.otcs import OTCS
8
+
9
+ from pyxecm_api.agents.models import Context
10
+ from pyxecm_api.common.functions import get_otcs_object_from_otcsticket
11
+
12
+ from .models import WorkspaceModel
13
+
14
+ router = APIRouter(prefix="/otcm_workspace_agent", tags=["csai agents"])
15
+
16
+ logger = logging.getLogger("pyxecm_api.agents.otcm_workspace_agent")
17
+
18
+
19
+ @router.post(
20
+ path="/find_workspace",
21
+ response_model=WorkspaceModel,
22
+ summary="Find the markdown link to a workspace by workspace name and workspace type and display the link.",
23
+ responses={
24
+ 200: {"description": "Workspace found"},
25
+ 403: {"description": "Invalid credentials"},
26
+ 404: {"description": "Workspace not found"},
27
+ },
28
+ )
29
+ def otcm_workspace_agent_find_workspace(
30
+ context: Context,
31
+ otcs: Annotated[OTCS, Depends(get_otcs_object_from_otcsticket)],
32
+ workspace: Annotated[WorkspaceModel, Body()],
33
+ ) -> WorkspaceModel | None:
34
+ """Find a workspace by workspace name and workspace type.
35
+
36
+ The returned workspace is an OTCS workspace object. Show the markdown link in the chat response, sothat the user can click on it.
37
+ """
38
+
39
+ logger.info("Got context -> %s", context)
40
+
41
+ response = otcs.get_workspace_by_type_and_name(type_name=workspace.type, name="contains_" + workspace.name)
42
+
43
+ if response and len(response["results"]) == 1:
44
+ result = WorkspaceModel(
45
+ id=otcs.get_result_value(response=response, key="id"),
46
+ name=otcs.get_result_value(response=response, key="name"),
47
+ type=workspace.type,
48
+ )
49
+ logger.info("Workspace found -> %s", result)
50
+ return result
51
+
52
+ if response is None or not response["results"]:
53
+ response = otcs.search(search_term=workspace.name)
54
+
55
+ if response is None or not response["results"]:
56
+ raise HTTPException(
57
+ status_code=status.HTTP_404_NOT_FOUND,
58
+ detail="Workspace not found.",
59
+ )
60
+
61
+ try:
62
+ return WorkspaceModel(
63
+ id=otcs.get_result_value(response=response, key="id"),
64
+ name=otcs.get_result_value(response=response, key="name"),
65
+ type=workspace.type,
66
+ )
67
+ except Exception as e:
68
+ raise HTTPException(
69
+ status_code=status.HTTP_404_NOT_FOUND,
70
+ detail="Workspace not found.",
71
+ ) from e
72
+
73
+
74
+ # end function definition
75
+
76
+
77
+ @router.post(
78
+ path="/lookup_workspace",
79
+ response_model=WorkspaceModel,
80
+ summary="Lookup a workspace based on its type and a value of one of the workspace attributes.",
81
+ responses={
82
+ 200: {"description": "Workspace found"},
83
+ 403: {"description": "Invalid credentials"},
84
+ 400: {"description": "Workspace not found"},
85
+ },
86
+ )
87
+ def otcm_workspace_agent_lookup_workspace(
88
+ otcs: Annotated[OTCS, Depends(get_otcs_object_from_otcsticket)],
89
+ workspace: Annotated[WorkspaceModel, Body()],
90
+ ) -> WorkspaceModel | None:
91
+ """Lookup a workspace based on its type and a value of one of the workspace attributes.
92
+
93
+ Use this tool if the workspace name is _not_ specified but the user asks for a specific
94
+ workspace attribute value like cities, products, or other attributes.
95
+
96
+ Return the workspace data if it is found. If it is not found confirm with the user if the workspace should be created or not.
97
+ If it should be created call the tool: otcm_workspace_agent_create_workspace
98
+ """
99
+
100
+ # otcs._otcs_ticket = otcsticket
101
+
102
+ workspace_attributes = workspace.attributes or {}
103
+ if not workspace_attributes:
104
+ raise HTTPException(
105
+ status_code=status.HTTP_400_BAD_REQUEST,
106
+ detail="Workspace attributes must be provided for lookup.",
107
+ )
108
+
109
+ category = next((attr.category for attr in workspace_attributes if attr.category), None)
110
+ attribute = next((attr.attribute for attr in workspace_attributes if attr.attribute), None)
111
+ attribute_set = next((attr.set for attr in workspace_attributes if attr.set), None)
112
+ value = next((attr.value for attr in workspace_attributes if attr.value), None)
113
+
114
+ response = otcs.lookup_workspace(
115
+ type_name=workspace.type, category=category, attribute=attribute, attribute_set=attribute_set, value=value
116
+ )
117
+ if response is None or not response["results"]:
118
+ raise HTTPException(
119
+ status_code=status.HTTP_404_NOT_FOUND,
120
+ detail="No workspace of type -> '{}' with attribute -> '{}'{} and value -> '{}' found!".format(
121
+ workspace.type, attribute, " (set -> {})".format(attribute_set), value
122
+ ),
123
+ )
124
+
125
+ try:
126
+ return WorkspaceModel(
127
+ id=otcs.get_result_value(response=response, key="id"),
128
+ name=otcs.get_result_value(response=response, key="name"),
129
+ type=workspace.type,
130
+ )
131
+ except Exception as e:
132
+ raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Workspace not found") from e
133
+
134
+
135
+ # end function definition
136
+
137
+
138
+ @router.post(
139
+ path="/create_workspace",
140
+ response_model=WorkspaceModel,
141
+ summary="Create a workspace with given workspace name and workspace type and display the link.",
142
+ responses={
143
+ 200: {"description": "Workspace created"},
144
+ 403: {"description": "Invalid credentials"},
145
+ 404: {"description": "Workspace creation failed"},
146
+ },
147
+ )
148
+ def otcm_workspace_agent_create_workspace(
149
+ otcs: Annotated[OTCS, Depends(get_otcs_object_from_otcsticket)],
150
+ workspace: Annotated[WorkspaceModel, Body()],
151
+ ) -> WorkspaceModel | None:
152
+ """Create a workspace with given workspace name and workspace type.
153
+
154
+ The ID of the created workspace, the final workspace name, and the workspace type will be returned.
155
+
156
+ """
157
+
158
+ workspace_type_id, workspace_templates = otcs.get_workspace_templates(type_name=workspace.type)
159
+ if workspace_type_id is None:
160
+ raise HTTPException(
161
+ status_code=status.HTTP_404_NOT_FOUND, detail="Workspace type -> '{}' not found!".format(workspace.type)
162
+ )
163
+ if not workspace_templates:
164
+ raise HTTPException(
165
+ status_code=status.HTTP_404_NOT_FOUND,
166
+ detail="Workspace type -> '{}' has no templates!".format(workspace.type),
167
+ )
168
+
169
+ workspace_template_id = workspace_templates[0].get("id")
170
+
171
+ response = otcs.create_workspace(
172
+ workspace_template_id=workspace_template_id,
173
+ workspace_type=workspace_type_id,
174
+ workspace_name=workspace.name,
175
+ workspace_description="",
176
+ )
177
+
178
+ if response is None or not response["results"]:
179
+ raise HTTPException(
180
+ status_code=status.HTTP_404_NOT_FOUND, detail="Failed to create workspace -> '{}'".format(workspace.name)
181
+ )
182
+
183
+ try:
184
+ workspace_id = response.get("results", {}).get("id")
185
+
186
+ # The resulting workspace name can be different from the given name,
187
+ # so we need to fetch the workspace details again to get the final name.
188
+ response = otcs.get_workspace(node_id=workspace_id)
189
+ workspace_name = otcs.get_result_value(response=response, key="name")
190
+
191
+ return WorkspaceModel(
192
+ id=workspace_id,
193
+ name=workspace_name,
194
+ type=workspace.type,
195
+ )
196
+ except Exception as e:
197
+ raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Workspace not found") from e
198
+
199
+
200
+ # end function definition
pyxecm_api/app.py ADDED
@@ -0,0 +1,221 @@
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 threading
12
+ from collections.abc import AsyncGenerator
13
+ from contextlib import asynccontextmanager
14
+ from datetime import UTC, datetime
15
+ from importlib.metadata import version
16
+
17
+ import uvicorn
18
+ from fastapi import FastAPI
19
+ from fastapi.middleware.cors import CORSMiddleware
20
+ from fastapi.staticfiles import StaticFiles
21
+ from opentelemetry.instrumentation.fastapi import FastAPIInstrumentor
22
+ from prometheus_fastapi_instrumentator import Instrumentator
23
+ from pyxecm.helper.otel_config import tracer
24
+ from pyxecm_maintenance_page import run_maintenance_page
25
+
26
+ from .agents.app import agent_routers
27
+ from .agents.functions import register_all
28
+ from .agents.otcm_knowledgegraph.functions import build_graph
29
+ from .auth.router import router as auth_router
30
+ from .common.functions import PAYLOAD_LIST
31
+ from .common.metrics import payload_logs_by_payload, payload_logs_total
32
+ from .common.router import router as common_router
33
+ from .settings import api_settings
34
+ from .terminal.router import router as terminal_router
35
+ from .v1_csai.router import router as v1_csai_router
36
+ from .v1_maintenance.router import router as v1_maintenance_router
37
+ from .v1_otcs.router import router as v1_otcs_router
38
+ from .v1_payload.functions import import_payload
39
+ from .v1_payload.router import router as v1_payload_router
40
+
41
+ logger = logging.getLogger("CustomizerAPI")
42
+
43
+
44
+ # Check if Logfile and folder exists and is unique
45
+ if os.path.isfile(os.path.join(api_settings.logfolder, api_settings.logfile)):
46
+ customizer_start_time = datetime.now(UTC).strftime(
47
+ "%Y-%m-%d_%H-%M",
48
+ )
49
+ api_settings.logfile = f"customizer_{customizer_start_time}.log"
50
+ elif not os.path.exists(api_settings.logfolder):
51
+ os.makedirs(api_settings.logfolder)
52
+
53
+ handlers = [logging.FileHandler(os.path.join(api_settings.logfolder, api_settings.logfile))]
54
+ if api_settings.log_payload_processing:
55
+ handlers.append(logging.StreamHandler())
56
+
57
+ logging.basicConfig(
58
+ format="%(asctime)s %(levelname)s [%(name)s] [%(threadName)s] %(message)s",
59
+ datefmt="%d-%b-%Y %H:%M:%S",
60
+ level=api_settings.loglevel,
61
+ handlers=handlers,
62
+ )
63
+
64
+
65
+ @asynccontextmanager
66
+ async def lifespan(
67
+ app: FastAPI, # noqa: ARG001
68
+ ) -> AsyncGenerator:
69
+ """Lifespan Method for FASTAPI to handle the startup and shutdown process.
70
+
71
+ Args:
72
+ app (FastAPI):
73
+ The application.
74
+
75
+ """
76
+
77
+ logger.debug("Settings -> %s", api_settings)
78
+
79
+ with tracer.start_as_current_span("import_payloads"):
80
+ if api_settings.import_payload:
81
+ logger.info("Importing filesystem payloads...")
82
+
83
+ # Base Payload
84
+ import_payload(payload=api_settings.payload)
85
+
86
+ # External Payload
87
+ import_payload(payload_dir=api_settings.payload_dir, dependencies=True)
88
+
89
+ # Optional Payload
90
+ import_payload(payload_dir=api_settings.payload_dir_optional)
91
+
92
+ logger.info("Starting maintenance_page thread...")
93
+ if api_settings.maintenance_page:
94
+ run_maintenance_page()
95
+
96
+ if api_settings.csai_studio_integration:
97
+ logger.info("Registering Content Aviator tools...")
98
+ register_all()
99
+ threading.Thread(name="KnowledgeGraph", target=build_graph).start()
100
+
101
+ yield
102
+ logger.info("Shutdown")
103
+ PAYLOAD_LIST.stop_payload_processing()
104
+
105
+
106
+ app = FastAPI(
107
+ docs_url="/api",
108
+ title=api_settings.title,
109
+ description=api_settings.description,
110
+ openapi_url=api_settings.openapi_url,
111
+ root_path=api_settings.root_path,
112
+ lifespan=lifespan,
113
+ version=version("pyxecm"),
114
+ openapi_tags=[
115
+ {
116
+ "name": "auth",
117
+ "description": "Authentication Endpoint - Users are authenticated against Opentext Directory Services",
118
+ },
119
+ {
120
+ "name": "payload",
121
+ "description": "Get status and manipulate payload objects ",
122
+ },
123
+ {
124
+ "name": "maintenance",
125
+ "description": "Enable, disable or alter the maintenance mode.",
126
+ },
127
+ ],
128
+ )
129
+
130
+ FastAPIInstrumentor.instrument_app(app)
131
+
132
+ ## Add Middlewares
133
+ app.add_middleware(
134
+ CORSMiddleware,
135
+ allow_origins=api_settings.trusted_origins,
136
+ allow_credentials=True,
137
+ allow_methods=["*"],
138
+ allow_headers=["*"],
139
+ )
140
+
141
+ ## Add all Routers
142
+ app.include_router(router=common_router)
143
+ app.include_router(router=auth_router)
144
+ app.include_router(router=v1_maintenance_router)
145
+ app.include_router(router=v1_otcs_router)
146
+ app.include_router(router=v1_payload_router)
147
+
148
+ if api_settings.ws_terminal:
149
+ app.include_router(router=terminal_router)
150
+
151
+ if api_settings.csai:
152
+ app.mount(
153
+ "/api/v1/csai/lib",
154
+ StaticFiles(packages=["pyxecm_api.v1_csai"]),
155
+ name="csai-statics",
156
+ )
157
+
158
+ app.include_router(router=v1_csai_router)
159
+ for agent_router in agent_routers:
160
+ app.include_router(prefix="/agents", router=agent_router)
161
+
162
+
163
+ ## Add Prometheus Instrumentator for /metrics,
164
+ if api_settings.metrics:
165
+ # Add Prometheus Instrumentator for /metricsf
166
+ instrumentator = Instrumentator().instrument(app).expose(app)
167
+ instrumentator.add(payload_logs_by_payload(PAYLOAD_LIST))
168
+ instrumentator.add(payload_logs_total(PAYLOAD_LIST))
169
+
170
+
171
+ ## Start the API Server
172
+ def run_api() -> None:
173
+ """Start the FASTAPI Webserver."""
174
+
175
+ # Check if Temp and Log dir exists
176
+ if not os.path.exists(api_settings.temp_dir):
177
+ os.makedirs(api_settings.temp_dir)
178
+ if not os.path.exists(api_settings.logfolder):
179
+ os.makedirs(api_settings.logfolder)
180
+
181
+ # Check if Logfile and exists and is unique
182
+ if os.path.isfile(os.path.join(api_settings.logfolder, api_settings.logfile)):
183
+ customizer_start_time = datetime.now(UTC).strftime(
184
+ "%Y-%m-%d_%H-%M",
185
+ )
186
+ api_settings.logfile = f"customizer_{customizer_start_time}.log"
187
+
188
+ # Configure Logging for uvicorn
189
+ log_config = uvicorn.config.LOGGING_CONFIG
190
+
191
+ # Stdout
192
+ log_config["formatters"]["pyxecm"] = {
193
+ "()": "uvicorn.logging.DefaultFormatter",
194
+ "fmt": "%(levelprefix)s [%(name)s] [%(threadName)s] %(message)s",
195
+ "use_colors": True,
196
+ }
197
+ log_config["handlers"]["pyxecm"] = {
198
+ "formatter": "pyxecm",
199
+ "class": "logging.StreamHandler",
200
+ "stream": "ext://sys.stdout",
201
+ }
202
+
203
+ log_config["loggers"]["pyxecm"] = {
204
+ "handlers": ["pyxecm"],
205
+ "level": api_settings.loglevel,
206
+ "propagate": False,
207
+ }
208
+
209
+ logger.info("Starting processing thread...")
210
+ PAYLOAD_LIST.run_payload_processing(concurrent=api_settings.concurrent_payloads)
211
+
212
+ uvicorn.run(
213
+ "pyxecm_api:app",
214
+ host=api_settings.bind_address,
215
+ port=api_settings.bind_port,
216
+ workers=api_settings.workers,
217
+ reload=api_settings.reload,
218
+ proxy_headers=True,
219
+ forwarded_allow_ips="*",
220
+ log_config=log_config,
221
+ )
@@ -7,8 +7,9 @@ import requests
7
7
  from fastapi import APIRouter, Depends, HTTPException, status
8
8
  from fastapi.security import APIKeyHeader, OAuth2PasswordBearer
9
9
 
10
- from pyxecm.customizer.api.auth.models import User
11
- from pyxecm.customizer.api.settings import api_settings
10
+ from pyxecm_api.settings import api_settings
11
+
12
+ from .models import User
12
13
 
13
14
  router = APIRouter()
14
15
 
@@ -19,6 +20,7 @@ oauth2_scheme = OAuth2PasswordBearer(
19
20
  scheme_name="OTDS Authentication",
20
21
  )
21
22
  apikey_header = APIKeyHeader(name="x-api-key", auto_error=False, scheme_name="APIKey")
23
+ otcsticket = APIKeyHeader(name="otcsticket", auto_error=True, scheme_name="OTCSTicket")
22
24
 
23
25
 
24
26
  def get_groups(response: dict, token: str) -> list:
@@ -97,3 +99,9 @@ async def get_authorized_user(current_user: Annotated[User, Depends(get_current_
97
99
  detail=f"User {current_user.id} is not authorized",
98
100
  )
99
101
  return current_user
102
+
103
+
104
+ async def get_otcsticket(otcsticket: Annotated[str, Depends(otcsticket)]) -> User:
105
+ """Check if the user is authorized (member of the Group otadmin@otds.admin)."""
106
+
107
+ return otcsticket
@@ -8,9 +8,10 @@ from fastapi import APIRouter, Depends, HTTPException
8
8
  from fastapi.responses import JSONResponse
9
9
  from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm
10
10
 
11
- from pyxecm.customizer.api.auth.functions import get_current_user
12
- from pyxecm.customizer.api.auth.models import User
13
- from pyxecm.customizer.api.settings import api_settings
11
+ from pyxecm_api.settings import api_settings
12
+
13
+ from .functions import get_current_user
14
+ from .models import User
14
15
 
15
16
  router = APIRouter()
16
17
 
@@ -2,14 +2,16 @@
2
2
 
3
3
  import logging
4
4
  import os
5
+ from typing import Annotated
5
6
 
6
- from pyxecm.customizer.api.common.payload_list import PayloadList
7
- from pyxecm.customizer.api.settings import CustomizerAPISettings, api_settings
8
- from pyxecm.customizer.k8s import K8s
9
- from pyxecm.customizer.settings import Settings
7
+ from fastapi import Depends
10
8
  from pyxecm.otcs import OTCS
9
+ from pyxecm_customizer import K8s, PayloadList, Settings
11
10
 
12
- logger = logging.getLogger("pyxecm.customizer.api")
11
+ from pyxecm_api.auth.functions import get_otcsticket
12
+ from pyxecm_api.settings import CustomizerAPISettings, api_settings
13
+
14
+ logger = logging.getLogger("pyxecm_api")
13
15
 
14
16
  # Create a LOCK dict for singleton logs collection
15
17
  LOGS_LOCK = {}
@@ -37,7 +39,7 @@ def get_otcs_object() -> OTCS:
37
39
  """
38
40
  settings = Settings()
39
41
 
40
- cs = OTCS(
42
+ otcs = OTCS(
41
43
  protocol=settings.otcs.url_backend.scheme,
42
44
  hostname=settings.otcs.url_backend.host,
43
45
  port=settings.otcs.url_backend.port,
@@ -50,12 +52,40 @@ def get_otcs_object() -> OTCS:
50
52
  support_path=settings.otcs.support_path,
51
53
  download_dir=settings.otcs.download_dir,
52
54
  feme_uri=settings.otcs.feme_uri,
53
- logger=logger,
55
+ logger=logger.getChild("otcs"),
56
+ )
57
+
58
+ otcs.authenticate()
59
+
60
+ return otcs
61
+
62
+
63
+ def get_otcs_object_from_otcsticket(otcsticket: Annotated[str, Depends(get_otcsticket)]) -> OTCS:
64
+ """Get an instance of a K8s object.
65
+
66
+ Returns:
67
+ K8s: Return a K8s object
68
+
69
+ """
70
+ settings = Settings()
71
+
72
+ otcs = OTCS(
73
+ protocol=settings.otcs.url_backend.scheme,
74
+ hostname=settings.otcs.url_backend.host,
75
+ port=settings.otcs.url_backend.port,
76
+ public_url=str(settings.otcs.url),
77
+ user_partition=settings.otcs.partition,
78
+ resource_name=settings.otcs.resource_name,
79
+ base_path=settings.otcs.base_path,
80
+ support_path=settings.otcs.support_path,
81
+ download_dir=settings.otcs.download_dir,
82
+ feme_uri=settings.otcs.feme_uri,
83
+ logger=logger.getChild("otcs"),
54
84
  )
55
85
 
56
- cs.authenticate()
86
+ otcs._otcs_ticket = otcsticket # noqa: SLF001
57
87
 
58
- return cs
88
+ return otcs
59
89
 
60
90
 
61
91
  def get_settings() -> CustomizerAPISettings:
@@ -4,8 +4,7 @@ from collections.abc import Callable
4
4
 
5
5
  from prometheus_client import Gauge
6
6
  from prometheus_fastapi_instrumentator.metrics import Info
7
-
8
- from pyxecm.customizer.api.common.payload_list import PayloadList
7
+ from pyxecm_customizer.payload_list import PayloadList
9
8
 
10
9
 
11
10
  ## By Payload
@@ -11,14 +11,15 @@ from typing import Annotated
11
11
  from fastapi import APIRouter, Depends, HTTPException, Query, Request, Response
12
12
  from fastapi.responses import FileResponse, JSONResponse, RedirectResponse
13
13
 
14
- from pyxecm.customizer.api.auth.functions import get_authorized_user
15
- from pyxecm.customizer.api.auth.models import User
16
- from pyxecm.customizer.api.common.functions import PAYLOAD_LIST, list_files_in_directory
17
- from pyxecm.customizer.api.common.models import CustomizerStatus
14
+ from pyxecm_api.auth.functions import get_authorized_user
15
+ from pyxecm_api.auth.models import User
16
+
17
+ from .functions import PAYLOAD_LIST, list_files_in_directory
18
+ from .models import CustomizerStatus
18
19
 
19
20
  router = APIRouter()
20
21
 
21
- logger = logging.getLogger("pyxecm.customizer.api.common")
22
+ logger = logging.getLogger("pyxecm_api.common")
22
23
 
23
24
 
24
25
  @router.get("/", include_in_schema=False)
@@ -29,10 +30,8 @@ async def redirect_to_api(request: Request) -> RedirectResponse:
29
30
  None
30
31
 
31
32
  """
32
- # Construct the new URL by appending /api
33
- new_url = f"{request.url.path!s}api"
33
+ return RedirectResponse(url=f"{request.url.path}api")
34
34
 
35
- return RedirectResponse(url=new_url)
36
35
 
37
36
  @router.get(path="/status", name="Get Status")
38
37
  async def get_status() -> CustomizerStatus: