sapiopycommons 2025.9.5rc727__py3-none-any.whl → 2025.9.8a730__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 (52) hide show
  1. sapiopycommons/ai/converter_service_base.py +132 -0
  2. sapiopycommons/ai/protoapi/fielddefinitions/fields_pb2.py +43 -0
  3. sapiopycommons/ai/protoapi/fielddefinitions/fields_pb2.pyi +31 -0
  4. sapiopycommons/ai/protoapi/fielddefinitions/fields_pb2_grpc.py +24 -0
  5. sapiopycommons/ai/protoapi/fielddefinitions/velox_field_def_pb2.py +123 -0
  6. sapiopycommons/ai/protoapi/fielddefinitions/velox_field_def_pb2.pyi +598 -0
  7. sapiopycommons/ai/protoapi/fielddefinitions/velox_field_def_pb2_grpc.py +24 -0
  8. sapiopycommons/ai/protoapi/plan/converter/converter_pb2.py +51 -0
  9. sapiopycommons/ai/protoapi/plan/converter/converter_pb2.pyi +63 -0
  10. sapiopycommons/ai/protoapi/plan/converter/converter_pb2_grpc.py +149 -0
  11. sapiopycommons/ai/protoapi/plan/item/item_container_pb2.py +55 -0
  12. sapiopycommons/ai/protoapi/plan/item/item_container_pb2.pyi +90 -0
  13. sapiopycommons/ai/protoapi/plan/item/item_container_pb2_grpc.py +24 -0
  14. sapiopycommons/ai/protoapi/plan/script/script_pb2.py +59 -0
  15. sapiopycommons/ai/protoapi/plan/script/script_pb2.pyi +102 -0
  16. sapiopycommons/ai/protoapi/plan/script/script_pb2_grpc.py +153 -0
  17. sapiopycommons/ai/protoapi/plan/step_output_pb2.py +45 -0
  18. sapiopycommons/ai/protoapi/plan/step_output_pb2.pyi +42 -0
  19. sapiopycommons/ai/protoapi/plan/step_output_pb2_grpc.py +24 -0
  20. sapiopycommons/ai/protoapi/plan/step_pb2.py +43 -0
  21. sapiopycommons/ai/protoapi/plan/step_pb2.pyi +43 -0
  22. sapiopycommons/ai/protoapi/plan/step_pb2_grpc.py +24 -0
  23. sapiopycommons/ai/protoapi/plan/tool/entry_pb2.py +41 -0
  24. sapiopycommons/ai/protoapi/plan/tool/entry_pb2.pyi +35 -0
  25. sapiopycommons/ai/protoapi/plan/tool/entry_pb2_grpc.py +24 -0
  26. sapiopycommons/ai/protoapi/plan/tool/tool_pb2.py +75 -0
  27. sapiopycommons/ai/protoapi/plan/tool/tool_pb2.pyi +237 -0
  28. sapiopycommons/ai/protoapi/plan/tool/tool_pb2_grpc.py +154 -0
  29. sapiopycommons/ai/protoapi/session/sapio_conn_info_pb2.py +39 -0
  30. sapiopycommons/ai/protoapi/session/sapio_conn_info_pb2.pyi +32 -0
  31. sapiopycommons/ai/protoapi/session/sapio_conn_info_pb2_grpc.py +24 -0
  32. sapiopycommons/ai/protobuf_utils.py +504 -0
  33. sapiopycommons/ai/server.py +132 -0
  34. sapiopycommons/ai/test_client.py +356 -0
  35. sapiopycommons/ai/tool_service_base.py +975 -0
  36. sapiopycommons/callbacks/callback_util.py +16 -26
  37. sapiopycommons/eln/experiment_handler.py +5 -12
  38. sapiopycommons/files/temp_files.py +49 -0
  39. sapiopycommons/flowcyto/flow_cyto.py +24 -2
  40. sapiopycommons/general/accession_service.py +28 -2
  41. sapiopycommons/multimodal/multimodal.py +24 -2
  42. sapiopycommons/recordmodel/record_handler.py +0 -4
  43. sapiopycommons/rules/eln_rule_handler.py +0 -3
  44. sapiopycommons/rules/on_save_rule_handler.py +0 -3
  45. sapiopycommons/webhook/webservice_handlers.py +1 -1
  46. {sapiopycommons-2025.9.5rc727.dist-info → sapiopycommons-2025.9.8a730.dist-info}/METADATA +2 -2
  47. {sapiopycommons-2025.9.5rc727.dist-info → sapiopycommons-2025.9.8a730.dist-info}/RECORD +49 -16
  48. sapiopycommons/ai/tool_of_tools.py +0 -917
  49. sapiopycommons/files/assay_plate_reader.py +0 -93
  50. sapiopycommons/files/file_text_converter.py +0 -207
  51. {sapiopycommons-2025.9.5rc727.dist-info → sapiopycommons-2025.9.8a730.dist-info}/WHEEL +0 -0
  52. {sapiopycommons-2025.9.5rc727.dist-info → sapiopycommons-2025.9.8a730.dist-info}/licenses/LICENSE +0 -0
