sapiopycommons 2025.9.5a726__py3-none-any.whl → 2025.9.5rc727__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 (51) hide show
  1. sapiopycommons/ai/tool_of_tools.py +917 -0
  2. sapiopycommons/callbacks/callback_util.py +26 -16
  3. sapiopycommons/eln/experiment_handler.py +12 -5
  4. sapiopycommons/files/assay_plate_reader.py +93 -0
  5. sapiopycommons/files/file_text_converter.py +207 -0
  6. sapiopycommons/flowcyto/flow_cyto.py +2 -24
  7. sapiopycommons/general/accession_service.py +2 -28
  8. sapiopycommons/multimodal/multimodal.py +2 -24
  9. sapiopycommons/recordmodel/record_handler.py +4 -0
  10. sapiopycommons/rules/eln_rule_handler.py +3 -0
  11. sapiopycommons/rules/on_save_rule_handler.py +3 -0
  12. sapiopycommons/webhook/webservice_handlers.py +1 -1
  13. {sapiopycommons-2025.9.5a726.dist-info → sapiopycommons-2025.9.5rc727.dist-info}/METADATA +2 -2
  14. {sapiopycommons-2025.9.5a726.dist-info → sapiopycommons-2025.9.5rc727.dist-info}/RECORD +16 -48
  15. sapiopycommons/ai/converter_service_base.py +0 -132
  16. sapiopycommons/ai/protoapi/fielddefinitions/fields_pb2.py +0 -43
  17. sapiopycommons/ai/protoapi/fielddefinitions/fields_pb2.pyi +0 -31
  18. sapiopycommons/ai/protoapi/fielddefinitions/fields_pb2_grpc.py +0 -24
  19. sapiopycommons/ai/protoapi/fielddefinitions/velox_field_def_pb2.py +0 -123
  20. sapiopycommons/ai/protoapi/fielddefinitions/velox_field_def_pb2.pyi +0 -598
  21. sapiopycommons/ai/protoapi/fielddefinitions/velox_field_def_pb2_grpc.py +0 -24
  22. sapiopycommons/ai/protoapi/plan/converter/converter_pb2.py +0 -51
  23. sapiopycommons/ai/protoapi/plan/converter/converter_pb2.pyi +0 -63
  24. sapiopycommons/ai/protoapi/plan/converter/converter_pb2_grpc.py +0 -149
  25. sapiopycommons/ai/protoapi/plan/item/item_container_pb2.py +0 -55
  26. sapiopycommons/ai/protoapi/plan/item/item_container_pb2.pyi +0 -90
  27. sapiopycommons/ai/protoapi/plan/item/item_container_pb2_grpc.py +0 -24
  28. sapiopycommons/ai/protoapi/plan/script/script_pb2.py +0 -59
  29. sapiopycommons/ai/protoapi/plan/script/script_pb2.pyi +0 -102
  30. sapiopycommons/ai/protoapi/plan/script/script_pb2_grpc.py +0 -153
  31. sapiopycommons/ai/protoapi/plan/step_output_pb2.py +0 -45
  32. sapiopycommons/ai/protoapi/plan/step_output_pb2.pyi +0 -42
  33. sapiopycommons/ai/protoapi/plan/step_output_pb2_grpc.py +0 -24
  34. sapiopycommons/ai/protoapi/plan/step_pb2.py +0 -43
  35. sapiopycommons/ai/protoapi/plan/step_pb2.pyi +0 -43
  36. sapiopycommons/ai/protoapi/plan/step_pb2_grpc.py +0 -24
  37. sapiopycommons/ai/protoapi/plan/tool/entry_pb2.py +0 -41
  38. sapiopycommons/ai/protoapi/plan/tool/entry_pb2.pyi +0 -35
  39. sapiopycommons/ai/protoapi/plan/tool/entry_pb2_grpc.py +0 -24
  40. sapiopycommons/ai/protoapi/plan/tool/tool_pb2.py +0 -75
  41. sapiopycommons/ai/protoapi/plan/tool/tool_pb2.pyi +0 -237
  42. sapiopycommons/ai/protoapi/plan/tool/tool_pb2_grpc.py +0 -154
  43. sapiopycommons/ai/protoapi/session/sapio_conn_info_pb2.py +0 -39
  44. sapiopycommons/ai/protoapi/session/sapio_conn_info_pb2.pyi +0 -32
  45. sapiopycommons/ai/protoapi/session/sapio_conn_info_pb2_grpc.py +0 -24
  46. sapiopycommons/ai/protobuf_utils.py +0 -504
  47. sapiopycommons/ai/server.py +0 -106
  48. sapiopycommons/ai/test_client.py +0 -356
  49. sapiopycommons/ai/tool_service_base.py +0 -951
  50. {sapiopycommons-2025.9.5a726.dist-info → sapiopycommons-2025.9.5rc727.dist-info}/WHEEL +0 -0
  51. {sapiopycommons-2025.9.5a726.dist-info → sapiopycommons-2025.9.5rc727.dist-info}/licenses/LICENSE +0 -0
