sapiopycommons 2025.4.17a488__py3-none-any.whl → 2025.4.22a490__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 (48) hide show
  1. sapiopycommons/ai/__init__.py +0 -0
  2. sapiopycommons/ai/api/plan/proto/step_output_pb2.py +45 -0
  3. sapiopycommons/ai/api/plan/proto/step_output_pb2.pyi +42 -0
  4. sapiopycommons/ai/api/plan/proto/step_output_pb2_grpc.py +24 -0
  5. sapiopycommons/ai/api/plan/proto/step_pb2.py +43 -0
  6. sapiopycommons/ai/api/plan/proto/step_pb2.pyi +43 -0
  7. sapiopycommons/ai/api/plan/proto/step_pb2_grpc.py +24 -0
  8. sapiopycommons/ai/api/plan/script/proto/script_pb2.py +51 -0
  9. sapiopycommons/ai/api/plan/script/proto/script_pb2.pyi +73 -0
  10. sapiopycommons/ai/api/plan/script/proto/script_pb2_grpc.py +153 -0
  11. sapiopycommons/ai/api/plan/tool/proto/entry_pb2.py +57 -0
  12. sapiopycommons/ai/api/plan/tool/proto/entry_pb2.pyi +96 -0
  13. sapiopycommons/ai/api/plan/tool/proto/entry_pb2_grpc.py +24 -0
  14. sapiopycommons/ai/api/plan/tool/proto/tool_pb2.py +61 -0
  15. sapiopycommons/ai/api/plan/tool/proto/tool_pb2.pyi +114 -0
  16. sapiopycommons/ai/api/plan/tool/proto/tool_pb2_grpc.py +154 -0
  17. sapiopycommons/ai/api/session/proto/sapio_conn_info_pb2.py +39 -0
  18. sapiopycommons/ai/api/session/proto/sapio_conn_info_pb2.pyi +32 -0
  19. sapiopycommons/ai/api/session/proto/sapio_conn_info_pb2_grpc.py +24 -0
  20. sapiopycommons/ai/tool_of_tools.py +917 -0
  21. sapiopycommons/ai/tool_service_base.py +271 -0
  22. sapiopycommons/callbacks/callback_util.py +64 -116
  23. sapiopycommons/callbacks/field_builder.py +0 -2
  24. sapiopycommons/customreport/auto_pagers.py +1 -2
  25. sapiopycommons/customreport/term_builder.py +1 -1
  26. sapiopycommons/datatype/pseudo_data_types.py +326 -349
  27. sapiopycommons/eln/experiment_handler.py +719 -336
  28. sapiopycommons/eln/plate_designer.py +2 -7
  29. sapiopycommons/files/file_util.py +4 -4
  30. sapiopycommons/general/accession_service.py +2 -2
  31. sapiopycommons/general/aliases.py +1 -4
  32. sapiopycommons/general/html_formatter.py +456 -0
  33. sapiopycommons/general/sapio_links.py +12 -4
  34. sapiopycommons/processtracking/custom_workflow_handler.py +1 -2
  35. sapiopycommons/recordmodel/record_handler.py +27 -357
  36. sapiopycommons/rules/eln_rule_handler.py +1 -8
  37. sapiopycommons/rules/on_save_rule_handler.py +1 -8
  38. sapiopycommons/webhook/webhook_handlers.py +0 -3
  39. sapiopycommons/webhook/webservice_handlers.py +2 -2
  40. {sapiopycommons-2025.4.17a488.dist-info → sapiopycommons-2025.4.22a490.dist-info}/METADATA +2 -2
  41. sapiopycommons-2025.4.22a490.dist-info/RECORD +85 -0
  42. sapiopycommons/eln/experiment_cache.py +0 -188
  43. sapiopycommons/eln/experiment_step_factory.py +0 -476
  44. sapiopycommons/eln/step_creation.py +0 -236
  45. sapiopycommons/general/data_structure_util.py +0 -115
  46. sapiopycommons-2025.4.17a488.dist-info/RECORD +0 -67
  47. {sapiopycommons-2025.4.17a488.dist-info → sapiopycommons-2025.4.22a490.dist-info}/WHEEL +0 -0
  48. {sapiopycommons-2025.4.17a488.dist-info → sapiopycommons-2025.4.22a490.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,271 @@
1
+ from __future__ import annotations
2
+
3
+ import json
4
+ import traceback
5
+ from abc import abstractmethod, ABC
6
+ from typing import Any
7
+
8
+ from grpc import ServicerContext
9
+ from sapiopycommons.general.aliases import FieldMap
10
+ from sapiopylib.rest.User import SapioUser
11
+
12
+ from sapiopycommons.ai.api.plan.tool.proto.entry_pb2 import StepEntryOutputData, StepEntryData, StepJsonData, DataType, \
13
+ StepImageData, StepTextData, StepCsvData, StepBinaryData, StepCsvHeaderRow, StepCsvRow
14
+ from sapiopycommons.ai.api.plan.tool.proto.tool_pb2 import ProcessStepRequest, ToolDetailsRequest, ToolDetailsResponse, \
15
+ ProcessStepResponse, ToolDetails, StepRecord, StepRecordFieldValue
16
+ from sapiopycommons.ai.api.plan.tool.proto.tool_pb2_grpc import ToolServiceServicer
17
+ from sapiopycommons.ai.api.session.proto.sapio_conn_info_pb2 import SapioConnectionInfo, SapioUserSecretType
18
+
19
+
20
+ class SapioToolResult(ABC):
21
+ """
22
+ A class representing a result from a Sapio tool. Instantiate one of the subclasses to create a result object.
23
+ """
24
+
25
+ @abstractmethod
26
+ def to_proto(self) -> StepEntryOutputData | list[StepRecord]:
27
+ """
28
+ Convert this SapioToolResult object to a StepEntryOutputData or list of StepRecord proto objects.
29
+ """
30
+ pass
31
+
32
+
33
+ class BinaryResult(SapioToolResult):
34
+ """
35
+ A class representing binary results from a Sapio tool.
36
+ """
37
+ binary_data: list[bytes]
38
+
39
+ def __init__(self, binary_data: list[bytes]):
40
+ """
41
+ :param binary_data: The binary data as a list of bytes. Each entry in the list represents a separate binary
42
+ entry.
43
+ """
44
+ self.binary_data = binary_data
45
+
46
+ def to_proto(self) -> StepEntryOutputData | list[StepRecord]:
47
+ return StepEntryOutputData(
48
+ entry_data=StepEntryData(
49
+ dataType=DataType.BINARY,
50
+ binary_data=StepBinaryData(entries=self.binary_data)
51
+ )
52
+ )
53
+
54
+
55
+ class CsvResult(SapioToolResult):
56
+ """
57
+ A class representing CSV results from a Sapio tool.
58
+ """
59
+ csv_data: list[dict[str, Any]]
60
+
61
+ def __init__(self, csv_data: list[dict[str, Any]]):
62
+ """
63
+ :param csv_data: The list of CSV data results, provided as a list of dictionaries of column name to value.
64
+ Each entry in the list represents a separate row in the CSV.
65
+ """
66
+ self.csv_data = csv_data
67
+
68
+ def to_proto(self) -> StepEntryOutputData | list[StepRecord]:
69
+ return StepEntryOutputData(
70
+ entry_data=StepEntryData(
71
+ dataType=DataType.CSV,
72
+ csv_data=StepCsvData(
73
+ header=StepCsvHeaderRow(cells=self.csv_data[0].keys()),
74
+ entries=[StepCsvRow(cells=[str(x) for x in row.values()]) for row in self.csv_data]
75
+ )
76
+ ) if self.csv_data else None
77
+ )
78
+
79
+
80
+ class FieldMapResult(SapioToolResult):
81
+ """
82
+ A class representing field map results from a Sapio tool.
83
+ """
84
+ field_maps: list[FieldMap]
85
+
86
+ def __init__(self, field_maps: list[FieldMap]):
87
+ """
88
+ :param field_maps: A list of field maps, where each map is a dictionary of field names to values. Each entry
89
+ will create a new data record in the system.
90
+ """
91
+ self.field_maps = field_maps
92
+
93
+ def to_proto(self) -> StepEntryOutputData | list[StepRecord]:
94
+ new_records: list[StepRecord] = []
95
+ for field_map in self.field_maps:
96
+ fields: dict[str, StepRecordFieldValue] = {}
97
+ for field, value in field_map.items():
98
+ field_value = StepRecordFieldValue()
99
+ if isinstance(value, str):
100
+ field_value.string_value = value
101
+ elif isinstance(value, int):
102
+ field_value.int_value = value
103
+ elif isinstance(value, float):
104
+ field_value.double_value = value
105
+ elif isinstance(value, bool):
106
+ field_value.bool_value = value
107
+ fields[field] = field_value
108
+ new_records.append(StepRecord(fields=fields))
109
+ return new_records
110
+
111
+
112
+ class ImageResult(SapioToolResult):
113
+ """
114
+ A class representing image results from a Sapio tool.
115
+ """
116
+ image_format: str
117
+ image_data: list[bytes]
118
+
119
+ def __init__(self, image_format: str, image_data: list[bytes]):
120
+ """
121
+ :param image_format: The format of the image (e.g., PNG, JPEG).
122
+ :param image_data: The image data as a list of bytes. Each entry in the list represents a separate image.
123
+ """
124
+ self.image_format = image_format
125
+ self.image_data = image_data
126
+
127
+ def to_proto(self) -> StepEntryOutputData | list[StepRecord]:
128
+ return StepEntryOutputData(
129
+ entry_data=StepEntryData(
130
+ dataType=DataType.IMAGE,
131
+ image_data=StepImageData(
132
+ image_format=self.image_format,
133
+ entries=self.image_data)
134
+ )
135
+ )
136
+
137
+
138
+ class JsonResult(SapioToolResult):
139
+ """
140
+ A class representing JSON results from a Sapio tool.
141
+ """
142
+ json_data: list[Any]
143
+
144
+ def __init__(self, json_data: list[Any]):
145
+ """
146
+ :param json_data: The list of JSON data results. Each entry in the list represents a separate JSON object.
147
+ These entries must be able to be serialized to JSON using json.dumps(). A common JSON data type is a
148
+ dictionary of strings to strings, integers, doubles, or booleans.
149
+ """
150
+ self.json_data = json_data
151
+
152
+ def to_proto(self) -> StepEntryOutputData | list[StepRecord]:
153
+ return StepEntryOutputData(
154
+ entry_data=StepEntryData(
155
+ dataType=DataType.JSON,
156
+ json_data=StepJsonData(entries=[json.dumps(x) for x in self.json_data])
157
+ )
158
+ )
159
+
160
+
161
+ class TextResult(SapioToolResult):
162
+ """
163
+ A class representing text results from a Sapio tool.
164
+ """
165
+ text_data: list[str]
166
+
167
+ def __init__(self, text_data: list[str]):
168
+ """
169
+ :param text_data: The text data as a list of strings. Each entry in the list represents a separate text entry.
170
+ """
171
+ self.text_data = text_data
172
+
173
+ def to_proto(self) -> StepEntryOutputData | list[StepRecord]:
174
+ return StepEntryOutputData(
175
+ entry_data=StepEntryData(
176
+ dataType=DataType.TEXT,
177
+ text_data=StepTextData(entries=self.text_data)
178
+ )
179
+ )
180
+
181
+
182
+ class ToolServiceBase(ToolServiceServicer, ABC):
183
+ """
184
+ A base class for implementing a tool service.
185
+
186
+ Subclasses must implement the get_details and run methods to provide specific functionality for the tool.
187
+ """
188
+ def GetToolDetails(self, request: ToolDetailsRequest, context: ServicerContext) -> ToolDetailsResponse:
189
+ try:
190
+ # Convert the SapioConnectionInfo proto object to a SapioUser object.
191
+ user = self.create_user(request.sapio_conn_info)
192
+ # Get the tool details from the subclass.
193
+ # TODO: Return something other than the ToolDetails proto objects? Something that's cleaner for the
194
+ # implementing class to work with?
195
+ details: list[ToolDetails] = self.get_details(user, request, context)
196
+ return ToolDetailsResponse(tool_framework_version=self.tool_version(), tool_details=details)
197
+ except Exception as e:
198
+ # TODO: This response doesn't even allow logs. What should we do if an exception occurs in this case?
199
+ return ToolDetailsResponse()
200
+
201
+ def ProcessData(self, request: ProcessStepRequest, context: ServicerContext) -> ProcessStepResponse:
202
+ try:
203
+ # Convert the SapioConnectionInfo proto object to a SapioUser object.
204
+ user = self.create_user(request.sapio_user)
205
+ # Get the tool results from the subclass and convert them to proto objects.
206
+ entry_data: list[StepEntryOutputData] = []
207
+ new_records: list[StepRecord] = []
208
+ for result in self.run(user, request, context):
209
+ data: StepEntryOutputData | list[StepRecord] = result.to_proto()
210
+ if isinstance(data, StepEntryOutputData):
211
+ entry_data.append(data)
212
+ else:
213
+ new_records.extend(data)
214
+ # Return a ProcessStepResponse proto object containing the output data and new records to the caller.
215
+ return ProcessStepResponse(entry_data=entry_data, new_records=new_records)
216
+ except Exception as e:
217
+ # TODO: Do something other than dump the full stack trace into the logs?
218
+ return ProcessStepResponse(log=[traceback.format_exc()])
219
+
220
+ @staticmethod
221
+ def create_user(info: SapioConnectionInfo, timeout_seconds: int = 60) -> SapioUser:
222
+ """
223
+ Create a SapioUser object from the given SapioConnectionInfo proto object.
224
+
225
+ :param info: The SapioConnectionInfo proto object.
226
+ :param timeout_seconds: The request timeout for calls made from this user object.
227
+ """
228
+ # TODO: Have a customizable request timeout? Would need to be added to the request object.
229
+ # TODO: How should the RMI hosts and port be used in the connection info?
230
+ user = SapioUser(info.webservice_url, True, timeout_seconds, guid=info.app_guid)
231
+ if info.secret_type == SapioUserSecretType.SESSION_TOKEN:
232
+ user.api_token = info.secret
233
+ elif info.secret_type == SapioUserSecretType.PASSWORD:
234
+ # TODO: Will the secret be base64 encoded if it's a password? That's how basic auth is normally handled.
235
+ user.password = info.secret
236
+ else:
237
+ raise Exception(f"Unexpected secret type: {info.secret_type}")
238
+ return user
239
+
240
+ @staticmethod
241
+ def tool_version() -> int:
242
+ """
243
+ :return: The version of this tool.
244
+ """
245
+ return 1
246
+
247
+ @abstractmethod
248
+ def get_details(self, user: SapioUser, request: ToolDetailsRequest, context: ServicerContext) -> list[ToolDetails]:
249
+ """
250
+ Get the details of the tool.
251
+
252
+ :param user: A user object that can be used to initialize manager classes using DataMgmtServer to query the
253
+ system.
254
+ :param request: The request object containing the input data.
255
+ :param context: The gRPC context.
256
+ :return: A ToolDetailsResponse object containing the tool details.
257
+ """
258
+ pass
259
+
260
+ @abstractmethod
261
+ def run(self, user: SapioUser, request: ProcessStepRequest, context: ServicerContext) -> list[SapioToolResult]:
262
+ """
263
+ Execute this tool.
264
+
265
+ :param user: A user object that can be used to initialize manager classes using DataMgmtServer to query the
266
+ system.
267
+ :param request: The request object containing the input data.
268
+ :param context: The gRPC context.
269
+ :return: A SapioToolResults object containing the response data.
270
+ """
271
+ pass