sapiopycommons 2025.2.6a421__py3-none-any.whl → 2025.2.7a424__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.

Files changed (27) hide show
  1. sapiopycommons/callbacks/callback_util.py +363 -1217
  2. sapiopycommons/chem/Molecules.py +2 -0
  3. sapiopycommons/datatype/data_fields.py +1 -1
  4. sapiopycommons/eln/experiment_handler.py +1 -2
  5. sapiopycommons/eln/experiment_report_util.py +7 -7
  6. sapiopycommons/files/file_bridge.py +0 -76
  7. sapiopycommons/files/file_bridge_handler.py +110 -325
  8. sapiopycommons/files/file_data_handler.py +2 -2
  9. sapiopycommons/files/file_validator.py +5 -6
  10. sapiopycommons/flowcyto/flow_cyto.py +1 -1
  11. sapiopycommons/general/accession_service.py +1 -1
  12. sapiopycommons/general/aliases.py +27 -40
  13. sapiopycommons/general/audit_log.py +2 -2
  14. sapiopycommons/general/custom_report_util.py +1 -24
  15. sapiopycommons/general/exceptions.py +2 -41
  16. sapiopycommons/multimodal/multimodal.py +0 -1
  17. sapiopycommons/processtracking/custom_workflow_handler.py +3 -3
  18. sapiopycommons/recordmodel/record_handler.py +3 -5
  19. sapiopycommons/webhook/webhook_handlers.py +55 -445
  20. {sapiopycommons-2025.2.6a421.dist-info → sapiopycommons-2025.2.7a424.dist-info}/METADATA +1 -1
  21. {sapiopycommons-2025.2.6a421.dist-info → sapiopycommons-2025.2.7a424.dist-info}/RECORD +23 -27
  22. sapiopycommons/customreport/auto_pagers.py +0 -270
  23. sapiopycommons/elain/__init__.py +0 -0
  24. sapiopycommons/elain/tool_of_tools.py +0 -510
  25. sapiopycommons/general/directive_util.py +0 -86
  26. {sapiopycommons-2025.2.6a421.dist-info → sapiopycommons-2025.2.7a424.dist-info}/WHEEL +0 -0
  27. {sapiopycommons-2025.2.6a421.dist-info → sapiopycommons-2025.2.7a424.dist-info}/licenses/LICENSE +0 -0
@@ -1,49 +1,28 @@
1
- from __future__ import annotations
2
-
3
- import json
4
- import sys
5
- import time
6
1
  import traceback
7
2
  from abc import abstractmethod
8
3
  from logging import Logger
9
4
 
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
5
  from sapiopylib.rest.DataMgmtService import DataMgmtServer
15
6
  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
7
  from sapiopylib.rest.User import SapioUser
23
8
  from sapiopylib.rest.WebhookService import AbstractWebhookHandler
24
9
  from sapiopylib.rest.pojo.Message import VeloxLogMessage, VeloxLogLevel
25
- from sapiopylib.rest.pojo.webhook.ClientCallbackRequest import PopupType
26
10
  from sapiopylib.rest.pojo.webhook.WebhookContext import SapioWebhookContext
27
11
  from sapiopylib.rest.pojo.webhook.WebhookEnums import WebhookEndpointType
28
12
  from sapiopylib.rest.pojo.webhook.WebhookResult import SapioWebhookResult
29
13
  from sapiopylib.rest.utils.DataTypeCacheManager import DataTypeCacheManager
30
- from sapiopylib.rest.utils.FoundationAccessioning import FoundationAccessionManager
31
14
  from sapiopylib.rest.utils.recordmodel.RecordModelManager import RecordModelManager, RecordModelInstanceManager, \
32
15
  RecordModelRelationshipManager
33
16
  from sapiopylib.rest.utils.recordmodel.ancestry import RecordModelAncestorManager
34
- from sapiopylib.rest.utils.recordmodel.last_saved import LastSavedValueManager
35
17
 
36
18
  from sapiopycommons.callbacks.callback_util import CallbackUtil
37
19
  from sapiopycommons.eln.experiment_handler import ExperimentHandler
38
- from sapiopycommons.general.directive_util import DirectiveUtil
39
20
  from sapiopycommons.general.exceptions import SapioUserErrorException, SapioCriticalErrorException, \
40
- SapioUserCancelledException, SapioException, SapioDialogTimeoutException, MessageDisplayType
21
+ SapioUserCancelledException, SapioException, SapioDialogTimeoutException
41
22
  from sapiopycommons.general.sapio_links import SapioNavigationLinker
42
- from sapiopycommons.general.time_util import TimeUtil
43
23
  from sapiopycommons.recordmodel.record_handler import RecordHandler
44
24
  from sapiopycommons.rules.eln_rule_handler import ElnRuleHandler
45
25
  from sapiopycommons.rules.on_save_rule_handler import OnSaveRuleHandler
46
- from sapiopycommons.webhook.webhook_context import ProcessQueueContext
47
26
 
48
27
 
49
28
  # FR-46064 - Initial port of PyWebhookUtils to sapiopycommons.
