sapiopycommons 2025.8.22a715__py3-none-any.whl → 2025.8.22a716__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 (49) hide show
  1. sapiopycommons/ai/tool_of_tools.py +917 -0
  2. sapiopycommons/callbacks/callback_util.py +26 -16
  3. sapiopycommons/chem/IndigoMolecules.py +12 -10
  4. sapiopycommons/chem/ps_commons.py +773 -0
  5. sapiopycommons/files/assay_plate_reader.py +93 -0
  6. sapiopycommons/files/file_text_converter.py +207 -0
  7. sapiopycommons/flowcyto/flow_cyto.py +2 -24
  8. sapiopycommons/general/accession_service.py +2 -28
  9. sapiopycommons/multimodal/multimodal.py +2 -24
  10. sapiopycommons/webhook/webservice_handlers.py +1 -1
  11. {sapiopycommons-2025.8.22a715.dist-info → sapiopycommons-2025.8.22a716.dist-info}/METADATA +2 -2
  12. {sapiopycommons-2025.8.22a715.dist-info → sapiopycommons-2025.8.22a716.dist-info}/RECORD +14 -45
  13. sapiopycommons/ai/converter_service_base.py +0 -131
  14. sapiopycommons/ai/protoapi/fielddefinitions/fields_pb2.py +0 -43
  15. sapiopycommons/ai/protoapi/fielddefinitions/fields_pb2.pyi +0 -31
  16. sapiopycommons/ai/protoapi/fielddefinitions/fields_pb2_grpc.py +0 -24
  17. sapiopycommons/ai/protoapi/fielddefinitions/velox_field_def_pb2.py +0 -123
  18. sapiopycommons/ai/protoapi/fielddefinitions/velox_field_def_pb2.pyi +0 -598
  19. sapiopycommons/ai/protoapi/fielddefinitions/velox_field_def_pb2_grpc.py +0 -24
  20. sapiopycommons/ai/protoapi/plan/converter/converter_pb2.py +0 -51
  21. sapiopycommons/ai/protoapi/plan/converter/converter_pb2.pyi +0 -63
  22. sapiopycommons/ai/protoapi/plan/converter/converter_pb2_grpc.py +0 -149
  23. sapiopycommons/ai/protoapi/plan/item/item_container_pb2.py +0 -55
  24. sapiopycommons/ai/protoapi/plan/item/item_container_pb2.pyi +0 -88
  25. sapiopycommons/ai/protoapi/plan/item/item_container_pb2_grpc.py +0 -24
  26. sapiopycommons/ai/protoapi/plan/script/script_pb2.py +0 -59
  27. sapiopycommons/ai/protoapi/plan/script/script_pb2.pyi +0 -102
  28. sapiopycommons/ai/protoapi/plan/script/script_pb2_grpc.py +0 -153
  29. sapiopycommons/ai/protoapi/plan/step_output_pb2.py +0 -45
  30. sapiopycommons/ai/protoapi/plan/step_output_pb2.pyi +0 -42
  31. sapiopycommons/ai/protoapi/plan/step_output_pb2_grpc.py +0 -24
  32. sapiopycommons/ai/protoapi/plan/step_pb2.py +0 -43
  33. sapiopycommons/ai/protoapi/plan/step_pb2.pyi +0 -43
  34. sapiopycommons/ai/protoapi/plan/step_pb2_grpc.py +0 -24
  35. sapiopycommons/ai/protoapi/plan/tool/entry_pb2.py +0 -41
  36. sapiopycommons/ai/protoapi/plan/tool/entry_pb2.pyi +0 -35
  37. sapiopycommons/ai/protoapi/plan/tool/entry_pb2_grpc.py +0 -24
  38. sapiopycommons/ai/protoapi/plan/tool/tool_pb2.py +0 -75
  39. sapiopycommons/ai/protoapi/plan/tool/tool_pb2.pyi +0 -237
  40. sapiopycommons/ai/protoapi/plan/tool/tool_pb2_grpc.py +0 -154
  41. sapiopycommons/ai/protoapi/session/sapio_conn_info_pb2.py +0 -39
  42. sapiopycommons/ai/protoapi/session/sapio_conn_info_pb2.pyi +0 -32
  43. sapiopycommons/ai/protoapi/session/sapio_conn_info_pb2_grpc.py +0 -24
  44. sapiopycommons/ai/protobuf_utils.py +0 -504
  45. sapiopycommons/ai/server.py +0 -104
  46. sapiopycommons/ai/test_client.py +0 -356
  47. sapiopycommons/ai/tool_service_base.py +0 -922
  48. {sapiopycommons-2025.8.22a715.dist-info → sapiopycommons-2025.8.22a716.dist-info}/WHEEL +0 -0
  49. {sapiopycommons-2025.8.22a715.dist-info → sapiopycommons-2025.8.22a716.dist-info}/licenses/LICENSE +0 -0