@@ -1765,11 +1765,8 @@ 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
- 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)
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)
1773
1770
 
1774
1771
  # FR-47690: Deprecated the require_authentication parameter.
1775
1772
  # noinspection PyUnusedLocal
@@ -1815,8 +1812,7 @@ class CallbackUtil:
1815
1812
  return response
1816
1813
 
1817
1814
  def request_file(self, title: str, exts: Iterable[str] | None = None,
1818
- show_image_editor: bool = False, show_camera_button: bool = False,
1819
- *, enforce_file_extensions: bool = True) -> tuple[str, bytes]:
1815
+ show_image_editor: bool = False, show_camera_button: bool = False) -> tuple[str, bytes]:
1820
1816
  """
1821
1817
  Request a single file from the user.
1822
1818
 
@@ -1826,8 +1822,6 @@ class CallbackUtil:
1826
1822
  :param show_image_editor: Whether the user will see an image editor when image is uploaded in this file prompt.
1827
1823
  :param show_camera_button: Whether the user will be able to use camera to take a picture as an upload request,
1828
1824
  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.
1831
1825
  :return: The file name and bytes of the uploaded file.
1832
1826
  """
1833
1827
  # If no extensions were provided, use an empty list for the extensions instead.
@@ -1847,12 +1841,11 @@ class CallbackUtil:
1847
1841
  file_path: str = self.__send_dialog(request, self.callback.show_file_dialog, data_sink=do_consume)
1848
1842
 
1849
1843
  # Verify that each of the file given matches the expected extension(s).
1850
- self.__verify_file(file_path, sink.data, exts if enforce_file_extensions else None)
1844
+ self.__verify_file(file_path, sink.data, exts)
1851
1845
  return file_path, sink.data
1852
1846
 
1853
1847
  def request_files(self, title: str, exts: Iterable[str] | None = None,
1854
- show_image_editor: bool = False, show_camera_button: bool = False,
1855
- *, enforce_file_extensions: bool = True) -> dict[str, bytes]:
1848
+ show_image_editor: bool = False, show_camera_button: bool = False) -> dict[str, bytes]:
1856
1849
  """
1857
1850
  Request multiple files from the user.
1858
1851
 
@@ -1862,8 +1855,6 @@ class CallbackUtil:
1862
1855
  :param show_image_editor: Whether the user will see an image editor when image is uploaded in this file prompt.
1863
1856
  :param show_camera_button: Whether the user will be able to use camera to take a picture as an upload request,
1864
1857
  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.
1867
1858
  :return: A dictionary of file name to file bytes for each file the user uploaded.
1868
1859
  """
1869
1860
  # If no extensions were provided, use an empty list for the extensions instead.
@@ -1879,7 +1870,7 @@ class CallbackUtil:
1879
1870
  for file_path in file_paths:
1880
1871
  sink = InMemoryRecordDataSink(self.user)
1881
1872
  sink.consume_client_callback_file_path_data(file_path)
1882
- self.__verify_file(file_path, sink.data, exts if enforce_file_extensions else None)
1873
+ self.__verify_file(file_path, sink.data, exts)
1883
1874
  ret_dict.update({file_path: sink.data})
1884
1875
 
1885
1876
  return ret_dict
@@ -1896,17 +1887,16 @@ class CallbackUtil:
1896
1887
  """
1897
1888
  if file_path is None or len(file_path) == 0 or file_bytes is None or len(file_bytes) == 0:
1898
1889
  raise SapioUserErrorException("Empty file provided or file unable to be read.")
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)))
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)))
1910
1900
 
1911
1901
  def write_file(self, file_name: str, file_data: str | bytes) -> None:
1912
1902
  """
@@ -206,11 +206,12 @@ class ExperimentHandler:
206
206
  else:
207
207
  user = context
208
208
  context = None
209
+ if context is not None and context.eln_experiment is not None and experiment is None:
210
+ experiment = context.eln_experiment
209
211
  # FR-46495 - Allow the init function of ExperimentHandler to take in an ElnExperiment that is separate from the
210
212
  # context.
211
213
  # CR-37038 - Allow other experiment object types to be provided. Convert them all down to ElnExperiment.
212
- # PR-47793 - Fix cases where both a SapioWebhookContext and an experiment parameter are provided.
213
- if experiment is not None:
214
+ if (context is None or context.eln_experiment is None) and experiment is not None:
214
215
  eln_manager = DataMgmtServer.get_eln_manager(user)
215
216
  # If this object is already an ElnExperiment, do nothing.
216
217
  if isinstance(experiment, ElnExperiment):
