sapiopycommons 2025.4.8a473__py3-none-any.whl → 2025.4.9a150__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 (42) hide show
  1. sapiopycommons/callbacks/callback_util.py +392 -1262
  2. sapiopycommons/callbacks/field_builder.py +0 -2
  3. sapiopycommons/chem/Molecules.py +2 -0
  4. sapiopycommons/customreport/term_builder.py +1 -1
  5. sapiopycommons/datatype/attachment_util.py +2 -4
  6. sapiopycommons/datatype/data_fields.py +1 -23
  7. sapiopycommons/eln/experiment_handler.py +279 -933
  8. sapiopycommons/eln/experiment_report_util.py +10 -15
  9. sapiopycommons/eln/plate_designer.py +59 -159
  10. sapiopycommons/files/file_bridge.py +0 -76
  11. sapiopycommons/files/file_bridge_handler.py +110 -325
  12. sapiopycommons/files/file_data_handler.py +2 -2
  13. sapiopycommons/files/file_util.py +15 -40
  14. sapiopycommons/files/file_validator.py +5 -6
  15. sapiopycommons/files/file_writer.py +1 -1
  16. sapiopycommons/flowcyto/flow_cyto.py +1 -1
  17. sapiopycommons/general/accession_service.py +3 -3
  18. sapiopycommons/general/aliases.py +28 -51
  19. sapiopycommons/general/audit_log.py +2 -2
  20. sapiopycommons/general/custom_report_util.py +1 -24
  21. sapiopycommons/general/exceptions.py +2 -41
  22. sapiopycommons/general/popup_util.py +2 -2
  23. sapiopycommons/multimodal/multimodal.py +0 -1
  24. sapiopycommons/processtracking/custom_workflow_handler.py +30 -46
  25. sapiopycommons/recordmodel/record_handler.py +159 -547
  26. sapiopycommons/rules/eln_rule_handler.py +30 -41
  27. sapiopycommons/rules/on_save_rule_handler.py +30 -41
  28. sapiopycommons/webhook/webhook_handlers.py +55 -448
  29. sapiopycommons/webhook/webservice_handlers.py +2 -2
  30. {sapiopycommons-2025.4.8a473.dist-info → sapiopycommons-2025.4.9a150.dist-info}/METADATA +1 -1
  31. sapiopycommons-2025.4.9a150.dist-info/RECORD +59 -0
  32. sapiopycommons/customreport/auto_pagers.py +0 -281
  33. sapiopycommons/eln/experiment_cache.py +0 -173
  34. sapiopycommons/eln/experiment_step_factory.py +0 -474
  35. sapiopycommons/eln/experiment_tags.py +0 -7
  36. sapiopycommons/eln/step_creation.py +0 -235
  37. sapiopycommons/general/data_structure_util.py +0 -115
  38. sapiopycommons/general/directive_util.py +0 -86
  39. sapiopycommons/samples/aliquot.py +0 -48
  40. sapiopycommons-2025.4.8a473.dist-info/RECORD +0 -67
  41. {sapiopycommons-2025.4.8a473.dist-info → sapiopycommons-2025.4.9a150.dist-info}/WHEEL +0 -0
  42. {sapiopycommons-2025.4.8a473.dist-info → sapiopycommons-2025.4.9a150.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
- 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.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,29 +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
- # CR-47526: Set the dialog timeout to 1 hour by default. This can be overridden by the webhook.
224
- self.callback.set_dialog_timeout(3600)
225
-
226
- # Set the default display types, titles, and messages for each type of exception that can display a message.
227
- self.default_user_error_display_type = MessageDisplayType.TOASTER_WARNING
228
- self.default_critical_error_display_type = MessageDisplayType.DISPLAY_ERROR
229
- self.default_dialog_timeout_display_type = MessageDisplayType.OK_DIALOG
230
- self.default_unexpected_error_display_type = MessageDisplayType.TOASTER_WARNING
231
-
232
- self.default_user_error_title = ""
233
- self.default_critical_error_title = ""
234
- self.default_dialog_timeout_title = "Dialog Timeout"
235
- self.default_unexpected_error_title = ""
236
-
237
- self.default_dialog_timeout_message = ("You have remained idle for too long and this dialog has timed out. "
238
- "Close and re-initiate it to continue.")
239
- self.default_unexpected_error_message = ("Unexpected error occurred during webhook execution. Please contact "
240
- "Sapio support.")
241
78
 
242
79
  # Wrap the execution of each webhook in a try/catch. If an exception occurs, handle any special sapiopycommons
243
80
  # exceptions. Otherwise, return a generic message stating that an error occurred.
@@ -287,18 +124,7 @@ class CommonsWebhookHandler(AbstractWebhookHandler):
287
124
  if result is not None:
288
125
  return result
289
126
  self.log_error(traceback.format_exc())
290
- self.handle_user_error_exception_extra(e)
291
-
292
- display_type: MessageDisplayType = e.display_type if e.display_type else self.default_user_error_display_type
293
- title: str = e.title if e.title is not None else self.default_user_error_title
294
- return self._display_exception(e.msg, display_type, title)
295
-
296
- def handle_user_error_exception_extra(self, e: SapioUserErrorException) -> None:
297
- """
298
- An additional function that can be overridden to provide extra behavior when a SapioUserErrorException is thrown.
299
- Default behavior does nothing.
300
- """
301
- pass
127
+ return SapioWebhookResult(False, display_text=e.args[0])
302
128
 
303
129
  def handle_critical_error_exception(self, e: SapioCriticalErrorException) -> SapioWebhookResult:
304
130
  """
@@ -313,18 +139,13 @@ class CommonsWebhookHandler(AbstractWebhookHandler):
313
139
  if result is not None:
314
140
  return result
315
141
  self.log_error(traceback.format_exc())
316
- self.handle_critical_error_exception_extra(e)
317
-
318
- display_type: MessageDisplayType = e.display_type if e.display_type else self.default_critical_error_display_type
319
- title: str = e.title if e.title is not None else self.default_critical_error_title
320
- return self._display_exception(e.msg, display_type, title)
321
-
322
- def handle_critical_error_exception_extra(self, e: SapioCriticalErrorException) -> None:
323
- """
324
- An additional function that can be overridden to provide extra behavior when a SapioCriticalErrorException is
325
- thrown. Default behavior does nothing.
326
- """
327
- 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)
328
149
 
329
150
  def handle_user_cancelled_exception(self, e: SapioUserCancelledException) -> SapioWebhookResult:
330
151
  """
@@ -339,17 +160,7 @@ class CommonsWebhookHandler(AbstractWebhookHandler):
339
160
  result: SapioWebhookResult | None = self.handle_any_exception(e)
340
161
  if result is not None:
341
162
  return result
342
- self.handle_user_cancelled_exception_extra(e)
343
- # FR-47390: Return a False result for user cancelled exceptions so that transactional webhooks cancel the
344
- # commit.
345
- return SapioWebhookResult(False)
346
-
347
- def handle_user_cancelled_exception_extra(self, e: SapioUserCancelledException) -> None:
348
- """
349
- An additional function that can be overridden to provide extra behavior when a SapioUserCancelledException is
350
- thrown. Default behavior does nothing.
351
- """
352
- pass
163
+ return SapioWebhookResult(True)
353
164
 
354
165
  def handle_dialog_timeout_exception(self, e: SapioDialogTimeoutException) -> SapioWebhookResult:
355
166
  """
@@ -364,17 +175,15 @@ class CommonsWebhookHandler(AbstractWebhookHandler):
364
175
  result: SapioWebhookResult | None = self.handle_any_exception(e)
365
176
  if result is not None:
366
177
  return result
367
- self.handle_dialog_timeout_exception_extra(e)
368
- return self._display_exception(self.default_dialog_timeout_message,
369
- self.default_dialog_timeout_display_type,
370
- self.default_dialog_timeout_title)
371
-
372
- def handle_dialog_timeout_exception_extra(self, e: SapioDialogTimeoutException) -> None:
373
- """
374
- An additional function that can be overridden to provide extra behavior when a SapioDialogTimeoutException is
375
- thrown. Default behavior does nothing.
376
- """
377
- 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)
378
187
 
379
188
  def handle_unexpected_exception(self, e: Exception) -> SapioWebhookResult:
380
189
  """
@@ -391,21 +200,13 @@ class CommonsWebhookHandler(AbstractWebhookHandler):
391
200
  if result is not None:
392
201
  return result
393
202
  msg: str = traceback.format_exc()
394
- self.log_error(msg, True)
203
+ self.log_error(msg)
395
204
  # FR-47079: Also log all unexpected exception messages to the webhook execution log within the platform.
396
- self.log_error_to_webhook_execution_log(msg, True)
397
- self.handle_unexpected_exception_extra(e)
398
- return self._display_exception(self.default_unexpected_error_message,
399
- self.default_unexpected_error_display_type,
400
- self.default_unexpected_error_title)
401
-
402
- def handle_unexpected_exception_extra(self, e: Exception) -> None:
403
- """
404
- An additional function that can be overridden to provide extra behavior when a generic exception is thrown.
405
- Default behavior does nothing.
406
- """
407
- 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.")
408
208
 
209
+ # noinspection PyMethodMayBeStatic,PyUnusedLocal
409
210
  def handle_any_exception(self, e: Exception) -> SapioWebhookResult | None:
410
211
  """
411
212
  An exception handler which runs regardless of the type of exception that was raised. Can be used to "rollback"
@@ -416,55 +217,7 @@ class CommonsWebhookHandler(AbstractWebhookHandler):
416
217
  sent by one of the normal exception handlers, or may return None if no result needs returned. If a result is
417
218
  returned, then the default behavior of other exception handlers is skipped.
418
219
  """
419
- pass
420
-
421
- def display_message(self, msg: str, display_type: MessageDisplayType, title: str = "") -> bool:
422
- """
423
- Display a message to the user. The form that the message takes depends on the display type.
424
-
425
- :param msg: The message to display to the user.
426
- :param display_type: The manner in which the message should be displayed.
427
- :param title: If the display type is able to have a title, this is the title that will be displayed.
428
- :return: True if the message was displayed. False if the message could not be displayed (because this
429
- webhook can't send client callbacks).
430
- """
431
- if not self.can_send_client_callback():
432
- return False
433
- if display_type == MessageDisplayType.TOASTER_SUCCESS:
434
- self.callback.toaster_popup(msg, title, PopupType.Success)
435
- elif display_type == MessageDisplayType.TOASTER_INFO:
436
- self.callback.toaster_popup(msg, title, PopupType.Info)
437
- elif display_type == MessageDisplayType.TOASTER_WARNING:
438
- self.callback.toaster_popup(msg, title, PopupType.Warning)
439
- elif display_type == MessageDisplayType.TOASTER_ERROR:
440
- self.callback.toaster_popup(msg, title, PopupType.Error)
441
- elif display_type == MessageDisplayType.OK_DIALOG:
442
- self.callback.ok_dialog(title, msg)
443
- elif display_type == MessageDisplayType.DISPLAY_INFO:
444
- self.callback.display_info(msg)
445
- elif display_type == MessageDisplayType.DISPLAY_WARNING:
446
- self.callback.display_warning(msg)
447
- elif display_type == MessageDisplayType.DISPLAY_ERROR:
448
- self.callback.display_error(msg)
449
- return True
450
-
451
- def _display_exception(self, msg: str, display_type: MessageDisplayType, title: str) -> SapioWebhookResult:
452
- """
453
- Display an exception message to the user and return a webhook result to end the webhook invocation.
454
- This handles the cases where the webhook invocation type is incapable of sending client callbacks and must
455
- instead return the message in the webhook result, and the case where the display type is an OK dialog, which
456
- may potentially cause a dialog timeout exception.
457
- """
458
- # If the display type is an OK dialog, then we need to handle the dialog timeout exception that could be thrown.
459
- try:
460
- # Set the dialog timeout to something low as to not hog the connection.
461
- self.callback.set_dialog_timeout(60)
462
- # If this invocation type can't send client callbacks, fallback to sending the message in the result.
463
- if self.display_message(msg, display_type, title):
464
- return SapioWebhookResult(False)
465
- return SapioWebhookResult(False, display_text=msg)
466
- except SapioDialogTimeoutException:
467
- return SapioWebhookResult(False)
220
+ return None
468
221
 
469
222
  def log_info(self, msg: str) -> None:
470
223
  """
@@ -474,69 +227,55 @@ class CommonsWebhookHandler(AbstractWebhookHandler):
474
227
  """
475
228
  self.logger.info(self._format_log(msg, "log_info call"))
476
229
 
477
- def log_error(self, msg: str, is_exception: bool = False) -> None:
230
+ def log_error(self, msg: str) -> None:
478
231
  """
479
232
  Write an info message to the webhook server log. Log destination is stdout. This message will include
480
233
  information about the user's group, their location in the system, the webhook invocation type, and other
481
234
  important information that can be gathered from the context that is useful for debugging.
482
235
  """
483
236
  # PR-46209: Use logger.error instead of logger.info when logging errors.
484
- self.logger.error(self._format_log(msg, "log_error call", is_exception))
237
+ self.logger.error(self._format_log(msg, "log_error call"))
485
238
 
486
- 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:
487
240
  """
488
241
  Write an error message to the platform's webhook execution log. This can be reviewed by navigating to the
489
242
  webhook configuration where the webhook that called this function is defined and clicking the "View Log"
490
243
  button. From there, select one of the rows for the webhook executions and click "Download Log" from the right
491
244
  side table.
492
245
  """
493
- msg = self._format_log(msg, "Error occurred during webhook execution.", is_exception)
494
- 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__))
495
250
 
