sapiopycommons 2024.10.1a338__tar.gz → 2024.10.8a340__tar.gz

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 (69) hide show
  1. {sapiopycommons-2024.10.1a338 → sapiopycommons-2024.10.8a340}/PKG-INFO +1 -1
  2. {sapiopycommons-2024.10.1a338 → sapiopycommons-2024.10.8a340}/pyproject.toml +1 -1
  3. {sapiopycommons-2024.10.1a338 → sapiopycommons-2024.10.8a340}/src/sapiopycommons/callbacks/callback_util.py +123 -16
  4. {sapiopycommons-2024.10.1a338 → sapiopycommons-2024.10.8a340}/src/sapiopycommons/chem/IndigoMolecules.py +1 -0
  5. {sapiopycommons-2024.10.1a338 → sapiopycommons-2024.10.8a340}/src/sapiopycommons/chem/Molecules.py +77 -19
  6. {sapiopycommons-2024.10.1a338 → sapiopycommons-2024.10.8a340}/src/sapiopycommons/eln/experiment_handler.py +1 -1
  7. sapiopycommons-2024.10.8a340/src/sapiopycommons/flowcyto/flow_cyto.py +77 -0
  8. sapiopycommons-2024.10.8a340/src/sapiopycommons/flowcyto/flowcyto_data.py +75 -0
  9. {sapiopycommons-2024.10.1a338 → sapiopycommons-2024.10.8a340}/src/sapiopycommons/general/exceptions.py +21 -8
  10. {sapiopycommons-2024.10.1a338 → sapiopycommons-2024.10.8a340}/src/sapiopycommons/multimodal/multimodal_data.py +6 -3
  11. {sapiopycommons-2024.10.1a338 → sapiopycommons-2024.10.8a340}/src/sapiopycommons/webhook/webhook_handlers.py +60 -22
  12. sapiopycommons-2024.10.8a340/tests/chem_test_curation_queue.py +31 -0
  13. sapiopycommons-2024.10.8a340/tests/curation_queue_test.sdf +168 -0
  14. {sapiopycommons-2024.10.1a338 → sapiopycommons-2024.10.8a340}/tests/data_type_models.py +38154 -17480
  15. sapiopycommons-2024.10.8a340/tests/flowcyto/101_DEN084Y5_15_E01_008_clean.fcs +0 -0
  16. sapiopycommons-2024.10.8a340/tests/flowcyto/101_DEN084Y5_15_E03_009_clean.fcs +0 -0
  17. sapiopycommons-2024.10.8a340/tests/flowcyto/101_DEN084Y5_15_E05_010_clean.fcs +0 -0
  18. sapiopycommons-2024.10.8a340/tests/flowcyto/8_color_ICS.wsp +4833 -0
  19. sapiopycommons-2024.10.8a340/tests/flowcyto/COVID19_W_001_O.fcs +0 -0
  20. sapiopycommons-2024.10.8a340/tests/flowcyto_test.py +71 -0
  21. {sapiopycommons-2024.10.1a338 → sapiopycommons-2024.10.8a340}/.gitignore +0 -0
  22. {sapiopycommons-2024.10.1a338 → sapiopycommons-2024.10.8a340}/LICENSE +0 -0
  23. {sapiopycommons-2024.10.1a338 → sapiopycommons-2024.10.8a340}/README.md +0 -0
  24. {sapiopycommons-2024.10.1a338 → sapiopycommons-2024.10.8a340}/src/sapiopycommons/__init__.py +0 -0
  25. {sapiopycommons-2024.10.1a338 → sapiopycommons-2024.10.8a340}/src/sapiopycommons/callbacks/__init__.py +0 -0
  26. {sapiopycommons-2024.10.1a338 → sapiopycommons-2024.10.8a340}/src/sapiopycommons/chem/__init__.py +0 -0
  27. {sapiopycommons-2024.10.1a338 → sapiopycommons-2024.10.8a340}/src/sapiopycommons/customreport/__init__.py +0 -0
  28. {sapiopycommons-2024.10.1a338 → sapiopycommons-2024.10.8a340}/src/sapiopycommons/customreport/column_builder.py +0 -0
  29. {sapiopycommons-2024.10.1a338 → sapiopycommons-2024.10.8a340}/src/sapiopycommons/customreport/custom_report_builder.py +0 -0
  30. {sapiopycommons-2024.10.1a338 → sapiopycommons-2024.10.8a340}/src/sapiopycommons/customreport/term_builder.py +0 -0
  31. {sapiopycommons-2024.10.1a338 → sapiopycommons-2024.10.8a340}/src/sapiopycommons/datatype/__init__.py +0 -0
  32. {sapiopycommons-2024.10.1a338 → sapiopycommons-2024.10.8a340}/src/sapiopycommons/datatype/attachment_util.py +0 -0
  33. {sapiopycommons-2024.10.1a338 → sapiopycommons-2024.10.8a340}/src/sapiopycommons/eln/__init__.py +0 -0
  34. {sapiopycommons-2024.10.1a338 → sapiopycommons-2024.10.8a340}/src/sapiopycommons/eln/experiment_report_util.py +0 -0
  35. {sapiopycommons-2024.10.1a338 → sapiopycommons-2024.10.8a340}/src/sapiopycommons/eln/plate_designer.py +0 -0
  36. {sapiopycommons-2024.10.1a338 → sapiopycommons-2024.10.8a340}/src/sapiopycommons/files/__init__.py +0 -0
  37. {sapiopycommons-2024.10.1a338 → sapiopycommons-2024.10.8a340}/src/sapiopycommons/files/complex_data_loader.py +0 -0
  38. {sapiopycommons-2024.10.1a338 → sapiopycommons-2024.10.8a340}/src/sapiopycommons/files/file_bridge.py +0 -0
  39. {sapiopycommons-2024.10.1a338 → sapiopycommons-2024.10.8a340}/src/sapiopycommons/files/file_bridge_handler.py +0 -0
  40. {sapiopycommons-2024.10.1a338 → sapiopycommons-2024.10.8a340}/src/sapiopycommons/files/file_data_handler.py +0 -0
  41. {sapiopycommons-2024.10.1a338 → sapiopycommons-2024.10.8a340}/src/sapiopycommons/files/file_util.py +0 -0
  42. {sapiopycommons-2024.10.1a338 → sapiopycommons-2024.10.8a340}/src/sapiopycommons/files/file_validator.py +0 -0
  43. {sapiopycommons-2024.10.1a338 → sapiopycommons-2024.10.8a340}/src/sapiopycommons/files/file_writer.py +0 -0
  44. {sapiopycommons-2024.10.1a338 → sapiopycommons-2024.10.8a340}/src/sapiopycommons/general/__init__.py +0 -0
  45. {sapiopycommons-2024.10.1a338 → sapiopycommons-2024.10.8a340}/src/sapiopycommons/general/accession_service.py +0 -0
  46. {sapiopycommons-2024.10.1a338 → sapiopycommons-2024.10.8a340}/src/sapiopycommons/general/aliases.py +0 -0
  47. {sapiopycommons-2024.10.1a338 → sapiopycommons-2024.10.8a340}/src/sapiopycommons/general/audit_log.py +0 -0
  48. {sapiopycommons-2024.10.1a338 → sapiopycommons-2024.10.8a340}/src/sapiopycommons/general/custom_report_util.py +0 -0
  49. {sapiopycommons-2024.10.1a338 → sapiopycommons-2024.10.8a340}/src/sapiopycommons/general/popup_util.py +0 -0
  50. {sapiopycommons-2024.10.1a338 → sapiopycommons-2024.10.8a340}/src/sapiopycommons/general/sapio_links.py +0 -0
  51. {sapiopycommons-2024.10.1a338 → sapiopycommons-2024.10.8a340}/src/sapiopycommons/general/storage_util.py +0 -0
  52. {sapiopycommons-2024.10.1a338 → sapiopycommons-2024.10.8a340}/src/sapiopycommons/general/time_util.py +0 -0
  53. {sapiopycommons-2024.10.1a338 → sapiopycommons-2024.10.8a340}/src/sapiopycommons/multimodal/multimodal.py +0 -0
  54. {sapiopycommons-2024.10.1a338 → sapiopycommons-2024.10.8a340}/src/sapiopycommons/processtracking/__init__.py +0 -0
  55. {sapiopycommons-2024.10.1a338 → sapiopycommons-2024.10.8a340}/src/sapiopycommons/processtracking/endpoints.py +0 -0
  56. {sapiopycommons-2024.10.1a338 → sapiopycommons-2024.10.8a340}/src/sapiopycommons/recordmodel/__init__.py +0 -0
  57. {sapiopycommons-2024.10.1a338 → sapiopycommons-2024.10.8a340}/src/sapiopycommons/recordmodel/record_handler.py +0 -0
  58. {sapiopycommons-2024.10.1a338 → sapiopycommons-2024.10.8a340}/src/sapiopycommons/rules/__init__.py +0 -0
  59. {sapiopycommons-2024.10.1a338 → sapiopycommons-2024.10.8a340}/src/sapiopycommons/rules/eln_rule_handler.py +0 -0
  60. {sapiopycommons-2024.10.1a338 → sapiopycommons-2024.10.8a340}/src/sapiopycommons/rules/on_save_rule_handler.py +0 -0
  61. {sapiopycommons-2024.10.1a338 → sapiopycommons-2024.10.8a340}/src/sapiopycommons/webhook/__init__.py +0 -0
  62. {sapiopycommons-2024.10.1a338 → sapiopycommons-2024.10.8a340}/src/sapiopycommons/webhook/webservice_handlers.py +0 -0
  63. {sapiopycommons-2024.10.1a338 → sapiopycommons-2024.10.8a340}/tests/_do_not_add_init_py_here +0 -0
  64. {sapiopycommons-2024.10.1a338 → sapiopycommons-2024.10.8a340}/tests/accession_test.py +0 -0
  65. {sapiopycommons-2024.10.1a338 → sapiopycommons-2024.10.8a340}/tests/bio_reg_test.py +0 -0
  66. {sapiopycommons-2024.10.1a338 → sapiopycommons-2024.10.8a340}/tests/chem_test.py +0 -0
  67. {sapiopycommons-2024.10.1a338 → sapiopycommons-2024.10.8a340}/tests/kappa.chains.fasta +0 -0
  68. {sapiopycommons-2024.10.1a338 → sapiopycommons-2024.10.8a340}/tests/mafft_test.py +0 -0
  69. {sapiopycommons-2024.10.1a338 → sapiopycommons-2024.10.8a340}/tests/test.gb +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: sapiopycommons