@@ -226,19 +227,13 @@ class ExperimentHandler:
226
227
  raise SapioException(f"No experiment with notebook ID {notebook_id} located in the system.")
227
228
  # If this object is a record, assume it is an experiment record that we can query the system with.
228
229
  else:
229
- record_id: int = AliasUtil.to_record_id(experiment)
230
+ record_id: int = AliasUtil.to_record_ids([experiment])[0]
230
231
  experiment: ElnExperiment = eln_manager.get_eln_experiment_by_record_id(record_id)
231
232
  if not experiment:
232
233
  raise SapioException(f"No experiment with record ID {record_id} located in the system.")
233
- elif context is not None and context.eln_experiment is not None:
234
- experiment = context.eln_experiment
235
-
236
234
  if experiment is None:
237
235
  raise SapioException("Cannot initialize ExperimentHandler. No ELN Experiment found in the provided "
238
236
  "parameters.")
239
- elif not isinstance(experiment, ElnExperiment):
240
- raise SapioException("Cannot initialize ExperimentHandler. The experiment variable is not an "
241
- "ElnExperiment!")
242
237
 
243
238
  return user, context, experiment
244
239
 
@@ -1430,9 +1425,7 @@ class ExperimentHandler:
1430
1425
  :return: The map of options for the input step.
1431
1426
  """
1432
1427
  step: ElnEntryStep = self.get_step(step)
1433
- # PR-47796: Fix the get_step_options function making a webservice query every time it is called instead of
1434
- # properly checking its cache of entry options.
1435
- if step.get_id() not in self._step_options:
1428
+ if step not in self._step_options:
1436
1429
  self._step_options.update(ExperimentReportUtil.get_experiment_entry_options(self.user,
1437
1430
  self.get_all_steps()))
1438
1431
  return self._step_options[step.get_id()]
@@ -0,0 +1,49 @@
1
+ import os
2
+ import shutil
3
+ import tempfile
4
+
5
+
6
+ # FR-47422: Created class.
7
+ class TempFileHandler:
8
+ """
9
+ A utility class to manage temporary files and directories.
10
+ """
11
+ directories: list[str]
12
+ files: list[str]
13
+
14
+ def __init__(self) -> None:
15
+ self.directories = []
16
+ self.files = []
17
+
18
+ def create_temp_directory(self) -> str:
19
+ """
20
+ :return: The path to a newly created temporary directory.
21
+ """
22
+ directory: str = tempfile.mkdtemp()
23
+ self.directories.append(directory)
24
+ return directory
25
+
26
+ def create_temp_file(self, data: str | bytes, suffix: str = "") -> str:
27
+ """
28
+ :param data: The data to write to the temporary file.
29
+ :param suffix: An optional suffix for the temporary file.
30
+ :return: The path to a newly created temporary file containing the provided data.
31
+ """
32
+ mode: str = 'w' if isinstance(data, str) else 'wb'
33
+ with tempfile.NamedTemporaryFile(mode=mode, suffix=suffix, delete=False) as tmp_file:
34
+ tmp_file.write(data)
35
+ file_path: str = tmp_file.name
36
+ self.files.append(file_path)
37
+ return file_path
38
+
39
+ def cleanup(self) -> None:
40
+ """
41
+ Delete all temporary files and directories created by this handler.
42
+ """
43
+ for directory in self.directories:
44
+ if os.path.exists(directory):
45
+ shutil.rmtree(directory)
46
+
47
+ for file_path in self.files:
48
+ if os.path.exists(file_path):
49
+ os.remove(file_path)
@@ -4,16 +4,38 @@ from weakref import WeakValueDictionary
4
4
 
5
5
  from databind.json import dumps
6
6
  from sapiopylib.rest.User import SapioUser
7
- from sapiopylib.rest.utils.singletons import SapioContextManager
8
7
 
9
8
  from sapiopycommons.flowcyto.flowcyto_data import FlowJoWorkspaceInputJson, UploadFCSInputJson, \
10
9
  ComputeFlowStatisticsInputJson
11
10
 
12
11
 
13
- class FlowCytoManager(SapioContextManager):
12
+ class FlowCytoManager:
14
13
  """
15
14
  This manager includes flow cytometry analysis tools that would require FlowCyto license to use.
16
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
17
39
 
18
40
  def create_flowjo_workspace(self, workspace_input: FlowJoWorkspaceInputJson) -> int:
19
41
  """
@@ -5,7 +5,6 @@ from typing import Any
5
5
  from weakref import WeakValueDictionary
6
6
 
7
7
  from sapiopylib.rest.User import SapioUser
8
- from sapiopylib.rest.utils.singletons import SapioContextManager
9
8
 
10
9
  _STR_JAVA_TYPE = "java.lang.String"
11
10
  _INT_JAVA_TYPE = "java.lang.Integer"
@@ -275,10 +274,37 @@ class AccessionServiceDescriptor:
275
274
  }
276
275
 
277
276
 
278
- class AccessionService(SapioContextManager):
277
+ class AccessionService:
279
278
  """