@@ -55,156 +34,37 @@ class CommonsWebhookHandler(AbstractWebhookHandler):
55
34
  """
56
35
  logger: Logger
57
36
 
58
- _start_time: float
59
- _start_time_epoch: int
60
-
61
37
  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
38
  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."""
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
39
 
40
+ dr_man: DataRecordManager
101
41
  rec_man: RecordModelManager
102
- """The record model manager. Used for committing record model changes to the system."""
103
42
  inst_man: RecordModelInstanceManager
104
- """The record model instance manager. Used for adding record models to the cache."""
105
43
  rel_man: RecordModelRelationshipManager
106
- """The record model relationship manager. Used for loading parent/child and side-link relationships between record
107
- models."""
108
44
  # FR-46329: Add the ancestor manager to CommonsWebhookHandler.
109
45
  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
46
 
114
47
  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
48
  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
49
  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
50
  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
51
  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."""
163
52
 
164
53
  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.
170
- self.context = context
171
-
172
- # Save the user and commonly sought after user information.
173
54
  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
55
+ self.context = context
176
56
 
177
- # Get the logger from the user.
178
57
  self.logger = self.user.logger
179
58
 
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)
185
59
  self.dr_man = context.data_record_manager
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
60
  self.rec_man = RecordModelManager(self.user)
197
61
  self.inst_man = self.rec_man.instance_manager
198
62
  self.rel_man = self.rec_man.relationship_manager
199
- self.an_man = self.rec_man.ancestor_manager
200
- self.saved_vals_man = self.rec_man.last_saved_manager
63
+ self.an_man = RecordModelAncestorManager(self.rec_man)
201
64
 
202
- # Initialize more complex classes from sapiopylib and sapiopycommons.
203
- self.fnd_acc_man = FoundationAccessionManager(self.user)
204
65
  self.dt_cache = DataTypeCacheManager(self.user)
205
66
  self.rec_handler = RecordHandler(context)
206
67
  self.callback = CallbackUtil(context)
207
- self.directive = DirectiveUtil(context)
208
68
  if context.eln_experiment is not None:
209
69
  self.exp_handler = ExperimentHandler(context)
210
70
  else:
@@ -215,26 +75,6 @@ class CommonsWebhookHandler(AbstractWebhookHandler):
215
75
  self.rule_handler = ElnRuleHandler(context)
216
76
  else:
217
77
  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.")
238
78
 
239
79
  # Wrap the execution of each webhook in a try/catch. If an exception occurs, handle any special sapiopycommons
240
80
  # exceptions. Otherwise, return a generic message stating that an error occurred.
@@ -284,18 +124,7 @@ class CommonsWebhookHandler(AbstractWebhookHandler):
284
124
  if result is not None:
285
125
  return result
286
126
  self.log_error(traceback.format_exc())
287
- self.handle_user_error_exception_extra(e)
288
-
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:
294
- """
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
127
+ return SapioWebhookResult(False, display_text=e.args[0])
299
128
 
300
129
  def handle_critical_error_exception(self, e: SapioCriticalErrorException) -> SapioWebhookResult:
301
130
  """
@@ -310,18 +139,13 @@ class CommonsWebhookHandler(AbstractWebhookHandler):
310
139
  if result is not None:
311
140
  return result
312
141
  self.log_error(traceback.format_exc())
313
- self.handle_critical_error_exception_extra(e)
314
-
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
142
+ # This error can be thrown by endpoints that can't send client callbacks. If that happens, fall back onto
143
+ # sending display text instead.
144
+ if self.can_send_client_callback():
145
+ self.callback.display_error(e.args[0])
146
+ else:
147
+ return SapioWebhookResult(False, e.args[0])
148
+ return SapioWebhookResult(False)
325
149
 
326
150
  def handle_user_cancelled_exception(self, e: SapioUserCancelledException) -> SapioWebhookResult:
327
151
  """
@@ -336,17 +160,7 @@ class CommonsWebhookHandler(AbstractWebhookHandler):
336
160
  result: SapioWebhookResult | None = self.handle_any_exception(e)
337
161
  if result is not None:
338
162
  return result
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
163
+ return SapioWebhookResult(True)
350
164
 
351
165
  def handle_dialog_timeout_exception(self, e: SapioDialogTimeoutException) -> SapioWebhookResult:
352
166
  """
@@ -361,17 +175,15 @@ class CommonsWebhookHandler(AbstractWebhookHandler):
361
175
  result: SapioWebhookResult | None = self.handle_any_exception(e)
362
176
  if result is not None:
363
177
  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
178
+ # This dialog could time out too! Ignore it if it does.
179
+ # No need to check can_send_client_callback() here, as this exception should only be thrown by endpoints that
180
+ # are capable of sending callbacks.
181
+ try:
182
+ self.callback.ok_dialog("Notice", "You have remained idle for too long and this dialog has timed out. "
183
+ "Close and re-initiate it to continue.")
184
+ except SapioDialogTimeoutException:
185
+ pass
186
+ return SapioWebhookResult(False)
375
187
 
376
188
  def handle_unexpected_exception(self, e: Exception) -> SapioWebhookResult:
377
189
  """
