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.

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.1.dist-info/METADATA +126 -0
  11. pyxecm-3.0.1.dist-info/RECORD +96 -0
  12. {pyxecm-2.0.4.dist-info → pyxecm-3.0.1.dist-info}/WHEEL +1 -2
  13. pyxecm-3.0.1.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,74 @@
1
+ """Define router for workspace endpoints."""
2
+
3
+ import logging
4
+ from typing import Annotated
5
+
6
+ from fastapi import APIRouter, Body, Depends, HTTPException
7
+ from pyxecm_customizer.knowledge_graph import KnowledgeGraph
8
+
9
+ from pyxecm_api.agents.models import Context
10
+ from pyxecm_api.agents.otcm_workspace_agent.models import WorkspaceModel
11
+
12
+ from .functions import (
13
+ KNOWLEDGEGRAPH_ONTOLOGY,
14
+ get_knowledgegraph_object,
15
+ )
16
+ from .models import (
17
+ KnowledgeGraphQueryModel,
18
+ )
19
+
20
+ router = APIRouter(prefix="/otcm_knowledgegraph_agent", tags=["csai agents"])
21
+
22
+ logger = logging.getLogger("pyxecm_api.agents.otcm_knowledgegraph_agent")
23
+
24
+
25
+ @router.post(
26
+ path="/query",
27
+ summary="Query the knowledge graph for a list of workspaces matching the user query.",
28
+ description=f"Use the following ontology to understand the relationship between workspace types and the direction of the relationships (either 'parent' or 'child'): {KNOWLEDGEGRAPH_ONTOLOGY}",
29
+ responses={
30
+ 200: {"description": "Workspaces found"},
31
+ 403: {"description": "Invalid credentials"},
32
+ 404: {"description": "No matching workspaces found"},
33
+ 500: {"description": "Knowledge Graph is not available"},
34
+ },
35
+ # response_model=ToolResponse,
36
+ # response_model=list[WorkspaceModel],
37
+ response_description="List of workspaces that match the query. Best presented as a list with hyperlinks to the workspace.",
38
+ )
39
+ def otcm_knowledgegraph_query(
40
+ context: Context,
41
+ knowledge_graph: Annotated[KnowledgeGraph, Depends(get_knowledgegraph_object)],
42
+ knowledge_graph_query: Annotated[KnowledgeGraphQueryModel, Body()],
43
+ ) -> dict | None: # ToolResponse | None: # list[WorkspaceModel] | None:
44
+ # ) -> list[WorkspaceModel] | None:
45
+ """Query the knowledge graph for a list of workspaces matching the user query. Workspaces are entities and workspace types are entitiy types."""
46
+
47
+ if not knowledge_graph:
48
+ raise HTTPException(status_code=500, detail="Knowledge Graph is not available")
49
+
50
+ logger.info("Got context -> %s", context)
51
+ results = knowledge_graph.graph_query(
52
+ source_type=knowledge_graph_query.source_type,
53
+ source_value=knowledge_graph_query.source_value,
54
+ intermediate_types=knowledge_graph_query.intermediate_types,
55
+ target_type=knowledge_graph_query.target_type,
56
+ target_value=knowledge_graph_query.target_value,
57
+ direction=knowledge_graph_query.direction,
58
+ max_hops=4,
59
+ )
60
+ if not results:
61
+ raise HTTPException(status_code=404, detail="No result found")
62
+
63
+ where_clause = [{"workspaceID": str(workspace[1])} for workspace in results]
64
+ context_update = Context(where=where_clause, query=context.query)
65
+ logger.info("Return context -> %s", context_update)
66
+
67
+ results = [WorkspaceModel(name=n, id=i, type=knowledge_graph_query.target_type) for n, i in results]
68
+
69
+ return {"where": where_clause}
70
+
71
+ # return results
72
+
73
+
74
+ # return ToolResponse(results=results, context_update=context_update)
@@ -0,0 +1,20 @@
1
+ """Define Models for users."""
2
+
3
+ from typing import Annotated
4
+
5
+ from pydantic import BaseModel, Field
6
+
7
+
8
+ class UserModel(BaseModel):
9
+ """Defines Model for describing users in OTCS.
10
+
11
+ To display the user data as a markdown table, use the `UserModel` class.
12
+ """
13
+
14
+ user_id: Annotated[int, Field(description="ID of the user")] = None
15
+ login: Annotated[str, Field(description="Login name of the user")] = None
16
+ first_name: Annotated[str, Field(description="First name of the user")] = None
17
+ last_name: Annotated[str, Field(description="Last name of the user")] = None
18
+ email: Annotated[str, Field(description="Email address of the user")] = None
19
+ department: Annotated[str, Field(description="Department of the user")] = None
20
+ business_phone: Annotated[str, Field(description="Business phone number of the user")] = None
@@ -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