280
279
  Provides Sapio Foundations Accession Service functionalities.
281
280
  """
281
+ _user: SapioUser
282
+
283
+ __instances: WeakValueDictionary[SapioUser, AccessionService] = WeakValueDictionary()
284
+ __initialized: bool
285
+
286
+ @property
287
+ def user(self) -> SapioUser:
288
+ return self._user
289
+
290
+ def __new__(cls, user: SapioUser):
291
+ """
292
+ Observes singleton pattern per record model manager object.
293
+
294
+ :param user: The user that will make the webservice request to the application.
295
+ """
296
+ obj = cls.__instances.get(user)
297
+ if not obj:
298
+ obj = object.__new__(cls)
299
+ obj.__initialized = False
300
+ cls.__instances[user] = obj
301
+ return obj
302
+
303
+ def __init__(self, user: SapioUser):
304
+ if self.__initialized:
305
+ return
306
+ self._user = user
307
+ self.__initialized = True
282
308
 
283
309
  def accession_with_config(self, data_type_name: str, data_field_name: str, num_ids: int) -> list[str]:
284
310
  """
@@ -7,13 +7,35 @@ from weakref import WeakValueDictionary
7
7
  from databind.json import dumps, loads
8
8
  from sapiopylib.rest.User import SapioUser
9
9
  from sapiopylib.rest.pojo.DataRecord import DataRecord
10
- from sapiopylib.rest.utils.singletons import SapioContextManager
11
10
 
12
11
  from sapiopycommons.general.exceptions import SapioException
13
12
  from sapiopycommons.multimodal.multimodal_data import *
14
13
 
15
14
 
16
- class MultiModalManager(SapioContextManager):
15
+ class MultiModalManager:
16
+ _user: SapioUser
17
+
18
+ __instances: WeakValueDictionary[SapioUser, MultiModalManager] = 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
17
39
 
18
40
  def load_image_data(self, request: ImageDataRequestPojo) -> list[str]:
19
41
  """
@@ -100,10 +100,6 @@ class RecordHandler:
100
100
  PyRecordModel instead of a WrappedRecordModel.
101
101
  :return: The record model for the input.
102
102
  """
103
- # PR-47792: Set the wrapper_type to None if a str was provided instead of a type[WrappedType]. The type hints
104
- # say this shouldn't be done anyway, but using this as a safeguard against user error.
105
- if isinstance(wrapper_type, str):
106
- wrapper_type = None
107
103
  if wrapper_type is not None:
108
104
  self.__verify_data_type(record, wrapper_type)
109
105
  if isinstance(record, PyRecordModel):
@@ -184,7 +184,4 @@ class ElnRuleHandler:
184
184
  instead of a model wrapper, then the returned records will be PyRecordModels instead of WrappedRecordModels.
185
185
  """
186
186
  dt: str = AliasUtil.to_data_type_name(wrapper_type)
187
- # PR-47792: Set the wrapper_type to None if a str was provided instead of a type[WrappedType].
188
- if isinstance(wrapper_type, str):
189
- wrapper_type = None
190
187
  return self._rec_handler.wrap_models(self.get_records(dt, entry), wrapper_type)
@@ -180,7 +180,4 @@ class OnSaveRuleHandler:
180
180
  instead of a model wrapper, then the returned records will be PyRecordModels instead of WrappedRecordModels.
181
181
  """
182
182
  dt: str = AliasUtil.to_data_type_name(wrapper_type)
183
- # PR-47792: Set the wrapper_type to None if a str was provided instead of a type[WrappedType].
184
- if isinstance(wrapper_type, str):
185
- wrapper_type = None
186
183
  return self._rec_handler.wrap_models(self.get_records(dt, record_id), wrapper_type)
@@ -140,7 +140,7 @@ class AbstractWebserviceHandler(AbstractWebhookHandler):
140
140
  # Get the login credentials from the headers.
141
141
  auth: str = headers.get("Authorization")
142
142
  if auth and auth.startswith("Basic "):
143
- credentials: list[str] = b64decode(auth.split("Basic ")[1]).decode().split(":")
143
+ credentials: list[str] = b64decode(auth.split("Basic ")[1]).decode().split(":", 1)
144
144
  user = self.basic_auth(url, credentials[0], credentials[1])
145
145
  elif auth and auth.startswith("Bearer "):
146
146
  user = self.bearer_token_auth(url, auth.split("Bearer ")[1])
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: sapiopycommons
3
- Version: 2025.9.5rc727
3
+ Version: 2025.9.8a730
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.7.31a279
20
+ Requires-Dist: sapiopylib>=2025.4.17.264
21
21
  Description-Content-Type: text/markdown
22
22
 
23
23
 
@@ -1,8 +1,42 @@
1
1
  sapiopycommons/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
