sapiopycommons 2024.10.1a338__py3-none-any.whl → 2024.10.4a339__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 +123 -16
- sapiopycommons/chem/IndigoMolecules.py +1 -0
- sapiopycommons/chem/Molecules.py +77 -19
- sapiopycommons/flowcyto/flow_cyto.py +77 -0
- sapiopycommons/flowcyto/flowcyto_data.py +75 -0
- sapiopycommons/general/exceptions.py +21 -8
- sapiopycommons/multimodal/multimodal_data.py +6 -3
- sapiopycommons/webhook/webhook_handlers.py +60 -22
- {sapiopycommons-2024.10.1a338.dist-info → sapiopycommons-2024.10.4a339.dist-info}/METADATA +1 -1
- {sapiopycommons-2024.10.1a338.dist-info → sapiopycommons-2024.10.4a339.dist-info}/RECORD +12 -10
- {sapiopycommons-2024.10.1a338.dist-info → sapiopycommons-2024.10.4a339.dist-info}/WHEEL +0 -0
- {sapiopycommons-2024.10.1a338.dist-info → sapiopycommons-2024.10.4a339.dist-info}/licenses/LICENSE +0 -0
|
@@ -3,6 +3,7 @@ from __future__ import annotations
|
|
|
3
3
|
import io
|
|
4
4
|
from weakref import WeakValueDictionary
|
|
5
5
|
|
|
6
|
+
from requests import ReadTimeout
|
|
6
7
|
from sapiopylib.rest.ClientCallbackService import ClientCallback
|
|
7
8
|
from sapiopylib.rest.DataMgmtService import DataMgmtServer
|
|
8
9
|
from sapiopylib.rest.User import SapioUser
|
|
@@ -27,7 +28,8 @@ from sapiopycommons.files.file_util import FileUtil
|
|
|
27
28
|
from sapiopycommons.general.aliases import FieldMap, SapioRecord, AliasUtil, RecordIdentifier, FieldValue, \
|
|
28
29
|
UserIdentifier
|
|
29
30
|
from sapiopycommons.general.custom_report_util import CustomReportUtil
|
|
30
|
-
from sapiopycommons.general.exceptions import SapioUserCancelledException, SapioException, SapioUserErrorException
|
|
31
|
+
from sapiopycommons.general.exceptions import SapioUserCancelledException, SapioException, SapioUserErrorException, \
|
|
32
|
+
SapioDialogTimeoutException
|
|
31
33
|
from sapiopycommons.recordmodel.record_handler import RecordHandler
|
|
32
34
|
|
|
33
35
|
|
|
@@ -35,6 +37,8 @@ class CallbackUtil:
|
|
|
35
37
|
user: SapioUser
|
|
36
38
|
callback: ClientCallback
|
|
37
39
|
dt_cache: DataTypeCacheManager
|
|
40
|
+
_original_timeout: int
|
|
41
|
+
timeout_seconds: int
|
|
38
42
|
width_pixels: int | None
|
|
39
43
|
width_percent: float | None
|
|
40
44
|
|
|
@@ -64,6 +68,8 @@ class CallbackUtil:
|
|
|
64
68
|
self.user = AliasUtil.to_sapio_user(context)
|
|
65
69
|
self.callback = DataMgmtServer.get_client_callback(self.user)
|
|
66
70
|
self.dt_cache = DataTypeCacheManager(self.user)
|
|
71
|
+
self._original_timeout = self.user.timeout_seconds
|
|
72
|
+
self.timeout_seconds = self.user.timeout_seconds
|
|
67
73
|
self.width_pixels = None
|
|
68
74
|
self.width_percent = None
|
|
69
75
|
|
|
@@ -80,6 +86,17 @@ class CallbackUtil:
|
|
|
80
86
|
self.width_pixels = width_pixels
|
|
81
87
|
self.width_percent = width_percent
|
|
82
88
|
|
|
89
|
+
def set_dialog_timeout(self, timeout: int):
|
|
90
|
+
"""
|
|
91
|
+
Alter the timeout time used for callback requests that create dialogs for the user to interact with. By default,
|
|
92
|
+
a CallbackUtil will use the timeout time of the SapioUser provided to it. By altering this, a different timeout
|
|
93
|
+
time is used.
|
|
94
|
+
|
|
95
|
+
:param timeout: The number of seconds that must elapse before a SapioDialogTimeoutException is thrown by
|
|
96
|
+
any callback that creates a dialog for the user to interact with.
|
|
97
|
+
"""
|
|
98
|
+
self.timeout_seconds = timeout
|
|
99
|
+
|
|
83
100
|
def toaster_popup(self, message: str, title: str = "", popup_type: PopupType = PopupType.Info) -> None:
|
|
84
101
|
"""
|
|
85
102
|
Display a toaster popup in the bottom right corner of the user's screen.
|
|
@@ -134,7 +151,13 @@ class CallbackUtil:
|
|
|
134
151
|
"""
|
|
135
152
|
request = OptionDialogRequest(title, msg, options, default_option, user_can_cancel,
|
|
136
153
|
width_in_pixels=self.width_pixels, width_percentage=self.width_percent)
|
|
137
|
-
|
|
154
|
+
try:
|
|
155
|
+
self.user.timeout_seconds = self.timeout_seconds
|
|
156
|
+
response: int | None = self.callback.show_option_dialog(request)
|
|
157
|
+
except ReadTimeout:
|
|
158
|
+
raise SapioDialogTimeoutException()
|
|
159
|
+
finally:
|
|
160
|
+
self.user.timeout_seconds = self._original_timeout
|
|
138
161
|
if response is None:
|
|
139
162
|
raise SapioUserCancelledException()
|
|
140
163
|
return options[response]
|
|
@@ -187,7 +210,13 @@ class CallbackUtil:
|
|
|
187
210
|
"""
|
|
188
211
|
request = ListDialogRequest(title, multi_select, options, preselected_values,
|
|
189
212
|
width_in_pixels=self.width_pixels, width_percentage=self.width_percent)
|
|
190
|
-
|
|
213
|
+
try:
|
|
214
|
+
self.user.timeout_seconds = self.timeout_seconds
|
|
215
|
+
response: list[str] | None = self.callback.show_list_dialog(request)
|
|
216
|
+
except ReadTimeout:
|
|
217
|
+
raise SapioDialogTimeoutException()
|
|
218
|
+
finally:
|
|
219
|
+
self.user.timeout_seconds = self._original_timeout
|
|
191
220
|
if response is None:
|
|
192
221
|
raise SapioUserCancelledException()
|
|
193
222
|
return response
|
|
@@ -239,7 +268,13 @@ class CallbackUtil:
|
|
|
239
268
|
|
|
240
269
|
request = FormEntryDialogRequest(title, msg, builder.get_temporary_data_type(), values,
|
|
241
270
|
width_in_pixels=self.width_pixels, width_percentage=self.width_percent)
|
|
242
|
-
|
|
271
|
+
try:
|
|
272
|
+
self.user.timeout_seconds = self.timeout_seconds
|
|
273
|
+
response: FieldMap | None = self.callback.show_form_entry_dialog(request)
|
|
274
|
+
except ReadTimeout:
|
|
275
|
+
raise SapioDialogTimeoutException()
|
|
276
|
+
finally:
|
|
277
|
+
self.user.timeout_seconds = self._original_timeout
|
|
243
278
|
if response is None:
|
|
244
279
|
raise SapioUserCancelledException()
|
|
245
280
|
return response
|
|
@@ -297,7 +332,13 @@ class CallbackUtil:
|
|
|
297
332
|
|
|
298
333
|
request = FormEntryDialogRequest(title, msg, builder.get_temporary_data_type(), values,
|
|
299
334
|
width_in_pixels=self.width_pixels, width_percentage=self.width_percent)
|
|
300
|
-
|
|
335
|
+
try:
|
|
336
|
+
self.user.timeout_seconds = self.timeout_seconds
|
|
337
|
+
response: FieldMap | None = self.callback.show_form_entry_dialog(request)
|
|
338
|
+
except ReadTimeout:
|
|
339
|
+
raise SapioDialogTimeoutException()
|
|
340
|
+
finally:
|
|
341
|
+
self.user.timeout_seconds = self._original_timeout
|
|
301
342
|
if response is None:
|
|
302
343
|
raise SapioUserCancelledException()
|
|
303
344
|
return response
|
|
@@ -313,7 +354,13 @@ class CallbackUtil:
|
|
|
313
354
|
"""
|
|
314
355
|
request = InputDialogCriteria(title, msg, field,
|
|
315
356
|
width_in_pixels=self.width_pixels, width_percentage=self.width_percent)
|
|
316
|
-
|
|
357
|
+
try:
|
|
358
|
+
self.user.timeout_seconds = self.timeout_seconds
|
|
359
|
+
response: FieldValue | None = self.callback.show_input_dialog(request)
|
|
360
|
+
except ReadTimeout:
|
|
361
|
+
raise SapioDialogTimeoutException()
|
|
362
|
+
finally:
|
|
363
|
+
self.user.timeout_seconds = self._original_timeout
|
|
317
364
|
if response is None:
|
|
318
365
|
raise SapioUserCancelledException()
|
|
319
366
|
return response
|
|
@@ -432,7 +479,13 @@ class CallbackUtil:
|
|
|
432
479
|
request = TableEntryDialogRequest(title, msg, builder.get_temporary_data_type(), values,
|
|
433
480
|
record_image_data_list=image_data, group_by_field=group_by,
|
|
434
481
|
width_in_pixels=self.width_pixels, width_percentage=self.width_percent)
|
|
435
|
-
|
|
482
|
+
try:
|
|
483
|
+
self.user.timeout_seconds = self.timeout_seconds
|
|
484
|
+
response: list[FieldMap] | None = self.callback.show_table_entry_dialog(request)
|
|
485
|
+
except ReadTimeout:
|
|
486
|
+
raise SapioDialogTimeoutException()
|
|
487
|
+
finally:
|
|
488
|
+
self.user.timeout_seconds = self._original_timeout
|
|
436
489
|
if response is None:
|
|
437
490
|
raise SapioUserCancelledException()
|
|
438
491
|
return response
|
|
@@ -496,7 +549,13 @@ class CallbackUtil:
|
|
|
496
549
|
request = TableEntryDialogRequest(title, msg, builder.get_temporary_data_type(), field_map_list,
|
|
497
550
|
record_image_data_list=image_data, group_by_field=group_by,
|
|
498
551
|
width_in_pixels=self.width_pixels, width_percentage=self.width_percent)
|
|
499
|
-
|
|
552
|
+
try:
|
|
553
|
+
self.user.timeout_seconds = self.timeout_seconds
|
|
554
|
+
response: list[FieldMap] | None = self.callback.show_table_entry_dialog(request)
|
|
555
|
+
except ReadTimeout:
|
|
556
|
+
raise SapioDialogTimeoutException()
|
|
557
|
+
finally:
|
|
558
|
+
self.user.timeout_seconds = self._original_timeout
|
|
500
559
|
if response is None:
|
|
501
560
|
raise SapioUserCancelledException()
|
|
502
561
|
return response
|
|
@@ -654,7 +713,13 @@ class CallbackUtil:
|
|
|
654
713
|
|
|
655
714
|
request = TableEntryDialogRequest(title, msg, builder.get_temporary_data_type(), values,
|
|
656
715
|
width_in_pixels=self.width_pixels, width_percentage=self.width_percent)
|
|
657
|
-
|
|
716
|
+
try:
|
|
717
|
+
self.user.timeout_seconds = self.timeout_seconds
|
|
718
|
+
response: list[FieldMap] | None = self.callback.show_table_entry_dialog(request)
|
|
719
|
+
except ReadTimeout:
|
|
720
|
+
raise SapioDialogTimeoutException()
|
|
721
|
+
finally:
|
|
722
|
+
self.user.timeout_seconds = self._original_timeout
|
|
658
723
|
if response is None:
|
|
659
724
|
raise SapioUserCancelledException()
|
|
660
725
|
return response
|
|
@@ -704,7 +769,13 @@ class CallbackUtil:
|
|
|
704
769
|
|
|
705
770
|
request = DataRecordDialogRequest(title, record, layout, minimized, access_level, plugin_path_list,
|
|
706
771
|
width_in_pixels=self.width_pixels, width_percentage=self.width_percent)
|
|
707
|
-
|
|
772
|
+
try:
|
|
773
|
+
self.user.timeout_seconds = self.timeout_seconds
|
|
774
|
+
response: bool = self.callback.data_record_form_view_dialog(request)
|
|
775
|
+
except ReadTimeout:
|
|
776
|
+
raise SapioDialogTimeoutException()
|
|
777
|
+
finally:
|
|
778
|
+
self.user.timeout_seconds = self._original_timeout
|
|
708
779
|
if not response:
|
|
709
780
|
raise SapioUserCancelledException()
|
|
710
781
|
|
|
@@ -744,7 +815,13 @@ class CallbackUtil:
|
|
|
744
815
|
|
|
745
816
|
request = TempTableSelectionRequest(builder.get_temporary_data_type(), msg, values,
|
|
746
817
|
multi_select=multi_select)
|
|
747
|
-
|
|
818
|
+
try:
|
|
819
|
+
self.user.timeout_seconds = self.timeout_seconds
|
|
820
|
+
response: list[FieldMap] | None = self.callback.show_temp_table_selection_dialog(request)
|
|
821
|
+
except ReadTimeout:
|
|
822
|
+
raise SapioDialogTimeoutException()
|
|
823
|
+
finally:
|
|
824
|
+
self.user.timeout_seconds = self._original_timeout
|
|
748
825
|
if response is None:
|
|
749
826
|
raise SapioUserCancelledException()
|
|
750
827
|
return response
|
|
@@ -796,7 +873,13 @@ class CallbackUtil:
|
|
|
796
873
|
|
|
797
874
|
request = TempTableSelectionRequest(builder.get_temporary_data_type(), msg, field_map_list,
|
|
798
875
|
multi_select=multi_select)
|
|
799
|
-
|
|
876
|
+
try:
|
|
877
|
+
self.user.timeout_seconds = self.timeout_seconds
|
|
878
|
+
response: list[FieldMap] | None = self.callback.show_temp_table_selection_dialog(request)
|
|
879
|
+
except ReadTimeout:
|
|
880
|
+
raise SapioDialogTimeoutException()
|
|
881
|
+
finally:
|
|
882
|
+
self.user.timeout_seconds = self._original_timeout
|
|
800
883
|
if response is None:
|
|
801
884
|
raise SapioUserCancelledException()
|
|
802
885
|
# Map the field maps in the response back to the record they come from, returning the chosen record instead of
|
|
@@ -872,7 +955,13 @@ class CallbackUtil:
|
|
|
872
955
|
request = InputSelectionRequest(data_type, msg, search_types, only_key_fields, record_blacklist,
|
|
873
956
|
record_whitelist, preselected_records, custom_search, scan_criteria,
|
|
874
957
|
multi_select)
|
|
875
|
-
|
|
958
|
+
try:
|
|
959
|
+
self.user.timeout_seconds = self.timeout_seconds
|
|
960
|
+
response: list[DataRecord] | None = self.callback.show_input_selection_dialog(request)
|
|
961
|
+
except ReadTimeout:
|
|
962
|
+
raise SapioDialogTimeoutException()
|
|
963
|
+
finally:
|
|
964
|
+
self.user.timeout_seconds = self._original_timeout
|
|
876
965
|
if response is None:
|
|
877
966
|
raise SapioUserCancelledException()
|
|
878
967
|
return RecordHandler(self.user).wrap_models(response, wrapper_type)
|
|
@@ -898,7 +987,13 @@ class CallbackUtil:
|
|
|
898
987
|
temp_dt = builder.get_temporary_data_type()
|
|
899
988
|
request = ESigningRequestPojo(title, msg, show_comment, temp_dt,
|
|
900
989
|
width_in_pixels=self.width_pixels, width_percentage=self.width_percent)
|
|
901
|
-
|
|
990
|
+
try:
|
|
991
|
+
self.user.timeout_seconds = self.timeout_seconds
|
|
992
|
+
response: ESigningResponsePojo | None = self.callback.show_esign_dialog(request)
|
|
993
|
+
except ReadTimeout:
|
|
994
|
+
raise SapioDialogTimeoutException()
|
|
995
|
+
finally:
|
|
996
|
+
self.user.timeout_seconds = self._original_timeout
|
|
902
997
|
if response is None:
|
|
903
998
|
raise SapioUserCancelledException()
|
|
904
999
|
return response
|
|
@@ -929,7 +1024,13 @@ class CallbackUtil:
|
|
|
929
1024
|
return sink.consume_data(chunk, io_obj)
|
|
930
1025
|
|
|
931
1026
|
request = FilePromptRequest(title, show_image_editor, ",".join(exts), show_camera_button)
|
|
932
|
-
|
|
1027
|
+
try:
|
|
1028
|
+
self.user.timeout_seconds = self.timeout_seconds
|
|
1029
|
+
file_path: str | None = self.callback.show_file_dialog(request, do_consume)
|
|
1030
|
+
except ReadTimeout:
|
|
1031
|
+
raise SapioDialogTimeoutException()
|
|
1032
|
+
finally:
|
|
1033
|
+
self.user.timeout_seconds = self._original_timeout
|
|
933
1034
|
if file_path is None:
|
|
934
1035
|
raise SapioUserCancelledException()
|
|
935
1036
|
|
|
@@ -954,7 +1055,13 @@ class CallbackUtil:
|
|
|
954
1055
|
exts: list[str] = []
|
|
955
1056
|
|
|
956
1057
|
request = MultiFilePromptRequest(title, show_image_editor, ",".join(exts), show_camera_button)
|
|
957
|
-
|
|
1058
|
+
try:
|
|
1059
|
+
self.user.timeout_seconds = self.timeout_seconds
|
|
1060
|
+
file_paths: list[str] | None = self.callback.show_multi_file_dialog(request)
|
|
1061
|
+
except ReadTimeout:
|
|
1062
|
+
raise SapioDialogTimeoutException()
|
|
1063
|
+
finally:
|
|
1064
|
+
self.user.timeout_seconds = self._original_timeout
|
|
958
1065
|
if not file_paths:
|
|
959
1066
|
raise SapioUserCancelledException()
|
|
960
1067
|
|
|
@@ -9,6 +9,7 @@ indigo.setOption("ignore-stereochemistry-errors", True)
|
|
|
9
9
|
indigo.setOption("render-stereo-style", "ext")
|
|
10
10
|
indigo.setOption("aromaticity-model", "generic")
|
|
11
11
|
indigo.setOption("render-coloring", True)
|
|
12
|
+
indigo.setOption("molfile-saving-mode", "3000")
|
|
12
13
|
indigo_inchi = IndigoInchi(indigo);
|
|
13
14
|
|
|
14
15
|
|
sapiopycommons/chem/Molecules.py
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
# Author Yechen Qiao
|
|
2
2
|
# Common Molecule Utilities for Molecule Transfers with Sapio
|
|
3
|
+
from typing import cast
|
|
3
4
|
|
|
4
5
|
from rdkit import Chem
|
|
5
6
|
from rdkit.Chem import Crippen, MolToInchi
|
|
@@ -20,6 +21,25 @@ tautomer_params.tautomerReassignStereo = False
|
|
|
20
21
|
tautomer_params.tautomerRemoveIsotopicHs = True
|
|
21
22
|
enumerator = rdMolStandardize.TautomerEnumerator(tautomer_params)
|
|
22
23
|
|
|
24
|
+
|
|
25
|
+
def get_enhanced_stereo_reg_hash(mol: Mol, enhanced_stereo: bool) -> str:
|
|
26
|
+
"""
|
|
27
|
+
Get the Registration Hash for the molecule by the current registration configuration.
|
|
28
|
+
When we are running if we are canonicalization of tautomers or cleaning up any other way, do they first before calling.
|
|
29
|
+
:param mol: The molecule to obtain hash for.
|
|
30
|
+
:param canonical_tautomer: Whether the registry system canonicalize the tautomers.
|
|
31
|
+
:param enhanced_stereo: Whether we are computing enhanced stereo at all.
|
|
32
|
+
:return: The enhanced stereo hash.
|
|
33
|
+
"""
|
|
34
|
+
if enhanced_stereo:
|
|
35
|
+
from rdkit.Chem.RegistrationHash import GetMolLayers, GetMolHash, HashScheme
|
|
36
|
+
layers = GetMolLayers(mol, enable_tautomer_hash_v2=True)
|
|
37
|
+
hash_scheme: HashScheme = HashScheme.TAUTOMER_INSENSITIVE_LAYERS
|
|
38
|
+
return GetMolHash(layers, hash_scheme=hash_scheme)
|
|
39
|
+
else:
|
|
40
|
+
return ""
|
|
41
|
+
|
|
42
|
+
|
|
23
43
|
def neutralize_atoms(mol) -> Mol:
|
|
24
44
|
"""
|
|
25
45
|
Neutralize atoms per https://baoilleach.blogspot.com/2019/12/no-charge-simple-approach-to.html
|
|
@@ -86,7 +106,6 @@ def mol_to_img(mol_str: str) -> str:
|
|
|
86
106
|
return renderer.renderToString(mol)
|
|
87
107
|
|
|
88
108
|
|
|
89
|
-
|
|
90
109
|
def mol_to_sapio_partial_pojo(mol: Mol):
|
|
91
110
|
"""
|
|
92
111
|
Get the minimum information about molecule to Sapio, just its SMILES, V3000, and image data.
|
|
@@ -96,7 +115,7 @@ def mol_to_sapio_partial_pojo(mol: Mol):
|
|
|
96
115
|
Chem.SanitizeMol(mol)
|
|
97
116
|
mol.UpdatePropertyCache()
|
|
98
117
|
smiles = Chem.MolToSmiles(mol)
|
|
99
|
-
molBlock = Chem.MolToMolBlock(mol)
|
|
118
|
+
molBlock = Chem.MolToMolBlock(mol, forceV3000=True)
|
|
100
119
|
img = mol_to_img(mol)
|
|
101
120
|
molecule = dict()
|
|
102
121
|
molecule["smiles"] = smiles
|
|
@@ -105,23 +124,52 @@ def mol_to_sapio_partial_pojo(mol: Mol):
|
|
|
105
124
|
return molecule
|
|
106
125
|
|
|
107
126
|
|
|
108
|
-
def
|
|
127
|
+
def get_cxs_smiles_hash(mol: Mol, enhanced_stereo: bool) -> str:
|
|
128
|
+
"""
|
|
129
|
+
Return the SHA1 CXS Smiles hash for the canonical, isomeric CXS SMILES of the molecule.
|
|
130
|
+
"""
|
|
131
|
+
if not enhanced_stereo:
|
|
132
|
+
return ""
|
|
133
|
+
import hashlib
|
|
134
|
+
return hashlib.sha1(Chem.MolToCXSmiles(mol, canonical=True, isomericSmiles=True).encode()).hexdigest()
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def get_has_or_group(mol: Mol, enhanced_stereo: bool) -> bool:
|
|
138
|
+
"""
|
|
139
|
+
Return true if and only if: enhanced stereochemistry is enabled and there is at least one OR group in mol.
|
|
140
|
+
"""
|
|
141
|
+
if not enhanced_stereo:
|
|
142
|
+
return False
|
|
143
|
+
from rdkit.Chem import StereoGroup_vect, STEREO_OR
|
|
144
|
+
stereo_groups: StereoGroup_vect = mol.GetStereoGroups()
|
|
145
|
+
for stereo_group in stereo_groups:
|
|
146
|
+
if stereo_group.GetGroupType() == STEREO_OR:
|
|
147
|
+
return True
|
|
148
|
+
return False
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
def mol_to_sapio_substance(mol: Mol, include_stereoisomers=False,
|
|
109
152
|
normalize: bool = False, remove_salt: bool = False, make_images: bool = False,
|
|
110
|
-
salt_def: str | None = None, canonical_tautomer: bool = True
|
|
153
|
+
salt_def: str | None = None, canonical_tautomer: bool = True,
|
|
154
|
+
enhanced_stereo: bool = False, remove_atom_map: bool = True):
|
|
111
155
|
"""
|
|
112
156
|
Convert a molecule in RDKit to a molecule POJO in Sapio.
|
|
113
157
|
|
|
114
158
|
:param mol: The molecule in RDKit.
|
|
115
|
-
:param include_stereoisomers: If true, will compute all stereoisomer permutations of this molecule.
|
|
116
159
|
:param normalize If true, will normalize the functional groups and return normalized result.
|
|
117
160
|
:param remove_salt If true, we will remove salts iteratively from the molecule before returning their data.
|
|
118
161
|
We will also populate desaltedList with molecules we deleted.
|
|
162
|
+
:param make_images Whether to make images as part of the result without having another script to resolve it.
|
|
119
163
|
:param salt_def: if not none, specifies custom salt to be used during the desalt process.
|
|
120
164
|
:param canonical_tautomer: if True, we will attempt to compute canonical tautomer for the molecule. Slow!
|
|
121
165
|
This is needed for a registry. Note it stops after enumeration of 1000.
|
|
166
|
+
:param enhanced_stereo: If enabled, enhanced stereo hash will be produced.
|
|
167
|
+
:param remove_atom_map: When set, clear all atom AAM maps that were set had it been merged into some reactions earlier.
|
|
122
168
|
:return: The molecule POJO for Sapio.
|
|
123
169
|
"""
|
|
124
170
|
molecule = dict()
|
|
171
|
+
if remove_atom_map:
|
|
172
|
+
[a.SetAtomMapNum(0) for a in mol.GetAtoms()]
|
|
125
173
|
Chem.SanitizeMol(mol)
|
|
126
174
|
mol.UpdatePropertyCache()
|
|
127
175
|
Chem.GetSymmSSSR(mol)
|
|
@@ -157,7 +205,7 @@ def mol_to_sapio_substance(mol: Mol, include_stereoisomers: bool = False,
|
|
|
157
205
|
exactMass = Descriptors.ExactMolWt(mol)
|
|
158
206
|
molFormula = rdMolDescriptors.CalcMolFormula(mol)
|
|
159
207
|
charge = Chem.GetFormalCharge(mol)
|
|
160
|
-
molBlock = Chem.MolToMolBlock(mol)
|
|
208
|
+
molBlock = Chem.MolToMolBlock(mol, forceV3000=True)
|
|
161
209
|
|
|
162
210
|
molecule["cLogP"] = cLogP
|
|
163
211
|
molecule["tpsa"] = tpsa
|
|
@@ -181,28 +229,38 @@ def mol_to_sapio_substance(mol: Mol, include_stereoisomers: bool = False,
|
|
|
181
229
|
# We need to test the INCHI can be loaded back to indigo.
|
|
182
230
|
indigo_mol = indigo.loadMolecule(molBlock)
|
|
183
231
|
indigo_mol.aromatize()
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
232
|
+
if enhanced_stereo:
|
|
233
|
+
# Remove enhanced stereo layer when generating InChI as the stereo hash is generated separately for reg.
|
|
234
|
+
mol_copy: Mol = Chem.MolFromMolBlock(Chem.MolToMolBlock(mol))
|
|
235
|
+
Chem.CanonicalizeEnhancedStereo(mol_copy)
|
|
236
|
+
molecule["inchi"] = Chem.MolToInchi(mol_copy)
|
|
237
|
+
molecule["inchiKey"] = Chem.MolToInchiKey(mol_copy)
|
|
238
|
+
else:
|
|
239
|
+
indigo_inchi.resetOptions()
|
|
240
|
+
indigo_inchi_str = indigo_inchi.getInchi(indigo_mol)
|
|
241
|
+
molecule["inchi"] = indigo_inchi_str
|
|
242
|
+
indigo_inchi_key_str = indigo_inchi.getInchiKey(indigo_inchi_str)
|
|
243
|
+
molecule["inchiKey"] = indigo_inchi_key_str
|
|
189
244
|
molecule["smiles"] = indigo_mol.smiles()
|
|
245
|
+
molecule["reg_hash"] = get_enhanced_stereo_reg_hash(mol, enhanced_stereo=enhanced_stereo)
|
|
246
|
+
molecule["cxsmiles_hash"] = get_cxs_smiles_hash(mol, enhanced_stereo=enhanced_stereo)
|
|
247
|
+
molecule["has_or_group"] = get_has_or_group(mol, enhanced_stereo=enhanced_stereo)
|
|
190
248
|
|
|
191
|
-
if include_stereoisomers and has_chiral_centers(mol):
|
|
192
|
-
stereoisomers = find_all_possible_stereoisomers(mol, only_unassigned=False, try_embedding=False, unique=True)
|
|
193
|
-
molecule["stereoisomers"] = [mol_to_sapio_partial_pojo(x) for x in stereoisomers]
|
|
194
249
|
return molecule
|
|
195
250
|
|
|
196
251
|
|
|
197
|
-
def mol_to_sapio_compound(mol: Mol, include_stereoisomers: bool = False,
|
|
252
|
+
def mol_to_sapio_compound(mol: Mol, include_stereoisomers=False, enhanced_stereo: bool = False,
|
|
198
253
|
salt_def: str | None = None, resolve_canonical: bool = True,
|
|
199
|
-
make_images: bool = False, canonical_tautomer: bool = True
|
|
254
|
+
make_images: bool = False, canonical_tautomer: bool = True,
|
|
255
|
+
remove_atom_map: bool = True):
|
|
200
256
|
ret = dict()
|
|
201
|
-
ret['originalMol'] = mol_to_sapio_substance(mol, include_stereoisomers,
|
|
257
|
+
ret['originalMol'] = mol_to_sapio_substance(mol, include_stereoisomers=False,
|
|
202
258
|
normalize=False, remove_salt=False, make_images=make_images,
|
|
203
|
-
canonical_tautomer=canonical_tautomer
|
|
259
|
+
canonical_tautomer=canonical_tautomer,
|
|
260
|
+
enhanced_stereo=enhanced_stereo, remove_atom_map=remove_atom_map)
|
|
204
261
|
if resolve_canonical:
|
|
205
262
|
ret['canonicalMol'] = mol_to_sapio_substance(mol, include_stereoisomers=False,
|
|
206
263
|
normalize=True, remove_salt=True, make_images=make_images,
|
|
207
|
-
salt_def=salt_def, canonical_tautomer=canonical_tautomer
|
|
264
|
+
salt_def=salt_def, canonical_tautomer=canonical_tautomer,
|
|
265
|
+
enhanced_stereo=enhanced_stereo, remove_atom_map=remove_atom_map)
|
|
208
266
|
return ret
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from weakref import WeakValueDictionary
|
|
4
|
+
|
|
5
|
+
from sapiopylib.rest.User import SapioUser
|
|
6
|
+
from databind.json import dumps
|
|
7
|
+
|
|
8
|
+
from sapiopycommons.flowcyto.flowcyto_data import FlowJoWorkspaceInputJson, UploadFCSInputJson, \
|
|
9
|
+
ComputeFlowStatisticsInputJson
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class FlowCytoManager:
|
|
13
|
+
"""
|
|
14
|
+
This manager includes flow cytometry analysis tools that would require FlowCyto license to use.
|
|
15
|
+
"""
|
|
16
|
+
_user: SapioUser
|
|
17
|
+
|
|
18
|
+
__instances: WeakValueDictionary[SapioUser, FlowCytoManager] = WeakValueDictionary()
|
|
19
|
+
__initialized: bool
|
|
20
|
+
|
|
21
|
+
def __new__(cls, user: SapioUser):
|
|
22
|
+
"""
|
|
23
|
+
Observes singleton pattern per record model manager object.
|
|
24
|
+
|
|
25
|
+
:param user: The user that will make the webservice request to the application.
|
|
26
|
+
"""
|
|
27
|
+
obj = cls.__instances.get(user)
|
|
28
|
+
if not obj:
|
|
29
|
+
obj = object.__new__(cls)
|
|
30
|
+
obj.__initialized = False
|
|
31
|
+
cls.__instances[user] = obj
|
|
32
|
+
return obj
|
|
33
|
+
|
|
34
|
+
def __init__(self, user: SapioUser):
|
|
35
|
+
if self.__initialized:
|
|
36
|
+
return
|
|
37
|
+
self._user = user
|
|
38
|
+
self.__initialized = True
|
|
39
|
+
|
|
40
|
+
def create_flowjo_workspace(self, workspace_input: FlowJoWorkspaceInputJson) -> int:
|
|
41
|
+
"""
|
|
42
|
+
Create FlowJo Workspace and return the workspace record ID of workspace root record,
|
|
43
|
+
after successful creation.
|
|
44
|
+
:param workspace_input: the request data payload.
|
|
45
|
+
:return: The new workspace record ID.
|
|
46
|
+
"""
|
|
47
|
+
payload = dumps(workspace_input, FlowJoWorkspaceInputJson)
|
|
48
|
+
response = self._user.plugin_post("flowcyto/workspace", payload=payload, is_payload_plain_text=True)
|
|
49
|
+
self._user.raise_for_status(response)
|
|
50
|
+
return int(response.json())
|
|
51
|
+
|
|
52
|
+
def upload_fcs_for_sample(self, upload_input: UploadFCSInputJson) -> int:
|
|
53
|
+
"""
|
|
54
|
+
Upload FCS file as root of the sample FCS.
|
|
55
|
+
:param upload_input: The request data payload
|
|
56
|
+
:return: The root FCS file uploaded under sample.
|
|
57
|
+
"""
|
|
58
|
+
payload = dumps(upload_input, UploadFCSInputJson)
|
|
59
|
+
response = self._user.plugin_post("flowcyto/fcs", payload=payload, is_payload_plain_text=True)
|
|
60
|
+
self._user.raise_for_status(response)
|
|
61
|
+
return int(response.json())
|
|
62
|
+
|
|
63
|
+
def compute_statistics(self, stat_compute_input: ComputeFlowStatisticsInputJson) -> list[int]:
|
|
64
|
+
"""
|
|
65
|
+
Requests to compute flow cytometry statistics.
|
|
66
|
+
The children are of type FCSStatistic.
|
|
67
|
+
If the FCS files have not been evaluated yet,
|
|
68
|
+
then the lazy evaluation will be performed immediately prior to computing statistics, which can take longer.
|
|
69
|
+
If any new statistics are computed as children of FCS, they will be returned in the result record id list.
|
|
70
|
+
Note: if input has multiple FCS files, the client should try to get parent FCS file from each record to figure out which one is for which FCS.
|
|
71
|
+
:param stat_compute_input:
|
|
72
|
+
:return:
|
|
73
|
+
"""
|
|
74
|
+
payload = dumps(stat_compute_input, ComputeFlowStatisticsInputJson)
|
|
75
|
+
response = self._user.plugin_post("flowcyto/statistics", payload=payload, is_payload_plain_text=True)
|
|
76
|
+
self._user.raise_for_status(response)
|
|
77
|
+
return list(response.json())
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import base64
|
|
2
|
+
from enum import Enum
|
|
3
|
+
|
|
4
|
+
from databind.core.dataclasses import dataclass
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class ChannelStatisticType(Enum):
|
|
8
|
+
"""
|
|
9
|
+
All supported channel statistics type.
|
|
10
|
+
"""
|
|
11
|
+
MEAN = "(Mean) MFI"
|
|
12
|
+
MEDIAN = "(Median) MFI"
|
|
13
|
+
STD_EV = "Std. Dev."
|
|
14
|
+
COEFFICIENT_OF_VARIATION = "CV"
|
|
15
|
+
|
|
16
|
+
display_name: str
|
|
17
|
+
|
|
18
|
+
def __init__(self, display_name: str):
|
|
19
|
+
self.display_name = display_name
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@dataclass
|
|
23
|
+
class ChannelStatisticsParameterJSON:
|
|
24
|
+
channelNameList: list[str]
|
|
25
|
+
statisticsType: ChannelStatisticType
|
|
26
|
+
|
|
27
|
+
def __init__(self, channel_name_list: list[str], stat_type: ChannelStatisticType):
|
|
28
|
+
self.channelNameList = channel_name_list
|
|
29
|
+
self.statisticsType = stat_type
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
@dataclass
|
|
33
|
+
class ComputeFlowStatisticsInputJson:
|
|
34
|
+
fcsFileRecordIdList: list[int]
|
|
35
|
+
statisticsParameterList: list[ChannelStatisticsParameterJSON]
|
|
36
|
+
|
|
37
|
+
def __init__(self, fcs_file_record_id_list: list[int], statistics_parameter_list: list[ChannelStatisticsParameterJSON]):
|
|
38
|
+
self.fcsFileRecordIdList = fcs_file_record_id_list
|
|
39
|
+
self.statisticsParameterList = statistics_parameter_list
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
@dataclass
|
|
43
|
+
class FlowJoWorkspaceInputJson:
|
|
44
|
+
filePath: str
|
|
45
|
+
base64Data: str
|
|
46
|
+
|
|
47
|
+
def __init__(self, filePath: str, file_data: bytes):
|
|
48
|
+
self.filePath = filePath
|
|
49
|
+
self.base64Data = base64.b64encode(file_data).decode('utf-8')
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
@dataclass
|
|
53
|
+
class UploadFCSInputJson:
|
|
54
|
+
"""
|
|
55
|
+
Request to upload new FCS file
|
|
56
|
+
Attributes:
|
|
57
|
+
filePath: The file name of the FCS file to be uploaded. For FlowJo workspace, this is important to match the file in group (via file names).
|
|
58
|
+
attachmentDataType: the attachment data type that contains already-uploaded FCS data.
|
|
59
|
+
attachmentRecordId: the attachment record ID that contains already-uploaded FCS data.
|
|
60
|
+
associatedRecordDataType: the "parent" association for the FCS. Can either be a workspace or a sample record.
|
|
61
|
+
associatedRecordId: the "parent" association for the FCS. Can either be a workspace or a sample record.
|
|
62
|
+
"""
|
|
63
|
+
filePath: str
|
|
64
|
+
attachmentDataType: str
|
|
65
|
+
attachmentRecordId: int
|
|
66
|
+
associatedRecordDataType: str
|
|
67
|
+
associatedRecordId: int
|
|
68
|
+
|
|
69
|
+
def __init__(self, associated_record_data_type: str, associated_record_id: int,
|
|
70
|
+
file_path: str, attachment_data_type: str, attachment_record_id: int):
|
|
71
|
+
self.filePath = file_path
|
|
72
|
+
self.attachmentDataType = attachment_data_type
|
|
73
|
+
self.attachmentRecordId = attachment_record_id
|
|
74
|
+
self.associatedRecordDataType = associated_record_data_type
|
|
75
|
+
self.associatedRecordId = associated_record_id
|
|
@@ -3,34 +3,47 @@ class SapioException(Exception):
|
|
|
3
3
|
"""
|
|
4
4
|
A generic exception thrown by sapiopycommons methods. Typically caused by programmer error, but may also be from
|
|
5
5
|
extremely edge case user errors. For expected user errors, use SapioUserErrorException.
|
|
6
|
+
|
|
7
|
+
CommonsWebhookHandler's default behavior for this and any other exception that doesn't extend SapioException is
|
|
8
|
+
to return a generic toaster message saying that an unexpected error has occurred.
|
|
6
9
|
"""
|
|
7
10
|
pass
|
|
8
11
|
|
|
9
12
|
|
|
10
|
-
# CommonsWebhookHandler catches this exception and returns "User Cancelled."
|
|
11
13
|
class SapioUserCancelledException(SapioException):
|
|
12
14
|
"""
|
|
13
15
|
An exception thrown when the user cancels a client callback.
|
|
16
|
+
|
|
17
|
+
CommonsWebhookHandler's default behavior is to simply end the webhook session with a true result without logging
|
|
18
|
+
the exception.
|
|
19
|
+
"""
|
|
20
|
+
pass
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class SapioDialogTimeoutException(SapioException):
|
|
24
|
+
"""
|
|
25
|
+
An exception thrown when the user leaves a client callback open for too long.
|
|
26
|
+
|
|
27
|
+
CommonsWebhookHandler's default behavior is to display an OK popup notifying the user that the dialog has timed out.
|
|
14
28
|
"""
|
|
15
29
|
pass
|
|
16
30
|
|
|
17
31
|
|
|
18
|
-
# CommonsWebhookHandler catches this exception and returns the text to the user as display text in a webhook result.
|
|
19
32
|
class SapioUserErrorException(SapioException):
|
|
20
33
|
"""
|
|
21
34
|
An exception caused by user error (e.g. user provided a CSV when an XLSX was expected), which promises to return a
|
|
22
|
-
user-friendly message explaining the error that should be displayed to the user.
|
|
23
|
-
|
|
24
|
-
|
|
35
|
+
user-friendly message explaining the error that should be displayed to the user.
|
|
36
|
+
|
|
37
|
+
CommonsWebhookHandler's default behavior is to return the error message in a toaster popup.
|
|
25
38
|
"""
|
|
26
39
|
pass
|
|
27
40
|
|
|
28
41
|
|
|
29
|
-
# CommonsWebhookHandler catches this exception and returns the text in a display_error client callback.
|
|
30
42
|
class SapioCriticalErrorException(SapioException):
|
|
31
43
|
"""
|
|
32
44
|
A critical exception caused by user error, which promises to return a user-friendly message explaining the error
|
|
33
|
-
that should be displayed to the user.
|
|
34
|
-
|
|
45
|
+
that should be displayed to the user.
|
|
46
|
+
|
|
47
|
+
CommonsWebhookHandler's default behavior is to return the error message in a display_error callback.
|
|
35
48
|
"""
|
|
36
49
|
pass
|
|
@@ -38,6 +38,9 @@ class PyMolecule:
|
|
|
38
38
|
normError: str | None
|
|
39
39
|
desaltError: str | None
|
|
40
40
|
desaltedList: list[str] | None
|
|
41
|
+
registrationHash: str | None
|
|
42
|
+
hasOrGroup: bool
|
|
43
|
+
CXSMILESHash: str | None
|
|
41
44
|
|
|
42
45
|
|
|
43
46
|
@dataclass
|
|
@@ -100,9 +103,9 @@ class PyMoleculeLoaderResult:
|
|
|
100
103
|
compoundList: the compounds successfully loaded.
|
|
101
104
|
errorList: an error record is added here for each one we failed to load in Sapio.
|
|
102
105
|
"""
|
|
103
|
-
compoundByStr: dict[str, PyCompound]
|
|
104
|
-
compoundList: list[PyCompound]
|
|
105
|
-
errorList: list[ChemLoadingError]
|
|
106
|
+
compoundByStr: dict[str, PyCompound] | None
|
|
107
|
+
compoundList: list[PyCompound] | None
|
|
108
|
+
errorList: list[ChemLoadingError] | None
|
|
106
109
|
|
|
107
110
|
|
|
108
111
|
@dataclass
|
|
@@ -18,7 +18,7 @@ from sapiopylib.rest.utils.recordmodel.ancestry import RecordModelAncestorManage
|
|
|
18
18
|
from sapiopycommons.callbacks.callback_util import CallbackUtil
|
|
19
19
|
from sapiopycommons.eln.experiment_handler import ExperimentHandler
|
|
20
20
|
from sapiopycommons.general.exceptions import SapioUserErrorException, SapioCriticalErrorException, \
|
|
21
|
-
SapioUserCancelledException, SapioException
|
|
21
|
+
SapioUserCancelledException, SapioException, SapioDialogTimeoutException
|
|
22
22
|
from sapiopycommons.general.sapio_links import SapioNavigationLinker
|
|
23
23
|
from sapiopycommons.recordmodel.record_handler import RecordHandler
|
|
24
24
|
from sapiopycommons.rules.eln_rule_handler import ElnRuleHandler
|
|
@@ -90,6 +90,8 @@ class CommonsWebhookHandler(AbstractWebhookHandler):
|
|
|
90
90
|
return self.handle_critical_error_exception(e)
|
|
91
91
|
except SapioUserCancelledException as e:
|
|
92
92
|
return self.handle_user_cancelled_exception(e)
|
|
93
|
+
except SapioDialogTimeoutException as e:
|
|
94
|
+
return self.handle_dialog_timeout_exception(e)
|
|
93
95
|
except Exception as e:
|
|
94
96
|
return self.handle_unexpected_exception(e)
|
|
95
97
|
|
|
@@ -111,11 +113,12 @@ class CommonsWebhookHandler(AbstractWebhookHandler):
|
|
|
111
113
|
# the run method and into their own functions.
|
|
112
114
|
def handle_user_error_exception(self, e: SapioUserErrorException) -> SapioWebhookResult:
|
|
113
115
|
"""
|
|
114
|
-
Handle a SapioUserErrorException.
|
|
115
|
-
|
|
116
|
+
Handle a SapioUserErrorException.
|
|
117
|
+
|
|
118
|
+
Default behavior returns a false result and the error message as display text in a webhook result.
|
|
116
119
|
|
|
117
120
|
:param e: The exception that was raised.
|
|
118
|
-
:return: A SapioWebhookResult
|
|
121
|
+
:return: A SapioWebhookResult to end the webhook session with.
|
|
119
122
|
"""
|
|
120
123
|
result: SapioWebhookResult | None = self.handle_any_exception(e)
|
|
121
124
|
if result is not None:
|
|
@@ -125,50 +128,84 @@ class CommonsWebhookHandler(AbstractWebhookHandler):
|
|
|
125
128
|
|
|
126
129
|
def handle_critical_error_exception(self, e: SapioCriticalErrorException) -> SapioWebhookResult:
|
|
127
130
|
"""
|
|
128
|
-
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.
|
|
129
134
|
|
|
130
135
|
:param e: The exception that was raised.
|
|
131
|
-
:return: A SapioWebhookResult
|
|
136
|
+
:return: A SapioWebhookResult to end the webhook session with.
|
|
132
137
|
"""
|
|
133
138
|
result: SapioWebhookResult | None = self.handle_any_exception(e)
|
|
134
139
|
if result is not None:
|
|
135
140
|
return result
|
|
136
141
|
self.log_error(traceback.format_exc())
|
|
142
|
+
# This error be thrown by endpoints that can't send client callbacks. If that happens, fall back onto sending
|
|
143
|
+
# display text instead.
|
|
137
144
|
if self.can_send_client_callback():
|
|
138
|
-
|
|
145
|
+
self.callback.display_error(e.args[0])
|
|
146
|
+
else:
|
|
147
|
+
return SapioWebhookResult(False, e.args[0])
|
|
139
148
|
return SapioWebhookResult(False)
|
|
140
149
|
|
|
141
|
-
def
|
|
150
|
+
def handle_user_cancelled_exception(self, e: SapioUserCancelledException) -> SapioWebhookResult:
|
|
142
151
|
"""
|
|
143
|
-
Handle a
|
|
144
|
-
|
|
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).
|
|
145
156
|
|
|
146
157
|
:param e: The exception that was raised.
|
|
147
|
-
:return: A SapioWebhookResult
|
|
158
|
+
:return: A SapioWebhookResult to end the webhook session with.
|
|
148
159
|
"""
|
|
149
160
|
result: SapioWebhookResult | None = self.handle_any_exception(e)
|
|
150
161
|
if result is not None:
|
|
151
162
|
return result
|
|
152
|
-
|
|
153
|
-
self.log_error(msg)
|
|
154
|
-
# FR-47079: Also log all unexpected exception messages to the webhook execution log within the platform.
|
|
155
|
-
self.log_error_to_webhook_execution_log(msg)
|
|
156
|
-
return SapioWebhookResult(False, display_text="Unexpected error occurred during webhook execution. "
|
|
157
|
-
"Please contact Sapio support.")
|
|
163
|
+
return SapioWebhookResult(True)
|
|
158
164
|
|
|
159
|
-
def
|
|
165
|
+
def handle_dialog_timeout_exception(self, e: SapioDialogTimeoutException) -> SapioWebhookResult:
|
|
160
166
|
"""
|
|
161
|
-
Handle a
|
|
162
|
-
|
|
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.
|
|
163
171
|
|
|
164
172
|
:param e: The exception that was raised.
|
|
165
|
-
:return: A SapioWebhookResult
|
|
173
|
+
:return: A SapioWebhookResult to end the webhook session with.
|
|
166
174
|
"""
|
|
167
175
|
result: SapioWebhookResult | None = self.handle_any_exception(e)
|
|
168
176
|
if result is not None:
|
|
169
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
|
|
170
186
|
return SapioWebhookResult(False)
|
|
171
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.
|
|
195
|
+
|
|
196
|
+
:param e: The exception that was raised.
|
|
197
|
+
:return: A SapioWebhookResult to end the webhook session with.
|
|
198
|
+
"""
|
|
199
|
+
result: SapioWebhookResult | None = self.handle_any_exception(e)
|
|
200
|
+
if result is not None:
|
|
201
|
+
return result
|
|
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.")
|
|
208
|
+
|
|
172
209
|
# noinspection PyMethodMayBeStatic,PyUnusedLocal
|
|
173
210
|
def handle_any_exception(self, e: Exception) -> SapioWebhookResult | None:
|
|
174
211
|
"""
|
|
@@ -177,7 +214,8 @@ class CommonsWebhookHandler(AbstractWebhookHandler):
|
|
|
177
214
|
|
|
178
215
|
:param e: The exception that was raised.
|
|
179
216
|
:return: An optional SapioWebhookResult. May return a custom message to the client that wouldn't have been
|
|
180
|
-
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.
|
|
181
219
|
"""
|
|
182
220
|
return None
|
|
183
221
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: sapiopycommons
|
|
3
|
-
Version: 2024.10.
|
|
3
|
+
Version: 2024.10.4a339
|
|
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>
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
sapiopycommons/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
2
|
sapiopycommons/callbacks/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
3
|
-
sapiopycommons/callbacks/callback_util.py,sha256=
|
|
4
|
-
sapiopycommons/chem/IndigoMolecules.py,sha256=
|
|
5
|
-
sapiopycommons/chem/Molecules.py,sha256=
|
|
3
|
+
sapiopycommons/callbacks/callback_util.py,sha256=nb6cXK8yFq96gtG0Z2NiK-qdNaRh88bavUH-ZoBjh18,67953
|
|
4
|
+
sapiopycommons/chem/IndigoMolecules.py,sha256=3f-aig3AJkKJhRmhlQ0cI-5G8oeaQk_3foJTDZCvoko,2040
|
|
5
|
+
sapiopycommons/chem/Molecules.py,sha256=0B_SsXB2swg2DiP50p0tcNOVO1ajlxumSI42YyDiSHI,11517
|
|
6
6
|
sapiopycommons/chem/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
7
7
|
sapiopycommons/customreport/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
8
8
|
sapiopycommons/customreport/column_builder.py,sha256=sS_wZYOR72rs3syTNjwCVP4h8M8N0b0burkTxFQItVU,3019
|
|
@@ -22,18 +22,20 @@ sapiopycommons/files/file_data_handler.py,sha256=SCsjODMJIPEBSsahzXUeOM7CfSCmYwP
|
|
|
22
22
|
sapiopycommons/files/file_util.py,sha256=wbL3rxcFc-t2mXaPWWkoFWYGopvTcQts9Wf-L5GkhT8,29498
|
|
23
23
|
sapiopycommons/files/file_validator.py,sha256=4OvY98ueJWPJdpndwnKv2nqVvLP9S2W7Il_dM0Y0ojo,28709
|
|
24
24
|
sapiopycommons/files/file_writer.py,sha256=96Xl8TTT46Krxe_J8rmmlEMtel4nzZB961f5Yqtl1-I,17616
|
|
25
|
+
sapiopycommons/flowcyto/flow_cyto.py,sha256=YlkKJR_zEHYRuNW0bnTqlTyZeXs0lOaeSCfG2fnfD7E,3227
|
|
26
|
+
sapiopycommons/flowcyto/flowcyto_data.py,sha256=mYKFuLbtpJ-EsQxLGtu4tNHVlygTxKixgJxJqD68F58,2596
|
|
25
27
|
sapiopycommons/general/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
26
28
|
sapiopycommons/general/accession_service.py,sha256=HYgyOsH_UaoRnoury-c2yTW8SeG4OtjLemdpCzoV4R8,13484
|
|
27
29
|
sapiopycommons/general/aliases.py,sha256=Gih9shHsj765q4HimfFTTI7wWDPAtXjoqCgHisyIQZY,10409
|
|
28
30
|
sapiopycommons/general/audit_log.py,sha256=tJi4uU4qRY2WWcK4ItkjRvoCHCwwiU9LwCNv4lP5-QQ,8713
|
|
29
31
|
sapiopycommons/general/custom_report_util.py,sha256=BGu9Ki0wn3m4Nk-LKM6inDSfe8ULUSG9d-HJJNOTtGc,15653
|
|
30
|
-
sapiopycommons/general/exceptions.py,sha256=
|
|
32
|
+
sapiopycommons/general/exceptions.py,sha256=GY7fe0qOgoy4kQVn_Pn3tdzHsJZyNIpa6VCChg6tzuM,1813
|
|
31
33
|
sapiopycommons/general/popup_util.py,sha256=L-4qpTemSZdlD6_6oEsDYIzLOCiZgDK6wC6DqUwzOYA,31925
|
|
32
34
|
sapiopycommons/general/sapio_links.py,sha256=o9Z-8y2rz6AI0Cy6tq58ElPge9RBnisGc9NyccbaJxs,2610
|
|
33
35
|
sapiopycommons/general/storage_util.py,sha256=ovmK_jN7v09BoX07XxwShpBUC5WYQOM7dbKV_VeLXJU,8892
|
|
34
36
|
sapiopycommons/general/time_util.py,sha256=sXThADCRAQDWYDD9C5CdhcKYIt3qOaVNyZfGBR7HW9A,8701
|
|
35
37
|
sapiopycommons/multimodal/multimodal.py,sha256=A1QsC8QTPmgZyPr7KtMbPRedn2Ie4WIErodUvQ9otgU,6724
|
|
36
|
-
sapiopycommons/multimodal/multimodal_data.py,sha256=
|
|
38
|
+
sapiopycommons/multimodal/multimodal_data.py,sha256=t-0uY4cVgm88uXaSOL4ZeB6zmdHufowXuLFlMk61wFg,15087
|
|
37
39
|
sapiopycommons/processtracking/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
38
40
|
sapiopycommons/processtracking/endpoints.py,sha256=w5bziI2xC7450M95rCF8JpRwkoni1kEDibyAux9B12Q,10848
|
|
39
41
|
sapiopycommons/recordmodel/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
@@ -42,9 +44,9 @@ sapiopycommons/rules/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hS
|
|
|
42
44
|
sapiopycommons/rules/eln_rule_handler.py,sha256=JYzDA_14D2nLnlqwbpIxVOrfKWzbOS27AYf4TQfGr4Q,10469
|
|
43
45
|
sapiopycommons/rules/on_save_rule_handler.py,sha256=Rkqvph20RbNq6m-RF4fbvCP-YfD2CZYBM2iTr3nl0eY,10236
|
|
44
46
|
sapiopycommons/webhook/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
45
|
-
sapiopycommons/webhook/webhook_handlers.py,sha256=
|
|
47
|
+
sapiopycommons/webhook/webhook_handlers.py,sha256=87dIuuf8-om9t4LoOBU36fpNvRIdPkMPgopan153fQE,18359
|
|
46
48
|
sapiopycommons/webhook/webservice_handlers.py,sha256=1J56zFI0pWl5MHoNTznvcZumITXgAHJMluj8-2BqYEw,3315
|
|
47
|
-
sapiopycommons-2024.10.
|
|
48
|
-
sapiopycommons-2024.10.
|
|
49
|
-
sapiopycommons-2024.10.
|
|
50
|
-
sapiopycommons-2024.10.
|
|
49
|
+
sapiopycommons-2024.10.4a339.dist-info/METADATA,sha256=wDqtJjXcWAxJdtYi_1XHCQM6TKatOEuFDJeFGhQoP6E,3176
|
|
50
|
+
sapiopycommons-2024.10.4a339.dist-info/WHEEL,sha256=1yFddiXMmvYK7QYTqtRNtX66WJ0Mz8PYEiEUoOUUxRY,87
|
|
51
|
+
sapiopycommons-2024.10.4a339.dist-info/licenses/LICENSE,sha256=HyVuytGSiAUQ6ErWBHTqt1iSGHhLmlC8fO7jTCuR8dU,16725
|
|
52
|
+
sapiopycommons-2024.10.4a339.dist-info/RECORD,,
|
|
File without changes
|
{sapiopycommons-2024.10.1a338.dist-info → sapiopycommons-2024.10.4a339.dist-info}/licenses/LICENSE
RENAMED
|
File without changes
|