3
- Version: 2024.10.1a338
3
+ Version: 2024.10.8a340
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>
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "sapiopycommons"
7
- version='2024.10.01a338'
7
+ version='2024.10.08a340'
8
8
  authors = [
9
9
  { name="Jonathan Steck", email="jsteck@sapiosciences.com" },
10
10
  { name="Yechen Qiao", email="yqiao@sapiosciences.com" },
@@ -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
- response: int | None = self.callback.show_option_dialog(request)
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
- response: list[str] | None = self.callback.show_list_dialog(request)
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
- response: FieldMap | None = self.callback.show_form_entry_dialog(request)
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
- response: FieldMap | None = self.callback.show_form_entry_dialog(request)
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
- response: FieldValue | None = self.callback.show_input_dialog(request)
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
- response: list[FieldMap] | None = self.callback.show_table_entry_dialog(request)
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
- response: list[FieldMap] | None = self.callback.show_table_entry_dialog(request)
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
- response: list[FieldMap] | None = self.callback.show_table_entry_dialog(request)
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
- response: bool = self.callback.data_record_form_view_dialog(request)
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
- response: list[FieldMap] | None = self.callback.show_temp_table_selection_dialog(request)
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
- response: list[FieldMap] | None = self.callback.show_temp_table_selection_dialog(request)
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
- response: list[DataRecord] | None = self.callback.show_input_selection_dialog(request)
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
- response: ESigningResponsePojo | None = self.callback.show_esign_dialog(request)
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
- file_path: str | None = self.callback.show_file_dialog(request, do_consume)
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
- file_paths: list[str] | None = self.callback.show_multi_file_dialog(request)
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
 
@@ -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 mol_to_sapio_substance(mol: Mol, include_stereoisomers: bool = False,
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
- indigo_inchi.resetOptions()
185
- indigo_inchi_str = indigo_inchi.getInchi(indigo_mol)
186
- molecule["inchi"] = indigo_inchi_str
187
- indigo_inchi_key_str = indigo_inchi.getInchiKey(indigo_inchi_str)
188
- molecule["inchiKey"] = indigo_inchi_key_str
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
@@ -806,7 +806,7 @@ class ExperimentHandler:
806
806
  for sample in samples:
807
807
  if sample.data_type_name != "Sample":
808
808
  raise SapioException(f"Received a {sample.data_type_name} record when Sample records were expected.")
809
- detail: PyRecordModel = sample.add(Child.of_type_name(dt))
809
+ detail: PyRecordModel = sample.add(Child.create_by_name(dt))
810
810
  detail.set_field_values({
811
811
  "SampleId": sample.get_field_value("SampleId"),
812
812
  "OtherSampleId": sample.get_field_value("OtherSampleId")
@@ -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