sapiopycommons 2025.4.25a497__py3-none-any.whl → 2025.4.30a499__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.
- sapiopycommons/ai/api/fielddefinitions/proto/fields_pb2.py +43 -0
- sapiopycommons/ai/api/fielddefinitions/proto/fields_pb2.pyi +31 -0
- sapiopycommons/ai/api/fielddefinitions/proto/fields_pb2_grpc.py +24 -0
- sapiopycommons/ai/api/fielddefinitions/proto/velox_field_def_pb2.py +89 -89
- sapiopycommons/ai/api/fielddefinitions/proto/velox_field_def_pb2.pyi +174 -174
- sapiopycommons/ai/api/plan/proto/step_output_pb2.py +11 -11
- sapiopycommons/ai/api/plan/proto/step_output_pb2.pyi +27 -27
- sapiopycommons/ai/api/plan/proto/step_pb2.py +9 -9
- sapiopycommons/ai/api/plan/proto/step_pb2.pyi +10 -10
- sapiopycommons/ai/api/plan/script/proto/script_pb2.py +13 -13
- sapiopycommons/ai/api/plan/script/proto/script_pb2.pyi +39 -39
- sapiopycommons/ai/api/plan/script/proto/script_pb2_grpc.py +12 -12
- sapiopycommons/ai/api/plan/tool/proto/entry_pb2.py +23 -23
- sapiopycommons/ai/api/plan/tool/proto/entry_pb2.pyi +63 -63
- sapiopycommons/ai/api/plan/tool/proto/tool_pb2.py +25 -31
- sapiopycommons/ai/api/plan/tool/proto/tool_pb2.pyi +136 -158
- sapiopycommons/ai/api/plan/tool/proto/tool_pb2_grpc.py +12 -12
- sapiopycommons/ai/api/session/proto/sapio_conn_info_pb2.py +5 -5
- sapiopycommons/ai/api/session/proto/sapio_conn_info_pb2.pyi +8 -8
- sapiopycommons/ai/protobuf_utils.py +452 -0
- sapiopycommons/ai/tool_service_base.py +288 -162
- {sapiopycommons-2025.4.25a497.dist-info → sapiopycommons-2025.4.30a499.dist-info}/METADATA +1 -1
- {sapiopycommons-2025.4.25a497.dist-info → sapiopycommons-2025.4.30a499.dist-info}/RECORD +25 -22
- sapiopycommons/ai/tool_of_tools.py +0 -917
- {sapiopycommons-2025.4.25a497.dist-info → sapiopycommons-2025.4.30a499.dist-info}/WHEEL +0 -0
- {sapiopycommons-2025.4.25a497.dist-info → sapiopycommons-2025.4.30a499.dist-info}/licenses/LICENSE +0 -0
|
@@ -3,21 +3,25 @@ from __future__ import annotations
|
|
|
3
3
|
import json
|
|
4
4
|
import traceback
|
|
5
5
|
from abc import abstractmethod, ABC
|
|
6
|
-
from typing import Any
|
|
6
|
+
from typing import Any, Iterable, Sequence
|
|
7
7
|
|
|
8
8
|
from grpc import ServicerContext
|
|
9
|
-
|
|
10
|
-
from sapiopycommons.ai.api.fielddefinitions.proto.velox_field_def_pb2 import VeloxFieldDefProto, FieldTypeProto, \
|
|
11
|
-
IntegerProperties, DoubleProperties, SelectionProperties, BooleanProperties
|
|
12
|
-
from sapiopycommons.general.aliases import FieldMap
|
|
13
9
|
from sapiopylib.rest.User import SapioUser
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
from sapiopycommons.ai.api.
|
|
18
|
-
|
|
10
|
+
from sapiopylib.rest.pojo.datatype.FieldDefinition import AbstractVeloxFieldDefinition
|
|
11
|
+
|
|
12
|
+
from sapiopycommons.ai.api.fielddefinitions.proto.fields_pb2 import FieldValueMapPbo, FieldValuePbo
|
|
13
|
+
from sapiopycommons.ai.api.fielddefinitions.proto.velox_field_def_pb2 import VeloxFieldDefPbo, FieldTypePbo, \
|
|
14
|
+
SelectionPropertiesPbo, IntegerPropertiesPbo, DoublePropertiesPbo, BooleanPropertiesPbo
|
|
15
|
+
from sapiopycommons.ai.api.plan.tool.proto.entry_pb2 import StepOutputBatchPbo, StepItemContainerPbo, DataTypePbo, \
|
|
16
|
+
StepBinaryContainerPbo, StepCsvContainerPbo, StepCsvHeaderRowPbo, StepCsvRowPbo, StepImageContainerPbo, \
|
|
17
|
+
StepJsonContainerPbo, StepTextContainerPbo, StepInputBatchPbo
|
|
18
|
+
from sapiopycommons.ai.api.plan.tool.proto.tool_pb2 import ToolDetailsRequestPbo, ToolDetailsResponsePbo, \
|
|
19
|
+
ToolDetailsPbo, ProcessStepRequestPbo, ProcessStepResponsePbo, ToolOutputDetailsPbo, ToolIoConfigBasePbo, \
|
|
20
|
+
ToolInputDetailsPbo
|
|
19
21
|
from sapiopycommons.ai.api.plan.tool.proto.tool_pb2_grpc import ToolServiceServicer
|
|
20
|
-
from sapiopycommons.ai.api.session.proto.sapio_conn_info_pb2 import
|
|
22
|
+
from sapiopycommons.ai.api.session.proto.sapio_conn_info_pb2 import SapioUserSecretTypePbo, SapioConnectionInfoPbo
|
|
23
|
+
from sapiopycommons.ai.protobuf_utils import ProtobufUtils
|
|
24
|
+
from sapiopycommons.general.aliases import FieldMap, FieldValue
|
|
21
25
|
|
|
22
26
|
|
|
23
27
|
class SapioToolResult(ABC):
|
|
@@ -26,9 +30,9 @@ class SapioToolResult(ABC):
|
|
|
26
30
|
"""
|
|
27
31
|
|
|
28
32
|
@abstractmethod
|
|
29
|
-
def to_proto(self) ->
|
|
33
|
+
def to_proto(self) -> StepOutputBatchPbo | list[FieldValueMapPbo]:
|
|
30
34
|
"""
|
|
31
|
-
Convert this SapioToolResult object to a
|
|
35
|
+
Convert this SapioToolResult object to a StepOutputBatchPbo or list of FieldValueMapPbo proto objects.
|
|
32
36
|
"""
|
|
33
37
|
pass
|
|
34
38
|
|
|
@@ -41,16 +45,15 @@ class BinaryResult(SapioToolResult):
|
|
|
41
45
|
|
|
42
46
|
def __init__(self, binary_data: list[bytes]):
|
|
43
47
|
"""
|
|
44
|
-
:param binary_data: The binary data as a list of bytes.
|
|
45
|
-
entry.
|
|
48
|
+
:param binary_data: The binary data as a list of bytes.
|
|
46
49
|
"""
|
|
47
50
|
self.binary_data = binary_data
|
|
48
51
|
|
|
49
|
-
def to_proto(self) ->
|
|
50
|
-
return
|
|
51
|
-
|
|
52
|
-
dataType=
|
|
53
|
-
|
|
52
|
+
def to_proto(self) -> StepOutputBatchPbo | list[FieldValueMapPbo]:
|
|
53
|
+
return StepOutputBatchPbo(
|
|
54
|
+
item_container=StepItemContainerPbo(
|
|
55
|
+
dataType=DataTypePbo.BINARY,
|
|
56
|
+
binary_container=StepBinaryContainerPbo(items=self.binary_data)
|
|
54
57
|
)
|
|
55
58
|
)
|
|
56
59
|
|
|
@@ -64,17 +67,16 @@ class CsvResult(SapioToolResult):
|
|
|
64
67
|
def __init__(self, csv_data: list[dict[str, Any]]):
|
|
65
68
|
"""
|
|
66
69
|
:param csv_data: The list of CSV data results, provided as a list of dictionaries of column name to value.
|
|
67
|
-
Each entry in the list represents a separate row in the CSV.
|
|
68
70
|
"""
|
|
69
71
|
self.csv_data = csv_data
|
|
70
72
|
|
|
71
|
-
def to_proto(self) ->
|
|
72
|
-
return
|
|
73
|
-
|
|
74
|
-
dataType=
|
|
75
|
-
|
|
76
|
-
header=
|
|
77
|
-
|
|
73
|
+
def to_proto(self) -> StepOutputBatchPbo | list[FieldValueMapPbo]:
|
|
74
|
+
return StepOutputBatchPbo(
|
|
75
|
+
item_container=StepItemContainerPbo(
|
|
76
|
+
dataType=DataTypePbo.CSV,
|
|
77
|
+
csv_container=StepCsvContainerPbo(
|
|
78
|
+
header=StepCsvHeaderRowPbo(cells=self.csv_data[0].keys()),
|
|
79
|
+
items=[StepCsvRowPbo(cells=[str(x) for x in row.values()]) for row in self.csv_data]
|
|
78
80
|
)
|
|
79
81
|
) if self.csv_data else None
|
|
80
82
|
)
|
|
@@ -89,16 +91,17 @@ class FieldMapResult(SapioToolResult):
|
|
|
89
91
|
def __init__(self, field_maps: list[FieldMap]):
|
|
90
92
|
"""
|
|
91
93
|
:param field_maps: A list of field maps, where each map is a dictionary of field names to values. Each entry
|
|
92
|
-
will create a new data record in the system
|
|
94
|
+
will create a new data record in the system, so long as the tool definition specifies an output data type
|
|
95
|
+
name.
|
|
93
96
|
"""
|
|
94
97
|
self.field_maps = field_maps
|
|
95
98
|
|
|
96
|
-
def to_proto(self) ->
|
|
97
|
-
new_records: list[
|
|
99
|
+
def to_proto(self) -> StepOutputBatchPbo | list[FieldValueMapPbo]:
|
|
100
|
+
new_records: list[FieldValueMapPbo] = []
|
|
98
101
|
for field_map in self.field_maps:
|
|
99
|
-
fields: dict[str,
|
|
102
|
+
fields: dict[str, FieldValuePbo] = {}
|
|
100
103
|
for field, value in field_map.items():
|
|
101
|
-
field_value =
|
|
104
|
+
field_value = FieldValuePbo()
|
|
102
105
|
if isinstance(value, str):
|
|
103
106
|
field_value.string_value = value
|
|
104
107
|
elif isinstance(value, int):
|
|
@@ -108,7 +111,7 @@ class FieldMapResult(SapioToolResult):
|
|
|
108
111
|
elif isinstance(value, bool):
|
|
109
112
|
field_value.bool_value = value
|
|
110
113
|
fields[field] = field_value
|
|
111
|
-
new_records.append(
|
|
114
|
+
new_records.append(FieldValueMapPbo(fields=fields))
|
|
112
115
|
return new_records
|
|
113
116
|
|
|
114
117
|
|
|
@@ -127,13 +130,13 @@ class ImageResult(SapioToolResult):
|
|
|
127
130
|
self.image_format = image_format
|
|
128
131
|
self.image_data = image_data
|
|
129
132
|
|
|
130
|
-
def to_proto(self) ->
|
|
131
|
-
return
|
|
132
|
-
|
|
133
|
-
dataType=
|
|
134
|
-
|
|
133
|
+
def to_proto(self) -> StepOutputBatchPbo | list[FieldValueMapPbo]:
|
|
134
|
+
return StepOutputBatchPbo(
|
|
135
|
+
item_container=StepItemContainerPbo(
|
|
136
|
+
dataType=DataTypePbo.IMAGE,
|
|
137
|
+
image_container=StepImageContainerPbo(
|
|
135
138
|
image_format=self.image_format,
|
|
136
|
-
|
|
139
|
+
items=self.image_data)
|
|
137
140
|
)
|
|
138
141
|
)
|
|
139
142
|
|
|
@@ -147,16 +150,15 @@ class JsonResult(SapioToolResult):
|
|
|
147
150
|
def __init__(self, json_data: list[Any]):
|
|
148
151
|
"""
|
|
149
152
|
:param json_data: The list of JSON data results. Each entry in the list represents a separate JSON object.
|
|
150
|
-
These entries must be able to be serialized to JSON using json.dumps().
|
|
151
|
-
dictionary of strings to strings, integers, doubles, or booleans.
|
|
153
|
+
These entries must be able to be serialized to JSON using json.dumps().
|
|
152
154
|
"""
|
|
153
155
|
self.json_data = json_data
|
|
154
156
|
|
|
155
|
-
def to_proto(self) ->
|
|
156
|
-
return
|
|
157
|
-
|
|
158
|
-
dataType=
|
|
159
|
-
|
|
157
|
+
def to_proto(self) -> StepOutputBatchPbo | list[FieldValueMapPbo]:
|
|
158
|
+
return StepOutputBatchPbo(
|
|
159
|
+
item_container=StepItemContainerPbo(
|
|
160
|
+
dataType=DataTypePbo.JSON,
|
|
161
|
+
json_container=StepJsonContainerPbo(items=[json.dumps(x) for x in self.json_data])
|
|
160
162
|
)
|
|
161
163
|
)
|
|
162
164
|
|
|
@@ -169,15 +171,15 @@ class TextResult(SapioToolResult):
|
|
|
169
171
|
|
|
170
172
|
def __init__(self, text_data: list[str]):
|
|
171
173
|
"""
|
|
172
|
-
:param text_data: The text data as a list of strings.
|
|
174
|
+
:param text_data: The text data as a list of strings.
|
|
173
175
|
"""
|
|
174
176
|
self.text_data = text_data
|
|
175
177
|
|
|
176
|
-
def to_proto(self) ->
|
|
177
|
-
return
|
|
178
|
-
|
|
179
|
-
dataType=
|
|
180
|
-
|
|
178
|
+
def to_proto(self) -> StepOutputBatchPbo | list[FieldValueMapPbo]:
|
|
179
|
+
return StepOutputBatchPbo(
|
|
180
|
+
item_container=StepItemContainerPbo(
|
|
181
|
+
dataType=DataTypePbo.TEXT,
|
|
182
|
+
text_container=StepTextContainerPbo(items=self.text_data)
|
|
181
183
|
)
|
|
182
184
|
)
|
|
183
185
|
|
|
@@ -187,38 +189,38 @@ class ToolServiceBase(ToolServiceServicer, ABC):
|
|
|
187
189
|
A base class for implementing a tool service. Subclasses should implement the register_tools method to register
|
|
188
190
|
their tools with the service.
|
|
189
191
|
"""
|
|
190
|
-
def
|
|
192
|
+
def GetToolDetailsPbo(self, request: ToolDetailsRequestPbo, context: ServicerContext) -> ToolDetailsResponsePbo:
|
|
191
193
|
try:
|
|
192
194
|
# Get the tool details from the registered tools.
|
|
193
|
-
details: list[
|
|
194
|
-
return
|
|
195
|
+
details: list[ToolDetailsPbo] = self.get_details()
|
|
196
|
+
return ToolDetailsResponsePbo(tool_framework_version=self.tool_version(), tool_details=details)
|
|
195
197
|
except Exception:
|
|
196
198
|
# TODO: This response doesn't even allow logs. What should we do if an exception occurs in this case?
|
|
197
|
-
return
|
|
199
|
+
return ToolDetailsResponsePbo()
|
|
198
200
|
|
|
199
|
-
def ProcessData(self, request:
|
|
201
|
+
def ProcessData(self, request: ProcessStepRequestPbo, context: ServicerContext) -> ProcessStepResponsePbo:
|
|
200
202
|
try:
|
|
201
203
|
# Convert the SapioConnectionInfo proto object to a SapioUser object.
|
|
202
204
|
user = self.create_user(request.sapio_user)
|
|
203
205
|
# Get the tool results from the registered tool matching the request and convert them to proto objects.
|
|
204
|
-
|
|
205
|
-
new_records: list[
|
|
206
|
+
output_data: list[StepOutputBatchPbo] = []
|
|
207
|
+
new_records: list[FieldValueMapPbo] = []
|
|
206
208
|
# TODO: Make use of the success value after the response object has a field for it.
|
|
207
209
|
success, results, logs = self.run(user, request, context)
|
|
208
210
|
for result in results:
|
|
209
|
-
data:
|
|
210
|
-
if isinstance(data,
|
|
211
|
-
|
|
211
|
+
data: StepOutputBatchPbo | list[FieldValueMapPbo] = result.to_proto()
|
|
212
|
+
if isinstance(data, StepOutputBatchPbo):
|
|
213
|
+
output_data.append(data)
|
|
212
214
|
else:
|
|
213
215
|
new_records.extend(data)
|
|
214
216
|
# Return a ProcessStepResponse proto object containing the output data and new records to the caller.
|
|
215
|
-
return
|
|
217
|
+
return ProcessStepResponsePbo(output=output_data, log=logs, new_records=new_records)
|
|
216
218
|
except Exception:
|
|
217
219
|
# TODO: Return a False success result after the response object has a field for it.
|
|
218
|
-
return
|
|
220
|
+
return ProcessStepResponsePbo(log=[traceback.format_exc()])
|
|
219
221
|
|
|
220
222
|
@staticmethod
|
|
221
|
-
def create_user(info:
|
|
223
|
+
def create_user(info: SapioConnectionInfoPbo, timeout_seconds: int = 60) -> SapioUser:
|
|
222
224
|
"""
|
|
223
225
|
Create a SapioUser object from the given SapioConnectionInfo proto object.
|
|
224
226
|
|
|
@@ -228,9 +230,9 @@ class ToolServiceBase(ToolServiceServicer, ABC):
|
|
|
228
230
|
# TODO: Have a customizable request timeout? Would need to be added to the request object.
|
|
229
231
|
# TODO: How should the RMI hosts and port be used in the connection info?
|
|
230
232
|
user = SapioUser(info.webservice_url, True, timeout_seconds, guid=info.app_guid)
|
|
231
|
-
if info.secret_type ==
|
|
233
|
+
if info.secret_type == SapioUserSecretTypePbo.SESSION_TOKEN:
|
|
232
234
|
user.api_token = info.secret
|
|
233
|
-
elif info.secret_type ==
|
|
235
|
+
elif info.secret_type == SapioUserSecretTypePbo.PASSWORD:
|
|
234
236
|
# TODO: Will the secret be base64 encoded if it's a password? That's how basic auth is normally handled.
|
|
235
237
|
user.password = info.secret
|
|
236
238
|
else:
|
|
@@ -276,18 +278,18 @@ class ToolServiceBase(ToolServiceServicer, ABC):
|
|
|
276
278
|
"""
|
|
277
279
|
pass
|
|
278
280
|
|
|
279
|
-
def get_details(self) -> list[
|
|
281
|
+
def get_details(self) -> list[ToolDetailsPbo]:
|
|
280
282
|
"""
|
|
281
283
|
Get the details of the tool.
|
|
282
284
|
|
|
283
285
|
:return: A ToolDetailsResponse object containing the tool details.
|
|
284
286
|
"""
|
|
285
|
-
tool_details: list[
|
|
287
|
+
tool_details: list[ToolDetailsPbo] = []
|
|
286
288
|
for tool in self._get_tools():
|
|
287
|
-
tool_details.append(tool.
|
|
289
|
+
tool_details.append(tool.to_pbo())
|
|
288
290
|
return tool_details
|
|
289
291
|
|
|
290
|
-
def run(self, user: SapioUser, request:
|
|
292
|
+
def run(self, user: SapioUser, request: ProcessStepRequestPbo, context: ServicerContext) \
|
|
291
293
|
-> tuple[bool, list[SapioToolResult], list[str]]:
|
|
292
294
|
"""
|
|
293
295
|
Execute a tool from this service.
|
|
@@ -300,7 +302,8 @@ class ToolServiceBase(ToolServiceServicer, ABC):
|
|
|
300
302
|
"""
|
|
301
303
|
tool = self._get_tool(request.tool_name)
|
|
302
304
|
try:
|
|
303
|
-
|
|
305
|
+
tool.setup(user, request, context)
|
|
306
|
+
results: list[SapioToolResult] = tool.run(user)
|
|
304
307
|
return True, results, tool.logs
|
|
305
308
|
except Exception:
|
|
306
309
|
tool.log_message(traceback.format_exc())
|
|
@@ -314,11 +317,16 @@ class ToolBase(ABC):
|
|
|
314
317
|
name: str
|
|
315
318
|
description: str
|
|
316
319
|
data_type_name: str | None
|
|
317
|
-
inputs: list[
|
|
318
|
-
outputs: list[
|
|
319
|
-
configs: list[
|
|
320
|
+
inputs: list[ToolInputDetailsPbo]
|
|
321
|
+
outputs: list[ToolOutputDetailsPbo]
|
|
322
|
+
configs: list[VeloxFieldDefPbo]
|
|
320
323
|
logs: list[str]
|
|
321
324
|
|
|
325
|
+
user: SapioUser
|
|
326
|
+
request: ProcessStepRequestPbo
|
|
327
|
+
context: ServicerContext
|
|
328
|
+
verbose_logging: bool
|
|
329
|
+
|
|
322
330
|
def __init__(self, name: str, description: str, data_type_name: str | None = None):
|
|
323
331
|
"""
|
|
324
332
|
:param name: The name of the tool.
|
|
@@ -334,25 +342,81 @@ class ToolBase(ABC):
|
|
|
334
342
|
self.configs = []
|
|
335
343
|
self.logs = []
|
|
336
344
|
|
|
337
|
-
def
|
|
345
|
+
def setup(self, user: SapioUser, request: ProcessStepRequestPbo, context: ServicerContext) -> None:
|
|
338
346
|
"""
|
|
339
|
-
|
|
340
|
-
|
|
347
|
+
Setup the tool with the user, request, and context. This method can be overridden by subclasses to perform
|
|
348
|
+
additional setup.
|
|
349
|
+
|
|
350
|
+
:param user: A user object that can be used to initialize manager classes using DataMgmtServer to query the
|
|
351
|
+
system.
|
|
352
|
+
:param request: The request object containing the input data.
|
|
353
|
+
:param context: The gRPC context.
|
|
354
|
+
"""
|
|
355
|
+
self.user = user
|
|
356
|
+
self.request = request
|
|
357
|
+
self.context = context
|
|
358
|
+
# TODO: Determine verbose logging from the request.
|
|
359
|
+
self.verbose_logging = False
|
|
341
360
|
|
|
342
|
-
|
|
361
|
+
def add_input(self, content_type: DataTypePbo, display_name: str, description: str, example: str | None = None,
|
|
362
|
+
validation: str | None = None, input_count: tuple[int, int] | None = None, is_paged: bool = False,
|
|
363
|
+
page_size: tuple[int, int] | None = None, max_request_bytes: int | None = None) -> None:
|
|
343
364
|
"""
|
|
344
|
-
|
|
365
|
+
Add an input configuration to the tool. This determines how many inputs this tool will accept in the plan
|
|
366
|
+
manager, as well as what those inputs are. The IO number of the input will be set to the current number of
|
|
367
|
+
inputs. That is, the first time this is called, the IO number will be 0, the second time it is called, the IO
|
|
368
|
+
number will be 1, and so on.
|
|
369
|
+
|
|
370
|
+
:param content_type: The content type of the input.
|
|
371
|
+
:param display_name: The display name of the input.
|
|
372
|
+
:param description: The description of the input.
|
|
373
|
+
:param example: An optional example of the input.
|
|
374
|
+
:param validation: An optional validation string for the input.
|
|
375
|
+
:param input_count: A tuple of the minimum and maximum number of inputs allowed for this tool.
|
|
376
|
+
:param is_paged: If true, this input will be paged. If false, this input will not be paged.
|
|
377
|
+
:param page_size: A tuple of the minimum and maximum page size for this tool. The input must be paged in order
|
|
378
|
+
for this to have an effect.
|
|
379
|
+
:param max_request_bytes: The maximum request size in bytes for this tool.
|
|
380
|
+
"""
|
|
381
|
+
self.inputs.append(ToolInputDetailsPbo(
|
|
382
|
+
base_config=ToolIoConfigBasePbo(
|
|
383
|
+
io_number=len(self.inputs),
|
|
384
|
+
content_type=ProtobufUtils.content_type_str(content_type),
|
|
385
|
+
display_name=display_name,
|
|
386
|
+
description=description,
|
|
387
|
+
example=example
|
|
388
|
+
),
|
|
389
|
+
validation=validation,
|
|
390
|
+
min_input_count=input_count[0] if input_count else None,
|
|
391
|
+
max_input_count=input_count[1] if input_count else None,
|
|
392
|
+
paged=is_paged,
|
|
393
|
+
min_page_size=page_size[0] if page_size else None,
|
|
394
|
+
max_page_size=page_size[1] if page_size else None,
|
|
395
|
+
max_request_bytes=max_request_bytes,
|
|
396
|
+
))
|
|
345
397
|
|
|
346
|
-
def add_output(self,
|
|
398
|
+
def add_output(self, content_type: DataTypePbo, display_name: str, descriptions: str, example: str | None = None) -> None:
|
|
347
399
|
"""
|
|
348
|
-
Add an output configuration to the tool. This determines how many
|
|
349
|
-
manager, as well as what those
|
|
400
|
+
Add an output configuration to the tool. This determines how many inputs this tool will accept in the plan
|
|
401
|
+
manager, as well as what those inputs are. The IO number of the output will be set to the current number of
|
|
402
|
+
outputs. That is, the first time this is called, the IO number will be 0, the second time it is called, the IO
|
|
403
|
+
number will be 1, and so on.
|
|
350
404
|
|
|
351
|
-
:param
|
|
405
|
+
:param content_type: The content type of the output.
|
|
406
|
+
:param display_name: The display name of the output.
|
|
407
|
+
:param descriptions: The description of the output.
|
|
408
|
+
:param example: An example of the output.
|
|
352
409
|
"""
|
|
353
|
-
self.outputs.append(
|
|
410
|
+
self.outputs.append(ToolOutputDetailsPbo(
|
|
411
|
+
base_config=ToolIoConfigBasePbo(
|
|
412
|
+
io_number=len(self.outputs),
|
|
413
|
+
content_type=ProtobufUtils.content_type_str(content_type),
|
|
414
|
+
display_name=display_name,
|
|
415
|
+
description=descriptions,
|
|
416
|
+
example=example
|
|
417
|
+
)))
|
|
354
418
|
|
|
355
|
-
def add_config_field(self, field:
|
|
419
|
+
def add_config_field(self, field: VeloxFieldDefPbo) -> None:
|
|
356
420
|
"""
|
|
357
421
|
Add a configuration field to the tool. This field will be used to configure the tool in the plan manager.
|
|
358
422
|
|
|
@@ -360,30 +424,34 @@ class ToolBase(ABC):
|
|
|
360
424
|
"""
|
|
361
425
|
self.configs.append(field)
|
|
362
426
|
|
|
363
|
-
def
|
|
364
|
-
default_value: int, min_value: int = -2**31, max_value: int = 2**31-1) -> None:
|
|
427
|
+
def add_config_field_def(self, field: AbstractVeloxFieldDefinition) -> None:
|
|
365
428
|
"""
|
|
366
|
-
Add
|
|
429
|
+
Add a configuration field to the tool. This field will be used to configure the tool in the plan manager.
|
|
430
|
+
|
|
431
|
+
:param field: The configuration field details.
|
|
432
|
+
"""
|
|
433
|
+
self.configs.append(ProtobufUtils.field_def_to_pbo(field))
|
|
434
|
+
|
|
435
|
+
def add_boolean_config_field(self, field_name: str, display_name: str, description: str, default_value: bool) \
|
|
436
|
+
-> None:
|
|
437
|
+
"""
|
|
438
|
+
Add a boolean configuration field to the tool. This field will be used to configure the tool in the plan
|
|
367
439
|
manager.
|
|
368
440
|
|
|
369
441
|
:param field_name: The name of the field.
|
|
370
442
|
:param display_name: The display name of the field.
|
|
371
443
|
:param description: The description of the field.
|
|
372
444
|
:param default_value: The default value of the field.
|
|
373
|
-
:param min_value: The minimum value of the field.
|
|
374
|
-
:param max_value: The maximum value of the field.
|
|
375
445
|
"""
|
|
376
|
-
self.configs.append(
|
|
377
|
-
data_field_type=
|
|
446
|
+
self.configs.append(VeloxFieldDefPbo(
|
|
447
|
+
data_field_type=FieldTypePbo.BOOLEAN,
|
|
378
448
|
data_field_name=field_name,
|
|
379
449
|
display_name=display_name,
|
|
380
450
|
description=description,
|
|
381
451
|
required=True,
|
|
382
452
|
editable=True,
|
|
383
|
-
|
|
384
|
-
default_value=default_value
|
|
385
|
-
min_value=min_value,
|
|
386
|
-
max_value=max_value
|
|
453
|
+
boolean_properties=BooleanPropertiesPbo(
|
|
454
|
+
default_value=default_value
|
|
387
455
|
)
|
|
388
456
|
))
|
|
389
457
|
|
|
@@ -401,14 +469,14 @@ class ToolBase(ABC):
|
|
|
401
469
|
:param max_value: The maximum value of the field.
|
|
402
470
|
:param precision: The precision of the field.
|
|
403
471
|
"""
|
|
404
|
-
self.configs.append(
|
|
405
|
-
data_field_type=
|
|
472
|
+
self.configs.append(VeloxFieldDefPbo(
|
|
473
|
+
data_field_type=FieldTypePbo.DOUBLE,
|
|
406
474
|
data_field_name=field_name,
|
|
407
475
|
display_name=display_name,
|
|
408
476
|
description=description,
|
|
409
477
|
required=True,
|
|
410
478
|
editable=True,
|
|
411
|
-
double_properties=
|
|
479
|
+
double_properties=DoublePropertiesPbo(
|
|
412
480
|
default_value=default_value,
|
|
413
481
|
min_value=min_value,
|
|
414
482
|
max_value=max_value,
|
|
@@ -416,59 +484,63 @@ class ToolBase(ABC):
|
|
|
416
484
|
)
|
|
417
485
|
))
|
|
418
486
|
|
|
419
|
-
def
|
|
420
|
-
|
|
487
|
+
def add_integer_config_field(self, field_name: str, display_name: str, description: str,
|
|
488
|
+
default_value: int, min_value: int = -2**31, max_value: int = 2**31-1) -> None:
|
|
421
489
|
"""
|
|
422
|
-
Add
|
|
490
|
+
Add an integer configuration field to the tool. This field will be used to configure the tool in the plan
|
|
423
491
|
manager.
|
|
424
492
|
|
|
425
493
|
:param field_name: The name of the field.
|
|
426
494
|
:param display_name: The display name of the field.
|
|
427
495
|
:param description: The description of the field.
|
|
428
496
|
:param default_value: The default value of the field.
|
|
429
|
-
:param
|
|
497
|
+
:param min_value: The minimum value of the field.
|
|
498
|
+
:param max_value: The maximum value of the field.
|
|
430
499
|
"""
|
|
431
|
-
self.configs.append(
|
|
432
|
-
data_field_type=
|
|
500
|
+
self.configs.append(VeloxFieldDefPbo(
|
|
501
|
+
data_field_type=FieldTypePbo.INTEGER,
|
|
433
502
|
data_field_name=field_name,
|
|
434
503
|
display_name=display_name,
|
|
435
504
|
description=description,
|
|
436
505
|
required=True,
|
|
437
506
|
editable=True,
|
|
438
|
-
|
|
507
|
+
integer_properties=IntegerPropertiesPbo(
|
|
439
508
|
default_value=default_value,
|
|
440
|
-
|
|
509
|
+
min_value=min_value,
|
|
510
|
+
max_value=max_value
|
|
441
511
|
)
|
|
442
512
|
))
|
|
443
513
|
|
|
444
|
-
def
|
|
445
|
-
|
|
514
|
+
def add_list_config_field(self, field_name: str, display_name: str, description: str, default_value: str,
|
|
515
|
+
allowed_values: list[str]) -> None:
|
|
446
516
|
"""
|
|
447
|
-
Add a
|
|
517
|
+
Add a list configuration field to the tool. This field will be used to configure the tool in the plan
|
|
448
518
|
manager.
|
|
449
519
|
|
|
450
520
|
:param field_name: The name of the field.
|
|
451
521
|
:param display_name: The display name of the field.
|
|
452
522
|
:param description: The description of the field.
|
|
453
523
|
:param default_value: The default value of the field.
|
|
524
|
+
:param allowed_values: The list of allowed values for the field.
|
|
454
525
|
"""
|
|
455
|
-
self.configs.append(
|
|
456
|
-
data_field_type=
|
|
526
|
+
self.configs.append(VeloxFieldDefPbo(
|
|
527
|
+
data_field_type=FieldTypePbo.SELECTION,
|
|
457
528
|
data_field_name=field_name,
|
|
458
529
|
display_name=display_name,
|
|
459
530
|
description=description,
|
|
460
531
|
required=True,
|
|
461
532
|
editable=True,
|
|
462
|
-
|
|
463
|
-
default_value=default_value
|
|
533
|
+
selection_properties=SelectionPropertiesPbo(
|
|
534
|
+
default_value=default_value,
|
|
535
|
+
static_list_values=allowed_values,
|
|
464
536
|
)
|
|
465
537
|
))
|
|
466
538
|
|
|
467
|
-
def
|
|
539
|
+
def to_pbo(self) -> ToolDetailsPbo:
|
|
468
540
|
"""
|
|
469
|
-
:return: The
|
|
541
|
+
:return: The ToolDetailsPbo proto object representing this tool.
|
|
470
542
|
"""
|
|
471
|
-
return
|
|
543
|
+
return ToolDetailsPbo(
|
|
472
544
|
name=self.name,
|
|
473
545
|
description=self.description,
|
|
474
546
|
input_configs=self.inputs,
|
|
@@ -477,39 +549,118 @@ class ToolBase(ABC):
|
|
|
477
549
|
config_fields=self.configs
|
|
478
550
|
)
|
|
479
551
|
|
|
480
|
-
|
|
552
|
+
@abstractmethod
|
|
553
|
+
def run(self, user: SapioUser) -> list[SapioToolResult]:
|
|
554
|
+
"""
|
|
555
|
+
Execute this tool.
|
|
556
|
+
|
|
557
|
+
The request inputs can be accessed using the self.get_input_*() methods.
|
|
558
|
+
The request settings can be accessed using the self.get_config_fields() method.
|
|
559
|
+
The request itself can be accessed using self.request.
|
|
560
|
+
|
|
561
|
+
:param user: A user object that can be used to initialize manager classes using DataMgmtServer to query the
|
|
562
|
+
system.
|
|
563
|
+
:return: A SapioToolResults object containing the response data. Each result in the list corresponds to a
|
|
564
|
+
separate output from the tool. Field map results do not appear as tool output in the plan manager, instead
|
|
565
|
+
appearing as records related to the plan step during the run.
|
|
566
|
+
"""
|
|
567
|
+
pass
|
|
568
|
+
|
|
569
|
+
def log_message(self, message: str, verbose: bool = False) -> None:
|
|
481
570
|
"""
|
|
482
571
|
Log a message for this tool. This message will be included in the logs returned to the caller.
|
|
483
572
|
|
|
484
573
|
:param message: The message to log.
|
|
574
|
+
:param verbose: If true, the message will only be logged if verbose logging is enabled. If false, the message
|
|
575
|
+
will be logged regardless of the verbose logging setting.
|
|
485
576
|
"""
|
|
486
|
-
self.
|
|
577
|
+
if not verbose or self.verbose_logging:
|
|
578
|
+
self.logs.append(f"{self.name}: {message}")
|
|
487
579
|
|
|
488
|
-
|
|
489
|
-
def run(self, user: SapioUser, request: ProcessStepRequest, context: ServicerContext) -> list[SapioToolResult]:
|
|
580
|
+
def get_input_binary(self, index: int = 0) -> list[bytes]:
|
|
490
581
|
"""
|
|
491
|
-
|
|
582
|
+
Get the binary data from the request object.
|
|
492
583
|
|
|
493
|
-
:param
|
|
494
|
-
|
|
495
|
-
:param request: The request object containing the input data.
|
|
496
|
-
:param context: The gRPC context.
|
|
497
|
-
:return: A SapioToolResults object containing the response data.
|
|
584
|
+
:param index: The index of the input to parse. Defaults to 0. Used for tools that accept multiple inputs.
|
|
585
|
+
:return: The binary data from the request object.
|
|
498
586
|
"""
|
|
499
|
-
|
|
587
|
+
return list(self.request.input[index].item_container.binary_container.items)
|
|
500
588
|
|
|
501
|
-
|
|
502
|
-
|
|
589
|
+
def get_input_csv(self, index: int = 0) -> tuple[list[str], list[dict[str, str]]]:
|
|
590
|
+
"""
|
|
591
|
+
Parse the CSV data from the request object.
|
|
592
|
+
|
|
593
|
+
:param index: The index of the input to parse. Defaults to 0. Used for tools that accept multiple inputs.
|
|
594
|
+
:return: A tuple containing the header row and the data rows. The header row is a list of strings representing
|
|
595
|
+
the column names, and the data rows are a list of dictionaries where each dictionary represents a row in the
|
|
596
|
+
CSV with the column names as keys and the corresponding values as strings.
|
|
597
|
+
"""
|
|
598
|
+
input_data: Sequence[StepInputBatchPbo] = self.request.input
|
|
599
|
+
ret_val: list[dict[str, str]] = []
|
|
600
|
+
headers: Iterable[str] = input_data[index].item_container.csv_container.header.cells
|
|
601
|
+
for row in input_data[index].item_container.csv_container.items:
|
|
602
|
+
row_dict: dict[str, str] = {}
|
|
603
|
+
for header, value in zip(headers, row.cells):
|
|
604
|
+
row_dict[header] = value
|
|
605
|
+
ret_val.append(row_dict)
|
|
606
|
+
return list(headers), ret_val
|
|
607
|
+
|
|
608
|
+
def get_input_images(self, index: int = 0) -> tuple[str, list[bytes]]:
|
|
609
|
+
"""
|
|
610
|
+
Parse the image data from the request object.
|
|
611
|
+
|
|
612
|
+
:param index: The index of the input to parse. Defaults to 0. Used for tools that accept multiple inputs.
|
|
613
|
+
:return: A tuple containing the image format and the image data. The image format is a string representing the
|
|
614
|
+
format of the image (e.g., PNG, JPEG), and the image data is a list of bytes representing the image data.
|
|
615
|
+
Each entry in the list represents a separate image.
|
|
616
|
+
"""
|
|
617
|
+
image_data: StepImageContainerPbo = self.request.input[index].item_container.image_container
|
|
618
|
+
return image_data.image_format, list(image_data.items)
|
|
619
|
+
|
|
620
|
+
def get_input_json(self, index: int = 0) -> list[list[Any]] | [list[dict[str, Any]]]:
|
|
503
621
|
"""
|
|
504
622
|
Parse the JSON data from the request object.
|
|
505
623
|
|
|
506
|
-
:param
|
|
507
|
-
:param index: The index of the input to parse. Defaults to 0.
|
|
624
|
+
:param index: The index of the input to parse. Defaults to 0. Used for tools that accept multiple inputs.
|
|
508
625
|
:return: A list of parsed JSON objects. Each entry in the list represents a separate JSON entry from the input.
|
|
626
|
+
Depending on this tool, this may be a list of dictionaries or a list of lists.
|
|
509
627
|
"""
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
628
|
+
return [json.loads(x) for x in self.request.input[index].item_container.json_container.items]
|
|
629
|
+
|
|
630
|
+
def get_input_text(self, index: int = 0) -> list[str]:
|
|
631
|
+
"""
|
|
632
|
+
Parse the text data from the request object.
|
|
633
|
+
|
|
634
|
+
:param index: The index of the input to parse. Defaults to 0. Used for tools that accept multiple inputs.
|
|
635
|
+
:return: A list of text data as strings.
|
|
636
|
+
"""
|
|
637
|
+
return list(self.request.input[index].item_container.text_container.items)
|
|
638
|
+
|
|
639
|
+
def get_config_defs(self) -> dict[str, VeloxFieldDefPbo]:
|
|
640
|
+
"""
|
|
641
|
+
Get the config field definitions for this tool.
|
|
642
|
+
|
|
643
|
+
:return: A dictionary of field definitions, where the keys are the field names and the values are the
|
|
644
|
+
VeloxFieldDefPbo objects representing the field definitions.
|
|
645
|
+
"""
|
|
646
|
+
field_defs: dict[str, VeloxFieldDefPbo] = {}
|
|
647
|
+
for field_def in self.to_pbo().config_fields:
|
|
648
|
+
field_defs[field_def.data_field_name] = field_def
|
|
649
|
+
return field_defs
|
|
650
|
+
|
|
651
|
+
def get_config_fields(self) -> dict[str, FieldValue]:
|
|
652
|
+
"""
|
|
653
|
+
Get the configuration fields from the request object.
|
|
654
|
+
|
|
655
|
+
:return: A dictionary of configuration field names and their values.
|
|
656
|
+
"""
|
|
657
|
+
config_fields: dict[str, Any] = {}
|
|
658
|
+
field_defs: dict[str, VeloxFieldDefPbo] = self.get_config_defs()
|
|
659
|
+
for field_name, value in self.request.config_field_values.items():
|
|
660
|
+
if field_name not in field_defs:
|
|
661
|
+
continue
|
|
662
|
+
config_fields[field_name] = ProtobufUtils.field_pbo_to_value(field_defs[field_name], value)
|
|
663
|
+
return config_fields
|
|
513
664
|
|
|
514
665
|
@staticmethod
|
|
515
666
|
def read_from_json(json_data: list[dict[str, Any]], key: str) -> list[Any]:
|
|
@@ -529,28 +680,3 @@ class ToolBase(ABC):
|
|
|
529
680
|
elif value is not None:
|
|
530
681
|
ret_val.append(value)
|
|
531
682
|
return ret_val
|
|
532
|
-
|
|
533
|
-
def get_config_fields(self, request: ProcessStepRequest) -> dict[str, Any]:
|
|
534
|
-
"""
|
|
535
|
-
Get the configuration fields from the request object.
|
|
536
|
-
|
|
537
|
-
:param request: The request object containing the input data.
|
|
538
|
-
:return: A dictionary of configuration field names and their values.
|
|
539
|
-
"""
|
|
540
|
-
config_fields: dict[str, Any] = {}
|
|
541
|
-
# noinspection PyUnresolvedReferences,PyTypeChecker
|
|
542
|
-
field_defs: dict[str, VeloxFieldDefProto] = {x.data_field_name: x for x in self.to_proto().config_fields}
|
|
543
|
-
for field, value in request.config_field_values.items():
|
|
544
|
-
if field not in field_defs:
|
|
545
|
-
continue
|
|
546
|
-
field_type: FieldTypeProto = field_defs[field].data_field_type
|
|
547
|
-
if field_type == FieldTypeProto.BOOLEAN:
|
|
548
|
-
value = value.bool_value
|
|
549
|
-
elif field_type == FieldTypeProto.DOUBLE:
|
|
550
|
-
value = value.double_value
|
|
551
|
-
elif field_type in [FieldTypeProto.INTEGER, FieldTypeProto.SHORT, FieldTypeProto.LONG, FieldTypeProto.ENUM]:
|
|
552
|
-
value = value.int_value
|
|
553
|
-
else:
|
|
554
|
-
value = value.string_value
|
|
555
|
-
config_fields[field] = value
|
|
556
|
-
return config_fields
|