@@ -1,356 +0,0 @@
1
- import base64
2
- import json
3
- from enum import Enum
4
- from typing import Any
5
-
6
- import grpc
7
- from sapiopylib.rest.User import SapioUser
8
-
9
- from sapiopycommons.ai.protoapi.fielddefinitions.fields_pb2 import FieldValuePbo
10
- from sapiopycommons.ai.protoapi.plan.converter.converter_pb2 import ConverterDetailsRequestPbo, \
11
- ConverterDetailsResponsePbo, ConvertResponsePbo, ConvertRequestPbo
12
- from sapiopycommons.ai.protoapi.plan.converter.converter_pb2_grpc import ConverterServiceStub
13
- from sapiopycommons.ai.protoapi.plan.item.item_container_pb2 import ContentTypePbo
14
- from sapiopycommons.ai.protoapi.plan.tool.entry_pb2 import StepBinaryContainerPbo, StepCsvRowPbo, \
15
- StepCsvHeaderRowPbo, StepCsvContainerPbo, StepJsonContainerPbo, StepTextContainerPbo, \
16
- StepItemContainerPbo, StepInputBatchPbo
17
- from sapiopycommons.ai.protoapi.plan.tool.tool_pb2 import ProcessStepResponsePbo, ProcessStepRequestPbo, \
18
- ToolDetailsRequestPbo, ToolDetailsResponsePbo, ProcessStepResponseStatusPbo
19
- from sapiopycommons.ai.protoapi.plan.tool.tool_pb2_grpc import ToolServiceStub
20
- from sapiopycommons.ai.protoapi.session.sapio_conn_info_pb2 import SapioConnectionInfoPbo, SapioUserSecretTypePbo
21
- from sapiopycommons.ai.protobuf_utils import ProtobufUtils
22
- from sapiopycommons.general.aliases import FieldValue
23
-
24
-
25
- class ContainerType(Enum):
26
- """
27
- An enum of the different container contents of a StepItemContainerPbo.
28
- """
29
- BINARY = "binary"
30
- CSV = "csv"
31
- JSON = "json"
32
- TEXT = "text"
33
-
34
-
35
- # FR-47422: Created class.
36
- class ToolOutput:
37
- """
38
- A class for holding the output of a TestClient that calls a ToolService. ToolOutput objects an be
39
- printed to show the output of the tool in a human-readable format.
40
- """
41
- tool_name: str
42
-
43
- status: str
44
- message: str
45
-
46
- binary_output: list[list[bytes]]
47
- csv_output: list[list[dict[str, Any]]]
48
- json_output: list[list[Any]]
49
- text_output: list[list[str]]
50
-
51
- new_records: list[dict[str, FieldValue]]
52
-
53
- logs: list[str]
54
-
55
- def __init__(self, tool_name: str):
56
- self.tool_name = tool_name
57
- self.binary_output = []
58
- self.csv_output = []
59
- self.json_output = []
60
- self.text_output = []
61
- self.new_records = []
62
- self.logs = []
63
-
64
- def __str__(self):
65
- ret_val: str = f"{self.tool_name} Output:\n"
66
- ret_val += f"\tStatus: {self.status}\n"
67
- ret_val += f"\tMessage: {self.message}\n"
68
- ret_val += "-" * 25 + "\n"
69
-
70
- ret_val += f"Binary Output: {sum(len(x) for x in self.binary_output)} item(s) across {len(self.binary_output)} outputs\n"
71
- for i, output in enumerate(self.binary_output, start=1):
72
- output: list[bytes]
73
- ret_val += f"\tBinary Output {i}:\n"
74
- for binary in output:
75
- ret_val += f"\t\t{len(binary)} byte(s): {binary[:50]}...\n"
76
-
77
- ret_val += f"CSV Output: {sum(len(x) for x in self.csv_output)} item(s) across {len(self.csv_output)} outputs\n"
78
- for i, output in enumerate(self.csv_output, start=1):
79
- output: list[dict[str, Any]]
80
- ret_val += f"\tCSV Output {i}:\n"
81
- ret_val += f"\t\tHeaders: {', '.join(output[0].keys())}\n"
82
- for j, csv_row in enumerate(output):
83
- ret_val += f"\t\t{j}: {', '.join(f'{v}' for k, v in csv_row.items())}\n"
84
-
85
- ret_val += f"JSON Output: {sum(len(x) for x in self.json_output)} item(s) across {len(self.json_output)} outputs\n"
86
- for i, output in enumerate(self.json_output, start=1):
87
- output: list[Any]
88
- ret_val += f"\tJSON Output {i}:\n"
89
- for json_obj in output:
90
- ret_val += f"\t\t"
91
- ret_val += json.dumps(json_obj, indent=2).replace("\n", "\n\t\t") + "\n"
92
-
93
- ret_val += f"Text Output: {sum(len(x) for x in self.text_output)} item(s) across {len(self.text_output)} outputs\n"
94
- for i, output in enumerate(self.text_output, start=1):
95
- output: list[str]
96
- ret_val += f"\tText Output {i}:\n"
97
- for text in output:
98
- ret_val += f"\t\t{text}\n"
99
-
100
- ret_val += f"New Records: {len(self.new_records)} item(s)\n"
101
- for record in self.new_records:
102
- ret_val += f"{json.dumps(record, indent=2)}\n"
103
-
104
- ret_val += f"Logs: {len(self.logs)} item(s)\n"
105
- for log in self.logs:
106
- ret_val += f"\t{log}\n"
107
- return ret_val
108
-
109
-
110
- class TestClient:
111
- """
112
- A client for testing a ToolService.
113
- """
114
- grpc_server_url: str
115
- options: list[tuple[str, Any]] | None
116
- connection: SapioConnectionInfoPbo
117
- _request_inputs: list[StepItemContainerPbo]
118
- _config_fields: dict[str, FieldValuePbo]
119
-
120
- def __init__(self, grpc_server_url: str, user: SapioUser | None = None,
121
- options: list[tuple[str, Any]] | None = None):
122
- """
123
- :param grpc_server_url: The URL of the gRPC server to connect to.
124
- :param user: Optional SapioUser object to use for the connection. If not provided, a default connection
125
- will be created with test credentials.
126
- :param options: Optional list of gRPC channel options.
127
- """
128
- self.grpc_server_url = grpc_server_url
129
- self.options = options
130
- self._create_connection(user)
131
- self._request_inputs = []
132
- self._config_fields = {}
133
-
134
- def _create_connection(self, user: SapioUser | None = None):
135
- """
136
- Create a SapioConnectionInfoPbo object with test credentials. This method can be overridden to
137
- create a user with specific credentials for testing.
138
- """
139
- self.connection = SapioConnectionInfoPbo()
140
- self.connection.username = user.username if user else "Testing"
141
- self.connection.webservice_url = user.url if user else "https://localhost:8080/webservice/api"
142
- self.connection.app_guid = user.guid if user else "1234567890"
143
- self.connection.rmi_host.append("Testing")
144
- self.connection.rmi_port = 9001
145
- if user and user.password:
146
- self.connection.secret_type = SapioUserSecretTypePbo.PASSWORD
147
- self.connection.secret = "Basic " + base64.b64encode(f'{user.username}:{user.password}'.encode()).decode()
148
- else:
149
- self.connection.secret_type = SapioUserSecretTypePbo.SESSION_TOKEN
150
- self.connection.secret = user.api_token if user and user.api_token else "test_api_token"
151
-
152
- def add_binary_input(self, input_data: list[bytes]) -> None:
153
- """
154
- Add a binary input to the the next request.
155
- """
156
- self._add_input(ContainerType.BINARY, StepBinaryContainerPbo(items=input_data))
157
-
158
- def add_csv_input(self, input_data: list[dict[str, Any]]) -> None:
159
- """
160
- Add a CSV input to the next request.
161
- """
162
- csv_items = []
163
- for row in input_data:
164
- csv_items.append(StepCsvRowPbo(cells=[str(value) for value in row.values()]))
165
- header = StepCsvHeaderRowPbo(cells=list(input_data[0].keys()))
166
- self._add_input(ContainerType.CSV, StepCsvContainerPbo(header=header, items=csv_items))
167
-
168
- def add_json_input(self, input_data: list[dict[str, Any]]) -> None:
169
- """
170
- Add a JSON input to the next request.
171
- """
172
- self._add_input(ContainerType.JSON, StepJsonContainerPbo(items=[json.dumps(x) for x in input_data]))
173
-
174
- def add_text_input(self, input_data: list[str]) -> None:
175
- """
176
- Add a text input to the next request.
177
- """
178
- self._add_input(ContainerType.TEXT, StepTextContainerPbo(items=input_data))
179
-
180
- def clear_inputs(self) -> None:
181
- """
182
- Clear all inputs that have been added to the next request.
183
- This is useful if you want to start a new request without the previous inputs.
184
- """
185
- self._request_inputs.clear()
186
-
187
- def add_config_field(self, field_name: str, value: FieldValue | list[str]) -> None:
188
- """
189
- Add a configuration field value to the next request.
190
-
191
- :param field_name: The name of the configuration field.
192
- :param value: The value to set for the configuration field. If a list is provided, it will be
193
- converted to a comma-separated string.
194
- """
195
- if isinstance(value, list):
196
- value = ",".join(str(x) for x in value)
197
- if not isinstance(value, FieldValuePbo):
198
- value = ProtobufUtils.value_to_field_pbo(value)
199
- self._config_fields[field_name] = value
200
-
201
- def add_config_fields(self, config_fields: dict[str, FieldValue]) -> None:
202
- """
203
- Add multiple configuration field values to the next request.
204
-
205
- :param config_fields: A dictionary of configuration field names and their corresponding values.
206
- """
207
- for x, y in config_fields.items():
208
- self.add_config_field(x, y)
209
-
210
- def clear_configs(self) -> None:
211
- """
212
- Clear all configuration field values that have been added to the next request.
213
- This is useful if you want to start a new request without the previous configurations.
214
- """
215
- self._config_fields.clear()
216
-
217
- def clear_request(self) -> None:
218
- """
219
- Clear all inputs and configuration fields that have been added to the next request.
220
- This is useful if you want to start a new request without the previous inputs and configurations.
221
- """
222
- self.clear_inputs()
223
- self.clear_configs()
224
-
225
- def _add_input(self, container_type: ContainerType, items: Any) -> None:
226
- """
227
- Helper method for adding inputs to the next request.
228
- """
229
- match container_type:
230
- # The content type doesn't matter when we're just testing.
231
- case ContainerType.BINARY:
232
- container = StepItemContainerPbo(content_type=ContentTypePbo(), binary_container=items)
233
- case ContainerType.CSV:
234
- container = StepItemContainerPbo(content_type=ContentTypePbo(), csv_container=items)
235
- case ContainerType.JSON:
236
- container = StepItemContainerPbo(content_type=ContentTypePbo(), json_container=items)
237
- case ContainerType.TEXT:
238
- container = StepItemContainerPbo(content_type=ContentTypePbo(), text_container=items)
239
- self._request_inputs.append(container)
240
-
241
- def get_service_details(self) -> ToolDetailsResponsePbo:
242
- """
243
- Get the details of the tools from the server.
244
-
245
- :return: A ToolDetailsResponsePbo object containing the details of the tool service.
246
- """
247
- with grpc.insecure_channel(self.grpc_server_url, options=self.options) as channel:
248
- stub = ToolServiceStub(channel)
249
- return stub.GetToolDetails(ToolDetailsRequestPbo(sapio_conn_info=self.connection))
250
-
251
- def call_tool(self, tool_name: str, is_dry_run: bool = False) -> ToolOutput:
252
- """
253
- Send the request to the tool service for a particular tool name. This will send all the inputs that have been
254
- added using the add_X_input functions.
255
-
256
- :param tool_name: The name of the tool to call on the server.
257
- :param is_dry_run: If True, the tool will not be executed, but the request will be validated.
258
- :return: A ToolOutput object containing the results of the tool service call.
259
- """
260
- with grpc.insecure_channel(self.grpc_server_url, options=self.options) as channel:
261
- stub = ToolServiceStub(channel)
262
-
263
- response: ProcessStepResponsePbo = stub.ProcessData(
264
- ProcessStepRequestPbo(
265
- sapio_user=self.connection,
266
- tool_name=tool_name,
267
- config_field_values=self._config_fields,
268
- dry_run=is_dry_run,
269
- verbose_logging=True,
270
- input=[
271
- StepInputBatchPbo(is_partial=False, item_container=item)
272
- for item in self._request_inputs
273
- ]
274
- )
275
- )
276
-
277
- results = ToolOutput(tool_name)
278
-
279
- match response.status:
280
- case ProcessStepResponseStatusPbo.SUCCESS:
281
- results.status = "Success"
282
- case ProcessStepResponseStatusPbo.FAILURE:
283
- results.status = "Failure"
284
- case _:
285
- results.status = "Unknown"
286
- results.message = response.status_message
287
-
288
- for item in response.output:
289
- container = item.item_container
290
-
291
- if container.HasField("binary_container"):
292
- results.binary_output.append(list(container.binary_container.items))
293
- elif container.HasField("csv_container"):
294
- csv_output: list[dict[str, Any]] = []
295
- for header in container.csv_container.header.cells:
296
- output_row: dict[str, Any] = {}
297
- for i, row in enumerate(container.csv_container.items):
298
- output_row[header] = row.cells[i]
299
- csv_output.append(output_row)
300
- results.csv_output.append(csv_output)
301
- elif container.HasField("json_container"):
302
- results.json_output.append([json.loads(x) for x in container.json_container.items])
303
- elif container.HasField("text_container"):
304
- results.text_output.append(list(container.text_container.items))
305
-
306
- for record in response.new_records:
307
- field_map: dict[str, Any] = {x: ProtobufUtils.field_pbo_to_value(y) for x, y in record.fields.items()}
308
- results.new_records.append(field_map)
309
-
310
- results.logs.extend(response.log)
311
-
312
- return results
313
-
314
-
315
- class TestConverterClient:
316
- """
317
- A client for testing a ConverterService.
318
- """
319
- grpc_server_url: str
320
- options: list[tuple[str, Any]] | None
321
-
322
- def __init__(self, grpc_server_url: str, options: list[tuple[str, Any]] | None = None):
323
- """
324
- :param grpc_server_url: The URL of the gRPC server to connect to.
325
- :param options: Optional list of gRPC channel options.
326
- """
327
- self.grpc_server_url = grpc_server_url
328
- self.options = options
329
-
330
- def get_converter_details(self) -> ConverterDetailsResponsePbo:
331
- """
332
- Get the details of the converters from the server.
333
-
334
- :return: A ToolDetailsResponsePbo object containing the details of the converter service.
335
- """
336
- with grpc.insecure_channel(self.grpc_server_url, options=self.options) as channel:
337
- stub = ConverterServiceStub(channel)
338
- return stub.GetConverterDetails(ConverterDetailsRequestPbo())
339
-
340
- def convert_content(self, input_container: StepItemContainerPbo, target_type: ContentTypePbo) \
341
- -> StepItemContainerPbo:
342
- """
343
- Convert the content of the input container to the target content type.
344
-
345
- :param input_container: The input container to convert. This container must have a ContentTypePbo set that
346
- matches one of the input types that the converter service supports.
347
- :param target_type: The target content type to convert to. This must match one of the target types that the
348
- converter service supports.
349
- :return: A StepItemContainerPbo object containing the converted content.
350
- """
351
- with grpc.insecure_channel(self.grpc_server_url, options=self.options) as channel:
352
- stub = ConverterServiceStub(channel)
353
- response: ConvertResponsePbo = stub.ConvertContent(
354
- ConvertRequestPbo(item_container=input_container, target_content_type=target_type)
355
- )
356
- return response.item_container