@@ -388,21 +200,13 @@ class CommonsWebhookHandler(AbstractWebhookHandler):
388
200
  if result is not None:
389
201
  return result
390
202
  msg: str = traceback.format_exc()
391
- self.log_error(msg, True)
203
+ self.log_error(msg)
392
204
  # 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
205
+ self.log_error_to_webhook_execution_log(msg)
206
+ return SapioWebhookResult(False, display_text="Unexpected error occurred during webhook execution. "
207
+ "Please contact Sapio support.")
405
208
 
209
+ # noinspection PyMethodMayBeStatic,PyUnusedLocal
406
210
  def handle_any_exception(self, e: Exception) -> SapioWebhookResult | None:
407
211
  """
408
212
  An exception handler which runs regardless of the type of exception that was raised. Can be used to "rollback"
@@ -413,55 +217,7 @@ class CommonsWebhookHandler(AbstractWebhookHandler):
413
217
  sent by one of the normal exception handlers, or may return None if no result needs returned. If a result is
414
218
  returned, then the default behavior of other exception handlers is skipped.
415
219
  """
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)
220
+ return None
465
221
 
466
222
  def log_info(self, msg: str) -> None:
467
223
  """
@@ -471,69 +227,55 @@ class CommonsWebhookHandler(AbstractWebhookHandler):
471
227
  """
472
228
  self.logger.info(self._format_log(msg, "log_info call"))
473
229
 
474
- def log_error(self, msg: str, is_exception: bool = False) -> None:
230
+ def log_error(self, msg: str) -> None:
475
231
  """
476
232
  Write an info message to the webhook server log. Log destination is stdout. This message will include
477
233
  information about the user's group, their location in the system, the webhook invocation type, and other
478
234
  important information that can be gathered from the context that is useful for debugging.
479
235
  """
480
236
  # PR-46209: Use logger.error instead of logger.info when logging errors.
481
- self.logger.error(self._format_log(msg, "log_error call", is_exception))
237
+ self.logger.error(self._format_log(msg, "log_error call"))
482
238
 
483
- def log_error_to_webhook_execution_log(self, msg: str, is_exception: bool = False) -> None:
239
+ def log_error_to_webhook_execution_log(self, msg: str) -> None:
484
240
  """
485
241
  Write an error message to the platform's webhook execution log. This can be reviewed by navigating to the
486
242
  webhook configuration where the webhook that called this function is defined and clicking the "View Log"
487
243
  button. From there, select one of the rows for the webhook executions and click "Download Log" from the right
488
244
  side table.
489
245
  """
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__))
246
+ messenger = DataMgmtServer.get_messenger(self.user)
247
+ messenger.log_message(VeloxLogMessage(message=self._format_log(msg, "Error occurred during webhook execution."),
248
+ log_level=VeloxLogLevel.ERROR,
249
+ originating_class=self.__class__.__name__))
492
250
 
493
- def _format_log(self, msg: str, prefix: str | None = None, is_exception: bool = False) -> str:
251
+ def _format_log(self, msg: str, prefix: str | None = None) -> str:
494
252
  """
495
253
  Given a message to log, populate it with some metadata about this particular webhook execution, including
496
254
  the group of the user and the invocation type of the webhook call.
497
255
  """
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"
256
+ # If we're able to, provide a link to the location that the error occurred at.
257
+ navigator = SapioNavigationLinker(self.context)
258
+ if self.context.eln_experiment is not None:
259
+ link = navigator.experiment(self.context.eln_experiment)
260
+ elif self.context.data_record and not self.context.data_record_list:
261
+ link = navigator.data_record(self.context.data_record)
262
+ elif self.context.base_data_record:
263
+ link = navigator.data_record(self.context.base_data_record)
264
+ else:
265
+ link = None
503
266
 
504
- # End the message with the provided msg parameter.
267
+ message: str = ""
268
+ if prefix:
269
+ message += prefix + "\n"
270
+ message += f"Webhook invocation type: {self.context.end_point_type.display_name}\n"
271
+ message += f"Username: {self.user.username}\n"
272
+ # CR-46333: Add the user's group to the logging message.
273
+ message += f"User group: {self.user.session_additional_data.current_group_name}\n"
274
+ if link:
275
+ message += f"User location: {link}\n"
505
276
  message += msg
506
277
  return message
507
278
 
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
536
-
537
279
  def is_main_toolbar(self) -> bool:
538
280
  """
539
281
  :return: True if this endpoint was invoked as a main toolbar button.
@@ -656,135 +398,3 @@ class CommonsWebhookHandler(AbstractWebhookHandler):
656
398
  :return: Whether client callbacks and directives can be sent from this webhook's endpoint type.
657
399
  """
658
400
  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
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: sapiopycommons
3
- Version: 2025.2.6a421
3
+ Version: 2025.2.7a424
4
4
  Summary: Official Sapio Python API Utilities Package
5
5
  Project-URL: Homepage, https://github.com/sapiosciences
6
6
  Author-email: Jonathan Steck <jsteck@sapiosciences.com>, Yechen Qiao <yqiao@sapiosciences.com>