sapiopycommons 2025.7.7a580__py3-none-any.whl → 2025.7.9a582__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of sapiopycommons might be problematic. Click here for more details.

Files changed (60) hide show
  1. sapiopycommons/ai/__init__.py +0 -0
  2. sapiopycommons/ai/api/fielddefinitions/proto/fields_pb2.py +43 -0
  3. sapiopycommons/ai/api/fielddefinitions/proto/fields_pb2.pyi +31 -0
  4. sapiopycommons/ai/api/fielddefinitions/proto/fields_pb2_grpc.py +24 -0
  5. sapiopycommons/ai/api/fielddefinitions/proto/velox_field_def_pb2.py +123 -0
  6. sapiopycommons/ai/api/fielddefinitions/proto/velox_field_def_pb2.pyi +598 -0
  7. sapiopycommons/ai/api/fielddefinitions/proto/velox_field_def_pb2_grpc.py +24 -0
  8. sapiopycommons/ai/api/plan/proto/step_output_pb2.py +45 -0
  9. sapiopycommons/ai/api/plan/proto/step_output_pb2.pyi +42 -0
  10. sapiopycommons/ai/api/plan/proto/step_output_pb2_grpc.py +24 -0
  11. sapiopycommons/ai/api/plan/proto/step_pb2.py +43 -0
  12. sapiopycommons/ai/api/plan/proto/step_pb2.pyi +43 -0
  13. sapiopycommons/ai/api/plan/proto/step_pb2_grpc.py +24 -0
  14. sapiopycommons/ai/api/plan/script/proto/script_pb2.py +55 -0
  15. sapiopycommons/ai/api/plan/script/proto/script_pb2.pyi +115 -0
  16. sapiopycommons/ai/api/plan/script/proto/script_pb2_grpc.py +153 -0
  17. sapiopycommons/ai/api/plan/tool/proto/entry_pb2.py +57 -0
  18. sapiopycommons/ai/api/plan/tool/proto/entry_pb2.pyi +96 -0
  19. sapiopycommons/ai/api/plan/tool/proto/entry_pb2_grpc.py +24 -0
  20. sapiopycommons/ai/api/plan/tool/proto/tool_pb2.py +67 -0
  21. sapiopycommons/ai/api/plan/tool/proto/tool_pb2.pyi +220 -0
  22. sapiopycommons/ai/api/plan/tool/proto/tool_pb2_grpc.py +154 -0
  23. sapiopycommons/ai/api/session/proto/sapio_conn_info_pb2.py +39 -0
  24. sapiopycommons/ai/api/session/proto/sapio_conn_info_pb2.pyi +32 -0
  25. sapiopycommons/ai/api/session/proto/sapio_conn_info_pb2_grpc.py +24 -0
  26. sapiopycommons/ai/protobuf_utils.py +508 -0
  27. sapiopycommons/ai/test_client.py +251 -0
  28. sapiopycommons/ai/tool_service_base.py +798 -0
  29. sapiopycommons/callbacks/callback_util.py +332 -665
  30. sapiopycommons/callbacks/field_builder.py +0 -2
  31. sapiopycommons/chem/IndigoMolecules.py +1 -31
  32. sapiopycommons/chem/Molecules.py +3 -3
  33. sapiopycommons/customreport/auto_pagers.py +1 -26
  34. sapiopycommons/customreport/term_builder.py +1 -1
  35. sapiopycommons/datatype/pseudo_data_types.py +326 -349
  36. sapiopycommons/eln/experiment_handler.py +767 -408
  37. sapiopycommons/eln/experiment_report_util.py +6 -11
  38. sapiopycommons/eln/plate_designer.py +2 -7
  39. sapiopycommons/files/file_util.py +5 -7
  40. sapiopycommons/general/accession_service.py +2 -2
  41. sapiopycommons/general/aliases.py +1 -3
  42. sapiopycommons/general/audit_log.py +0 -7
  43. sapiopycommons/general/custom_report_util.py +0 -12
  44. sapiopycommons/processtracking/custom_workflow_handler.py +1 -11
  45. sapiopycommons/processtracking/endpoints.py +0 -27
  46. sapiopycommons/recordmodel/record_handler.py +317 -657
  47. sapiopycommons/rules/eln_rule_handler.py +1 -8
  48. sapiopycommons/rules/on_save_rule_handler.py +1 -8
  49. sapiopycommons/webhook/webhook_handlers.py +0 -3
  50. sapiopycommons/webhook/webservice_handlers.py +2 -2
  51. {sapiopycommons-2025.7.7a580.dist-info → sapiopycommons-2025.7.9a582.dist-info}/METADATA +2 -2
  52. sapiopycommons-2025.7.9a582.dist-info/RECORD +92 -0
  53. sapiopycommons/chem/ps_commons.py +0 -455
  54. sapiopycommons/eln/experiment_cache.py +0 -188
  55. sapiopycommons/eln/experiment_step_factory.py +0 -476
  56. sapiopycommons/eln/step_creation.py +0 -236
  57. sapiopycommons/general/data_structure_util.py +0 -115
  58. sapiopycommons-2025.7.7a580.dist-info/RECORD +0 -69
  59. {sapiopycommons-2025.7.7a580.dist-info → sapiopycommons-2025.7.9a582.dist-info}/WHEEL +0 -0
  60. {sapiopycommons-2025.7.7a580.dist-info → sapiopycommons-2025.7.9a582.dist-info}/licenses/LICENSE +0 -0
@@ -126,16 +126,12 @@ class ElnRuleHandler:
126
126
  """
127
127
  return list(self._entry_to_field_maps.keys())
128
128
 
129
- # CR-47529: Add info about HVDT behavior to the docstring of these functions.
130
129
  def get_records(self, data_type: DataTypeIdentifier, entry: str | None = None) -> list[DataRecord]:
131
130
  """
132
131
  Get records from the cached context with the given data type. Capable of being filtered to searching within
133
132
  the context of an entry name. If the given data type or entry does not exist in the context,
134
133
  returns an empty list.
135
134
 
