sapiopycommons 2024.3.19a157__py3-none-any.whl → 2025.1.17a402__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 sapiopycommons might be problematic. Click here for more details.
- sapiopycommons/callbacks/__init__.py +0 -0
- sapiopycommons/callbacks/callback_util.py +2041 -0
- sapiopycommons/callbacks/field_builder.py +545 -0
- sapiopycommons/chem/IndigoMolecules.py +46 -1
- sapiopycommons/chem/Molecules.py +100 -21
- sapiopycommons/customreport/__init__.py +0 -0
- sapiopycommons/customreport/column_builder.py +60 -0
- sapiopycommons/customreport/custom_report_builder.py +137 -0
- sapiopycommons/customreport/term_builder.py +315 -0
- sapiopycommons/datatype/attachment_util.py +14 -15
- sapiopycommons/datatype/data_fields.py +61 -0
- sapiopycommons/datatype/pseudo_data_types.py +440 -0
- sapiopycommons/eln/experiment_handler.py +355 -91
- sapiopycommons/eln/experiment_report_util.py +649 -0
- sapiopycommons/eln/plate_designer.py +152 -0
- sapiopycommons/files/complex_data_loader.py +31 -0
- sapiopycommons/files/file_bridge.py +149 -25
- sapiopycommons/files/file_bridge_handler.py +555 -0
- sapiopycommons/files/file_data_handler.py +633 -0
- sapiopycommons/files/file_util.py +263 -163
- sapiopycommons/files/file_validator.py +569 -0
- sapiopycommons/files/file_writer.py +377 -0
- sapiopycommons/flowcyto/flow_cyto.py +77 -0
- sapiopycommons/flowcyto/flowcyto_data.py +75 -0
- sapiopycommons/general/accession_service.py +375 -0
- sapiopycommons/general/aliases.py +250 -15
- sapiopycommons/general/audit_log.py +185 -0
- sapiopycommons/general/custom_report_util.py +251 -31
- sapiopycommons/general/directive_util.py +86 -0
- sapiopycommons/general/exceptions.py +69 -7
- sapiopycommons/general/popup_util.py +59 -7
- sapiopycommons/general/sapio_links.py +50 -0
- sapiopycommons/general/storage_util.py +148 -0
- sapiopycommons/general/time_util.py +91 -7
- sapiopycommons/multimodal/multimodal.py +146 -0
- sapiopycommons/multimodal/multimodal_data.py +490 -0
- sapiopycommons/processtracking/__init__.py +0 -0
- sapiopycommons/processtracking/custom_workflow_handler.py +406 -0
- sapiopycommons/processtracking/endpoints.py +192 -0
- sapiopycommons/recordmodel/record_handler.py +621 -148
- sapiopycommons/rules/eln_rule_handler.py +87 -8
- sapiopycommons/rules/on_save_rule_handler.py +87 -12
- sapiopycommons/sftpconnect/__init__.py +0 -0
- sapiopycommons/sftpconnect/sftp_builder.py +70 -0
- sapiopycommons/webhook/webhook_context.py +39 -0
- sapiopycommons/webhook/webhook_handlers.py +614 -71
- sapiopycommons/webhook/webservice_handlers.py +317 -0
- {sapiopycommons-2024.3.19a157.dist-info → sapiopycommons-2025.1.17a402.dist-info}/METADATA +5 -4
- sapiopycommons-2025.1.17a402.dist-info/RECORD +60 -0
- {sapiopycommons-2024.3.19a157.dist-info → sapiopycommons-2025.1.17a402.dist-info}/WHEEL +1 -1
- sapiopycommons-2024.3.19a157.dist-info/RECORD +0 -28
- {sapiopycommons-2024.3.19a157.dist-info → sapiopycommons-2025.1.17a402.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,19 +1,49 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import sys
|
|
5
|
+
import time
|
|
1
6
|
import traceback
|
|
2
7
|
from abc import abstractmethod
|
|
3
8
|
from logging import Logger
|
|
4
9
|
|
|
10
|
+
from sapiopylib.rest import UserManagerService, GroupManagerService, MessengerService
|
|
11
|
+
from sapiopylib.rest.AccessionService import AccessionManager
|
|
12
|
+
from sapiopylib.rest.CustomReportService import CustomReportManager
|
|
13
|
+
from sapiopylib.rest.DashboardManager import DashboardManager
|
|
14
|
+
from sapiopylib.rest.DataMgmtService import DataMgmtServer
|
|
5
15
|
from sapiopylib.rest.DataRecordManagerService import DataRecordManager
|
|
16
|
+
from sapiopylib.rest.DataService import DataManager
|
|
17
|
+
from sapiopylib.rest.DataTypeService import DataTypeManager
|
|
18
|
+
from sapiopylib.rest.ELNService import ElnManager
|
|
19
|
+
from sapiopylib.rest.PicklistService import PickListManager
|
|
20
|
+
from sapiopylib.rest.ReportManager import ReportManager
|
|
21
|
+
from sapiopylib.rest.SesssionManagerService import SessionManager
|
|
22
|
+
from sapiopylib.rest.User import SapioUser
|
|
6
23
|
from sapiopylib.rest.WebhookService import AbstractWebhookHandler
|
|
7
|
-
from sapiopylib.rest.pojo.
|
|
24
|
+
from sapiopylib.rest.pojo.Message import VeloxLogMessage, VeloxLogLevel
|
|
25
|
+
from sapiopylib.rest.pojo.webhook.ClientCallbackRequest import PopupType
|
|
8
26
|
from sapiopylib.rest.pojo.webhook.WebhookContext import SapioWebhookContext
|
|
9
27
|
from sapiopylib.rest.pojo.webhook.WebhookEnums import WebhookEndpointType
|
|
10
28
|
from sapiopylib.rest.pojo.webhook.WebhookResult import SapioWebhookResult
|
|
29
|
+
from sapiopylib.rest.utils.DataTypeCacheManager import DataTypeCacheManager
|
|
30
|
+
from sapiopylib.rest.utils.FoundationAccessioning import FoundationAccessionManager
|
|
11
31
|
from sapiopylib.rest.utils.recordmodel.RecordModelManager import RecordModelManager, RecordModelInstanceManager, \
|
|
12
32
|
RecordModelRelationshipManager
|
|
13
33
|
from sapiopylib.rest.utils.recordmodel.ancestry import RecordModelAncestorManager
|
|
34
|
+
from sapiopylib.rest.utils.recordmodel.last_saved import LastSavedValueManager
|
|
14
35
|
|
|
15
|
-
from sapiopycommons.
|
|
16
|
-
from sapiopycommons.
|
|
36
|
+
from sapiopycommons.callbacks.callback_util import CallbackUtil
|
|
37
|
+
from sapiopycommons.eln.experiment_handler import ExperimentHandler
|
|
38
|
+
from sapiopycommons.general.directive_util import DirectiveUtil
|
|
39
|
+
from sapiopycommons.general.exceptions import SapioUserErrorException, SapioCriticalErrorException, \
|
|
40
|
+
SapioUserCancelledException, SapioException, SapioDialogTimeoutException, MessageDisplayType
|
|
41
|
+
from sapiopycommons.general.sapio_links import SapioNavigationLinker
|
|
42
|
+
from sapiopycommons.general.time_util import TimeUtil
|
|
43
|
+
from sapiopycommons.recordmodel.record_handler import RecordHandler
|
|
44
|
+
from sapiopycommons.rules.eln_rule_handler import ElnRuleHandler
|
|
45
|
+
from sapiopycommons.rules.on_save_rule_handler import OnSaveRuleHandler
|
|
46
|
+
from sapiopycommons.webhook.webhook_context import ProcessQueueContext
|
|
17
47
|
|
|
18
48
|
|
|
19
49
|
# FR-46064 - Initial port of PyWebhookUtils to sapiopycommons.
|
|
@@ -25,41 +55,213 @@ class CommonsWebhookHandler(AbstractWebhookHandler):
|
|
|
25
55
|
"""
|
|
26
56
|
logger: Logger
|
|
27
57
|
|
|
28
|
-
|
|
58
|
+
_start_time: float
|
|
59
|
+
_start_time_epoch: int
|
|
29
60
|
|
|
61
|
+
user: SapioUser
|
|
62
|
+
"""The user who invoked this webhook. Used for authenticating requests back to the Sapio server."""
|
|
63
|
+
group_name: str
|
|
64
|
+
"""The name of the group that the user invoked this webhook with."""
|
|
65
|
+
user_utc_offset_seconds: int
|
|
66
|
+
"""The number of seconds that the user is offset from the UTC timezone. Able to be used with TimeUtil to display
|
|
67
|
+
timestamps in the user's timezone."""
|
|
68
|
+
context: SapioWebhookContext
|
|
69
|
+
"""The context from the server of this webhook's invocation."""
|
|
70
|
+
|
|
71
|
+
# CR-47383: Include every manager from DataMgmtServer for easier access.
|
|
72
|
+
acc_man: AccessionManager
|
|
73
|
+
"""A class for making requests to the accession webservice endpoints."""
|
|
74
|
+
fnd_acc_man: FoundationAccessionManager
|
|
75
|
+
"""A class for making requests to the Foundations accession webservice endpoints."""
|
|
76
|
+
custom_report_man: CustomReportManager
|
|
77
|
+
"""A class for making requests to the custom report webservice endpoints."""
|
|
78
|
+
dash_man: DashboardManager
|
|
79
|
+
"""A class for making requests to the dashboard management webservice endpoints."""
|
|
80
|
+
xml_data_man: DataManager
|
|
81
|
+
"""A class for making requests to the data record import/export via XML webservice endpoints."""
|
|
30
82
|
dr_man: DataRecordManager
|
|
83
|
+
"""A class for making requests to the data record webservice endpoints."""
|
|
84
|
+
dt_man: DataTypeManager
|
|
85
|
+
"""A class for making requests to the data type webservice endpoints."""
|
|
86
|
+
eln_man: ElnManager
|
|
87
|
+
"""A class for making requests to the ELN management webservice endpoints."""
|
|
88
|
+
group_man: GroupManagerService
|
|
89
|
+
"""A class for making requests to the group management webservice endpoints."""
|
|
90
|
+
messenger: MessengerService
|
|
91
|
+
"""A class for making requests to the message webservice endpoints."""
|
|
92
|
+
list_man: PickListManager
|
|
93
|
+
"""A class for making requests to the pick list webservice endpoints."""
|
|
94
|
+
pdf_report_man: ReportManager
|
|
95
|
+
"""A class for making requests to the report webservice endpoints."""
|
|
96
|
+
session_man: SessionManager
|
|
97
|
+
"""A class for making requests to the session management webservice endpoints."""
|
|
98
|
+
user_man: UserManagerService
|
|
99
|
+
"""A class for making requests to the user management webservice endpoints."""
|
|
100
|
+
|
|
31
101
|
rec_man: RecordModelManager
|
|
102
|
+
"""The record model manager. Used for committing record model changes to the system."""
|
|
32
103
|
inst_man: RecordModelInstanceManager
|
|
104
|
+
"""The record model instance manager. Used for adding record models to the cache."""
|
|
33
105
|
rel_man: RecordModelRelationshipManager
|
|
106
|
+
"""The record model relationship manager. Used for loading parent/child and side-link relationships between record
|
|
107
|
+
models."""
|
|
34
108
|
# FR-46329: Add the ancestor manager to CommonsWebhookHandler.
|
|
35
109
|
an_man: RecordModelAncestorManager
|
|
110
|
+
"""The record model ancestor manager. Used for loading ancestor relationships between record models."""
|
|
111
|
+
saved_vals_man: LastSavedValueManager
|
|
112
|
+
"""The last saved values manager. Used for determining what the record values were prior to this commit."""
|
|
113
|
+
|
|
114
|
+
dt_cache: DataTypeCacheManager
|
|
115
|
+
"""A class that calls the same endpoints as the DataTypeManager (self.dt_man), except the results are cached so that
|
|
116
|
+
repeated calls to the same function don't result in duplicate webservice calls. """
|
|
117
|
+
rec_handler: RecordHandler
|
|
118
|
+
"""A class that behaves like a combination between the DataRecordManager and RecordModelInstanceManager, allowing
|
|
119
|
+
you to query and wrap record as record models in a single function call, among other functions useful for dealing
|
|
120
|
+
with record models."""
|
|
121
|
+
callback: CallbackUtil
|
|
122
|
+
"""A class for making requests to the client callback webservice endpoints."""
|
|
123
|
+
directive: DirectiveUtil
|
|
124
|
+
"""A class for making directives that redirect the user to a new webpage after this webhook returns a result."""
|
|
125
|
+
exp_handler: ExperimentHandler | None
|
|
126
|
+
"""If this webhook was invoked from within an ELN experiment, this variable will be populated with an
|
|
127
|
+
ExperimentHandler initialized from the context. """
|
|
128
|
+
rule_handler: OnSaveRuleHandler | ElnRuleHandler | None
|
|
129
|
+
"""If this is an ELN or on save rule endpoint, this variable will be populated with an ElnRuleHandler or
|
|
130
|
+
OnSaveRuleHandler, depending on the endpoint type."""
|
|
131
|
+
custom_context: ProcessQueueContext | None
|
|
132
|
+
"""If this is a custom endpoint, this variable will be populated with an object that parses the custom context
|
|
133
|
+
data."""
|
|
134
|
+
|
|
135
|
+
# FR-47390: Allow for classes that extend CommonsWebhookHandler to change how exception messages are displayed
|
|
136
|
+
# to the user be changing these variables instead of needing to override the exception handling functions.
|
|
137
|
+
default_user_error_display_type: MessageDisplayType
|
|
138
|
+
"""The default message display type for user error exceptions. If a user error exception is thrown and doesn't
|
|
139
|
+
specify a display type, this type will be used."""
|
|
140
|
+
default_critical_error_display_type: MessageDisplayType
|
|
141
|
+
"""The default message display type for critical error exceptions. If a critical error exception is thrown and
|
|
142
|
+
doesn't specify a display type, this type will be used."""
|
|
143
|
+
default_dialog_timeout_display_type: MessageDisplayType
|
|
144
|
+
"""The default message display type for dialog timeout exceptions."""
|
|
145
|
+
default_unexpected_error_display_type: MessageDisplayType
|
|
146
|
+
"""The default message display type for unexpected exceptions."""
|
|
147
|
+
|
|
148
|
+
default_user_error_title: str
|
|
149
|
+
"""The default title to display to the user when a user error occurs. If a user error exception is thrown and
|
|
150
|
+
doesn't specify a title, this title will be used."""
|
|
151
|
+
default_critical_error_title: str
|
|
152
|
+
"""The default title to display to the user when a critical error occurs. If a critical error exception is thrown
|
|
153
|
+
and doesn't specify a title, this title will be used."""
|
|
154
|
+
default_dialog_timeout_title: str
|
|
155
|
+
"""The default title to display to the user when a dialog times out."""
|
|
156
|
+
default_unexpected_error_title: str
|
|
157
|
+
"""The default title to display to the user when an unexpected exception occurs."""
|
|
158
|
+
|
|
159
|
+
default_dialog_timeout_message: str
|
|
160
|
+
"""The default message to display to the user when a dialog times out."""
|
|
161
|
+
default_unexpected_error_message: str
|
|
162
|
+
"""The default message to display to the user when an unexpected exception occurs."""
|
|
36
163
|
|
|
37
164
|
def run(self, context: SapioWebhookContext) -> SapioWebhookResult:
|
|
165
|
+
# Timestamps used for measuring performance.
|
|
166
|
+
self._start_time = time.perf_counter()
|
|
167
|
+
self._start_time_epoch = TimeUtil.now_in_millis()
|
|
168
|
+
|
|
169
|
+
# Save the webhook context so that any function of this class can access it.
|
|
38
170
|
self.context = context
|
|
39
|
-
self.logger = context.user.logger
|
|
40
171
|
|
|
172
|
+
# Save the user and commonly sought after user information.
|
|
173
|
+
self.user = context.user
|
|
174
|
+
self.group_name = self.user.session_additional_data.current_group_name
|
|
175
|
+
self.user_utc_offset_seconds = self.user.session_additional_data.utc_offset_seconds
|
|
176
|
+
|
|
177
|
+
# Get the logger from the user.
|
|
178
|
+
self.logger = self.user.logger
|
|
179
|
+
|
|
180
|
+
# Initialize basic manager classes from sapiopylib.
|
|
181
|
+
self.acc_man = DataMgmtServer.get_accession_manager(self.user)
|
|
182
|
+
self.custom_report_man = DataMgmtServer.get_custom_report_manager(self.user)
|
|
183
|
+
self.dash_man = DataMgmtServer.get_dashboard_manager(self.user)
|
|
184
|
+
self.xml_data_man = DataMgmtServer.get_data_manager(self.user)
|
|
41
185
|
self.dr_man = context.data_record_manager
|
|
42
|
-
self.
|
|
186
|
+
self.dt_man = DataMgmtServer.get_data_type_manager(self.user)
|
|
187
|
+
self.eln_man = context.eln_manager
|
|
188
|
+
self.group_man = DataMgmtServer.get_group_manager(self.user)
|
|
189
|
+
self.messenger = DataMgmtServer.get_messenger(self.user)
|
|
190
|
+
self.list_man = DataMgmtServer.get_picklist_manager(self.user)
|
|
191
|
+
self.pdf_report_man = DataMgmtServer.get_report_manager(self.user)
|
|
192
|
+
self.session_man = DataMgmtServer.get_session_manager(self.user)
|
|
193
|
+
self.user_man = DataMgmtServer.get_user_manager(self.user)
|
|
194
|
+
|
|
195
|
+
# Initialize record model managers.
|
|
196
|
+
self.rec_man = RecordModelManager(self.user)
|
|
43
197
|
self.inst_man = self.rec_man.instance_manager
|
|
44
198
|
self.rel_man = self.rec_man.relationship_manager
|
|
45
|
-
self.an_man =
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
199
|
+
self.an_man = self.rec_man.ancestor_manager
|
|
200
|
+
self.saved_vals_man = self.rec_man.last_saved_manager
|
|
201
|
+
|
|
202
|
+
# Initialize more complex classes from sapiopylib and sapiopycommons.
|
|
203
|
+
self.fnd_acc_man = FoundationAccessionManager(self.user)
|
|
204
|
+
self.dt_cache = DataTypeCacheManager(self.user)
|
|
205
|
+
self.rec_handler = RecordHandler(context)
|
|
206
|
+
self.callback = CallbackUtil(context)
|
|
207
|
+
self.directive = DirectiveUtil(context)
|
|
208
|
+
if context.eln_experiment is not None:
|
|
209
|
+
self.exp_handler = ExperimentHandler(context)
|
|
210
|
+
else:
|
|
211
|
+
self.exp_handler = None
|
|
212
|
+
if self.is_on_save_rule():
|
|
213
|
+
self.rule_handler = OnSaveRuleHandler(context)
|
|
214
|
+
elif self.is_eln_rule():
|
|
215
|
+
self.rule_handler = ElnRuleHandler(context)
|
|
216
|
+
else:
|
|
217
|
+
self.rule_handler = None
|
|
218
|
+
if self.is_custom():
|
|
219
|
+
self.custom_context = ProcessQueueContext(context)
|
|
220
|
+
else:
|
|
221
|
+
self.custom_context = None
|
|
222
|
+
|
|
223
|
+
# Set the default display types, titles, and messages for each type of exception that can display a message.
|
|
224
|
+
self.default_user_error_display_type = MessageDisplayType.TOASTER_WARNING
|
|
225
|
+
self.default_critical_error_display_type = MessageDisplayType.DISPLAY_ERROR
|
|
226
|
+
self.default_dialog_timeout_display_type = MessageDisplayType.OK_DIALOG
|
|
227
|
+
self.default_unexpected_error_display_type = MessageDisplayType.TOASTER_WARNING
|
|
228
|
+
|
|
229
|
+
self.default_user_error_title = ""
|
|
230
|
+
self.default_critical_error_title = ""
|
|
231
|
+
self.default_dialog_timeout_title = "Dialog Timeout"
|
|
232
|
+
self.default_unexpected_error_title = ""
|
|
233
|
+
|
|
234
|
+
self.default_dialog_timeout_message = ("You have remained idle for too long and this dialog has timed out. "
|
|
235
|
+
"Close and re-initiate it to continue.")
|
|
236
|
+
self.default_unexpected_error_message = ("Unexpected error occurred during webhook execution. Please contact "
|
|
237
|
+
"Sapio support.")
|
|
51
238
|
|
|
52
239
|
# Wrap the execution of each webhook in a try/catch. If an exception occurs, handle any special sapiopycommons
|
|
53
240
|
# exceptions. Otherwise, return a generic message stating that an error occurred.
|
|
54
241
|
try:
|
|
55
|
-
|
|
242
|
+
self.initialize(context)
|
|
243
|
+
result = self.execute(context)
|
|
244
|
+
if result is None:
|
|
245
|
+
raise SapioException("Your execute function returned a None result! Don't forget your return statement!")
|
|
246
|
+
return result
|
|
56
247
|
except SapioUserErrorException as e:
|
|
57
248
|
return self.handle_user_error_exception(e)
|
|
58
249
|
except SapioCriticalErrorException as e:
|
|
59
250
|
return self.handle_critical_error_exception(e)
|
|
251
|
+
except SapioUserCancelledException as e:
|
|
252
|
+
return self.handle_user_cancelled_exception(e)
|
|
253
|
+
except SapioDialogTimeoutException as e:
|
|
254
|
+
return self.handle_dialog_timeout_exception(e)
|
|
60
255
|
except Exception as e:
|
|
61
256
|
return self.handle_unexpected_exception(e)
|
|
62
257
|
|
|
258
|
+
def initialize(self, context: SapioWebhookContext) -> None:
|
|
259
|
+
"""
|
|
260
|
+
A function that can be optionally overridden by your webhooks to initialize additional instance variables,
|
|
261
|
+
or set up whatever else you wish to set up before the execute function is ran. Default behavior does nothing.
|
|
262
|
+
"""
|
|
263
|
+
pass
|
|
264
|
+
|
|
63
265
|
@abstractmethod
|
|
64
266
|
def execute(self, context: SapioWebhookContext) -> SapioWebhookResult:
|
|
65
267
|
"""
|
|
@@ -69,68 +271,138 @@ class CommonsWebhookHandler(AbstractWebhookHandler):
|
|
|
69
271
|
|
|
70
272
|
# CR-46153: Make CommonsWebhookHandler exception handling more easily overridable by splitting them out of
|
|
71
273
|
# the run method and into their own functions.
|
|
72
|
-
def
|
|
274
|
+
def handle_user_error_exception(self, e: SapioUserErrorException) -> SapioWebhookResult:
|
|
73
275
|
"""
|
|
74
|
-
Handle
|
|
75
|
-
|
|
76
|
-
|
|
276
|
+
Handle a SapioUserErrorException.
|
|
277
|
+
|
|
278
|
+
Default behavior returns a false result and the error message as display text in a webhook result.
|
|
77
279
|
|
|
78
|
-
:
|
|
280
|
+
:param e: The exception that was raised.
|
|
281
|
+
:return: A SapioWebhookResult to end the webhook session with.
|
|
79
282
|
"""
|
|
80
|
-
|
|
81
|
-
result = self.context.client_callback_result
|
|
283
|
+
result: SapioWebhookResult | None = self.handle_any_exception(e)
|
|
82
284
|
if result is not None:
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
return None
|
|
285
|
+
return result
|
|
286
|
+
self.log_error(traceback.format_exc())
|
|
287
|
+
self.handle_user_error_exception_extra(e)
|
|
87
288
|
|
|
88
|
-
|
|
289
|
+
display_type: MessageDisplayType = e.display_type if e.display_type else self.default_user_error_display_type
|
|
290
|
+
title: str = e.title if e.title is not None else self.default_user_error_title
|
|
291
|
+
return self._display_exception(e.msg, display_type, title)
|
|
292
|
+
|
|
293
|
+
def handle_user_error_exception_extra(self, e: SapioUserErrorException) -> None:
|
|
89
294
|
"""
|
|
90
|
-
|
|
91
|
-
|
|
295
|
+
An additional function that can be overridden to provide extra behavior when a SapioUserErrorException is thrown.
|
|
296
|
+
Default behavior does nothing.
|
|
297
|
+
"""
|
|
298
|
+
pass
|
|
299
|
+
|
|
300
|
+
def handle_critical_error_exception(self, e: SapioCriticalErrorException) -> SapioWebhookResult:
|
|
301
|
+
"""
|
|
302
|
+
Handle a SapioCriticalErrorException.
|
|
303
|
+
|
|
304
|
+
Default behavior makes a display_error client callback with the error message and returns a false result.
|
|
92
305
|
|
|
93
306
|
:param e: The exception that was raised.
|
|
94
|
-
:return: A SapioWebhookResult
|
|
307
|
+
:return: A SapioWebhookResult to end the webhook session with.
|
|
95
308
|
"""
|
|
96
309
|
result: SapioWebhookResult | None = self.handle_any_exception(e)
|
|
97
310
|
if result is not None:
|
|
98
311
|
return result
|
|
99
312
|
self.log_error(traceback.format_exc())
|
|
100
|
-
|
|
313
|
+
self.handle_critical_error_exception_extra(e)
|
|
101
314
|
|
|
102
|
-
|
|
315
|
+
display_type: MessageDisplayType = e.display_type if e.display_type else self.default_critical_error_display_type
|
|
316
|
+
title: str = e.title if e.title is not None else self.default_critical_error_title
|
|
317
|
+
return self._display_exception(e.msg, display_type, title)
|
|
318
|
+
|
|
319
|
+
def handle_critical_error_exception_extra(self, e: SapioCriticalErrorException) -> None:
|
|
320
|
+
"""
|
|
321
|
+
An additional function that can be overridden to provide extra behavior when a SapioCriticalErrorException is
|
|
322
|
+
thrown. Default behavior does nothing.
|
|
323
|
+
"""
|
|
324
|
+
pass
|
|
325
|
+
|
|
326
|
+
def handle_user_cancelled_exception(self, e: SapioUserCancelledException) -> SapioWebhookResult:
|
|
103
327
|
"""
|
|
104
|
-
Handle a
|
|
105
|
-
|
|
106
|
-
|
|
328
|
+
Handle a SapioUserCancelledException.
|
|
329
|
+
|
|
330
|
+
Default behavior simply ends the webhook session with a true result (since the user cancelling is a valid
|
|
331
|
+
action).
|
|
107
332
|
|
|
108
333
|
:param e: The exception that was raised.
|
|
109
|
-
:return: A SapioWebhookResult
|
|
334
|
+
:return: A SapioWebhookResult to end the webhook session with.
|
|
110
335
|
"""
|
|
111
336
|
result: SapioWebhookResult | None = self.handle_any_exception(e)
|
|
112
337
|
if result is not None:
|
|
113
338
|
return result
|
|
114
|
-
self.
|
|
115
|
-
|
|
116
|
-
|
|
339
|
+
self.handle_user_cancelled_exception_extra(e)
|
|
340
|
+
# FR-47390: Return a False result for user cancelled exceptions so that transactional webhooks cancel the
|
|
341
|
+
# commit.
|
|
342
|
+
return SapioWebhookResult(False)
|
|
343
|
+
|
|
344
|
+
def handle_user_cancelled_exception_extra(self, e: SapioUserCancelledException) -> None:
|
|
345
|
+
"""
|
|
346
|
+
An additional function that can be overridden to provide extra behavior when a SapioUserCancelledException is
|
|
347
|
+
thrown. Default behavior does nothing.
|
|
348
|
+
"""
|
|
349
|
+
pass
|
|
350
|
+
|
|
351
|
+
def handle_dialog_timeout_exception(self, e: SapioDialogTimeoutException) -> SapioWebhookResult:
|
|
352
|
+
"""
|
|
353
|
+
Handle a SapioDialogTimeoutException.
|
|
354
|
+
|
|
355
|
+
Default behavior displays an OK popup notifying the user that the dialog has timed out and returns a false
|
|
356
|
+
webhook result.
|
|
357
|
+
|
|
358
|
+
:param e: The exception that was raised.
|
|
359
|
+
:return: A SapioWebhookResult to end the webhook session with.
|
|
360
|
+
"""
|
|
361
|
+
result: SapioWebhookResult | None = self.handle_any_exception(e)
|
|
362
|
+
if result is not None:
|
|
363
|
+
return result
|
|
364
|
+
self.handle_dialog_timeout_exception_extra(e)
|
|
365
|
+
return self._display_exception(self.default_dialog_timeout_message,
|
|
366
|
+
self.default_dialog_timeout_display_type,
|
|
367
|
+
self.default_dialog_timeout_title)
|
|
368
|
+
|
|
369
|
+
def handle_dialog_timeout_exception_extra(self, e: SapioDialogTimeoutException) -> None:
|
|
370
|
+
"""
|
|
371
|
+
An additional function that can be overridden to provide extra behavior when a SapioDialogTimeoutException is
|
|
372
|
+
thrown. Default behavior does nothing.
|
|
373
|
+
"""
|
|
374
|
+
pass
|
|
117
375
|
|
|
118
376
|
def handle_unexpected_exception(self, e: Exception) -> SapioWebhookResult:
|
|
119
377
|
"""
|
|
120
|
-
Handle a generic exception which isn't
|
|
121
|
-
|
|
378
|
+
Handle a generic exception which isn't one of the handled Sapio exceptions.
|
|
379
|
+
|
|
380
|
+
Default behavior returns a false webhook result with a generic error message as display text informing the user
|
|
381
|
+
to contact Sapio support. Additionally, the stack trace of the exception that was thrown is logged to the
|
|
382
|
+
execution log for the webhook call in the system.
|
|
122
383
|
|
|
123
384
|
:param e: The exception that was raised.
|
|
124
|
-
:return: A SapioWebhookResult
|
|
385
|
+
:return: A SapioWebhookResult to end the webhook session with.
|
|
125
386
|
"""
|
|
126
387
|
result: SapioWebhookResult | None = self.handle_any_exception(e)
|
|
127
388
|
if result is not None:
|
|
128
389
|
return result
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
390
|
+
msg: str = traceback.format_exc()
|
|
391
|
+
self.log_error(msg, True)
|
|
392
|
+
# FR-47079: Also log all unexpected exception messages to the webhook execution log within the platform.
|
|
393
|
+
self.log_error_to_webhook_execution_log(msg, True)
|
|
394
|
+
self.handle_unexpected_exception_extra(e)
|
|
395
|
+
return self._display_exception(self.default_unexpected_error_message,
|
|
396
|
+
self.default_unexpected_error_display_type,
|
|
397
|
+
self.default_unexpected_error_title)
|
|
398
|
+
|
|
399
|
+
def handle_unexpected_exception_extra(self, e: Exception) -> None:
|
|
400
|
+
"""
|
|
401
|
+
An additional function that can be overridden to provide extra behavior when a generic exception is thrown.
|
|
402
|
+
Default behavior does nothing.
|
|
403
|
+
"""
|
|
404
|
+
pass
|
|
132
405
|
|
|
133
|
-
# noinspection PyMethodMayBeStatic,PyUnusedLocal
|
|
134
406
|
def handle_any_exception(self, e: Exception) -> SapioWebhookResult | None:
|
|
135
407
|
"""
|
|
136
408
|
An exception handler which runs regardless of the type of exception that was raised. Can be used to "rollback"
|
|
@@ -138,38 +410,129 @@ class CommonsWebhookHandler(AbstractWebhookHandler):
|
|
|
138
410
|
|
|
139
411
|
:param e: The exception that was raised.
|
|
140
412
|
:return: An optional SapioWebhookResult. May return a custom message to the client that wouldn't have been
|
|
141
|
-
sent by one of the normal exception handlers, or may return None if no result needs returned.
|
|
413
|
+
sent by one of the normal exception handlers, or may return None if no result needs returned. If a result is
|
|
414
|
+
returned, then the default behavior of other exception handlers is skipped.
|
|
142
415
|
"""
|
|
143
|
-
|
|
416
|
+
pass
|
|
417
|
+
|
|
418
|
+
def display_message(self, msg: str, display_type: MessageDisplayType, title: str = "") -> bool:
|
|
419
|
+
"""
|
|
420
|
+
Display a message to the user. The form that the message takes depends on the display type.
|
|
421
|
+
|
|
422
|
+
:param msg: The message to display to the user.
|
|
423
|
+
:param display_type: The manner in which the message should be displayed.
|
|
424
|
+
:param title: If the display type is able to have a title, this is the title that will be displayed.
|
|
425
|
+
:return: True if the message was displayed. False if the message could not be displayed (because this
|
|
426
|
+
webhook can't send client callbacks).
|
|
427
|
+
"""
|
|
428
|
+
if not self.can_send_client_callback():
|
|
429
|
+
return False
|
|
430
|
+
if display_type == MessageDisplayType.TOASTER_SUCCESS:
|
|
431
|
+
self.callback.toaster_popup(msg, title, PopupType.Success)
|
|
432
|
+
elif display_type == MessageDisplayType.TOASTER_INFO:
|
|
433
|
+
self.callback.toaster_popup(msg, title, PopupType.Info)
|
|
434
|
+
elif display_type == MessageDisplayType.TOASTER_WARNING:
|
|
435
|
+
self.callback.toaster_popup(msg, title, PopupType.Warning)
|
|
436
|
+
elif display_type == MessageDisplayType.TOASTER_ERROR:
|
|
437
|
+
self.callback.toaster_popup(msg, title, PopupType.Error)
|
|
438
|
+
elif display_type == MessageDisplayType.OK_DIALOG:
|
|
439
|
+
self.callback.ok_dialog(title, msg)
|
|
440
|
+
elif display_type == MessageDisplayType.DISPLAY_INFO:
|
|
441
|
+
self.callback.display_info(msg)
|
|
442
|
+
elif display_type == MessageDisplayType.DISPLAY_WARNING:
|
|
443
|
+
self.callback.display_warning(msg)
|
|
444
|
+
elif display_type == MessageDisplayType.DISPLAY_ERROR:
|
|
445
|
+
self.callback.display_error(msg)
|
|
446
|
+
return True
|
|
447
|
+
|
|
448
|
+
def _display_exception(self, msg: str, display_type: MessageDisplayType, title: str) -> SapioWebhookResult:
|
|
449
|
+
"""
|
|
450
|
+
Display an exception message to the user and return a webhook result to end the webhook invocation.
|
|
451
|
+
This handles the cases where the webhook invocation type is incapable of sending client callbacks and must
|
|
452
|
+
instead return the message in the webhook result, and the case where the display type is an OK dialog, which
|
|
453
|
+
may potentially cause a dialog timeout exception.
|
|
454
|
+
"""
|
|
455
|
+
# If the display type is an OK dialog, then we need to handle the dialog timeout exception that could be thrown.
|
|
456
|
+
try:
|
|
457
|
+
# Set the dialog timeout to something low as to not hog the connection.
|
|
458
|
+
self.callback.set_dialog_timeout(60)
|
|
459
|
+
# If this invocation type can't send client callbacks, fallback to sending the message in the result.
|
|
460
|
+
if self.display_message(msg, display_type, title):
|
|
461
|
+
return SapioWebhookResult(False)
|
|
462
|
+
return SapioWebhookResult(False, display_text=msg)
|
|
463
|
+
except SapioDialogTimeoutException:
|
|
464
|
+
return SapioWebhookResult(False)
|
|
144
465
|
|
|
145
466
|
def log_info(self, msg: str) -> None:
|
|
146
467
|
"""
|
|
147
|
-
Write an info message to the log. Log destination is stdout. This message will
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
def log_error(self, msg: str) -> None:
|
|
160
|
-
"""
|
|
161
|
-
Write an error message to the log. Log destination is stderr. This message will be prepended with the user's
|
|
162
|
-
username and the experiment ID of the experiment they are in, if any.
|
|
163
|
-
"""
|
|
164
|
-
exp_id = None
|
|
165
|
-
if self.context.eln_experiment is not None:
|
|
166
|
-
exp_id = self.context.eln_experiment.notebook_experiment_id
|
|
167
|
-
# CR-46333: Add the user's group to the logging message.
|
|
168
|
-
user = self.context.user
|
|
169
|
-
username = user.username
|
|
170
|
-
group_name = user.session_additional_data.current_group_name
|
|
468
|
+
Write an info message to the webhook server log. Log destination is stdout. This message will include
|
|
469
|
+
information about the user's group, their location in the system, the webhook invocation type, and other
|
|
470
|
+
important information that can be gathered from the context that is useful for debugging.
|
|
471
|
+
"""
|
|
472
|
+
self.logger.info(self._format_log(msg, "log_info call"))
|
|
473
|
+
|
|
474
|
+
def log_error(self, msg: str, is_exception: bool = False) -> None:
|
|
475
|
+
"""
|
|
476
|
+
Write an info message to the webhook server log. Log destination is stdout. This message will include
|
|
477
|
+
information about the user's group, their location in the system, the webhook invocation type, and other
|
|
478
|
+
important information that can be gathered from the context that is useful for debugging.
|
|
479
|
+
"""
|
|
171
480
|
# PR-46209: Use logger.error instead of logger.info when logging errors.
|
|
172
|
-
self.logger.error(
|
|
481
|
+
self.logger.error(self._format_log(msg, "log_error call", is_exception))
|
|
482
|
+
|
|
483
|
+
def log_error_to_webhook_execution_log(self, msg: str, is_exception: bool = False) -> None:
|
|
484
|
+
"""
|
|
485
|
+
Write an error message to the platform's webhook execution log. This can be reviewed by navigating to the
|
|
486
|
+
webhook configuration where the webhook that called this function is defined and clicking the "View Log"
|
|
487
|
+
button. From there, select one of the rows for the webhook executions and click "Download Log" from the right
|
|
488
|
+
side table.
|
|
489
|
+
"""
|
|
490
|
+
msg = self._format_log(msg, "Error occurred during webhook execution.", is_exception)
|
|
491
|
+
self.messenger.log_message(VeloxLogMessage(msg, VeloxLogLevel.ERROR, self.__class__.__name__))
|
|
492
|
+
|
|
493
|
+
def _format_log(self, msg: str, prefix: str | None = None, is_exception: bool = False) -> str:
|
|
494
|
+
"""
|
|
495
|
+
Given a message to log, populate it with some metadata about this particular webhook execution, including
|
|
496
|
+
the group of the user and the invocation type of the webhook call.
|
|
497
|
+
"""
|
|
498
|
+
# Start the message with the provided prefix.
|
|
499
|
+
message: str = prefix + "\n" if prefix else ""
|
|
500
|
+
|
|
501
|
+
# Construct a summary of the current state of this webhook.
|
|
502
|
+
message += f"{WebhookStateSummary(self, is_exception)}\n"
|
|
503
|
+
|
|
504
|
+
# End the message with the provided msg parameter.
|
|
505
|
+
message += msg
|
|
506
|
+
return message
|
|
507
|
+
|
|
508
|
+
@property
|
|
509
|
+
def start_time(self) -> float:
|
|
510
|
+
"""
|
|
511
|
+
:return: The time that this webhook was invoked, represented in seconds. This time comes from a performance
|
|
512
|
+
counter and is not guaranteed to correspond to a date. Only use in comparison to other performance counters.
|
|
513
|
+
"""
|
|
514
|
+
return self._start_time
|
|
515
|
+
|
|
516
|
+
@property
|
|
517
|
+
def start_time_millis(self) -> int:
|
|
518
|
+
"""
|
|
519
|
+
:return: The epoch timestamp in milliseconds for the time that this webhook was invoked.
|
|
520
|
+
"""
|
|
521
|
+
return self._start_time_epoch
|
|
522
|
+
|
|
523
|
+
def elapsed_time(self) -> float:
|
|
524
|
+
"""
|
|
525
|
+
:return: The number of seconds that have elapsed since this webhook was invoked to the time that this function
|
|
526
|
+
is called. Measures using a performance counter to a high degree of accuracy.
|
|
527
|
+
"""
|
|
528
|
+
return time.perf_counter() - self._start_time
|
|
529
|
+
|
|
530
|
+
def elapsed_time_millis(self) -> int:
|
|
531
|
+
"""
|
|
532
|
+
:return: The number of milliseconds that have elapsed since this webhook was invoked to the time that this
|
|
533
|
+
function is called.
|
|
534
|
+
"""
|
|
535
|
+
return TimeUtil.now_in_millis() - self._start_time_epoch
|
|
173
536
|
|
|
174
537
|
def is_main_toolbar(self) -> bool:
|
|
175
538
|
"""
|
|
@@ -245,3 +608,183 @@ class CommonsWebhookHandler(AbstractWebhookHandler):
|
|
|
245
608
|
:return: True if this endpoint was invoked as a scheduled action.
|
|
246
609
|
"""
|
|
247
610
|
return self.context.end_point_type == WebhookEndpointType.SCHEDULEDPLUGIN
|
|
611
|
+
|
|
612
|
+
def is_action_button_field(self) -> bool:
|
|
613
|
+
"""
|
|
614
|
+
:return: True if this endpoint was invoked as an action button field.
|
|
615
|
+
"""
|
|
616
|
+
return self.context.end_point_type == WebhookEndpointType.ACTIONDATAFIELD
|
|
617
|
+
|
|
618
|
+
def is_action_text_field(self) -> bool:
|
|
619
|
+
"""
|
|
620
|
+
:return: True if this endpoint was invoked as an action text field.
|
|
621
|
+
"""
|
|
622
|
+
return self.context.end_point_type == WebhookEndpointType.ACTION_TEXT_FIELD
|
|
623
|
+
|
|
624
|
+
def is_custom(self) -> bool:
|
|
625
|
+
"""
|
|
626
|
+
:return: True if this endpoint was invoked from a custom point, such as a custom queue.
|
|
627
|
+
"""
|
|
628
|
+
return self.context.end_point_type == WebhookEndpointType.CUSTOM
|
|
629
|
+
|
|
630
|
+
def is_calendar_event_click_handler(self) -> bool:
|
|
631
|
+
"""
|
|
632
|
+
:return: True if this endpoint was invoked from a calendar event click handler.
|
|
633
|
+
"""
|
|
634
|
+
return self.context.end_point_type == WebhookEndpointType.CALENDAR_EVENT_CLICK_HANDLER
|
|
635
|
+
|
|
636
|
+
def is_eln_menu_grabber(self) -> bool:
|
|
637
|
+
"""
|
|
638
|
+
:return: True if this endpoint was invoked as a notebook entry grabber.
|
|
639
|
+
"""
|
|
640
|
+
return self.context.end_point_type == WebhookEndpointType.NOTEBOOKEXPERIMENTGRABBER
|
|
641
|
+
|
|
642
|
+
def is_conversation_bot(self) -> bool:
|
|
643
|
+
"""
|
|
644
|
+
:return: True if this endpoint was invoked as from a conversation bot.
|
|
645
|
+
"""
|
|
646
|
+
return self.context.end_point_type == WebhookEndpointType.CONVERSATION_BOT
|
|
647
|
+
|
|
648
|
+
def is_multi_data_type_table_toolbar(self) -> bool:
|
|
649
|
+
"""
|
|
650
|
+
:return: True if this endpoint was invoked as a multi data type table toolbar button.
|
|
651
|
+
"""
|
|
652
|
+
return self.context.end_point_type == WebhookEndpointType.REPORTTOOLBAR
|
|
653
|
+
|
|
654
|
+
def can_send_client_callback(self) -> bool:
|
|
655
|
+
"""
|
|
656
|
+
:return: Whether client callbacks and directives can be sent from this webhook's endpoint type.
|
|
657
|
+
"""
|
|
658
|
+
return self.context.is_client_callback_available
|
|
659
|
+
|
|
660
|
+
|
|
661
|
+
# FR-47390: Move the gathering of webhook information out of log_error_to_webhook_execution_log and into its own class.
|
|
662
|
+
class WebhookStateSummary:
|
|
663
|
+
"""
|
|
664
|
+
A class that summarizes the state of a webhook at the time that it is created. This class is useful for logging
|
|
665
|
+
information about the webhook invocation to the execution log.
|
|
666
|
+
"""
|
|
667
|
+
username: str
|
|
668
|
+
"""The username of the user who invoked the webhook."""
|
|
669
|
+
user_group: str
|
|
670
|
+
"""The group that the user is currently in."""
|
|
671
|
+
start_timestamp: int
|
|
672
|
+
"""The epoch timestamp in milliseconds for when the webhook was invoked."""
|
|
673
|
+
start_utc_time: str
|
|
674
|
+
"""The time that the webhook was invoked in UTC."""
|
|
675
|
+
start_server_time: str | None
|
|
676
|
+
"""The time that the webhook was invoked on the server, if the TimeUtil class has a default timezone set."""
|
|
677
|
+
start_user_time: str
|
|
678
|
+
"""The time that the webhook was invoked for the user, adjusted for their timezone."""
|
|
679
|
+
timestamp: int
|
|
680
|
+
"""The current epoch timestamp in milliseconds."""
|
|
681
|
+
utc_time: str
|
|
682
|
+
"""The current time in UTC."""
|
|
683
|
+
server_time: str | None
|
|
684
|
+
"""The current time on the webhook server, if the TimeUtil class has a default timezone set."""
|
|
685
|
+
user_time: str
|
|
686
|
+
"""The current time for the user, adjusted for their timezone."""
|
|
687
|
+
invocation_type: str
|
|
688
|
+
"""The type of endpoint that this webhook was invoked from."""
|
|
689
|
+
class_name: str
|
|
690
|
+
"""The name of the class that this webhook is an instance of."""
|
|
691
|
+
link: str | None
|
|
692
|
+
"""A link to the location that the webhook was invoked from, if applicable."""
|
|
693
|
+
exc_summary: str | None
|
|
694
|
+
"""A summary of the exception that occurred, if this summary is being created for an exception."""
|
|
695
|
+
|
|
696
|
+
def __init__(self, webhook: CommonsWebhookHandler, summarize_exception: bool = False):
|
|
697
|
+
"""
|
|
698
|
+
:param webhook: The webhook that this summary is being created for.
|
|
699
|
+
:param summarize_exception: If true, then this summary will include information about the most recent exception
|
|
700
|
+
that occurred during the execution of the webhook.
|
|
701
|
+
"""
|
|
702
|
+
# User information.
|
|
703
|
+
self.username = webhook.user.username
|
|
704
|
+
self.user_group = webhook.group_name
|
|
705
|
+
|
|
706
|
+
# Time information.
|
|
707
|
+
fmt: str = "%Y-%m-%d %H:%M:%S.%f"
|
|
708
|
+
self.start_timestamp = webhook.start_time_millis
|
|
709
|
+
self.start_utc_time = TimeUtil.millis_to_format(self.start_timestamp, fmt, "UTC")
|
|
710
|
+
self.start_server_time = None
|
|
711
|
+
self.start_user_time = TimeUtil.millis_to_format(self.start_timestamp, fmt, webhook.user_utc_offset_seconds)
|
|
712
|
+
|
|
713
|
+
self.timestamp = TimeUtil.now_in_millis()
|
|
714
|
+
self.utc_time = TimeUtil.millis_to_format(self.timestamp, fmt, "UTC")
|
|
715
|
+
self.server_time = None
|
|
716
|
+
self.user_time = TimeUtil.millis_to_format(self.timestamp, fmt, webhook.user_utc_offset_seconds)
|
|
717
|
+
|
|
718
|
+
if TimeUtil.get_default_timezone():
|
|
719
|
+
self.start_server_time = TimeUtil.millis_to_format(self.start_timestamp, fmt)
|
|
720
|
+
self.server_time = TimeUtil.millis_to_format(self.timestamp, fmt)
|
|
721
|
+
|
|
722
|
+
# Webhook invocation information.
|
|
723
|
+
self.invocation_type = webhook.context.end_point_type.display_name
|
|
724
|
+
self.class_name = webhook.__class__.__name__
|
|
725
|
+
|
|
726
|
+
# User location information.
|
|
727
|
+
self.link = None
|
|
728
|
+
context = webhook.context
|
|
729
|
+
navigator = SapioNavigationLinker(context)
|
|
730
|
+
if context.eln_experiment is not None:
|
|
731
|
+
self.link = navigator.experiment(context.eln_experiment)
|
|
732
|
+
elif context.base_data_record:
|
|
733
|
+
self.link = navigator.data_record(context.base_data_record)
|
|
734
|
+
elif context.data_record and not context.data_record_list:
|
|
735
|
+
self.link = navigator.data_record(context.data_record)
|
|
736
|
+
|
|
737
|
+
# If this is logging an exception, get the system's info on the most recent exception and produce a summary.
|
|
738
|
+
self.exc_summary = None
|
|
739
|
+
if summarize_exception:
|
|
740
|
+
exc_type, exc_value, exc_traceback = sys.exc_info()
|
|
741
|
+
|
|
742
|
+
# If this is a SapioServerException, then it can contain json with the exception message from the server.
|
|
743
|
+
# This provides a more useful summary than args[0] does, so use it instead.
|
|
744
|
+
exception_name: str = exc_type.__name__
|
|
745
|
+
if exception_name == "SapioServerException":
|
|
746
|
+
# Sometimes the SapioServerException is HTML instead of JSON. If that's the case, just try/catch the
|
|
747
|
+
# failure to parse the JSON and continue to use args[0] as the exception message.
|
|
748
|
+
try:
|
|
749
|
+
exc_str: str = str(exc_value)
|
|
750
|
+
exception_msg = json.loads(exc_str[exc_str.find("{"):]).get("message")
|
|
751
|
+
except Exception:
|
|
752
|
+
exception_msg = exc_value.args[0]
|
|
753
|
+
else:
|
|
754
|
+
# For all other exceptions, assume that the first argument is a message.
|
|
755
|
+
exception_msg = exc_value.args[0]
|
|
756
|
+
|
|
757
|
+
self.exc_summary = f"{exception_name}: {exception_msg}"
|
|
758
|
+
del (exc_type, exc_value, exc_traceback)
|
|
759
|
+
|
|
760
|
+
def __str__(self) -> str:
|
|
761
|
+
message: str = ""
|
|
762
|
+
|
|
763
|
+
# Record the time that the webhook was started and this state was created.
|
|
764
|
+
message += "Webhook Invocation Time:\n"
|
|
765
|
+
message += f"\tUTC: {self.start_utc_time}\n"
|
|
766
|
+
if TimeUtil.get_default_timezone() is not None:
|
|
767
|
+
message += f"\tServer: {self.start_server_time}\n"
|
|
768
|
+
message += f"\tUser: {self.start_user_time}\n"
|
|
769
|
+
|
|
770
|
+
message += "Current Time:\n"
|
|
771
|
+
message += f"\tUTC: {self.utc_time}\n"
|
|
772
|
+
if TimeUtil.get_default_timezone() is not None:
|
|
773
|
+
message += f"\tServer: {self.server_time}\n"
|
|
774
|
+
message += f"\tUser: {self.user_time}\n"
|
|
775
|
+
|
|
776
|
+
# Record information about the user and how the webhook was invoked.
|
|
777
|
+
message += f"Username: {self.username}\n"
|
|
778
|
+
message += f"User group: {self.user_group}\n"
|
|
779
|
+
message += f"Webhook invocation type: {self.invocation_type}\n"
|
|
780
|
+
message += f"Class name: {self.class_name}\n"
|
|
781
|
+
|
|
782
|
+
# If we're able to, provide a link to the location that the error occurred at.
|
|
783
|
+
if self.link:
|
|
784
|
+
message += f"User location: {self.link}\n"
|
|
785
|
+
|
|
786
|
+
# If this state summary is for an exception, include the exception summary.
|
|
787
|
+
if self.exc_summary:
|
|
788
|
+
message += f"{self.exc_summary}\n"
|
|
789
|
+
|
|
790
|
+
return message
|