2
  sapiopycommons/ai/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
3
- sapiopycommons/ai/tool_of_tools.py,sha256=zYmQ4rNX-qYQnc-vNDnYZjtv9JgmQAmVVuHfVOdBF3w,46984
3
+ sapiopycommons/ai/converter_service_base.py,sha256=TMSyEekbbqMk9dRuAtLlSJ1sA1H8KpyCDlSOeqGFMWI,5115
4
+ sapiopycommons/ai/protobuf_utils.py,sha256=cBjbxoFAwU02kNUxEce95WnMU2CMuDD-qFaeWgvQJMQ,24599
5
+ sapiopycommons/ai/server.py,sha256=AR86WNJkL8_atUqFztXDYobyd06AiWCNCRtrdRdaBx0,5273
6
+ sapiopycommons/ai/test_client.py,sha256=iPhn7cvKNLmDAXrjpmIkZpW2pDWlUhJZHDLHJbEoWsg,15673
7
+ sapiopycommons/ai/tool_service_base.py,sha256=s6NJaOLUN3Ewkn1yoByKqHn7jn40yopD0ohySnKaOB0,46526
8
+ sapiopycommons/ai/protoapi/fielddefinitions/fields_pb2.py,sha256=8tKXwLXcqFGdQHHSEBSi6Fd7dcaCFoOqmhjzqhenb_M,2372
9
+ sapiopycommons/ai/protoapi/fielddefinitions/fields_pb2.pyi,sha256=FwtXmNAf7iYGEFm4kbqb04v77jNHbZg18ZmEDhle_bU,1444
10
+ sapiopycommons/ai/protoapi/fielddefinitions/fields_pb2_grpc.py,sha256=uO25bcnfGqXpP4ggUur54Nr73Wj-DGWftExzLNcxdHI,931
11
+ sapiopycommons/ai/protoapi/fielddefinitions/velox_field_def_pb2.py,sha256=in9iHiLPYcnLWoLeqy4nWSI0jZGHD_bMhEFRIRtJPuo,20864
12
+ sapiopycommons/ai/protoapi/fielddefinitions/velox_field_def_pb2.pyi,sha256=U5zXrbBxsWilLTsRWJd1TqjdjLKFsr3enF9OJ8GfyWw,34028
13
+ sapiopycommons/ai/protoapi/fielddefinitions/velox_field_def_pb2_grpc.py,sha256=Vj6qDKvsHgl25iBi3UjtTuGxihekgqCuufExvnJKzQI,940
14
+ sapiopycommons/ai/protoapi/plan/step_output_pb2.py,sha256=EBNCzLUDwwCqDCh35zSfFdfq0RP8WrmTMXEzEPu_1_E,2655
15
+ sapiopycommons/ai/protoapi/plan/step_output_pb2.pyi,sha256=yuxOYnDZ9DRuu-TLzaKOW_B4LUiYxTrNc2AbssXg4kE,2022
16
+ sapiopycommons/ai/protoapi/plan/step_output_pb2_grpc.py,sha256=ebWLIfOFeVE2WuUIThMBerVweH-1phviGX195UTwYyg,924
17
+ sapiopycommons/ai/protoapi/plan/step_pb2.py,sha256=mKTm_syaX99GzhWtGIPkxMTsfcsvW0QbRqjv06eHSM0,2433
18
+ sapiopycommons/ai/protoapi/plan/step_pb2.pyi,sha256=QPIcsjcUvEGQkdZMUMiVzFFNDl8yOUe_qJtf5XEp5Ck,2062
19
+ sapiopycommons/ai/protoapi/plan/step_pb2_grpc.py,sha256=1CBna5NBBxPwQhrkN8-Fim_j3FGmOfDo5C4c8sIBV8Q,917
20
+ sapiopycommons/ai/protoapi/plan/converter/converter_pb2.py,sha256=rZYBRfR0umwDYvBdYnzxR1VZSutRqunhd3QsdtQXiCM,3593
21
+ sapiopycommons/ai/protoapi/plan/converter/converter_pb2.pyi,sha256=_35yHfKTJH3SMdA5_c6qF6OZG6UwFWXpNh6dwRFDKkk,4250
22
+ sapiopycommons/ai/protoapi/plan/converter/converter_pb2_grpc.py,sha256=6T5FCmT_vEFSywUVlAlWWfBDPaILb0Dq8yCGuO_Q-BU,6448
23
+ sapiopycommons/ai/protoapi/plan/item/item_container_pb2.py,sha256=VIXmIw8-9jtH7peJZ16BEmGDfaVjjxTKhmlfcHWT82M,3863
24
+ sapiopycommons/ai/protoapi/plan/item/item_container_pb2.pyi,sha256=bbPNQDwfFDd7_S7yU99Co1O7sRhxjle-RM0_nK8jgjc,4246
25
+ sapiopycommons/ai/protoapi/plan/item/item_container_pb2_grpc.py,sha256=1NzBWBUINC0Rk-NGYZ-97BVKvVUxcn_I44IjQO2h-nQ,932
26
+ sapiopycommons/ai/protoapi/plan/script/script_pb2.py,sha256=swyahlxM7fKm-RqtgMd1Czqd1JrbXRw3sYqFTkjbbyM,5144
27
+ sapiopycommons/ai/protoapi/plan/script/script_pb2.pyi,sha256=GqlqLf9UX_B5vCR9cm-VoYLHFJNmZjJafcPJ6EDa8Qw,6192
28
+ sapiopycommons/ai/protoapi/plan/script/script_pb2_grpc.py,sha256=ginPYpsiI6U6dB6gfWHJM8LWjSHUgcqz_gR7Dmdsyck,6864
29
+ sapiopycommons/ai/protoapi/plan/tool/entry_pb2.py,sha256=yNyyyVvz94ewjGaHw3t0pUP-KH7ACg5hLC0QzrsFPis,2130
30
+ sapiopycommons/ai/protoapi/plan/tool/entry_pb2.pyi,sha256=2vI0WSy0KGFHDewMSRyX5rUkmmmSaMBLvFO7txqA6Ug,2354
31
+ sapiopycommons/ai/protoapi/plan/tool/entry_pb2_grpc.py,sha256=i24BfJEt7LtyROxHVEyS9RLYqLZKPvyJMKRFZj6NUTg,923
32
+ sapiopycommons/ai/protoapi/plan/tool/tool_pb2.py,sha256=Gy6YjakkicKi4JthUFOxOyPLen-af7XI-ijinVs7EOY,8181
33
+ sapiopycommons/ai/protoapi/plan/tool/tool_pb2.pyi,sha256=x33Ko2KC5_lw8omA1hYRGoUWvDitxNdloxswTxaMaqc,16713
34
+ sapiopycommons/ai/protoapi/plan/tool/tool_pb2_grpc.py,sha256=CLSGEgSpN4D6wcE-P_5lzb2Nttjr4XjumxGUrZQSip0,6915
35
+ sapiopycommons/ai/protoapi/session/sapio_conn_info_pb2.py,sha256=MhWzTyJz3xZgpdW8_LCVKuzKXT0cv6iHMRB-UNCUNzM,2094
36
+ sapiopycommons/ai/protoapi/session/sapio_conn_info_pb2.pyi,sha256=vLYA8Tkzq2AwgVadoUp5vAg4HgGlgga0kzeS3e_XkCQ,1621
37
+ sapiopycommons/ai/protoapi/session/sapio_conn_info_pb2_grpc.py,sha256=imcciy_kbmm7OV_W3jYZ53R6GQ6Yh-eUcVW0A9GkWdg,931
4
38
  sapiopycommons/callbacks/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
