pyxecm 3.0.1__py3-none-any.whl → 3.1.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.
- pyxecm/avts.py +4 -4
- pyxecm/coreshare.py +14 -15
- pyxecm/helper/data.py +2 -1
- pyxecm/helper/web.py +11 -11
- pyxecm/helper/xml.py +41 -10
- pyxecm/otac.py +1 -1
- pyxecm/otawp.py +19 -19
- pyxecm/otca.py +870 -67
- pyxecm/otcs.py +1567 -280
- pyxecm/otds.py +332 -153
- pyxecm/otkd.py +4 -4
- pyxecm/otmm.py +1 -1
- pyxecm/otpd.py +246 -30
- {pyxecm-3.0.1.dist-info → pyxecm-3.1.0.dist-info}/METADATA +2 -1
- pyxecm-3.1.0.dist-info/RECORD +82 -0
- pyxecm_api/app.py +45 -35
- pyxecm_api/auth/functions.py +2 -2
- pyxecm_api/auth/router.py +2 -3
- pyxecm_api/common/functions.py +164 -12
- pyxecm_api/settings.py +0 -8
- pyxecm_api/terminal/router.py +1 -1
- pyxecm_api/v1_csai/router.py +33 -18
- pyxecm_customizer/browser_automation.py +98 -48
- pyxecm_customizer/customizer.py +43 -25
- pyxecm_customizer/guidewire.py +422 -8
- pyxecm_customizer/k8s.py +23 -27
- pyxecm_customizer/knowledge_graph.py +501 -20
- pyxecm_customizer/m365.py +45 -44
- pyxecm_customizer/payload.py +1684 -1159
- pyxecm_customizer/payload_list.py +3 -0
- pyxecm_customizer/salesforce.py +122 -79
- pyxecm_customizer/servicenow.py +27 -7
- pyxecm_customizer/settings.py +3 -1
- pyxecm_customizer/successfactors.py +2 -2
- pyxecm_customizer/translate.py +1 -1
- pyxecm-3.0.1.dist-info/RECORD +0 -96
- pyxecm_api/agents/__init__.py +0 -7
- pyxecm_api/agents/app.py +0 -13
- pyxecm_api/agents/functions.py +0 -119
- pyxecm_api/agents/models.py +0 -10
- pyxecm_api/agents/otcm_knowledgegraph/__init__.py +0 -1
- pyxecm_api/agents/otcm_knowledgegraph/functions.py +0 -85
- pyxecm_api/agents/otcm_knowledgegraph/models.py +0 -61
- pyxecm_api/agents/otcm_knowledgegraph/router.py +0 -74
- pyxecm_api/agents/otcm_user_agent/__init__.py +0 -1
- pyxecm_api/agents/otcm_user_agent/models.py +0 -20
- pyxecm_api/agents/otcm_user_agent/router.py +0 -65
- pyxecm_api/agents/otcm_workspace_agent/__init__.py +0 -1
- pyxecm_api/agents/otcm_workspace_agent/models.py +0 -40
- pyxecm_api/agents/otcm_workspace_agent/router.py +0 -200
- {pyxecm-3.0.1.dist-info → pyxecm-3.1.0.dist-info}/WHEEL +0 -0
- {pyxecm-3.0.1.dist-info → pyxecm-3.1.0.dist-info}/entry_points.txt +0 -0
pyxecm_api/common/functions.py
CHANGED
|
@@ -2,11 +2,15 @@
|
|
|
2
2
|
|
|
3
3
|
import logging
|
|
4
4
|
import os
|
|
5
|
+
import time
|
|
6
|
+
from datetime import UTC, datetime
|
|
5
7
|
from typing import Annotated
|
|
6
8
|
|
|
7
9
|
from fastapi import Depends
|
|
10
|
+
from pyxecm.otca import OTCA
|
|
8
11
|
from pyxecm.otcs import OTCS
|
|
9
12
|
from pyxecm_customizer import K8s, PayloadList, Settings
|
|
13
|
+
from pyxecm_customizer.knowledge_graph import KnowledgeGraph
|
|
10
14
|
|
|
11
15
|
from pyxecm_api.auth.functions import get_otcsticket
|
|
12
16
|
from pyxecm_api.settings import CustomizerAPISettings, api_settings
|
|
@@ -18,6 +22,100 @@ LOGS_LOCK = {}
|
|
|
18
22
|
# Initialize the globel Payloadlist object
|
|
19
23
|
PAYLOAD_LIST = PayloadList(logger=logger)
|
|
20
24
|
|
|
25
|
+
# This object is initialized in the build_graph() function below.
|
|
26
|
+
KNOWLEDGEGRAPH_OBJECT: KnowledgeGraph = None
|
|
27
|
+
|
|
28
|
+
# The following ontology is fed into the knowledge graph tool description.
|
|
29
|
+
# This is currently hard-coded. Ideally this should be derived from OTCM
|
|
30
|
+
# or provided via a payload file:
|
|
31
|
+
|
|
32
|
+
KNOWLEDGEGRAPH_ONTOLOGY = {
|
|
33
|
+
("Vendor", "Material", "child"): ["offers", "supplies", "provides"],
|
|
34
|
+
("Vendor", "Purchase Order", "child"): ["supplies", "provides"],
|
|
35
|
+
("Vendor", "Purchase Contract", "child"): ["signs", "owns"],
|
|
36
|
+
("Material", "Vendor", "parent"): ["is supplied by"],
|
|
37
|
+
("Purchase Order", "Material", "child"): ["includes", "is part of"],
|
|
38
|
+
("Customer", "Sales Order", "child"): ["has ordered"],
|
|
39
|
+
("Customer", "Sales Contract", "child"): ["signs", "owns"],
|
|
40
|
+
("Sales Order", "Customer", "parent"): ["belongs to", "is initiated by"],
|
|
41
|
+
("Sales Order", "Material", "child"): ["includes", "consists of"],
|
|
42
|
+
("Sales Order", "Delivery", "child"): ["triggers", "is followed by"],
|
|
43
|
+
("Sales Order", "Production Order", "child"): ["triggers", "is followed by"],
|
|
44
|
+
("Sales Contract", "Material", "child"): ["includes", "consists of"],
|
|
45
|
+
("Production Order", "Material", "child"): ["includes", "consists of"],
|
|
46
|
+
("Production Order", "Delivery", "child"): ["triggers", "is followed by"],
|
|
47
|
+
("Production Order", "Goods Movement", "child"): ["triggers", "is followed by"],
|
|
48
|
+
("Delivery", "Goods Movement", "child"): ["triggers", "is followed by"],
|
|
49
|
+
("Delivery", "Material", "child"): ["triggers", "is followed by"],
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
### Functions
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def get_ontology() -> dict:
|
|
57
|
+
"""Get the ontology for the knowledge graph.
|
|
58
|
+
|
|
59
|
+
Returns:
|
|
60
|
+
dict: The ontology as a dictionary.
|
|
61
|
+
|
|
62
|
+
"""
|
|
63
|
+
|
|
64
|
+
return KNOWLEDGEGRAPH_ONTOLOGY
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def get_knowledgegraph_object() -> KnowledgeGraph:
|
|
68
|
+
"""Get the Knowledge Graph object."""
|
|
69
|
+
|
|
70
|
+
global KNOWLEDGEGRAPH_OBJECT # noqa: PLW0603
|
|
71
|
+
|
|
72
|
+
if KNOWLEDGEGRAPH_OBJECT is None:
|
|
73
|
+
KNOWLEDGEGRAPH_OBJECT = KnowledgeGraph(otcs_object=get_otcs_object(), ontology=KNOWLEDGEGRAPH_ONTOLOGY)
|
|
74
|
+
|
|
75
|
+
return KNOWLEDGEGRAPH_OBJECT
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def build_graph() -> None:
|
|
79
|
+
"""Build the knowledge Graph. And keep it updated every hour."""
|
|
80
|
+
|
|
81
|
+
def build() -> None:
|
|
82
|
+
"""Build the knowledge graph once."""
|
|
83
|
+
|
|
84
|
+
logger.info("Starting knowledge graph build...")
|
|
85
|
+
start_time = datetime.now(UTC)
|
|
86
|
+
result = get_knowledgegraph_object().build_graph(
|
|
87
|
+
workspace_type_exclusions=None,
|
|
88
|
+
workspace_type_inclusions=[
|
|
89
|
+
"Vendor",
|
|
90
|
+
"Purchase Contract",
|
|
91
|
+
"Purchase Order",
|
|
92
|
+
"Material",
|
|
93
|
+
"Customer",
|
|
94
|
+
"Sales Order",
|
|
95
|
+
"Sales Contract",
|
|
96
|
+
"Delivery",
|
|
97
|
+
"Goods Movement",
|
|
98
|
+
],
|
|
99
|
+
workers=20, # for multi-threaded traversal
|
|
100
|
+
filter_at_traversal=True, # also filter for workspace types if following relationships
|
|
101
|
+
relationship_types=["child"], # only go from parent to child
|
|
102
|
+
strategy="BFS", # Breadth-First-Search
|
|
103
|
+
metadata=True, # don't include workspace metadata
|
|
104
|
+
)
|
|
105
|
+
end_time = datetime.now(UTC)
|
|
106
|
+
logger.info(
|
|
107
|
+
"Knowledge graph completed in %s. Processed %d workspace nodes and traversed %d workspace relationships.",
|
|
108
|
+
str(end_time - start_time),
|
|
109
|
+
result["processed"],
|
|
110
|
+
result["traversed"],
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
# Endless loop to build knowledge graph and update it every hour:
|
|
114
|
+
while True:
|
|
115
|
+
build()
|
|
116
|
+
logger.info("Waiting for 1 hour before rebuilding the knowledge graph...")
|
|
117
|
+
time.sleep(3600)
|
|
118
|
+
|
|
21
119
|
|
|
22
120
|
def get_k8s_object() -> K8s:
|
|
23
121
|
"""Get an instance of a K8s object.
|
|
@@ -31,12 +129,14 @@ def get_k8s_object() -> K8s:
|
|
|
31
129
|
|
|
32
130
|
|
|
33
131
|
def get_otcs_object() -> OTCS:
|
|
34
|
-
"""Get an instance of a
|
|
132
|
+
"""Get an instance of a Content Server (OTCS) object.
|
|
35
133
|
|
|
36
134
|
Returns:
|
|
37
|
-
|
|
135
|
+
OTCS:
|
|
136
|
+
Return a new OTCS object.
|
|
38
137
|
|
|
39
138
|
"""
|
|
139
|
+
|
|
40
140
|
settings = Settings()
|
|
41
141
|
|
|
42
142
|
otcs = OTCS(
|
|
@@ -52,27 +152,34 @@ def get_otcs_object() -> OTCS:
|
|
|
52
152
|
support_path=settings.otcs.support_path,
|
|
53
153
|
download_dir=settings.otcs.download_dir,
|
|
54
154
|
feme_uri=settings.otcs.feme_uri,
|
|
55
|
-
logger=logger
|
|
155
|
+
logger=logger,
|
|
56
156
|
)
|
|
57
157
|
|
|
158
|
+
# Authenticate at Content Server:
|
|
58
159
|
otcs.authenticate()
|
|
59
160
|
|
|
60
161
|
return otcs
|
|
61
162
|
|
|
62
163
|
|
|
63
|
-
def get_otcs_object_from_otcsticket(
|
|
64
|
-
"""Get an instance of a
|
|
164
|
+
def get_otcs_object_from_otcsticket(otcs_ticket: Annotated[str, Depends(get_otcsticket)]) -> OTCS:
|
|
165
|
+
"""Get an instance of a Content Server (OTCS) object.
|
|
65
166
|
|
|
66
167
|
Returns:
|
|
67
|
-
|
|
168
|
+
OTCS:
|
|
169
|
+
Return an OTCS object.
|
|
68
170
|
|
|
69
171
|
"""
|
|
172
|
+
|
|
70
173
|
settings = Settings()
|
|
71
174
|
|
|
175
|
+
# Create an OTCS object without defining the username and password:
|
|
72
176
|
otcs = OTCS(
|
|
73
|
-
protocol=settings.otcs.url_backend.scheme,
|
|
74
|
-
hostname=settings.otcs.url_backend.host,
|
|
75
|
-
port=settings.otcs.url_backend.port,
|
|
177
|
+
# protocol=settings.otcs.url_backend.scheme,
|
|
178
|
+
# hostname=settings.otcs.url_backend.host,
|
|
179
|
+
# port=settings.otcs.url_backend.port,
|
|
180
|
+
protocol=settings.otcs.url_frontend.scheme,
|
|
181
|
+
hostname=settings.otcs.url_frontend.host,
|
|
182
|
+
port=settings.otcs.url_frontend.port,
|
|
76
183
|
public_url=str(settings.otcs.url),
|
|
77
184
|
user_partition=settings.otcs.partition,
|
|
78
185
|
resource_name=settings.otcs.resource_name,
|
|
@@ -80,19 +187,62 @@ def get_otcs_object_from_otcsticket(otcsticket: Annotated[str, Depends(get_otcst
|
|
|
80
187
|
support_path=settings.otcs.support_path,
|
|
81
188
|
download_dir=settings.otcs.download_dir,
|
|
82
189
|
feme_uri=settings.otcs.feme_uri,
|
|
83
|
-
logger=logger
|
|
190
|
+
logger=logger,
|
|
84
191
|
)
|
|
85
192
|
|
|
86
|
-
|
|
193
|
+
# Instead set the OTCS authentication ticket directly:
|
|
194
|
+
otcs._otcs_ticket = otcs_ticket # noqa: SLF001
|
|
87
195
|
|
|
88
196
|
return otcs
|
|
89
197
|
|
|
90
198
|
|
|
199
|
+
def get_otca_object(otcs_object: OTCS | None = None) -> OTCA:
|
|
200
|
+
"""Get the Content Aviator (OTCA) object.
|
|
201
|
+
|
|
202
|
+
Args:
|
|
203
|
+
otcs_object (OTCS | None, optional):
|
|
204
|
+
The Content Server (OTCS) object. Defaults to None.
|
|
205
|
+
|
|
206
|
+
Returns:
|
|
207
|
+
OTCA:
|
|
208
|
+
The new Content Aviator object.
|
|
209
|
+
|
|
210
|
+
"""
|
|
211
|
+
|
|
212
|
+
settings = Settings()
|
|
213
|
+
|
|
214
|
+
# Get the Kubernetes object:
|
|
215
|
+
k8s_object = get_k8s_object()
|
|
216
|
+
content_system = {}
|
|
217
|
+
# Read the content system (e.g. OTCM) from the Kubernetes Config Map:
|
|
218
|
+
for service in ["chat", "embed"]:
|
|
219
|
+
cm = k8s_object.get_config_map(f"csai-{service}-svc")
|
|
220
|
+
if cm:
|
|
221
|
+
content_system[service] = cm.data.get("CONTENT_SYSTEM", "none")
|
|
222
|
+
logger.info("Set content system for '%s' to -> '%s'.", service, content_system[service])
|
|
223
|
+
|
|
224
|
+
# Create the Content Aviator object (OTCA class):
|
|
225
|
+
otca = OTCA(
|
|
226
|
+
chat_url=str(settings.aviator.chat_svc_url),
|
|
227
|
+
embed_url=str(settings.aviator.embed_svc_url),
|
|
228
|
+
studio_url=str(settings.aviator.studio_url),
|
|
229
|
+
otds_url=str(settings.otds.url_internal),
|
|
230
|
+
client_id=settings.aviator.oauth_client,
|
|
231
|
+
client_secret=settings.aviator.oauth_secret,
|
|
232
|
+
otcs_object=otcs_object,
|
|
233
|
+
content_system=content_system,
|
|
234
|
+
logger=logger.getChild("otca"),
|
|
235
|
+
)
|
|
236
|
+
|
|
237
|
+
return otca
|
|
238
|
+
|
|
239
|
+
|
|
91
240
|
def get_settings() -> CustomizerAPISettings:
|
|
92
241
|
"""Get the API Settings object.
|
|
93
242
|
|
|
94
243
|
Returns:
|
|
95
|
-
CustomizerPISettings:
|
|
244
|
+
CustomizerPISettings:
|
|
245
|
+
Returns the API Settings.
|
|
96
246
|
|
|
97
247
|
"""
|
|
98
248
|
|
|
@@ -112,6 +262,7 @@ def get_otcs_logs_lock() -> dict:
|
|
|
112
262
|
|
|
113
263
|
def list_files_in_directory(directory: str) -> dict:
|
|
114
264
|
"""Recursively list files in a directory and return a nested JSON structure with URLs."""
|
|
265
|
+
|
|
115
266
|
result = {}
|
|
116
267
|
for root, dirs, files in os.walk(directory):
|
|
117
268
|
# Sort directories and files alphabetically
|
|
@@ -128,4 +279,5 @@ def list_files_in_directory(directory: str) -> dict:
|
|
|
128
279
|
for file in files:
|
|
129
280
|
file_path = os.path.join(relative_path, file)
|
|
130
281
|
current_level[file] = file_path
|
|
282
|
+
|
|
131
283
|
return result
|
pyxecm_api/settings.py
CHANGED
|
@@ -56,9 +56,6 @@ class CustomizerAPISettings(BaseSettings):
|
|
|
56
56
|
)
|
|
57
57
|
|
|
58
58
|
loglevel: Literal["INFO", "DEBUG", "WARNING", "ERROR"] = "INFO"
|
|
59
|
-
log_payload_processing: bool = Field(
|
|
60
|
-
default=False, description="Print the customizer payload processing log messages to stdout"
|
|
61
|
-
)
|
|
62
59
|
logfolder: str = Field(
|
|
63
60
|
default=os.path.join(tempfile.gettempdir(), "customizer"),
|
|
64
61
|
description="Logfolder for Customizer logfiles",
|
|
@@ -122,11 +119,6 @@ class CustomizerAPISettings(BaseSettings):
|
|
|
122
119
|
description="Prefix for the CSAI",
|
|
123
120
|
)
|
|
124
121
|
|
|
125
|
-
csai_studio_integration: bool = Field(
|
|
126
|
-
default=False,
|
|
127
|
-
description="Enable the CSAI Studio Integration",
|
|
128
|
-
)
|
|
129
|
-
|
|
130
122
|
upload_folder: str = Field(default=os.path.join(tempfile.gettempdir(), "upload"), description="Folder for uploads")
|
|
131
123
|
|
|
132
124
|
upload_key: str = Field(default=str(uuid.uuid4()), description="Upload key for the Logs")
|
pyxecm_api/terminal/router.py
CHANGED
|
@@ -93,7 +93,7 @@ async def ws_terminal(
|
|
|
93
93
|
read_task = asyncio.create_task(read_from_pty())
|
|
94
94
|
write_task = asyncio.create_task(write_to_pty())
|
|
95
95
|
|
|
96
|
-
|
|
96
|
+
_done, pending = await asyncio.wait([read_task, write_task], return_when=asyncio.FIRST_COMPLETED)
|
|
97
97
|
|
|
98
98
|
# Cancel other task
|
|
99
99
|
for task in pending:
|
pyxecm_api/v1_csai/router.py
CHANGED
|
@@ -4,15 +4,14 @@ import logging
|
|
|
4
4
|
from http import HTTPStatus
|
|
5
5
|
from typing import Annotated
|
|
6
6
|
|
|
7
|
-
from fastapi import APIRouter, Body, Depends, Query
|
|
7
|
+
from fastapi import APIRouter, Body, Depends, HTTPException, Query
|
|
8
8
|
from fastapi.responses import HTMLResponse, JSONResponse
|
|
9
9
|
from pyxecm.otcs import OTCS
|
|
10
10
|
from pyxecm_customizer.k8s import K8s
|
|
11
11
|
|
|
12
|
-
from pyxecm_api.agents.functions import get_otca_object
|
|
13
12
|
from pyxecm_api.auth.functions import get_authorized_user
|
|
14
13
|
from pyxecm_api.auth.models import User
|
|
15
|
-
from pyxecm_api.common.functions import get_k8s_object, get_otcs_object, get_settings
|
|
14
|
+
from pyxecm_api.common.functions import get_k8s_object, get_otca_object, get_otcs_object, get_settings
|
|
16
15
|
from pyxecm_api.settings import CustomizerAPISettings
|
|
17
16
|
|
|
18
17
|
from .models import CSAIEmbedMetadata
|
|
@@ -31,12 +30,16 @@ def embed_metadata(
|
|
|
31
30
|
"""Embed the Metadata of the given objects.
|
|
32
31
|
|
|
33
32
|
Args:
|
|
34
|
-
user (Annotated[User, Depends):
|
|
35
|
-
|
|
36
|
-
|
|
33
|
+
user (Annotated[User, Depends):
|
|
34
|
+
User required for authentication.
|
|
35
|
+
otcs_object (Annotated[OTCS, Depends(get_otcs_object)]):
|
|
36
|
+
The OTCS object to interact with OTCM (Content Server).
|
|
37
|
+
body (Annotated[CSAIEmbedMetadata, Body):
|
|
38
|
+
The request body.
|
|
37
39
|
|
|
38
40
|
Returns:
|
|
39
|
-
JSONResponse:
|
|
41
|
+
JSONResponse:
|
|
42
|
+
JSONResponse with success=true/false
|
|
40
43
|
|
|
41
44
|
"""
|
|
42
45
|
|
|
@@ -53,7 +56,7 @@ def get_csai_config_data(
|
|
|
53
56
|
) -> JSONResponse:
|
|
54
57
|
"""Get the csai config data."""
|
|
55
58
|
|
|
56
|
-
logger.info("
|
|
59
|
+
logger.info("Read CSAI config data by user -> %s", user.id)
|
|
57
60
|
|
|
58
61
|
config_data = {}
|
|
59
62
|
|
|
@@ -79,9 +82,9 @@ def set_csai_config_data(
|
|
|
79
82
|
k8s_object: Annotated[K8s, Depends(get_k8s_object)],
|
|
80
83
|
config: Annotated[dict, Body()],
|
|
81
84
|
) -> JSONResponse:
|
|
82
|
-
"""
|
|
85
|
+
"""Set the CSAI config data."""
|
|
83
86
|
|
|
84
|
-
logger.info("
|
|
87
|
+
logger.info("Write CSAI config data by user -> %s", user.id)
|
|
85
88
|
|
|
86
89
|
for config_map in config:
|
|
87
90
|
if not config_map.startswith(settings.csai_prefix):
|
|
@@ -115,27 +118,39 @@ def set_csai_config_data(
|
|
|
115
118
|
|
|
116
119
|
|
|
117
120
|
@router.get("/graph")
|
|
118
|
-
def get_csai_graph(name: Annotated[str, Query(..., description="Name of the
|
|
121
|
+
def get_csai_graph(name: Annotated[str, Query(..., description="Name of the graph")]) -> HTMLResponse:
|
|
119
122
|
"""Display the graph of the given name.
|
|
120
123
|
|
|
121
124
|
Args:
|
|
122
|
-
|
|
123
|
-
|
|
125
|
+
name (str):
|
|
126
|
+
The name of the CSAI graph.
|
|
124
127
|
|
|
125
128
|
Returns:
|
|
126
|
-
HTMLResponse:
|
|
129
|
+
HTMLResponse: Visualization of the CSAI graph
|
|
127
130
|
|
|
128
131
|
"""
|
|
132
|
+
|
|
133
|
+
# Get the Content Aviator object:
|
|
129
134
|
otca = get_otca_object(otcs_object=None)
|
|
130
135
|
|
|
136
|
+
# Get all graphs configured in Content Aviator:
|
|
131
137
|
graphs = otca.get_graphs()
|
|
138
|
+
# Find the graph (LangGraph) with the given name:
|
|
132
139
|
graph = [g for g in graphs if g["name"] == name]
|
|
133
140
|
|
|
134
|
-
if graph:
|
|
141
|
+
if not graph:
|
|
142
|
+
logger.error("Couldn't find graph -> '%s' for visualization!", name)
|
|
143
|
+
raise HTTPException(status_code=404, detail="Graph -> '{}' not found!".format(name))
|
|
144
|
+
|
|
145
|
+
try:
|
|
135
146
|
filename = otca.visualize_graph(graph[0]["id"])
|
|
136
147
|
|
|
137
|
-
|
|
138
|
-
|
|
148
|
+
with open(filename) as f:
|
|
149
|
+
file_content = f.read()
|
|
150
|
+
except Exception as e:
|
|
151
|
+
logger.error("Error visualizing graph -> '%s': %s", name, str(e))
|
|
152
|
+
raise HTTPException(status_code=500, detail="Failed to visualize graph -> '{}'!".format(name)) from e
|
|
153
|
+
|
|
154
|
+
logger.info("Successfully visualized graph -> '%s'", name)
|
|
139
155
|
|
|
140
|
-
logger.info("name: %s", name)
|
|
141
156
|
return HTMLResponse(status_code=200, content=file_content)
|