sapiopycommons 2024.11.10a363__py3-none-any.whl → 2024.11.12a365__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/callback_util.py +532 -83
- sapiopycommons/callbacks/field_builder.py +537 -0
- sapiopycommons/chem/IndigoMolecules.py +1 -0
- sapiopycommons/chem/Molecules.py +1 -0
- sapiopycommons/customreport/__init__.py +0 -0
- sapiopycommons/customreport/column_builder.py +60 -0
- sapiopycommons/customreport/custom_report_builder.py +130 -0
- sapiopycommons/customreport/term_builder.py +299 -0
- sapiopycommons/datatype/attachment_util.py +11 -10
- sapiopycommons/datatype/data_fields.py +61 -0
- sapiopycommons/datatype/pseudo_data_types.py +440 -0
- sapiopycommons/eln/experiment_handler.py +272 -70
- sapiopycommons/eln/experiment_report_util.py +653 -0
- sapiopycommons/files/complex_data_loader.py +5 -4
- sapiopycommons/files/file_bridge.py +31 -24
- sapiopycommons/files/file_bridge_handler.py +340 -0
- sapiopycommons/files/file_data_handler.py +2 -5
- sapiopycommons/files/file_util.py +59 -9
- sapiopycommons/files/file_validator.py +92 -6
- sapiopycommons/files/file_writer.py +44 -15
- sapiopycommons/general/accession_service.py +375 -0
- sapiopycommons/general/aliases.py +207 -6
- sapiopycommons/general/audit_log.py +189 -0
- sapiopycommons/general/custom_report_util.py +212 -37
- sapiopycommons/general/exceptions.py +21 -8
- sapiopycommons/general/popup_util.py +21 -0
- sapiopycommons/general/sapio_links.py +50 -0
- sapiopycommons/general/time_util.py +8 -2
- sapiopycommons/multimodal/multimodal.py +146 -0
- sapiopycommons/multimodal/multimodal_data.py +486 -0
- sapiopycommons/processtracking/custom_workflow_handler.py +406 -0
- sapiopycommons/processtracking/endpoints.py +22 -22
- sapiopycommons/recordmodel/record_handler.py +481 -97
- sapiopycommons/rules/eln_rule_handler.py +34 -25
- sapiopycommons/rules/on_save_rule_handler.py +34 -31
- sapiopycommons/sftpconnect/__init__.py +0 -0
- sapiopycommons/sftpconnect/sftp_builder.py +69 -0
- sapiopycommons/webhook/webhook_context.py +39 -0
- sapiopycommons/webhook/webhook_handlers.py +201 -42
- sapiopycommons/webhook/webservice_handlers.py +67 -0
- {sapiopycommons-2024.11.10a363.dist-info → sapiopycommons-2024.11.12a365.dist-info}/METADATA +4 -2
- sapiopycommons-2024.11.12a365.dist-info/RECORD +57 -0
- sapiopycommons-2024.11.10a363.dist-info/RECORD +0 -38
- {sapiopycommons-2024.11.10a363.dist-info → sapiopycommons-2024.11.12a365.dist-info}/WHEEL +0 -0
- {sapiopycommons-2024.11.10a363.dist-info → sapiopycommons-2024.11.12a365.dist-info}/licenses/LICENSE +0 -0
|
@@ -4,16 +4,25 @@ from logging import Logger
|
|
|
4
4
|
|
|
5
5
|
from sapiopylib.rest.DataMgmtService import DataMgmtServer
|
|
6
6
|
from sapiopylib.rest.DataRecordManagerService import DataRecordManager
|
|
7
|
+
from sapiopylib.rest.User import SapioUser
|
|
7
8
|
from sapiopylib.rest.WebhookService import AbstractWebhookHandler
|
|
9
|
+
from sapiopylib.rest.pojo.Message import VeloxLogMessage, VeloxLogLevel
|
|
8
10
|
from sapiopylib.rest.pojo.webhook.WebhookContext import SapioWebhookContext
|
|
9
11
|
from sapiopylib.rest.pojo.webhook.WebhookEnums import WebhookEndpointType
|
|
10
12
|
from sapiopylib.rest.pojo.webhook.WebhookResult import SapioWebhookResult
|
|
13
|
+
from sapiopylib.rest.utils.DataTypeCacheManager import DataTypeCacheManager
|
|
11
14
|
from sapiopylib.rest.utils.recordmodel.RecordModelManager import RecordModelManager, RecordModelInstanceManager, \
|
|
12
15
|
RecordModelRelationshipManager
|
|
13
16
|
from sapiopylib.rest.utils.recordmodel.ancestry import RecordModelAncestorManager
|
|
14
17
|
|
|
18
|
+
from sapiopycommons.callbacks.callback_util import CallbackUtil
|
|
19
|
+
from sapiopycommons.eln.experiment_handler import ExperimentHandler
|
|
15
20
|
from sapiopycommons.general.exceptions import SapioUserErrorException, SapioCriticalErrorException, \
|
|
16
|
-
SapioUserCancelledException
|
|
21
|
+
SapioUserCancelledException, SapioException, SapioDialogTimeoutException
|
|
22
|
+
from sapiopycommons.general.sapio_links import SapioNavigationLinker
|
|
23
|
+
from sapiopycommons.recordmodel.record_handler import RecordHandler
|
|
24
|
+
from sapiopycommons.rules.eln_rule_handler import ElnRuleHandler
|
|
25
|
+
from sapiopycommons.rules.on_save_rule_handler import OnSaveRuleHandler
|
|
17
26
|
|
|
18
27
|
|
|
19
28
|
# FR-46064 - Initial port of PyWebhookUtils to sapiopycommons.
|
|
@@ -25,6 +34,7 @@ class CommonsWebhookHandler(AbstractWebhookHandler):
|
|
|
25
34
|
"""
|
|
26
35
|
logger: Logger
|
|
27
36
|
|
|
37
|
+
user: SapioUser
|
|
28
38
|
context: SapioWebhookContext
|
|
29
39
|
|
|
30
40
|
dr_man: DataRecordManager
|
|
@@ -34,29 +44,64 @@ class CommonsWebhookHandler(AbstractWebhookHandler):
|
|
|
34
44
|
# FR-46329: Add the ancestor manager to CommonsWebhookHandler.
|
|
35
45
|
an_man: RecordModelAncestorManager
|
|
36
46
|
|
|
47
|
+
dt_cache: DataTypeCacheManager
|
|
48
|
+
rec_handler: RecordHandler
|
|
49
|
+
callback: CallbackUtil
|
|
50
|
+
exp_handler: ExperimentHandler | None
|
|
51
|
+
rule_handler: OnSaveRuleHandler | ElnRuleHandler | None
|
|
52
|
+
|
|
37
53
|
def run(self, context: SapioWebhookContext) -> SapioWebhookResult:
|
|
54
|
+
self.user = context.user
|
|
38
55
|
self.context = context
|
|
39
|
-
|
|
56
|
+
|
|
57
|
+
self.logger = self.user.logger
|
|
40
58
|
|
|
41
59
|
self.dr_man = context.data_record_manager
|
|
42
|
-
self.rec_man = RecordModelManager(
|
|
60
|
+
self.rec_man = RecordModelManager(self.user)
|
|
43
61
|
self.inst_man = self.rec_man.instance_manager
|
|
44
62
|
self.rel_man = self.rec_man.relationship_manager
|
|
45
63
|
self.an_man = RecordModelAncestorManager(self.rec_man)
|
|
46
64
|
|
|
65
|
+
self.dt_cache = DataTypeCacheManager(self.user)
|
|
66
|
+
self.rec_handler = RecordHandler(context)
|
|
67
|
+
self.callback = CallbackUtil(context)
|
|
68
|
+
if context.eln_experiment is not None:
|
|
69
|
+
self.exp_handler = ExperimentHandler(context)
|
|
70
|
+
else:
|
|
71
|
+
self.exp_handler = None
|
|
72
|
+
if self.is_on_save_rule():
|
|
73
|
+
self.rule_handler = OnSaveRuleHandler(context)
|
|
74
|
+
elif self.is_eln_rule():
|
|
75
|
+
self.rule_handler = ElnRuleHandler(context)
|
|
76
|
+
else:
|
|
77
|
+
self.rule_handler = None
|
|
78
|
+
|
|
47
79
|
# Wrap the execution of each webhook in a try/catch. If an exception occurs, handle any special sapiopycommons
|
|
48
80
|
# exceptions. Otherwise, return a generic message stating that an error occurred.
|
|
49
81
|
try:
|
|
50
|
-
|
|
82
|
+
self.initialize(context)
|
|
83
|
+
result = self.execute(context)
|
|
84
|
+
if result is None:
|
|
85
|
+
raise SapioException("Your execute function returned a None result! Don't forget your return statement!")
|
|
86
|
+
return result
|
|
51
87
|
except SapioUserErrorException as e:
|
|
52
88
|
return self.handle_user_error_exception(e)
|
|
53
89
|
except SapioCriticalErrorException as e:
|
|
54
90
|
return self.handle_critical_error_exception(e)
|
|
55
91
|
except SapioUserCancelledException as e:
|
|
56
92
|
return self.handle_user_cancelled_exception(e)
|
|
93
|
+
except SapioDialogTimeoutException as e:
|
|
94
|
+
return self.handle_dialog_timeout_exception(e)
|
|
57
95
|
except Exception as e:
|
|
58
96
|
return self.handle_unexpected_exception(e)
|
|
59
97
|
|
|
98
|
+
def initialize(self, context: SapioWebhookContext) -> None:
|
|
99
|
+
"""
|
|
100
|
+
A function that can be optionally overridden by your webhooks to initialize additional instance variables,
|
|
101
|
+
or set up whatever else you wish to set up before the execute function is ran. Default behavior does nothing.
|
|
102
|
+
"""
|
|
103
|
+
pass
|
|
104
|
+
|
|
60
105
|
@abstractmethod
|
|
61
106
|
def execute(self, context: SapioWebhookContext) -> SapioWebhookResult:
|
|
62
107
|
"""
|
|
@@ -68,11 +113,12 @@ class CommonsWebhookHandler(AbstractWebhookHandler):
|
|
|
68
113
|
# the run method and into their own functions.
|
|
69
114
|
def handle_user_error_exception(self, e: SapioUserErrorException) -> SapioWebhookResult:
|
|
70
115
|
"""
|
|
71
|
-
Handle a SapioUserErrorException.
|
|
72
|
-
|
|
116
|
+
Handle a SapioUserErrorException.
|
|
117
|
+
|
|
118
|
+
Default behavior returns a false result and the error message as display text in a webhook result.
|
|
73
119
|
|
|
74
120
|
:param e: The exception that was raised.
|
|
75
|
-
:return: A SapioWebhookResult
|
|
121
|
+
:return: A SapioWebhookResult to end the webhook session with.
|
|
76
122
|
"""
|
|
77
123
|
result: SapioWebhookResult | None = self.handle_any_exception(e)
|
|
78
124
|
if result is not None:
|
|
@@ -82,45 +128,83 @@ class CommonsWebhookHandler(AbstractWebhookHandler):
|
|
|
82
128
|
|
|
83
129
|
def handle_critical_error_exception(self, e: SapioCriticalErrorException) -> SapioWebhookResult:
|
|
84
130
|
"""
|
|
85
|
-
Handle a SapioCriticalErrorException.
|
|
131
|
+
Handle a SapioCriticalErrorException.
|
|
132
|
+
|
|
133
|
+
Default behavior makes a display_error client callback with the error message and returns a false result.
|
|
86
134
|
|
|
87
135
|
:param e: The exception that was raised.
|
|
88
|
-
:return: A SapioWebhookResult
|
|
136
|
+
:return: A SapioWebhookResult to end the webhook session with.
|
|
89
137
|
"""
|
|
90
138
|
result: SapioWebhookResult | None = self.handle_any_exception(e)
|
|
91
139
|
if result is not None:
|
|
92
140
|
return result
|
|
93
141
|
self.log_error(traceback.format_exc())
|
|
94
|
-
|
|
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])
|
|
95
148
|
return SapioWebhookResult(False)
|
|
96
149
|
|
|
97
|
-
def
|
|
150
|
+
def handle_user_cancelled_exception(self, e: SapioUserCancelledException) -> SapioWebhookResult:
|
|
98
151
|
"""
|
|
99
|
-
Handle a
|
|
100
|
-
|
|
152
|
+
Handle a SapioUserCancelledException.
|
|
153
|
+
|
|
154
|
+
Default behavior simply ends the webhook session with a true result (since the user cancelling is a valid
|
|
155
|
+
action).
|
|
101
156
|
|
|
102
157
|
:param e: The exception that was raised.
|
|
103
|
-
:return: A SapioWebhookResult
|
|
158
|
+
:return: A SapioWebhookResult to end the webhook session with.
|
|
104
159
|
"""
|
|
105
160
|
result: SapioWebhookResult | None = self.handle_any_exception(e)
|
|
106
161
|
if result is not None:
|
|
107
162
|
return result
|
|
108
|
-
|
|
109
|
-
return SapioWebhookResult(False, display_text="Unexpected error occurred during webhook execution. "
|
|
110
|
-
"Please contact Sapio support.")
|
|
163
|
+
return SapioWebhookResult(True)
|
|
111
164
|
|
|
112
|
-
def
|
|
165
|
+
def handle_dialog_timeout_exception(self, e: SapioDialogTimeoutException) -> SapioWebhookResult:
|
|
113
166
|
"""
|
|
114
|
-
Handle a
|
|
115
|
-
|
|
167
|
+
Handle a SapioDialogTimeoutException.
|
|
168
|
+
|
|
169
|
+
Default behavior displays an OK popup notifying the user that the dialog has timed out and returns a false
|
|
170
|
+
webhook result.
|
|
171
|
+
|
|
172
|
+
:param e: The exception that was raised.
|
|
173
|
+
:return: A SapioWebhookResult to end the webhook session with.
|
|
174
|
+
"""
|
|
175
|
+
result: SapioWebhookResult | None = self.handle_any_exception(e)
|
|
176
|
+
if result is not None:
|
|
177
|
+
return result
|
|
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)
|
|
187
|
+
|
|
188
|
+
def handle_unexpected_exception(self, e: Exception) -> SapioWebhookResult:
|
|
189
|
+
"""
|
|
190
|
+
Handle a generic exception which isn't one of the handled Sapio exceptions.
|
|
191
|
+
|
|
192
|
+
Default behavior returns a false webhook result with a generic error message as display text informing the user
|
|
193
|
+
to contact Sapio support. Additionally, the stace trace of the exception that was thrown is logged to the
|
|
194
|
+
execution log for the webhook call in the system.
|
|
116
195
|
|
|
117
196
|
:param e: The exception that was raised.
|
|
118
|
-
:return: A SapioWebhookResult
|
|
197
|
+
:return: A SapioWebhookResult to end the webhook session with.
|
|
119
198
|
"""
|
|
120
199
|
result: SapioWebhookResult | None = self.handle_any_exception(e)
|
|
121
200
|
if result is not None:
|
|
122
201
|
return result
|
|
123
|
-
|
|
202
|
+
msg: str = traceback.format_exc()
|
|
203
|
+
self.log_error(msg)
|
|
204
|
+
# FR-47079: Also log all unexpected exception messages to the webhook execution log within the platform.
|
|
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.")
|
|
124
208
|
|
|
125
209
|
# noinspection PyMethodMayBeStatic,PyUnusedLocal
|
|
126
210
|
def handle_any_exception(self, e: Exception) -> SapioWebhookResult | None:
|
|
@@ -130,38 +214,65 @@ class CommonsWebhookHandler(AbstractWebhookHandler):
|
|
|
130
214
|
|
|
131
215
|
:param e: The exception that was raised.
|
|
132
216
|
:return: An optional SapioWebhookResult. May return a custom message to the client that wouldn't have been
|
|
133
|
-
sent by one of the normal exception handlers, or may return None if no result needs returned.
|
|
217
|
+
sent by one of the normal exception handlers, or may return None if no result needs returned. It a result is
|
|
218
|
+
returned, then the default behavior of other exception handlers is skipped.
|
|
134
219
|
"""
|
|
135
220
|
return None
|
|
136
221
|
|
|
137
222
|
def log_info(self, msg: str) -> None:
|
|
138
223
|
"""
|
|
139
|
-
Write an info message to the log. Log destination is stdout. This message will be prepended with
|
|
140
|
-
username and the experiment ID of the experiment they are in, if any.
|
|
224
|
+
Write an info message to the webhook server log. Log destination is stdout. This message will be prepended with
|
|
225
|
+
the user's username and the experiment ID of the experiment they are in, if any.
|
|
141
226
|
"""
|
|
142
|
-
|
|
143
|
-
if self.context.eln_experiment is not None:
|
|
144
|
-
exp_id = self.context.eln_experiment.notebook_experiment_id
|
|
145
|
-
# CR-46333: Add the user's group to the logging message.
|
|
146
|
-
user = self.context.user
|
|
147
|
-
username = user.username
|
|
148
|
-
group_name = user.session_additional_data.current_group_name
|
|
149
|
-
self.logger.info(f"(User: {username}, Group: {group_name}, Experiment: {exp_id}):\n{msg}")
|
|
227
|
+
self.logger.info(self._format_log(msg, "log_info call"))
|
|
150
228
|
|
|
151
229
|
def log_error(self, msg: str) -> None:
|
|
152
230
|
"""
|
|
153
|
-
Write an error message to the log. Log destination is stderr. This message will be prepended with
|
|
154
|
-
username and the experiment ID of the experiment they are in, if any.
|
|
231
|
+
Write an error message to the webhook server log. Log destination is stderr. This message will be prepended with
|
|
232
|
+
the user's username and the experiment ID of the experiment they are in, if any.
|
|
233
|
+
"""
|
|
234
|
+
# PR-46209: Use logger.error instead of logger.info when logging errors.
|
|
235
|
+
self.logger.error(self._format_log(msg, "log_error call"))
|
|
236
|
+
|
|
237
|
+
def log_error_to_webhook_execution_log(self, msg: str) -> None:
|
|
238
|
+
"""
|
|
239
|
+
Write an error message to the platform's webhook execution log. This can be reviewed by navigating to the
|
|
240
|
+
webhook configuration where the webhook that called this function is defined and clicking the "View Log"
|
|
241
|
+
button. From there, select one of the rows for the webhook executions and click "Download Log" from the right
|
|
242
|
+
side table.
|
|
155
243
|
"""
|
|
156
|
-
|
|
244
|
+
messenger = DataMgmtServer.get_messenger(self.user)
|
|
245
|
+
messenger.log_message(VeloxLogMessage(message=self._format_log(msg, "Error occurred during webhook execution."),
|
|
246
|
+
log_level=VeloxLogLevel.ERROR,
|
|
247
|
+
originating_class=self.__class__.__name__))
|
|
248
|
+
|
|
249
|
+
def _format_log(self, msg: str, prefix: str | None = None) -> str:
|
|
250
|
+
"""
|
|
251
|
+
Given a message to log, populate it with some metadata about this particular webhook execution, including
|
|
252
|
+
the group of the user and the invocation type of the webhook call.
|
|
253
|
+
"""
|
|
254
|
+
# If we're able to, provide a link to the location that the error occurred at.
|
|
255
|
+
navigator = SapioNavigationLinker(self.context)
|
|
157
256
|
if self.context.eln_experiment is not None:
|
|
158
|
-
|
|
257
|
+
link = navigator.experiment(self.context.eln_experiment)
|
|
258
|
+
elif self.context.data_record and not self.context.data_record_list:
|
|
259
|
+
link = navigator.data_record(self.context.data_record)
|
|
260
|
+
elif self.context.base_data_record:
|
|
261
|
+
link = navigator.data_record(self.context.base_data_record)
|
|
262
|
+
else:
|
|
263
|
+
link = None
|
|
264
|
+
|
|
265
|
+
message: str = ""
|
|
266
|
+
if prefix:
|
|
267
|
+
message += prefix + "\n"
|
|
268
|
+
message += f"Webhook invocation type: {self.context.end_point_type.display_name}\n"
|
|
269
|
+
message += f"Username: {self.user.username}\n"
|
|
159
270
|
# CR-46333: Add the user's group to the logging message.
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
271
|
+
message += f"User group: {self.user.session_additional_data.current_group_name}\n"
|
|
272
|
+
if link:
|
|
273
|
+
message += f"User location: {link}\n"
|
|
274
|
+
message += msg
|
|
275
|
+
return message
|
|
165
276
|
|
|
166
277
|
def is_main_toolbar(self) -> bool:
|
|
167
278
|
"""
|
|
@@ -237,3 +348,51 @@ class CommonsWebhookHandler(AbstractWebhookHandler):
|
|
|
237
348
|
:return: True if this endpoint was invoked as a scheduled action.
|
|
238
349
|
"""
|
|
239
350
|
return self.context.end_point_type == WebhookEndpointType.SCHEDULEDPLUGIN
|
|
351
|
+
|
|
352
|
+
def is_action_button_field(self) -> bool:
|
|
353
|
+
"""
|
|
354
|
+
:return: True if this endpoint was invoked as an action button field.
|
|
355
|
+
"""
|
|
356
|
+
return self.context.end_point_type == WebhookEndpointType.ACTIONDATAFIELD
|
|
357
|
+
|
|
358
|
+
def is_action_text_field(self) -> bool:
|
|
359
|
+
"""
|
|
360
|
+
:return: True if this endpoint was invoked as an action text field.
|
|
361
|
+
"""
|
|
362
|
+
return self.context.end_point_type == WebhookEndpointType.ACTION_TEXT_FIELD
|
|
363
|
+
|
|
364
|
+
def is_custom(self) -> bool:
|
|
365
|
+
"""
|
|
366
|
+
:return: True if this endpoint was invoked from a custom point, such as a custom queue.
|
|
367
|
+
"""
|
|
368
|
+
return self.context.end_point_type == WebhookEndpointType.CUSTOM
|
|
369
|
+
|
|
370
|
+
def is_calendar_event_click_handler(self) -> bool:
|
|
371
|
+
"""
|
|
372
|
+
:return: True if this endpoint was invoked from a calendar event click handler.
|
|
373
|
+
"""
|
|
374
|
+
return self.context.end_point_type == WebhookEndpointType.CALENDAR_EVENT_CLICK_HANDLER
|
|
375
|
+
|
|
376
|
+
def is_eln_menu_grabber(self) -> bool:
|
|
377
|
+
"""
|
|
378
|
+
:return: True if this endpoint was invoked as a notebook entry grabber.
|
|
379
|
+
"""
|
|
380
|
+
return self.context.end_point_type == WebhookEndpointType.NOTEBOOKEXPERIMENTGRABBER
|
|
381
|
+
|
|
382
|
+
def is_conversation_bot(self) -> bool:
|
|
383
|
+
"""
|
|
384
|
+
:return: True if this endpoint was invoked as from a conversation bot.
|
|
385
|
+
"""
|
|
386
|
+
return self.context.end_point_type == WebhookEndpointType.CONVERSATION_BOT
|
|
387
|
+
|
|
388
|
+
def is_multi_data_type_table_toolbar(self) -> bool:
|
|
389
|
+
"""
|
|
390
|
+
:return: True if this endpoint was invoked as a multi data type table toolbar button.
|
|
391
|
+
"""
|
|
392
|
+
return self.context.end_point_type == WebhookEndpointType.REPORTTOOLBAR
|
|
393
|
+
|
|
394
|
+
def can_send_client_callback(self) -> bool:
|
|
395
|
+
"""
|
|
396
|
+
:return: Whether client callbacks and directives can be sent from this webhook's endpoint type.
|
|
397
|
+
"""
|
|
398
|
+
return self.context.is_client_callback_available
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
import traceback
|
|
3
|
+
from abc import abstractmethod
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
from flask import request
|
|
7
|
+
from sapiopylib.rest.User import SapioUser
|
|
8
|
+
from sapiopylib.rest.WebhookService import AbstractWebhookHandler
|
|
9
|
+
from sapiopylib.rest.pojo.webhook.WebhookResult import SapioWebhookResult
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class AbstractWebserviceHandler(AbstractWebhookHandler):
|
|
13
|
+
"""
|
|
14
|
+
A base class for constructing "webservice" endpoints on your webhook server. These are endpoints that can be
|
|
15
|
+
communicated with by external sources without needing to format the payload JSON in the webhook context format that
|
|
16
|
+
webhook handlers expect.
|
|
17
|
+
|
|
18
|
+
The entire payload JSON is sent to the run method of this class. It is up to the run method to determine how
|
|
19
|
+
this JSON should be parsed. In order to communicate with a Sapio system, a SapioUser object must be able to be
|
|
20
|
+
defined using the payload. Functions have been provided for constructing users with various authentication methods.
|
|
21
|
+
|
|
22
|
+
Since this extends AbstractWebhookHandler, you can still register endpoints from this class in the same way you
|
|
23
|
+
would normal webhook endpoints.
|
|
24
|
+
"""
|
|
25
|
+
def post(self) -> dict[str, Any]:
|
|
26
|
+
"""
|
|
27
|
+
Internal method to be executed to translate incoming requests.
|
|
28
|
+
"""
|
|
29
|
+
# noinspection PyBroadException
|
|
30
|
+
try:
|
|
31
|
+
return self.run(request.json).to_json()
|
|
32
|
+
except Exception:
|
|
33
|
+
print('Error occurred while running webservice custom logic. See traceback.', file=sys.stderr)
|
|
34
|
+
traceback.print_exc()
|
|
35
|
+
return SapioWebhookResult(False, display_text="Error occurred during webservice execution.").to_json()
|
|
36
|
+
|
|
37
|
+
@abstractmethod
|
|
38
|
+
def run(self, payload: dict[str, Any]) -> SapioWebhookResult:
|
|
39
|
+
pass
|
|
40
|
+
|
|
41
|
+
def basic_auth(self, url: str, username: str, password: str) -> SapioUser:
|
|
42
|
+
"""
|
|
43
|
+
:param url: The URL of the Sapio system that requests from this user will be sent to.
|
|
44
|
+
Must end in /webservice/api
|
|
45
|
+
:param username: The username to authenticate requests with.
|
|
46
|
+
:param password: The password to authenticate requests with.
|
|
47
|
+
:return: A SapioUser that will authenticate requests using basic auth.
|
|
48
|
+
"""
|
|
49
|
+
return SapioUser(url, self.verify_sapio_cert, self.client_timeout_seconds, username=username, password=password)
|
|
50
|
+
|
|
51
|
+
def api_token_auth(self, url: str, api_token: str) -> SapioUser:
|
|
52
|
+
"""
|
|
53
|
+
:param url: The URL of the Sapio system that requests from this user will be sent to.
|
|
54
|
+
Must end in /webservice/api
|
|
55
|
+
:param api_token: The API token to authenticate requests with.
|
|
56
|
+
:return: A SapioUser that will authenticate requests using an API token.
|
|
57
|
+
"""
|
|
58
|
+
return SapioUser(url, self.verify_sapio_cert, self.client_timeout_seconds, api_token=api_token)
|
|
59
|
+
|
|
60
|
+
def bearer_token_auth(self, url: str, bearer_token: str) -> SapioUser:
|
|
61
|
+
"""
|
|
62
|
+
:param url: The URL of the Sapio system that requests from this user will be sent to.
|
|
63
|
+
Must end in /webservice/api
|
|
64
|
+
:param bearer_token: The bearer token to authenticate requests with.
|
|
65
|
+
:return: A SapioUser that will authenticate requests using a bearer token.
|
|
66
|
+
"""
|
|
67
|
+
return SapioUser(url, self.verify_sapio_cert, self.client_timeout_seconds, bearer_token=bearer_token)
|
{sapiopycommons-2024.11.10a363.dist-info → sapiopycommons-2024.11.12a365.dist-info}/METADATA
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: sapiopycommons
|
|
3
|
-
Version: 2024.11.
|
|
3
|
+
Version: 2024.11.12a365
|
|
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>
|
|
@@ -15,7 +15,8 @@ Classifier: Programming Language :: Python :: 3.10
|
|
|
15
15
|
Classifier: Topic :: Scientific/Engineering :: Bio-Informatics
|
|
16
16
|
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
17
17
|
Requires-Python: >=3.10
|
|
18
|
-
Requires-Dist:
|
|
18
|
+
Requires-Dist: databind>=4.5
|
|
19
|
+
Requires-Dist: sapiopylib>=2024.5.24.210
|
|
19
20
|
Description-Content-Type: text/markdown
|
|
20
21
|
|
|
21
22
|
|
|
@@ -48,6 +49,7 @@ This license does not provide any rights to use any other copyrighted artifacts
|
|
|
48
49
|
## Dependencies
|
|
49
50
|
The following dependencies are required for this package:
|
|
50
51
|
- [sapiopylib - The official Sapio Informatics Platform Python API package.](https://pypi.org/project/sapiopylib/)
|
|
52
|
+
- [databind - Databind is a library inspired by jackson-databind to de-/serialize Python dataclasses.](https://pypi.org/project/databind/)
|
|
51
53
|
|
|
52
54
|
## Getting Help
|
|
53
55
|
If you have a support contract with Sapio Sciences, please use our [technical support channels](https://sapio-sciences.atlassian.net/servicedesk/customer/portals).
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
sapiopycommons/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
|
+
sapiopycommons/callbacks/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
3
|
+
sapiopycommons/callbacks/callback_util.py,sha256=nb6cXK8yFq96gtG0Z2NiK-qdNaRh88bavUH-ZoBjh18,67953
|
|
4
|
+
sapiopycommons/callbacks/field_builder.py,sha256=8n0jcbMgtMUHjie4C1-IkpAuHz4zBxbZtWpr4y0kABU,36868
|
|
5
|
+
sapiopycommons/chem/IndigoMolecules.py,sha256=QqFDi9CKERj6sn_ZwVcS2xZq4imlkaTeCrpq1iNcEJA,1992
|
|
6
|
+
sapiopycommons/chem/Molecules.py,sha256=t80IsQBPJ9mwE8ZxnWomAGrZDhdsOuPvLaTPb_N6jGU,8639
|
|
7
|
+
sapiopycommons/chem/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
8
|
+
sapiopycommons/customreport/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
9
|
+
sapiopycommons/customreport/column_builder.py,sha256=0RO53e9rKPZ07C--KcepN6_tpRw_FxF3O9vdG0ilKG8,3014
|
|
10
|
+
sapiopycommons/customreport/custom_report_builder.py,sha256=Lsy8DQryb7wC9RmEVVLQ6Q74JiNxU-ywFX-m5zL5CSk,6896
|
|
11
|
+
sapiopycommons/customreport/term_builder.py,sha256=oVsr7iFPnug2TrZUCcAMhyps-b62kDodPcBxyQeneUY,16763
|
|
12
|
+
sapiopycommons/datatype/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
13
|
+
sapiopycommons/datatype/attachment_util.py,sha256=_l2swuP8noIGAl4bwzBUEhr6YlN_OVZl3-gi1XqFHYA,3364
|
|
14
|
+
sapiopycommons/datatype/data_fields.py,sha256=g8Ib6LH8ikNu9AzeVJs8Z2qS8A-cplACeFU7vYguNEY,4063
|
|
15
|
+
sapiopycommons/datatype/pseudo_data_types.py,sha256=Fe75Rnq5evyeJM1nC0sLkLGKAC74g2-GEeTdMeId80o,27649
|
|
16
|
+
sapiopycommons/eln/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
17
|
+
sapiopycommons/eln/experiment_handler.py,sha256=VJPBQSP_4QmhSGDCRDOb5neOjGM6sZ9krvJEmDkpVV8,69282
|
|
18
|
+
sapiopycommons/eln/experiment_report_util.py,sha256=9wWV6oEdKtfn2rI5V0BtmuW9OJlGFd9U07FIf889Gjw,37679
|
|
19
|
+
sapiopycommons/eln/plate_designer.py,sha256=FYJfhhNq8hdfuXgDYOYHy6g0m2zNwQXZWF_MTPzElDg,7184
|
|
20
|
+
sapiopycommons/files/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
21
|
+
sapiopycommons/files/complex_data_loader.py,sha256=T39veNhvYl6j_uZjIIJ8Mk5Aa7otR5RB-g8XlAdkksA,1421
|
|
22
|
+
sapiopycommons/files/file_bridge.py,sha256=WwCVegk0OGA8eqho8chsOsLlqg1nXctO75zfh-rHF-g,5950
|
|
23
|
+
sapiopycommons/files/file_bridge_handler.py,sha256=bt2IfIsxJ4lcJYo_NHvCQ17ZV6C4fSAEa8Zcgixh7B4,14263
|
|
24
|
+
sapiopycommons/files/file_data_handler.py,sha256=SCsjODMJIPEBSsahzXUeOM7CfSCmYwPPoGAM6aOfelo,36743
|
|
25
|
+
sapiopycommons/files/file_util.py,sha256=wbL3rxcFc-t2mXaPWWkoFWYGopvTcQts9Wf-L5GkhT8,29498
|
|
26
|
+
sapiopycommons/files/file_validator.py,sha256=4OvY98ueJWPJdpndwnKv2nqVvLP9S2W7Il_dM0Y0ojo,28709
|
|
27
|
+
sapiopycommons/files/file_writer.py,sha256=96Xl8TTT46Krxe_J8rmmlEMtel4nzZB961f5Yqtl1-I,17616
|
|
28
|
+
sapiopycommons/general/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
29
|
+
sapiopycommons/general/accession_service.py,sha256=HYgyOsH_UaoRnoury-c2yTW8SeG4OtjLemdpCzoV4R8,13484
|
|
30
|
+
sapiopycommons/general/aliases.py,sha256=tdDBNWSGx6s39eHJ3n2kscc4xxW3ZYaUfDftct6FmJE,12910
|
|
31
|
+
sapiopycommons/general/audit_log.py,sha256=KQI0AGgN9WLwKqnHE4Tm0xeBCfpVBf8rIQ2HFmnyFGI,8956
|
|
32
|
+
sapiopycommons/general/custom_report_util.py,sha256=BGu9Ki0wn3m4Nk-LKM6inDSfe8ULUSG9d-HJJNOTtGc,15653
|
|
33
|
+
sapiopycommons/general/exceptions.py,sha256=GY7fe0qOgoy4kQVn_Pn3tdzHsJZyNIpa6VCChg6tzuM,1813
|
|
34
|
+
sapiopycommons/general/popup_util.py,sha256=L-4qpTemSZdlD6_6oEsDYIzLOCiZgDK6wC6DqUwzOYA,31925
|
|
35
|
+
sapiopycommons/general/sapio_links.py,sha256=o9Z-8y2rz6AI0Cy6tq58ElPge9RBnisGc9NyccbaJxs,2610
|
|
36
|
+
sapiopycommons/general/storage_util.py,sha256=ovmK_jN7v09BoX07XxwShpBUC5WYQOM7dbKV_VeLXJU,8892
|
|
37
|
+
sapiopycommons/general/time_util.py,sha256=jUAWmQLNcLHZa4UYB4ht_I3d6uoi63VxYdo7T80Ydw0,7458
|
|
38
|
+
sapiopycommons/multimodal/multimodal.py,sha256=A1QsC8QTPmgZyPr7KtMbPRedn2Ie4WIErodUvQ9otgU,6724
|
|
39
|
+
sapiopycommons/multimodal/multimodal_data.py,sha256=p_caXW0vrURkzDHHspUptEI7lVFpZUrmyF7foz2fAvA,14983
|
|
40
|
+
sapiopycommons/processtracking/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
41
|
+
sapiopycommons/processtracking/custom_workflow_handler.py,sha256=0Si5RQ1YFmqmcZWV8jNDKTffix2iZnQJ6b97fn31pbc,23859
|
|
42
|
+
sapiopycommons/processtracking/endpoints.py,sha256=w5bziI2xC7450M95rCF8JpRwkoni1kEDibyAux9B12Q,10848
|
|
43
|
+
sapiopycommons/recordmodel/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
44
|
+
sapiopycommons/recordmodel/record_handler.py,sha256=Uxjrq6f_cWFbqi7KRLySdOvmQGtbIBrCNyStRewqzx8,64751
|
|
45
|
+
sapiopycommons/rules/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
46
|
+
sapiopycommons/rules/eln_rule_handler.py,sha256=JYzDA_14D2nLnlqwbpIxVOrfKWzbOS27AYf4TQfGr4Q,10469
|
|
47
|
+
sapiopycommons/rules/on_save_rule_handler.py,sha256=Rkqvph20RbNq6m-RF4fbvCP-YfD2CZYBM2iTr3nl0eY,10236
|
|
48
|
+
sapiopycommons/sftpconnect/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
49
|
+
sapiopycommons/sftpconnect/sftp_builder.py,sha256=eKYMiyBc10DNTfbeidQUcfZgFTwhu5ZU-nNJMCK_eos,3014
|
|
50
|
+
sapiopycommons/webhook/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
51
|
+
sapiopycommons/webhook/webhook_context.py,sha256=D793uLsb1691SalaPnBUk3rOSxn_hYLhdvkaIxjNXss,1909
|
|
52
|
+
sapiopycommons/webhook/webhook_handlers.py,sha256=JTquLBln49L1pJ9txJ4oc4Hpzy9kYtMKs0m4SLaFx78,18363
|
|
53
|
+
sapiopycommons/webhook/webservice_handlers.py,sha256=1J56zFI0pWl5MHoNTznvcZumITXgAHJMluj8-2BqYEw,3315
|
|
54
|
+
sapiopycommons-2024.11.12a365.dist-info/METADATA,sha256=ctxNOhOs_M-2srh1Irl3iuy7OrAG-phNHTk_QdLRy8w,3127
|
|
55
|
+
sapiopycommons-2024.11.12a365.dist-info/WHEEL,sha256=3U_NnUcV_1B1kPkYaPzN-irRckL5VW_lytn0ytO_kRY,87
|
|
56
|
+
sapiopycommons-2024.11.12a365.dist-info/licenses/LICENSE,sha256=HyVuytGSiAUQ6ErWBHTqt1iSGHhLmlC8fO7jTCuR8dU,16725
|
|
57
|
+
sapiopycommons-2024.11.12a365.dist-info/RECORD,,
|
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
sapiopycommons/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
|
-
sapiopycommons/callbacks/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
3
|
-
sapiopycommons/callbacks/callback_util.py,sha256=D6whWxYWvs5rXOG2Slpi1icC18SLKmG9-MP9f0YDDNE,43256
|
|
4
|
-
sapiopycommons/chem/IndigoMolecules.py,sha256=ukZcX6TMEgkNdD1L1GnH3tp5rGplFNPlGoChAHXbsxw,1945
|
|
5
|
-
sapiopycommons/chem/Molecules.py,sha256=tOkn3fg4QizgqjkRLuvRdVy0JpTD3QEOSvZPxmIyT4c,8607
|
|
6
|
-
sapiopycommons/chem/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
7
|
-
sapiopycommons/datatype/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
8
|
-
sapiopycommons/datatype/attachment_util.py,sha256=YlnMprj5IGBbAZDLG2khS1P7JIYTw_NYfpJAfRZfP3M,3219
|
|
9
|
-
sapiopycommons/eln/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
10
|
-
sapiopycommons/eln/experiment_handler.py,sha256=v1pG4qtZb8OSNWfKtFo6NjnEkReqnu5R9i_hqWh_xxg,57198
|
|
11
|
-
sapiopycommons/eln/plate_designer.py,sha256=FYJfhhNq8hdfuXgDYOYHy6g0m2zNwQXZWF_MTPzElDg,7184
|
|
12
|
-
sapiopycommons/files/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
13
|
-
sapiopycommons/files/complex_data_loader.py,sha256=XSJOl676mIklJo78v07-70u1b015a5DI4sqZPI3C-Tw,1475
|
|
14
|
-
sapiopycommons/files/file_bridge.py,sha256=6yjUi0ejypb1nvcEvn21EuquB-SmEjB-fCZiMaNZg7Q,5757
|
|
15
|
-
sapiopycommons/files/file_data_handler.py,sha256=3-guAdhJdeJWAFq1a27ijspkO7uMMZ6CapMCD_6o4jA,36746
|
|
16
|
-
sapiopycommons/files/file_util.py,sha256=92SzwRif4dOcGqZ9ri90QeC20NOCenT8DxQjdSH5Uyc,25556
|
|
17
|
-
sapiopycommons/files/file_validator.py,sha256=5DUI_h0WB1AvfoPgx8En3-sC5xlzm5Z2deoSf9qviKQ,24499
|
|
18
|
-
sapiopycommons/files/file_writer.py,sha256=5u_iZXTQvuUU7ceHZr8Q001_tvgJhOqBwAnB_pxcAbQ,16027
|
|
19
|
-
sapiopycommons/general/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
20
|
-
sapiopycommons/general/aliases.py,sha256=i6af5o2oVFGNcyk7GkvTWXQs0H9xTbFKc_GIah8NKVU,3594
|
|
21
|
-
sapiopycommons/general/custom_report_util.py,sha256=Yrq-Ize1M1jh9g3BmQT9Egedufi3Nl9xNmgNI_LGiho,4828
|
|
22
|
-
sapiopycommons/general/exceptions.py,sha256=DOlLKnpCatxQF-lVCToa8ryJgusWLvip6N_1ALN00QE,1679
|
|
23
|
-
sapiopycommons/general/popup_util.py,sha256=-mN5IgYPrLrOEHJ4CHPi2rec4_WAN6X0yMxHwD5h3Bs,30126
|
|
24
|
-
sapiopycommons/general/storage_util.py,sha256=ovmK_jN7v09BoX07XxwShpBUC5WYQOM7dbKV_VeLXJU,8892
|
|
25
|
-
sapiopycommons/general/time_util.py,sha256=jiJUh7jc1ZRCOem880S3HaLPZ4RboBtSl4_U9sqAQuM,7290
|
|
26
|
-
sapiopycommons/processtracking/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
27
|
-
sapiopycommons/processtracking/endpoints.py,sha256=g5h_uCVByqacYm9zWAz8TyAdRsGfaO2o0b5RSJdOaSA,10926
|
|
28
|
-
sapiopycommons/recordmodel/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
29
|
-
sapiopycommons/recordmodel/record_handler.py,sha256=VYUJ0bgZbyc6-XYRKvsxrpWHLdCwxzhv13Ce2tZpAQQ,39348
|
|
30
|
-
sapiopycommons/rules/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
31
|
-
sapiopycommons/rules/eln_rule_handler.py,sha256=qfkBZtck0KK1i9s9Xe2UZqkzQOgPCzDxRkhxE8Si1xk,10671
|
|
32
|
-
sapiopycommons/rules/on_save_rule_handler.py,sha256=JY9F30IcHwFVdgPAMQtTYuRastV1jeezhVktyrzNASU,10763
|
|
33
|
-
sapiopycommons/webhook/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
34
|
-
sapiopycommons/webhook/webhook_handlers.py,sha256=K_K7CEAMZ-bNb2LCIKdt0CxHsBKkwSBzfnp0JSdGJUM,11102
|
|
35
|
-
sapiopycommons-2024.11.10a363.dist-info/METADATA,sha256=nu48xNfx19HiJtUOJIX-WQxv3hciR2XoVAnj5TrYiNc,2960
|
|
36
|
-
sapiopycommons-2024.11.10a363.dist-info/WHEEL,sha256=3U_NnUcV_1B1kPkYaPzN-irRckL5VW_lytn0ytO_kRY,87
|
|
37
|
-
sapiopycommons-2024.11.10a363.dist-info/licenses/LICENSE,sha256=HyVuytGSiAUQ6ErWBHTqt1iSGHhLmlC8fO7jTCuR8dU,16725
|
|
38
|
-
sapiopycommons-2024.11.10a363.dist-info/RECORD,,
|
|
File without changes
|
{sapiopycommons-2024.11.10a363.dist-info → sapiopycommons-2024.11.12a365.dist-info}/licenses/LICENSE
RENAMED
|
File without changes
|