5
- sapiopycommons/callbacks/callback_util.py,sha256=Z1LcXnRRjXyhmcSDUwh4NzcA6ICtcbFUMKcvAqQcS8E,153811
39
+ sapiopycommons/callbacks/callback_util.py,sha256=OuPJ1o6jcDQ7qV-dxrjAkJerGbVI9_9P-xu0r3ODaMM,153008
6
40
  sapiopycommons/callbacks/field_builder.py,sha256=rnIP-RJafk3mZlAx1eJ8a0eSW9Ps_L6_WadCmusnENw,38772
7
41
  sapiopycommons/chem/IndigoMolecules.py,sha256=7ucCaRMLu1zfH2uPIvXwRTSdpNcS03O1P9p_O-5B4xQ,5110
8
42
  sapiopycommons/chem/Molecules.py,sha256=mVqPn32MPMjF0iZas-5MFkS-upIdoW5OB72KKZmJRJA,12523
@@ -18,26 +52,25 @@ sapiopycommons/datatype/data_fields.py,sha256=pczUlEcE0TeHEDU0Gkvu7voacSLPXCB7l9
18
52
  sapiopycommons/datatype/pseudo_data_types.py,sha256=lAJDnFuStrUP0mK5AuYlFvLerwjEB-ABd6Z4qlCrwJA,40637
19
53
  sapiopycommons/eln/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
20
54
  sapiopycommons/eln/experiment_cache.py,sha256=Zv4IcsAl95ftO2ul3DRc_Hyno0AfC3OvFV7RYb72ITo,9560
21
- sapiopycommons/eln/experiment_handler.py,sha256=1RzrWOicF9VjcWlaziV46yBEk1BOP4BuTJ0JeYdn50I,99204
55
+ sapiopycommons/eln/experiment_handler.py,sha256=kMQuccmGyPzc8wYKsds6AZ_VjH9WLjtUY6hsSWXJx6s,98786
22
56
  sapiopycommons/eln/experiment_report_util.py,sha256=GLpgwSEPuUqnY1v4oJ1ao60Va-YcgXh7E-cH9YnVeAg,37256