496
- 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:
497
252
  """
498
253
  Given a message to log, populate it with some metadata about this particular webhook execution, including
499
254
  the group of the user and the invocation type of the webhook call.
500
255
  """
501
- # Start the message with the provided prefix.
502
- message: str = prefix + "\n" if prefix else ""
503
-
504
- # Construct a summary of the current state of this webhook.
505
- 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
506
266
 
507
- # 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"
508
276
  message += msg
509
277
  return message
510
278
 
511
- @property
512
- def start_time(self) -> float:
513
- """
514
- :return: The time that this webhook was invoked, represented in seconds. This time comes from a performance
515
- counter and is not guaranteed to correspond to a date. Only use in comparison to other performance counters.
516
- """
517
- return self._start_time
518
-
519
- @property
520
- def start_time_millis(self) -> int:
521
- """
522
- :return: The epoch timestamp in milliseconds for the time that this webhook was invoked.
523
- """
524
- return self._start_time_epoch
525
-
526
- def elapsed_time(self) -> float:
527
- """
528
- :return: The number of seconds that have elapsed since this webhook was invoked to the time that this function
529
- is called. Measures using a performance counter to a high degree of accuracy.
530
- """
531
- return time.perf_counter() - self._start_time
532
-
533
- def elapsed_time_millis(self) -> int:
534
- """
535
- :return: The number of milliseconds that have elapsed since this webhook was invoked to the time that this
536
- function is called.
537
- """
538
- return TimeUtil.now_in_millis() - self._start_time_epoch
539
-
540
279
  def is_main_toolbar(self) -> bool:
541
280
  """
542
281
  :return: True if this endpoint was invoked as a main toolbar button.
@@ -659,135 +398,3 @@ class CommonsWebhookHandler(AbstractWebhookHandler):
659
398
  :return: Whether client callbacks and directives can be sent from this webhook's endpoint type.
660
399
  """
661
400
  return self.context.is_client_callback_available
662
-
663
-
664
- # FR-47390: Move the gathering of webhook information out of log_error_to_webhook_execution_log and into its own class.
665
- class WebhookStateSummary:
666
- """
667
- A class that summarizes the state of a webhook at the time that it is created. This class is useful for logging
668
- information about the webhook invocation to the execution log.
669
- """
670
- username: str
671
- """The username of the user who invoked the webhook."""
672
- user_group: str
673
- """The group that the user is currently in."""
674
- start_timestamp: int
675
- """The epoch timestamp in milliseconds for when the webhook was invoked."""
676
- start_utc_time: str
677
- """The time that the webhook was invoked in UTC."""
678
- start_server_time: str | None
679
- """The time that the webhook was invoked on the server, if the TimeUtil class has a default timezone set."""
680
- start_user_time: str
681
- """The time that the webhook was invoked for the user, adjusted for their timezone."""
682
- timestamp: int
683
- """The current epoch timestamp in milliseconds."""
684
- utc_time: str
685
- """The current time in UTC."""
686
- server_time: str | None
687
- """The current time on the webhook server, if the TimeUtil class has a default timezone set."""
688
- user_time: str
689
- """The current time for the user, adjusted for their timezone."""
690
- invocation_type: str
691
- """The type of endpoint that this webhook was invoked from."""
692
- class_name: str
693
- """The name of the class that this webhook is an instance of."""
694
- link: str | None
695
- """A link to the location that the webhook was invoked from, if applicable."""
696
- exc_summary: str | None
697
- """A summary of the exception that occurred, if this summary is being created for an exception."""
698
-
699
- def __init__(self, webhook: CommonsWebhookHandler, summarize_exception: bool = False):
700
- """
701
- :param webhook: The webhook that this summary is being created for.
702
- :param summarize_exception: If true, then this summary will include information about the most recent exception
703
- that occurred during the execution of the webhook.
704
- """
705
- # User information.
706
- self.username = webhook.user.username
707
- self.user_group = webhook.group_name
708
-
709
- # Time information.
710
- fmt: str = "%Y-%m-%d %H:%M:%S.%f"
711
- self.start_timestamp = webhook.start_time_millis
712
- self.start_utc_time = TimeUtil.millis_to_format(self.start_timestamp, fmt, "UTC")
713
- self.start_server_time = None
714
- self.start_user_time = TimeUtil.millis_to_format(self.start_timestamp, fmt, webhook.user_utc_offset_seconds)
715
-
716
- self.timestamp = TimeUtil.now_in_millis()
717
- self.utc_time = TimeUtil.millis_to_format(self.timestamp, fmt, "UTC")
718
- self.server_time = None
719
- self.user_time = TimeUtil.millis_to_format(self.timestamp, fmt, webhook.user_utc_offset_seconds)
720
-
721
- if TimeUtil.get_default_timezone():
722
- self.start_server_time = TimeUtil.millis_to_format(self.start_timestamp, fmt)
723
- self.server_time = TimeUtil.millis_to_format(self.timestamp, fmt)
724
-
725
- # Webhook invocation information.
726
- self.invocation_type = webhook.context.end_point_type.display_name
727
- self.class_name = webhook.__class__.__name__
728
-
729
- # User location information.
730
- self.link = None
731
- context = webhook.context
732
- navigator = SapioNavigationLinker(context)
733
- if context.eln_experiment is not None:
734
- self.link = navigator.experiment(context.eln_experiment)
735
- elif context.base_data_record:
736
- self.link = navigator.data_record(context.base_data_record)
737
- elif context.data_record and not context.data_record_list:
738
- self.link = navigator.data_record(context.data_record)
739
-
740
- # If this is logging an exception, get the system's info on the most recent exception and produce a summary.
741
- self.exc_summary = None
742
- if summarize_exception:
743
- exc_type, exc_value, exc_traceback = sys.exc_info()
744
-
745
- # If this is a SapioServerException, then it can contain json with the exception message from the server.
746
- # This provides a more useful summary than args[0] does, so use it instead.
747
- exception_name: str = exc_type.__name__
748
- if exception_name == "SapioServerException":
749
- # Sometimes the SapioServerException is HTML instead of JSON. If that's the case, just try/catch the
750
- # failure to parse the JSON and continue to use args[0] as the exception message.
751
- try:
752
- exc_str: str = str(exc_value)
753
- exception_msg = json.loads(exc_str[exc_str.find("{"):]).get("message")
754
- except Exception:
755
- exception_msg = exc_value.args[0]
756
- else:
757
- # For all other exceptions, assume that the first argument is a message.
758
- exception_msg = exc_value.args[0]
759
-
760
- self.exc_summary = f"{exception_name}: {exception_msg}"
761
- del (exc_type, exc_value, exc_traceback)
762
-
763
- def __str__(self) -> str:
764
- message: str = ""
765
-
766
- # Record the time that the webhook was started and this state was created.
767
- message += "Webhook Invocation Time:\n"
768
- message += f"\tUTC: {self.start_utc_time}\n"
769
- if TimeUtil.get_default_timezone() is not None:
770
- message += f"\tServer: {self.start_server_time}\n"
771
- message += f"\tUser: {self.start_user_time}\n"
772
-
773
- message += "Current Time:\n"
774
- message += f"\tUTC: {self.utc_time}\n"
775
- if TimeUtil.get_default_timezone() is not None:
776
- message += f"\tServer: {self.server_time}\n"
777
- message += f"\tUser: {self.user_time}\n"
778
-
779
- # Record information about the user and how the webhook was invoked.
780
- message += f"Username: {self.username}\n"
781
- message += f"User group: {self.user_group}\n"
782
- message += f"Webhook invocation type: {self.invocation_type}\n"
783
- message += f"Class name: {self.class_name}\n"
784
-
785
- # If we're able to, provide a link to the location that the error occurred at.
786
- if self.link:
787
- message += f"User location: {self.link}\n"
788
-
789
- # If this state summary is for an exception, include the exception summary.
790
- if self.exc_summary:
791
- message += f"{self.exc_summary}\n"
792
-
793
- return message
@@ -3,7 +3,7 @@ import traceback
3
3
  from abc import abstractmethod, ABC
4
4
  from base64 import b64decode
5
5
  from logging import Logger
6
- from typing import Any, Mapping
6
+ from typing import Any
7
7
 
8
8
  from flask import request, Response, Request
9
9
  from sapiopylib.rest.DataRecordManagerService import DataRecordManager
@@ -122,7 +122,7 @@ class AbstractWebserviceHandler(AbstractWebhookHandler):
122
122
  """
123
123
  pass
124
124
 
125
- def authenticate_user(self, headers: Mapping[str, str]) -> SapioUser:
125
+ def authenticate_user(self, headers: dict[str, str]) -> SapioUser:
126
126
  """
127
127
  Authenticate a user for making requests to a Sapio server using the provided headers. If no user can be
128
128
  authenticated, then an exception will be thrown.