@@ -1765,8 +1765,11 @@ class CallbackUtil:
1765
1765
  blank_result_handling = BlankResultHandling.REPEAT
1766
1766
  def not_blank_func(r: list[DataRecord]) -> bool:
1767
1767
  return bool(r)
1768
- return self.__send_dialog_blank_results(request, self.callback.show_input_selection_dialog, not_blank_func,
1769
- blank_result_handling, repeat_message, cancel_message)
1768
+ response: list[DataRecord] = self.__send_dialog_blank_results(request,
1769
+ self.callback.show_input_selection_dialog,
1770
+ not_blank_func, blank_result_handling,
1771
+ repeat_message, cancel_message)
1772
+ return self.rec_handler.wrap_models(response, wrapper_type)
1770
1773
 
1771
1774
  # FR-47690: Deprecated the require_authentication parameter.
1772
1775
  # noinspection PyUnusedLocal
@@ -1812,7 +1815,8 @@ class CallbackUtil:
1812
1815
  return response
1813
1816
 
1814
1817
  def request_file(self, title: str, exts: Iterable[str] | None = None,
1815
- show_image_editor: bool = False, show_camera_button: bool = False) -> tuple[str, bytes]:
1818
+ show_image_editor: bool = False, show_camera_button: bool = False,
1819
+ *, enforce_file_extensions: bool = True) -> tuple[str, bytes]:
1816
1820
  """
1817
1821
  Request a single file from the user.
1818
1822
 
@@ -1822,6 +1826,8 @@ class CallbackUtil:
1822
1826
  :param show_image_editor: Whether the user will see an image editor when image is uploaded in this file prompt.
1823
1827
  :param show_camera_button: Whether the user will be able to use camera to take a picture as an upload request,
1824
1828
  rather than selecting an existing file.
1829
+ :param enforce_file_extensions: If true, then the file extensions provided in the exts parameter will be
1830
+ enforced. If false, then the user may upload any file type.
1825
1831
  :return: The file name and bytes of the uploaded file.
1826
1832
  """
1827
1833
  # If no extensions were provided, use an empty list for the extensions instead.
@@ -1841,11 +1847,12 @@ class CallbackUtil:
1841
1847
  file_path: str = self.__send_dialog(request, self.callback.show_file_dialog, data_sink=do_consume)
1842
1848
 
1843
1849
  # Verify that each of the file given matches the expected extension(s).
1844
- self.__verify_file(file_path, sink.data, exts)
1850
+ self.__verify_file(file_path, sink.data, exts if enforce_file_extensions else None)
1845
1851
  return file_path, sink.data
1846
1852
 
1847
1853
  def request_files(self, title: str, exts: Iterable[str] | None = None,
1848
- show_image_editor: bool = False, show_camera_button: bool = False) -> dict[str, bytes]:
1854
+ show_image_editor: bool = False, show_camera_button: bool = False,
1855
+ *, enforce_file_extensions: bool = True) -> dict[str, bytes]:
1849
1856
  """
1850
1857
  Request multiple files from the user.
1851
1858
 
@@ -1855,6 +1862,8 @@ class CallbackUtil:
1855
1862
  :param show_image_editor: Whether the user will see an image editor when image is uploaded in this file prompt.
1856
1863
  :param show_camera_button: Whether the user will be able to use camera to take a picture as an upload request,
1857
1864
  rather than selecting an existing file.
1865
+ :param enforce_file_extensions: If true, then the file extensions provided in the exts parameter will be
1866
+ enforced. If false, then the user may upload any file type.
1858
1867
  :return: A dictionary of file name to file bytes for each file the user uploaded.
1859
1868
  """
1860
1869
  # If no extensions were provided, use an empty list for the extensions instead.
@@ -1870,7 +1879,7 @@ class CallbackUtil:
1870
1879
  for file_path in file_paths:
1871
1880
  sink = InMemoryRecordDataSink(self.user)
1872
1881
  sink.consume_client_callback_file_path_data(file_path)
1873
- self.__verify_file(file_path, sink.data, exts)
1882
+ self.__verify_file(file_path, sink.data, exts if enforce_file_extensions else None)
1874
1883
  ret_dict.update({file_path: sink.data})
1875
1884
 
1876
1885
  return ret_dict
@@ -1887,16 +1896,17 @@ class CallbackUtil:
1887
1896
  """
1888
1897
  if file_path is None or len(file_path) == 0 or file_bytes is None or len(file_bytes) == 0:
1889
1898
  raise SapioUserErrorException("Empty file provided or file unable to be read.")
1890
- if allowed_extensions:
1891
- matches: bool = False
1892
- for ext in allowed_extensions:
1893
- # FR-47690: Changed to a case-insensitive match.
1894
- if file_path.casefold().endswith("." + ext.lstrip(".").casefold()):
1895
- matches = True
1896
- break
1897
- if matches is False:
1898
- raise SapioUserErrorException("Unsupported file type. Expecting the following extension(s): "
1899
- + (",".join(allowed_extensions)))
1899
+ if not allowed_extensions:
1900
+ return
1901
+ matches: bool = False
1902
+ for ext in allowed_extensions:
1903
+ # FR-47690: Changed to a case-insensitive match.
1904
+ if file_path.casefold().endswith("." + ext.lstrip(".").casefold()):
1905
+ matches = True
1906
+ break
1907
+ if not matches:
1908
+ raise SapioUserErrorException("Unsupported file type. Expecting the following extension(s): "
1909
+ + (",".join(allowed_extensions)))
1900
1910
 
1901
1911
  def write_file(self, file_name: str, file_data: str | bytes) -> None:
1902
1912
  """
@@ -6,11 +6,15 @@ indigo = Indigo()
6
6
  renderer = IndigoRenderer(indigo)
7
7
  indigo.setOption("render-output-format", "svg")
8
8
  indigo.setOption("ignore-stereochemistry-errors", True)
9
+ # Ignore only if loading as non-query object. That is the meaning of this flag. Does nothing if it's query molecule.
10
+ indigo.setOption("ignore-noncritical-query-features", True)
9
11
  indigo.setOption("render-stereo-style", "ext")
10
12
  indigo.setOption("aromaticity-model", "generic")
11
13
  indigo.setOption("render-coloring", True)
12
14
  indigo.setOption("molfile-saving-mode", "3000")
13
15
  indigo.setOption("dearomatize-verification", False)
16
+ #YQ: Sapio-Only Option, this is my modification.
17
+ indigo.setOption("rpe-bypass-saturation-check", True)
14
18
  indigo_inchi = IndigoInchi(indigo)
15
19
 
16
20
 
@@ -22,17 +26,15 @@ def get_aromatic_dearomatic_forms(m: IndigoObject):
22
26
  :return: pair of indigo objects, first is aromatic, second is dearomatic.
23
27
  """
24
28
  try:
25
- aromatic_reaction = m.clone()
26
- aromatic_reaction.aromatize()
27
- dearomatic_reaction = aromatic_reaction.clone()
29
+ # Note: there are problems with aromatization being applied more than once, because it can crash.
30
+ # This is because an implicit assumption made on consequence of a particular perception may cause further
31
+ # aromatization to fail to find next steps and also fails to find termination.
32
+ # So, to ensure we only apply aromatization once, we dearomatize first, then aromatize.
33
+ dearomatic_reaction = m.clone()
28
34
  dearomatic_reaction.dearomatize()
29
- second_aromatic_reaction = dearomatic_reaction.clone()
30
- second_aromatic_reaction.aromatize()
31
- match = indigo.exactMatch(aromatic_reaction, second_aromatic_reaction)
32
- if match:
33
- return aromatic_reaction, dearomatic_reaction
34
- else:
35
- return m, dearomatic_reaction
35
+ aromatic_reaction = dearomatic_reaction.clone()
36
+ aromatic_reaction.aromatize()
37
+ return aromatic_reaction, dearomatic_reaction
36
38
  except (Exception):
37
39
  # If aromatization then following deromatization fails, we just skip it.
38
40
  dearomatic_reaction = m.clone()