23
57
  sapiopycommons/eln/experiment_step_factory.py,sha256=qw9UfLslVzB6dEIZPOZ85XHKpld81RhD4-csM6TgQNg,26099
24
58
  sapiopycommons/eln/experiment_tags.py,sha256=7-fpOiSqrjbXmWIJhEhaxMgLsVCPAtKqH8xRzpDVKoE,356
25
59
  sapiopycommons/eln/plate_designer.py,sha256=XFazSvhTbSy47t80-jc2tyx_-fQ_IUjKd18JQKEFcsY,13939
26
60
  sapiopycommons/eln/step_creation.py,sha256=CFkGC-SxwAQpQlcs_obqLAVgmsNxKSGMqMtO_E6IVmw,10171
27
61
  sapiopycommons/files/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
28
- sapiopycommons/files/assay_plate_reader.py,sha256=3c2PQiiAbc2QJU9ZfNLzcTmvJrUwsbkIHO7R6R52xGU,3020
29
62
  sapiopycommons/files/complex_data_loader.py,sha256=T39veNhvYl6j_uZjIIJ8Mk5Aa7otR5RB-g8XlAdkksA,1421
30
63
  sapiopycommons/files/file_bridge.py,sha256=vKbqxPexi15epr_-_qLrEfYoxNxB031mXN92iVtOMqE,9511
31
64
  sapiopycommons/files/file_bridge_handler.py,sha256=SEYDIQhSCmjI6qyLdDJE8JVKSd0WYvF7JvAq_Ahp9Do,25503
32
65
  sapiopycommons/files/file_data_handler.py,sha256=f96MlkMuQhUCi4oLnzJK5AiuElCp5jLI8_sJkZVwpws,36779
33
- sapiopycommons/files/file_text_converter.py,sha256=Gaj_divTiKXWd6flDOgrxNXpcn9fDWqxX6LUG0joePk,7516
34
66
  sapiopycommons/files/file_util.py,sha256=djouyGjsYgWzjz2OBRnSeMDgj6NrsJUm1a2J93J8Wco,31915
35
67
  sapiopycommons/files/file_validator.py,sha256=ryg22-93csmRO_Pv0ZpWphNkB74xWZnHyJ23K56qLj0,28761
36
68
  sapiopycommons/files/file_writer.py,sha256=hACVl0duCjP28gJ1NPljkjagNCLod0ygUlPbvUmRDNM,17605
37
- sapiopycommons/flowcyto/flow_cyto.py,sha256=B6DFquLi-gcWfJWyP4vYfwTXXJKl6O9W5-k8FzkM0Oo,2610
69
+ sapiopycommons/files/temp_files.py,sha256=Puv59qtGwiXVJnTm4YuyeZPKw_leXDW906Uz_xbIt6A,1542
70
+ sapiopycommons/flowcyto/flow_cyto.py,sha256=vs9WhXXKz3urpjL8QKSk56B-NSmQR3O3x_WFBKoeO10,3227
38
71
  sapiopycommons/flowcyto/flowcyto_data.py,sha256=mYKFuLbtpJ-EsQxLGtu4tNHVlygTxKixgJxJqD68F58,2596
39
72
  sapiopycommons/general/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
40
- sapiopycommons/general/accession_service.py,sha256=ZvtvZg7d_siMJUedjrF14mcqo5ZqVA5IJxDa5enlB-8,12792
73
+ sapiopycommons/general/accession_service.py,sha256=3e__bVs7CYZ1CduLlGA9plnK7nCtdy7GXjCrNObPFgo,13484
41
74
  sapiopycommons/general/aliases.py,sha256=VwnWf_P803pcteoAIs0DkLScVChCS5XNgryTp8FzaNc,14696
42
75
  sapiopycommons/general/audit_log.py,sha256=sQAMcJx0cNkgZm7nTZSaGPxWvHG0_x6dBtU0jESavb4,9131
43
76
  sapiopycommons/general/custom_report_util.py,sha256=9elLEUSgfM0gli8nRPz1uYkhaXN4Vnx3piSiNHv5IBs,19156
@@ -49,24 +82,24 @@ sapiopycommons/general/popup_util.py,sha256=HKILegU1uCL_6abNlNL0Wn3xgX2JNa_kJeq7
49
82
  sapiopycommons/general/sapio_links.py,sha256=YkcVKNLrSGoM7tCCXBAsIbIxylctwdcEyhePrRMODe0,2859
50
83
  sapiopycommons/general/storage_util.py,sha256=ovmK_jN7v09BoX07XxwShpBUC5WYQOM7dbKV_VeLXJU,8892
51
84
  sapiopycommons/general/time_util.py,sha256=jU1urPoZRv6evNucR0-288EyT4PrsDpCr-H1-7BKq9A,12363