136
- Note that if you are attempting to retrieve record that are high volume data types and are receiving nothing,
137
- the HVDTs may have been sent as field maps. Consider using the get_field_maps function if this occurs.
138
-
139
135
  :param data_type: The data type of the records to return.
140
136
  :param entry: The name of the entry to grab the records from. If None, returns the records that match the data
141
137
  type from every entry. If an entry is provided, but it does not exist in the context, returns an empty list.
@@ -154,7 +150,7 @@ class ElnRuleHandler:
154
150
 
155
151
  Field maps will only exist in the context if the data record that the fields are from is no longer accessible
156
152
  to the user. This can occur because the data record was deleted, or because the user does not have access to the
157
- record due to ACL. This can also occur under certain circumstances if the records are HVDTs.
153
+ record due to ACL.
158
154
 
159
155
  :param data_type: The data type of the field maps to return.
160
156
  :param entry: The name of the entry to grab the field maps from. If None, returns the field maps that match the
@@ -174,9 +170,6 @@ class ElnRuleHandler:
174
170
  within the context of an entry name. If the given data type or entry does not exist in the context,
175
171
  returns an empty list.
176
172
 
177
- Note that if you are attempting to retrieve record that are high volume data types and are receiving nothing,
178
- the HVDTs may have been sent as field maps. Consider using the get_field_maps function if this occurs.
179
-
180
173
  :param wrapper_type: The record model wrapper or data type name of the record to get from the context.
181
174
  :param entry: The name of the entry to grab the records from. If None, returns the records that match the data
182
175
  type from every entry. If an entry is provided, but it does not exist in the context, returns an empty list.
@@ -122,16 +122,12 @@ class OnSaveRuleHandler:
122
122
  """
123
123
  return list(self._base_id_to_field_maps.keys())
124
124
 
125
- # CR-47529: Add info about HVDT behavior to the docstring of these functions.
126
125
  def get_records(self, data_type: DataTypeIdentifier, record_id: int | None = None) -> list[DataRecord]:
127
126
  """
128
127
  Get records from the cached context with the given data type. Capable of being filtered to searching within
129
128
  the context of a record ID. If the given data type or record ID does not exist in the context,
130
129
  returns an empty list.
131
130
 
132
- Note that if you are attempting to retrieve record that are high volume data types and are receiving nothing,
133
- the HVDTs may have been sent as field maps. Consider using the get_field_maps function if this occurs.
134
-
135
131
  :param data_type: The data type of the records to return.
136
132
  :param record_id: The record ID of the base record to search from. If None, returns the records that match the
137
133
  data type from every ID. If an ID is provided, but it does not exist in the context, returns an empty list.
@@ -150,7 +146,7 @@ class OnSaveRuleHandler:
150
146
 
151
147
  Field maps will only exist in the context if the data record that the fields are from is no longer accessible
152
148
  to the user. This can occur because the data record was deleted, or because the user does not have access to the
153
- record due to ACL. This can also occur under certain circumstances if the records are HVDTs.
149
+ record due to ACL.
154
150
 
155
151
  :param data_type: The data type of the field maps to return.
156
152
  :param record_id: The record ID of the base record to search from. If None, returns the field maps that match
@@ -170,9 +166,6 @@ class OnSaveRuleHandler:
170
166
  the context of a record ID. If the given data type or record ID does not exist in the context,
171
167
  returns an empty list.
172
168
 
173
- Note that if you are attempting to retrieve record that are high volume data types and are receiving nothing,
174
- the HVDTs may have been sent as field maps. Consider using the get_field_maps function if this occurs.
175
-
176
169
  :param wrapper_type: The record model wrapper or data type name of the record to get from the context.
177
170
  :param record_id: The record ID of the base record to search from. If None, returns the records that match the
178
171
  data type from ID. If an ID is provided, but it does not exist in the context, returns an empty list.
@@ -220,9 +220,6 @@ class CommonsWebhookHandler(AbstractWebhookHandler):
220
220
  else:
221
221
  self.custom_context = None
222
222
 
223
- # CR-47526: Set the dialog timeout to 1 hour by default. This can be overridden by the webhook.
224
- self.callback.set_dialog_timeout(3600)
225
-
226
223
  # Set the default display types, titles, and messages for each type of exception that can display a message.
227
224
  self.default_user_error_display_type = MessageDisplayType.TOASTER_WARNING
228
225
  self.default_critical_error_display_type = MessageDisplayType.DISPLAY_ERROR
@@ -3,7 +3,7 @@ import traceback
3
3
  from abc import abstractmethod, ABC
4
4
  from base64 import b64decode
5
5
  from logging import Logger
6
- from typing import Any, Mapping
6
+ from typing import Any
7
7
 
8
8
  from flask import request, Response, Request
9
9
  from sapiopylib.rest.DataRecordManagerService import DataRecordManager
@@ -122,7 +122,7 @@ class AbstractWebserviceHandler(AbstractWebhookHandler):
122
122
  """
123
123
  pass
124
124
 
125
- def authenticate_user(self, headers: Mapping[str, str]) -> SapioUser:
125
+ def authenticate_user(self, headers: dict[str, str]) -> SapioUser:
126
126
  """
127
127
  Authenticate a user for making requests to a Sapio server using the provided headers. If no user can be
128
128
  authenticated, then an exception will be thrown.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: sapiopycommons
3
- Version: 2025.7.7a580
3
+ Version: 2025.7.9a582
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>
@@ -17,7 +17,7 @@ Classifier: Topic :: Scientific/Engineering :: Bio-Informatics
17
17
  Classifier: Topic :: Software Development :: Libraries :: Python Modules
18
18
  Requires-Python: >=3.10
19
19
  Requires-Dist: databind>=4.5
20
- Requires-Dist: sapiopylib>=2025.4.17.264
20
+ Requires-Dist: sapiopylib>=2024.5.24.210
21
21
  Description-Content-Type: text/markdown
22
22
 
23
23
 
@@ -0,0 +1,92 @@
1
+ sapiopycommons/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
+ sapiopycommons/ai/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
3
+ sapiopycommons/ai/protobuf_utils.py,sha256=5Y0z_lRVipbHPJcHJf9GosK-ZjAh_I2JI3QwV8iyfKg,24884
4
+ sapiopycommons/ai/test_client.py,sha256=8AlRTuy7XUC40aU4SgTQkNc_ZBp_ANhVNEKbUx8ABRM,10430
5
+ sapiopycommons/ai/tool_service_base.py,sha256=8it0853p-kw3Y1SdEyEfmxCbxM3_poUTJKFUaZ5MrCQ,35366
6
+ sapiopycommons/ai/api/fielddefinitions/proto/fields_pb2.py,sha256=YcZjb_YM-XeLErM8hEC_S7vGMVGvcXAMGs2b-u5zvOE,2377
7
+ sapiopycommons/ai/api/fielddefinitions/proto/fields_pb2.pyi,sha256=FwtXmNAf7iYGEFm4kbqb04v77jNHbZg18ZmEDhle_bU,1444
8
+ sapiopycommons/ai/api/fielddefinitions/proto/fields_pb2_grpc.py,sha256=wPImJPdCUZNVEVoUWzsba9kGIXjEKPdUkawP5SnVyiU,932
9
+ sapiopycommons/ai/api/fielddefinitions/proto/velox_field_def_pb2.py,sha256=nWC91vR2pMgMUyNOZRZ0YiuL1-8ntnjXLqt1daxsD34,20869
10
+ sapiopycommons/ai/api/fielddefinitions/proto/velox_field_def_pb2.pyi,sha256=U5zXrbBxsWilLTsRWJd1TqjdjLKFsr3enF9OJ8GfyWw,34028
11
+ sapiopycommons/ai/api/fielddefinitions/proto/velox_field_def_pb2_grpc.py,sha256=4vD4jWanaJ4uclSkFmS7JIz_lwYXDWBE3DomuPjUyII,941
12
+ sapiopycommons/ai/api/plan/proto/step_output_pb2.py,sha256=JpBZSyoYyPTEaaXjW664PeJNK0zxV1mly_kp5re42z4,2661
13
+ sapiopycommons/ai/api/plan/proto/step_output_pb2.pyi,sha256=yuxOYnDZ9DRuu-TLzaKOW_B4LUiYxTrNc2AbssXg4kE,2022
14
+ sapiopycommons/ai/api/plan/proto/step_output_pb2_grpc.py,sha256=vDRY_pIIshQ4UpdW-ra1F5zBmntdsW2scySkMAA-zfc,925
15
+ sapiopycommons/ai/api/plan/proto/step_pb2.py,sha256=nL976oTFdX4ih4gg7_J-8eFoGB69tFvERB2gT3L2-6s,2439
16
+ sapiopycommons/ai/api/plan/proto/step_pb2.pyi,sha256=QPIcsjcUvEGQkdZMUMiVzFFNDl8yOUe_qJtf5XEp5Ck,2062
17
+ sapiopycommons/ai/api/plan/proto/step_pb2_grpc.py,sha256=DgiBYFvTNiDG_2a9Tpt5iel2fRUfePZWP41fZTC-KWk,918
18
+ sapiopycommons/ai/api/plan/script/proto/script_pb2.py,sha256=D668fAG3dAvE8ijCzaflj7o6D5M5szW4X8Wq-8EYxBU,4712
19
+ sapiopycommons/ai/api/plan/script/proto/script_pb2.pyi,sha256=VS9WXKcGuUj6ulziLxhgEBMRXP-QgHlFeMISDDOhAQY,7174
20
+ sapiopycommons/ai/api/plan/script/proto/script_pb2_grpc.py,sha256=RkShHpe_d5EJHk3qp-or1JpvSEqShb7cCiaXnJ2YSww,6931
21
+ sapiopycommons/ai/api/plan/tool/proto/entry_pb2.py,sha256=A-ufAWwbJ0odVXBZBQKvke6LYPijPl2dpb2IFRaXmPE,4124
22
+ sapiopycommons/ai/api/plan/tool/proto/entry_pb2.pyi,sha256=NNBrdHz5PzAuUEXuGgTU4THy9rx8Sr9iGTb65QtVH4Q,4589
23
+ sapiopycommons/ai/api/plan/tool/proto/entry_pb2_grpc.py,sha256=YKkX2kexERUx4asLCShufSnZhgf339Zk8Xw1FAgLfHQ,924
24
+ sapiopycommons/ai/api/plan/tool/proto/tool_pb2.py,sha256=jrSgsSx8mGPWuxYPxOom7l925bavWaX4NEQAkC9niJo,6854
25
+ sapiopycommons/ai/api/plan/tool/proto/tool_pb2.pyi,sha256=KzOmH-ic0anjRta4jhScMRCOI1OtO5rSZrQDr_W3eZ0,15992
26
+ sapiopycommons/ai/api/plan/tool/proto/tool_pb2_grpc.py,sha256=68u5E1ZKha5frP5GuY8Ad-c0c79vBhtfnj5Q4u-8xOY,6982
27
+ sapiopycommons/ai/api/session/proto/sapio_conn_info_pb2.py,sha256=WKzNi-d5dqeJbmEXUVE5qJ4Qm34HmsqRXRtXih382g8,2100
28
+ sapiopycommons/ai/api/session/proto/sapio_conn_info_pb2.pyi,sha256=vLYA8Tkzq2AwgVadoUp5vAg4HgGlgga0kzeS3e_XkCQ,1621
29
+ sapiopycommons/ai/api/session/proto/sapio_conn_info_pb2_grpc.py,sha256=2W0YzT4SfnGTLq98AdvkyNM0n75Tkl8DervPS1ryGao,932
30
+ sapiopycommons/callbacks/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
31
+ sapiopycommons/callbacks/callback_util.py,sha256=sz76LzD9sVLPMcoOpmzPe1aILGXcpHfyZ-qex8oR11c,130849
32
+ sapiopycommons/callbacks/field_builder.py,sha256=p2XacN99MuKk3ite8GAqstUMpixqugul2CsC4gB83-o,38620
33
+ sapiopycommons/chem/IndigoMolecules.py,sha256=slM2y39zZFHc468c366EqR8T-GYJ24UnM9HWAqWFEwQ,3900
34
+ sapiopycommons/chem/Molecules.py,sha256=5PzRyE1s-Z3nfwh3Y4dCNdQOIJGhog08wyZvgTkKwyU,12384
35
+ sapiopycommons/chem/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
36
+ sapiopycommons/customreport/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
37
+ sapiopycommons/customreport/auto_pagers.py,sha256=3-XXWrP7r41a_Y-8YLPnfm0s65m4qEEUqu4azX47oPI,14828
38
+ sapiopycommons/customreport/column_builder.py,sha256=0RO53e9rKPZ07C--KcepN6_tpRw_FxF3O9vdG0ilKG8,3014
39
+ sapiopycommons/customreport/custom_report_builder.py,sha256=BlTxZ4t1sfZA2Ciur1EfYvkZxHxJ7ADwYNAe2zwiN0c,7176
40
+ sapiopycommons/customreport/term_builder.py,sha256=PNp71NF1vFxidk5v6uQNi9oQR9KJIk8WfhyntvvZN-U,18573
41
+ sapiopycommons/datatype/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
42
+ sapiopycommons/datatype/attachment_util.py,sha256=N-nhsJ0oxa_Ft6Y6VWeNFYLzfuQqsjhHA6_-yIt2wVw,3596
43
+ sapiopycommons/datatype/data_fields.py,sha256=pczUlEcE0TeHEDU0Gkvu7voacSLPXCB7l9UbI1Tb6V0,5656
44
+ sapiopycommons/datatype/pseudo_data_types.py,sha256=6TG7aJxgmUZ8FQkWBcgmbK5oy7AFFNtKOPpi1w1OOYA,27657
45
+ sapiopycommons/eln/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
46
+ sapiopycommons/eln/experiment_handler.py,sha256=FeqmjnxN5WyDDstN3_AMSo9r7nQi-5zou4m-3K5JsYM,123363
47
+ sapiopycommons/eln/experiment_report_util.py,sha256=NNNNPVD3_2ZAjoOqCMOnlnmPD0SCjDcgYi453ATSJBs,37027
48
+ sapiopycommons/eln/experiment_tags.py,sha256=7-fpOiSqrjbXmWIJhEhaxMgLsVCPAtKqH8xRzpDVKoE,356
49
+ sapiopycommons/eln/plate_designer.py,sha256=ix2cflz13PAHyu4deS3d5Qd3kQXk0C7IQxBQ2Dm9fEM,13692
50
+ sapiopycommons/files/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
51
+ sapiopycommons/files/complex_data_loader.py,sha256=T39veNhvYl6j_uZjIIJ8Mk5Aa7otR5RB-g8XlAdkksA,1421
52
+ sapiopycommons/files/file_bridge.py,sha256=vKbqxPexi15epr_-_qLrEfYoxNxB031mXN92iVtOMqE,9511
53
+ sapiopycommons/files/file_bridge_handler.py,sha256=SEYDIQhSCmjI6qyLdDJE8JVKSd0WYvF7JvAq_Ahp9Do,25503
54
+ sapiopycommons/files/file_data_handler.py,sha256=f96MlkMuQhUCi4oLnzJK5AiuElCp5jLI8_sJkZVwpws,36779
55
+ sapiopycommons/files/file_util.py,sha256=w4Q7zYJb9YaPxrecmT4RT_OOibKMRP0NI1CyoOLfAP4,31747
56
+ sapiopycommons/files/file_validator.py,sha256=ryg22-93csmRO_Pv0ZpWphNkB74xWZnHyJ23K56qLj0,28761
57
+ sapiopycommons/files/file_writer.py,sha256=hACVl0duCjP28gJ1NPljkjagNCLod0ygUlPbvUmRDNM,17605
58
+ sapiopycommons/flowcyto/flow_cyto.py,sha256=vs9WhXXKz3urpjL8QKSk56B-NSmQR3O3x_WFBKoeO10,3227
59
+ sapiopycommons/flowcyto/flowcyto_data.py,sha256=mYKFuLbtpJ-EsQxLGtu4tNHVlygTxKixgJxJqD68F58,2596
60
+ sapiopycommons/general/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
61
+ sapiopycommons/general/accession_service.py,sha256=QeNGd78Zr08ezWpThTuAkxO4bXusEcp5x9jJRZRwliA,13483
62
+ sapiopycommons/general/aliases.py,sha256=alc2R5TOSKbZif3pcGZ706y23Y9F4HQPC-Kj4xJZ_Us,14503
63
+ sapiopycommons/general/audit_log.py,sha256=KQq0PsvukUoE3l6TQb3-vpu5-MbSINpWlnQ9e7jojPg,8743
64
+ sapiopycommons/general/custom_report_util.py,sha256=NwwmejSQLwSbrndEk1gPyFNYk9GZoS7Wrp9ab9moFgw,18014
65
+ sapiopycommons/general/directive_util.py,sha256=7SeQrd2Ye5JHlXZtJZaVGgtaSLdq_Vm9EObuxf44Pz8,3905
66
+ sapiopycommons/general/exceptions.py,sha256=aPlzK1cvxeMU5UsokYlLrIBGltUfJZ7LH8zvLh9DxpI,3233
67
+ sapiopycommons/general/html_formatter.py,sha256=HE3OeGgwOw6x53zGSc4-UzP4-JoOmQIz3pX-DzNVg94,17138
68
+ sapiopycommons/general/popup_util.py,sha256=HKILegU1uCL_6abNlNL0Wn3xgX2JNa_kJeq7e5CZu6Q,31923
69
+ sapiopycommons/general/sapio_links.py,sha256=YkcVKNLrSGoM7tCCXBAsIbIxylctwdcEyhePrRMODe0,2859
70
+ sapiopycommons/general/storage_util.py,sha256=ovmK_jN7v09BoX07XxwShpBUC5WYQOM7dbKV_VeLXJU,8892
71
+ sapiopycommons/general/time_util.py,sha256=jU1urPoZRv6evNucR0-288EyT4PrsDpCr-H1-7BKq9A,12363
72
+ sapiopycommons/multimodal/multimodal.py,sha256=PFaGJPbKvW__tnxb8KkgkJZOKjQdgxF_kGfD5chet1s,6779
73
+ sapiopycommons/multimodal/multimodal_data.py,sha256=0BeVPr9HaC0hNTF1v1phTIKGruvNnwerHsD994qJKBg,15099
74
+ sapiopycommons/processtracking/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
75
+ sapiopycommons/processtracking/custom_workflow_handler.py,sha256=QZVRDUXpHfYIKD9LtaOcOt0Sr3RGDaeGQb-LZYAgkCc,25117
76
+ sapiopycommons/processtracking/endpoints.py,sha256=w5bziI2xC7450M95rCF8JpRwkoni1kEDibyAux9B12Q,10848
77
+ sapiopycommons/recordmodel/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
78
+ sapiopycommons/recordmodel/record_handler.py,sha256=aPbR3OS0RlsKOGztY-LZaOmkXKUB8ZTw3qqdJKXwd6U,70872
79
+ sapiopycommons/rules/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
80
+ sapiopycommons/rules/eln_rule_handler.py,sha256=1aC88brATcjL1O0Hd2hQ0XNguKsKh8xELXComotk3mQ,10772
81
+ sapiopycommons/rules/on_save_rule_handler.py,sha256=J1YKjOGA1KUTwpnZMa7oIi5QU_4mBJrPygSHNDsMIIA,10539
82
+ sapiopycommons/samples/aliquot.py,sha256=mWOJUqaQh0t3HklNuGdmuV7D5zzXs6fpLwtDdM6_XTo,3018
83
+ sapiopycommons/sftpconnect/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
84
+ sapiopycommons/sftpconnect/sftp_builder.py,sha256=lFK3FeXk-sFLefW0hqY8WGUQDeYiGaT6yDACzT_zFgQ,3015
85
+ sapiopycommons/webhook/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
86
+ sapiopycommons/webhook/webhook_context.py,sha256=D793uLsb1691SalaPnBUk3rOSxn_hYLhdvkaIxjNXss,1909
87
+ sapiopycommons/webhook/webhook_handlers.py,sha256=L0HetSm43NvA5KyW3xbLpGFh2DbAaeZJVtXIEl2fvV8,39689
88
+ sapiopycommons/webhook/webservice_handlers.py,sha256=Y5dHx_UFWFuSqaoPL6Re-fsKYRuxvCWZ8bj6KSZ3jfM,14285
89
+ sapiopycommons-2025.7.9a582.dist-info/METADATA,sha256=HBCsgpHoxOvmMOEjgVaJ8QFBOYbCWTpP8Ctd8N4rWf8,3142
90
+ sapiopycommons-2025.7.9a582.dist-info/WHEEL,sha256=C2FUgwZgiLbznR-k0b_5k3Ai_1aASOXDss3lzCUsUug,87
91
+ sapiopycommons-2025.7.9a582.dist-info/licenses/LICENSE,sha256=HyVuytGSiAUQ6ErWBHTqt1iSGHhLmlC8fO7jTCuR8dU,16725
92
+ sapiopycommons-2025.7.9a582.dist-info/RECORD,,
@@ -1,455 +0,0 @@
1
- """
2
- Parallel Synthesis Commons
3
- Author: Yechen Qiao
4
- """
5
- import json
6
- from dataclasses import dataclass
7
- from typing import Any
8
-
9
- from indigo import IndigoObject
10
- from sapiopycommons.chem.IndigoMolecules import indigo, get_aromatic_dearomatic_forms, renderer
11
-
12
-
13
- class SerializableQueryMolecule:
14
- mol_block: str
15
- smarts: str
16
- render_svg: str
17
-
18
- @staticmethod
19
- def create(query_molecule: IndigoObject):
20
- aromatic, dearomatic = get_aromatic_dearomatic_forms(query_molecule)
21
- ret: SerializableQueryMolecule = SerializableQueryMolecule()
22
- ret.mol_block = aromatic.molfile()
23
- ret.smarts = aromatic.smarts()
24
- ret.render_svg = renderer.renderToString(dearomatic)
25
- return ret
26
-
27
- def to_json(self) -> dict[str, Any]:
28
- """
29
- Save the SerializableQueryMolecule to a JSON string.
30
- :return: A JSON string representation of the query molecule.
31
- """
32
- return {
33
- "mol_block": self.mol_block,
34
- "smarts": self.smarts,
35
- "render_svg": self.render_svg
36
- }
37
-
38
-
39
- class SerializableMoleculeMatch:
40
- """
41
- A serializable match that stores and loads a match that can be serialized to JSON.
42
- """
43
- _query_atom_to_atom: dict[int, int]
44
- _query_bond_to_bond: dict[int, int]
45
- _query_molecule_file: str
46
- _matching_molecule_file: str
47
- _query_molecule: IndigoObject
48
- _matching_molecule: IndigoObject
49
- _record_id: int # Only when received from Sapio.
50
-
51
- @property
52
- def record_id(self) -> int:
53
- """
54
- Get the record ID of the match.
55
- :return: The record ID.
56
- """
57
- return self._record_id
58
-
59
- def __str__(self):
60
- return json.dumps(self.to_json())
61
-
62
- def __hash__(self):
63
- return hash(self._query_molecule.smarts())
64
-
65
- def __eq__(self, other):
66
- if not isinstance(other, SerializableMoleculeMatch):
67
- return False
68
- if self._query_atom_to_atom == other._query_atom_to_atom and \
69
- self._query_bond_to_bond == other._query_bond_to_bond and \
70
- self._query_molecule_file == other._query_molecule_file and \
71
- self._matching_molecule_file == other._matching_molecule_file and \
72
- self._record_id == other._record_id:
73
- return True
74
- if self._query_molecule.smarts() != other._query_molecule.smarts():
75
- return False
76
- return are_symmetrical_subs(self, other)
77
-
78
- def mapAtom(self, atom: IndigoObject) -> IndigoObject | None:
79
- if not self._query_atom_to_atom or atom.index() not in self._query_atom_to_atom:
80
- return None
81
- index = self._query_atom_to_atom[atom.index()]
82
- return self._matching_molecule.getAtom(index)
83
-
84
- def mapBond(self, bond: IndigoObject) -> IndigoObject | None:
85
- if not self._query_bond_to_bond or bond.index() not in self._query_bond_to_bond:
86
- return None
87
- index = self._query_bond_to_bond[bond.index()]
88
- return self._matching_molecule.getBond(index)
89
-
90
- def to_json(self) -> dict[str, Any]:
91
- """
92
- Save the SerializableMoleculeMatch to a JSON string.
93
- :return: A JSON string representation of the match.
94
- """
95
- return {
96
- "query_molecule_file": self._query_molecule_file,
97
- "matching_molecule_file": self._matching_molecule_file,
98
- "query_atom_to_atom": self._query_atom_to_atom,
99
- "query_bond_to_bond": self._query_bond_to_bond,
100
- "record_id": self._record_id
101
- }
102
-
103
- @staticmethod
104
- def from_json(json_dct: dict[str, Any]) -> 'SerializableMoleculeMatch':
105
- """
106
- Load a SerializableMoleculeMatch from a JSON string.
107
- :param json_dct: A JSON string representation of the match.
108
- :return: A new SerializableMoleculeMatch instance.
109
- """
110
- smm = SerializableMoleculeMatch()
111
- smm._query_atom_to_atom = {}
112
- for key, value in json_dct.get("query_atom_to_atom", {}).items():
113
- smm._query_atom_to_atom[int(key)] = int(value)
114
- smm._query_bond_to_bond = {}
115
- for key, value in json_dct.get("query_bond_to_bond", {}).items():
116
- smm._query_bond_to_bond[int(key)] = int(value)
117
- smm._query_molecule_file = json_dct.get("query_molecule_file")
118
- smm._matching_molecule_file = json_dct.get("matching_molecule_file")
119
- smm._query_molecule = indigo.loadQueryMolecule(smm._query_molecule_file)
120
- smm._matching_molecule = indigo.loadMolecule(smm._matching_molecule_file)
121
- smm._record_id = json_dct.get("record_id", 0) # Default to 0 if not present
122
- return smm
123
-
124
- @staticmethod
125
- def create(query_molecule: IndigoObject, matching_molecule: IndigoObject,
126
- match: IndigoObject) -> 'SerializableMoleculeMatch':
127
- """
128
- Create a SerializableMoleculeMatch from a query molecule, matching molecule, and match.
129
- :param query_molecule: The query molecule.
130
- :param matching_molecule: The matching molecule.
131
- :param match: The match object containing atom mappings.
132
- :return: A new SerializableMoleculeMatch instance.
133
- """
134
- smm = SerializableMoleculeMatch()
135
- smm._query_atom_to_atom = {}
136
- smm._query_bond_to_bond = {}
137
- smm._query_molecule = query_molecule.clone()
138
- smm._matching_molecule = matching_molecule.clone()
139
- smm._query_molecule_file = query_molecule.molfile()
140
- smm._matching_molecule_file = matching_molecule.molfile()
141
- smm._record_id = 0
142
-
143
- for qatom in query_molecule.iterateAtoms():
144
- concrete_atom = match.mapAtom(qatom)
145
- if concrete_atom is None:
146
- continue
147
- smm._query_atom_to_atom[qatom.index()] = concrete_atom.index()
148
-
149
- for qbond in query_molecule.iterateBonds():
150
- concrete_bond = match.mapBond(qbond)
151
- if concrete_bond is None:
152
- continue
153
- smm._query_bond_to_bond[qbond.index()] = concrete_bond.index()
154
- return smm
155
-
156
- def get_matched_molecule_copy(self):
157
- return self._matching_molecule.clone()
158
-
159
-
160
- @dataclass
161
- class ReplacementReaction:
162
- """
163
- A replacement reaction stores reactio template with 1 reactant replaced by specific user match.
164
- """
165
- reaction: IndigoObject
166
- reaction_reactant: IndigoObject
167
- replacement_reactant: IndigoObject
168
- replacement_query_reaction_match: SerializableMoleculeMatch
169
-
170
-
171
- # noinspection PyProtectedMember
172
- def highlight_mol_substructure_serial_match(molecule: IndigoObject, serializable_match: SerializableMoleculeMatch):
173
- """
174
- Highlight the substructure in the molecule based on the SerializableMoleculeMatch.
175
- :param molecule: The molecule to highlight.
176
- :param serializable_match: The SerializableMoleculeMatch containing atom mappings.
177
- """
178
- for qatom in serializable_match._query_molecule.iterateAtoms():
179
- atom = serializable_match.mapAtom(qatom)
180
- if atom is None:
181
- continue
182
- atom.highlight()
183
-
184
- for nei in atom.iterateNeighbors():
185
- if not nei.isPseudoatom() and not nei.isRSite() and nei.atomicNumber() == 1:
186
- nei.highlight()
187
- nei.bond().highlight()
188
-
189
- for bond in serializable_match._query_molecule.iterateBonds():
190
- bond = serializable_match.mapBond(bond)
191
- if bond is None:
192
- continue
193
- bond.highlight()
194
-
195
-
196
- def clear_highlights(molecule: IndigoObject):
197
- """
198
- Clear all highlights in the molecule.
199
- :param molecule: The molecule to clear highlights from.
200
- """
201
- for atom in molecule.iterateAtoms():
202
- atom.unhighlight()
203
- for bond in molecule.iterateBonds():
204
- bond.unhighlight()
205
-
206
-
207
- def clear_reaction_highlights(reaction: IndigoObject):
208
- """
209
- Clear all highlights in the reaction.
210
- :param reaction: The reaction to clear highlights from.
211
- """
212
- for reactant in reaction.iterateReactants():
213
- clear_highlights(reactant)
214
- for product in reaction.iterateProducts():
215
- clear_highlights(product)
216
-
217
-
218
- def reserve_atom_mapping_number_of_search_result(q_reaction: IndigoObject, q_reactant: IndigoObject,
219
- new_reaction_reactant: IndigoObject, new_reaction: IndigoObject,
220
- sub_match: SerializableMoleculeMatch) -> None:
221
- """
222
- Set the atom mapping number on the query molecule based on the atom mapping number of the sub_match molecule, if it exists.
223
- :param new_reaction: The new reaction where the new reaction's reactant is found. This will be the target reaciton to write AAM to.
224
- :param new_reaction_reactant: The new reaction's reactant where the AAM will be written to.
225
- :param q_reactant: The query reactant from the query reaction that is being matched.
226
- :param q_reaction: The query reaction that contains the query reactant for the sub_match.
227
- :param sub_match: The substructure search match obtained from indigo.substructureMatcher(mol).match(query).
228
- """
229
- for query_atom in q_reactant.iterateAtoms():
230
- concrete_atom = sub_match.mapAtom(query_atom)
231
- if concrete_atom is None:
232
- continue
233
- reaction_atom = q_reactant.getAtom(query_atom.index())
234
- map_num = q_reaction.atomMappingNumber(reaction_atom)
235
- if map_num:
236
- concrete_atom = new_reaction_reactant.getAtom(concrete_atom.index())
237
- new_reaction.setAtomMappingNumber(concrete_atom, map_num)
238
-
239
-
240
- def clean_product_aam(reaction: IndigoObject):
241
- """
242
- Remove atom mappings from product that are not present in the reactants.
243
- """
244
- existing_mapping_numbers = set()
245
- for reactant in reaction.iterateReactants():
246
- for atom in reactant.iterateAtoms():
247
- map_num = reaction.atomMappingNumber(atom)
248
- if map_num:
249
- existing_mapping_numbers.add(map_num)
250
-
251
- for product in reaction.iterateProducts():
252
- for atom in product.iterateAtoms():
253
- map_num = reaction.atomMappingNumber(atom)
254
- if map_num and map_num not in existing_mapping_numbers:
255
- reaction.setAtomMappingNumber(atom, 0) # YQ: atom number 0 means no mapping number in Indigo
256
-
257
-
258
- def make_concrete_reaction(reactants: list[IndigoObject], products: list[IndigoObject], replacement: IndigoObject,
259
- replacement_index: int) -> tuple[IndigoObject, IndigoObject]:
260
- """
261
- Create a concrete reaction from the given reactants and products, replacing the specified reactant with the replacement molecule.
262
- :param reactants: List of reactant molecules.
263
- :param products: List of product molecules.
264
- :param replacement: The molecule to replace in the reactants.
265
- :param replacement_index: The index of the reactant to replace.
266
- :return: A new IndigoObject representing the concrete reaction.
267
- """
268
- concrete_reaction = indigo.createQueryReaction()
269
- for i, reactant in enumerate(reactants):
270
- if i == replacement_index:
271
- concrete_reaction.addReactant(indigo.loadQueryMolecule(replacement.molfile()))
272
- else:
273
- concrete_reaction.addReactant(reactant.clone())
274
- for product in products:
275
- concrete_reaction.addProduct(product.clone())
276
- return concrete_reaction, concrete_reaction.getMolecule(replacement_index)
277
-
278
-
279
- def is_ambiguous_atom(atom: IndigoObject) -> bool:
280
- """
281
- Test whether the symbol is an adjacent matching wildcard.
282
- """
283
- if atom.isPseudoatom() or atom.isRSite():
284
- return True
285
- symbol = atom.symbol()
286
- if symbol in {'A', 'Q', 'X', 'M', 'AH', 'QH', 'XH', 'MH', 'NOT', 'R', '*'}:
287
- return True
288
- return "[" in symbol and "]" in symbol
289
-
290
-
291
- def get_react_site_highlights(product, ignored_atom_indexes):
292
- """
293
- Get the highlights for the reaction site in the product, ignoring the atoms that are not part of the reaction site.
294
- :param product: The product molecule.
295
- :param ignored_atom_indexes: A set of atom indexes to ignore.
296
- :return: An IndigoObject with highlighted atoms and bonds that are part of the reaction site.
297
- """
298
- highlight = product.clone()
299
- for atom in highlight.iterateAtoms():
300
- if atom.index() not in ignored_atom_indexes:
301
- atom.highlight()
302
- for nei in atom.iterateNeighbors():
303
- if nei.index() not in ignored_atom_indexes:
304
- nei.highlight()
305
- nei.bond().highlight()
306
- return highlight
307
-
308
-
309
- def get_used_reactants_for_match(
310
- reaction: IndigoObject, q_reaction: IndigoObject, reaction_match: IndigoObject,
311
- kept_replacement_reaction_list_list: list[list[ReplacementReaction]]) -> list[ReplacementReaction]:
312
- """
313
- Find the replacement reactions that correspond to the reactants in reaction that also matches the query reaction.
314
- Return None if any of the reactants do not have a corresponding replacement reaction, even though reaction may have matches directly to the query reaction.
315
- Otherwise, return a list of ReplacementReaction objects that correspond to the reactants in the reaction ordered by the reactants in the query reaction.
316
- """
317
- q_reactants = []
318
- for q_reactant in q_reaction.iterateReactants():
319
- q_reactants.append(q_reactant)
320
- q_products = []
321
- for q_product in q_reaction.iterateProducts():
322
- q_products.append(q_product)
323
- reactants = []
324
- for enum_r in reaction.iterateReactants():
325
- reactants.append(enum_r)
326
- products = []
327
- for enum_p in reaction.iterateProducts():
328
- products.append(enum_p)
329
- ret: list[ReplacementReaction] = []
330
- for i, q_reactant in enumerate(q_reactants):
331
- replacement_list = kept_replacement_reaction_list_list[i]
332
- enum_r = reactants[i]
333
- useful_enumr_atom_indexes = set()
334
- for rr_atom in q_reactant.iterateAtoms():
335
- enum_atom = reaction_match.mapAtom(rr_atom)
336
- if enum_atom:
337
- useful_enumr_atom_indexes.add(enum_atom.index())
338
- found: ReplacementReaction | None = None
339
- for rr in replacement_list:
340
- if found:
341
- break
342
- exact_match = indigo.exactMatch(enum_r, rr.replacement_reactant)
343
- if not exact_match:
344
- # YQ Skip if this enumeration is not meant to be the same reactant as replacement we are iterating.
345
- continue
346
- # YQ these are atoms in replacement reactant that are actually used in the query reactant
347
- useful_rr_atom_indexes = set(rr.replacement_query_reaction_match._query_atom_to_atom.values())
348
- useful_rr_neighbor_indexes = set()
349
- # YQ furthermore, an atom is also considered useful for purpose of matching, if it neighbors a useful index, and is an ambiguous atom in query.
350
- q_reactant: IndigoObject
351
- for q_atom in q_reactant.iterateAtoms():
352
- if is_ambiguous_atom(q_atom):
353
- for q_neighbor in q_atom.iterateNeighbors():
354
- mapped_atom = rr.replacement_query_reaction_match.mapAtom(q_neighbor)
355
- if mapped_atom:
356
- rr_atom_index = mapped_atom.index()
357
- useful_rr_neighbor_indexes.add(rr_atom_index)
358
-
359
- useful_enum_r_mapping_numbers = set()
360
- all_enum_r_mapping_numbers = set()
361
- for enum_atom in enum_r.iterateAtoms():
362
- enum_atom_index = enum_atom.index()
363
- mapping_num = reaction.atomMappingNumber(enum_atom)
364
- if mapping_num > 0:
365
- all_enum_r_mapping_numbers.add(mapping_num)
366
- # Assuming rr atom indexes are exact match to enum r.
367
- if enum_atom_index in useful_rr_atom_indexes:
368
- useful_enum_r_mapping_numbers.add(mapping_num)
369
- ignoring_enum_r_mapping_numbers = all_enum_r_mapping_numbers - useful_enum_r_mapping_numbers - useful_rr_neighbor_indexes
370
- all_products_match = True
371
-
372
- rr_products = []
373
- for rr_product in rr.reaction.iterateProducts():
374
- rr_products.append(rr_product)
375
- for j, product in enumerate(products):
376
- q_product = rr_products[j]
377
- product_matcher = indigo.substructureMatcher(product)
378
- ignored_atom_indexes = set()
379
- for enum_product_atom in product.iterateAtoms():
380
- enum_mapping_number = reaction.atomMappingNumber(enum_product_atom)
381
- # YQ For each atom in product: either it is not related to this reactant, or it must be inside the reactant site of the reactant.
382
- if enum_mapping_number in ignoring_enum_r_mapping_numbers:
383
- product_matcher.ignoreAtom(enum_product_atom)
384
- ignored_atom_indexes.add(enum_product_atom.index())
385
- match = product_matcher.match(q_product)
386
- if not match:
387
- all_products_match = False
388
- else:
389
- found = rr
390
-
391
- if all_products_match:
392
- break
393
- if found:
394
- ret.append(found)
395
- else:
396
- return []
397
- return ret
398
-
399
-
400
- def are_symmetrical_subs(match1: SerializableMoleculeMatch, match2: SerializableMoleculeMatch) -> bool:
401
- """
402
- Check if two SerializableMoleculeMatch objects are symmetrical.
403
- That is, if we only get the atoms and bonds in the mapping, the two molecules are identical.
404
- :param match1: The first SerializableMoleculeMatch object.
405
- :param match2: The second SerializableMoleculeMatch object.
406
- :return: True if the matches are symmetrical, False otherwise.
407
- """
408
- match1_test = match1.get_matched_molecule_copy()
409
- match1_atom_indexes = set(match1._query_atom_to_atom.values())
410
- match1_bond_indexes = set(match1._query_bond_to_bond.values())
411
- atom_delete_list: list[int] = []
412
- atom_mirror_list: list[int] = []
413
- bond_delete_list: list[int] = []
414
- bond_mirror_list: list[int] = []
415
- for atom in match1_test.iterateAtoms():
416
- if atom.index() not in match1_atom_indexes:
417
- atom_delete_list.append(atom.index())
418
- else:
419
- atom_mirror_list.append(atom.index())
420
- for bond in match1_test.iterateBonds():
421
- if bond.index() not in match1_bond_indexes:
422
- bond_delete_list.append(bond.index())
423
- else:
424
- bond_mirror_list.append(bond.index())
425
- match1_test.removeBonds(bond_delete_list)
426
- match1_test.removeAtoms(atom_delete_list)
427
- match1_mirror_test = match1.get_matched_molecule_copy()
428
- match1_mirror_test.removeBonds(bond_mirror_list)
429
- match1_mirror_test.removeAtoms(atom_mirror_list)
430
-
431
- match2_test = match2.get_matched_molecule_copy()
432
- match2_atom_indexes = set(match2._query_atom_to_atom.values())
433
- match2_bond_indexes = set(match2._query_bond_to_bond.values())
434
- atom_delete_list = []
435
- bond_delete_list = []
436
- atom_mirror_list = []
437
- bond_mirror_list = []
438
- for atom in match2_test.iterateAtoms():
439
- if atom.index() not in match2_atom_indexes:
440
- atom_delete_list.append(atom.index())
441
- else:
442
- atom_mirror_list.append(atom.index())
443
- for bond in match2_test.iterateBonds():
444
- if bond.index() not in match2_bond_indexes:
445
- bond_delete_list.append(bond.index())
446
- else:
447
- bond_mirror_list.append(bond.index())
448
- match2_test.removeBonds(bond_delete_list)
449
- match2_test.removeAtoms(atom_delete_list)
450
- match2_mirror_test = match2.get_matched_molecule_copy()
451
- match2_mirror_test.removeBonds(bond_mirror_list)
452
- match2_mirror_test.removeAtoms(atom_mirror_list)
453
-
454
- return match1_test.canonicalSmiles() == match2_test.canonicalSmiles() and \
455
- match1_mirror_test.canonicalSmiles() == match2_mirror_test.canonicalSmiles()