52
- sapiopycommons/multimodal/multimodal.py,sha256=EP9WYzx1CvidmEBlvzO6tiF4HJwsPB1FgxpnbWzxnpA,6161
85
+ sapiopycommons/multimodal/multimodal.py,sha256=PFaGJPbKvW__tnxb8KkgkJZOKjQdgxF_kGfD5chet1s,6779
53
86
  sapiopycommons/multimodal/multimodal_data.py,sha256=0BeVPr9HaC0hNTF1v1phTIKGruvNnwerHsD994qJKBg,15099
54
87
  sapiopycommons/processtracking/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
55
88
  sapiopycommons/processtracking/custom_workflow_handler.py,sha256=eYKdYlwo8xx-6AkB_iPUBNV9yDoNvW2h_Sm3i8JpmRU,25844
56
89
  sapiopycommons/processtracking/endpoints.py,sha256=5AJLbhRKQsOeeOdQa888xcCJZD5aavxD-DHZ36Qob_M,12548
57
90
  sapiopycommons/recordmodel/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
58
- sapiopycommons/recordmodel/record_handler.py,sha256=HsJlgB6Nvmtlc1fiC1zn_vP-0l-b_cvUQSlQd3pEiL8,95308
91
+ sapiopycommons/recordmodel/record_handler.py,sha256=WxmgrWQ3nX3eVZSHJY7e8fj7CI7azSyEyovmYcy9098,95021
59
92
  sapiopycommons/rules/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
60
- sapiopycommons/rules/eln_rule_handler.py,sha256=Ec2hvxn6gmBvZjhX9-7WCFqafxFE9JSy2zCsvFsVyS4,11565
61
- sapiopycommons/rules/on_save_rule_handler.py,sha256=HLdgUkxmaoHBK3jaycZlUHWam4kk36zmw7VDuRRiAx8,11332
93
+ sapiopycommons/rules/eln_rule_handler.py,sha256=MnE-eSl1kNfaXWFi9elTOC9V2fdUzrwWTvCHUprC8_I,11388
94
+ sapiopycommons/rules/on_save_rule_handler.py,sha256=fkNIlslAZZ0BUrRiwecyvf42JBR8FpCCQ6DBNKXP2jE,11155
62
95
  sapiopycommons/samples/aliquot.py,sha256=mWOJUqaQh0t3HklNuGdmuV7D5zzXs6fpLwtDdM6_XTo,3018
63
96
  sapiopycommons/sftpconnect/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
64
97
  sapiopycommons/sftpconnect/sftp_builder.py,sha256=lFK3FeXk-sFLefW0hqY8WGUQDeYiGaT6yDACzT_zFgQ,3015
65
98
  sapiopycommons/webhook/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
66
99
  sapiopycommons/webhook/webhook_context.py,sha256=D793uLsb1691SalaPnBUk3rOSxn_hYLhdvkaIxjNXss,1909
67
100
  sapiopycommons/webhook/webhook_handlers.py,sha256=7o_wXOruhT9auNh8OfhJAh4WhhiPKij67FMBSpGPICc,39939
68
- sapiopycommons/webhook/webservice_handlers.py,sha256=tyaYGG1-v_JJrJHZ6cy5mGCxX9z1foLw7pM4MDJlFxs,14297
69
- sapiopycommons-2025.9.5rc727.dist-info/METADATA,sha256=ijpV_R8_npLSLpDBipXWhQhvYfJWynUGqYBdKTkNXwk,3143
70
- sapiopycommons-2025.9.5rc727.dist-info/WHEEL,sha256=C2FUgwZgiLbznR-k0b_5k3Ai_1aASOXDss3lzCUsUug,87
71
- sapiopycommons-2025.9.5rc727.dist-info/licenses/LICENSE,sha256=HyVuytGSiAUQ6ErWBHTqt1iSGHhLmlC8fO7jTCuR8dU,16725
72
- sapiopycommons-2025.9.5rc727.dist-info/RECORD,,
101
+ sapiopycommons/webhook/webservice_handlers.py,sha256=cvW6Mk_110BzYqkbk63Kg7jWrltBCDALOlkJRu8h4VQ,14300
102
+ sapiopycommons-2025.9.8a730.dist-info/METADATA,sha256=H3V8_3GzL5vDRZE0eywVDqRIJ0RCyOBcLchKtOqmRyA,3142
103
+ sapiopycommons-2025.9.8a730.dist-info/WHEEL,sha256=C2FUgwZgiLbznR-k0b_5k3Ai_1aASOXDss3lzCUsUug,87
104
+ sapiopycommons-2025.9.8a730.dist-info/licenses/LICENSE,sha256=HyVuytGSiAUQ6ErWBHTqt1iSGHhLmlC8fO7jTCuR8dU,16725
105
+ sapiopycommons-2025.9.8a730.dist-info/RECORD,,