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.

Files changed (26) hide show
  1. sapiopycommons/ai/api/fielddefinitions/proto/fields_pb2.py +43 -0
  2. sapiopycommons/ai/api/fielddefinitions/proto/fields_pb2.pyi +31 -0
  3. sapiopycommons/ai/api/fielddefinitions/proto/fields_pb2_grpc.py +24 -0
  4. sapiopycommons/ai/api/fielddefinitions/proto/velox_field_def_pb2.py +89 -89
  5. sapiopycommons/ai/api/fielddefinitions/proto/velox_field_def_pb2.pyi +174 -174
  6. sapiopycommons/ai/api/plan/proto/step_output_pb2.py +11 -11
  7. sapiopycommons/ai/api/plan/proto/step_output_pb2.pyi +27 -27
  8. sapiopycommons/ai/api/plan/proto/step_pb2.py +9 -9
  9. sapiopycommons/ai/api/plan/proto/step_pb2.pyi +10 -10
  10. sapiopycommons/ai/api/plan/script/proto/script_pb2.py +13 -13
  11. sapiopycommons/ai/api/plan/script/proto/script_pb2.pyi +39 -39
  12. sapiopycommons/ai/api/plan/script/proto/script_pb2_grpc.py +12 -12
  13. sapiopycommons/ai/api/plan/tool/proto/entry_pb2.py +23 -23
  14. sapiopycommons/ai/api/plan/tool/proto/entry_pb2.pyi +63 -63
  15. sapiopycommons/ai/api/plan/tool/proto/tool_pb2.py +25 -31
  16. sapiopycommons/ai/api/plan/tool/proto/tool_pb2.pyi +136 -158
  17. sapiopycommons/ai/api/plan/tool/proto/tool_pb2_grpc.py +12 -12
  18. sapiopycommons/ai/api/session/proto/sapio_conn_info_pb2.py +5 -5
  19. sapiopycommons/ai/api/session/proto/sapio_conn_info_pb2.pyi +8 -8
  20. sapiopycommons/ai/protobuf_utils.py +452 -0
  21. sapiopycommons/ai/tool_service_base.py +288 -162
  22. {sapiopycommons-2025.4.25a497.dist-info → sapiopycommons-2025.4.30a499.dist-info}/METADATA +1 -1
  23. {sapiopycommons-2025.4.25a497.dist-info → sapiopycommons-2025.4.30a499.dist-info}/RECORD +25 -22
  24. sapiopycommons/ai/tool_of_tools.py +0 -917
  25. {sapiopycommons-2025.4.25a497.dist-info → sapiopycommons-2025.4.30a499.dist-info}/WHEEL +0 -0
  26. {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
- from sapiopycommons.ai.api.plan.tool.proto.entry_pb2 import StepEntryOutputData, StepEntryData, StepJsonData, DataType, \
16
- StepImageData, StepTextData, StepCsvData, StepBinaryData, StepCsvHeaderRow, StepCsvRow
17
- from sapiopycommons.ai.api.plan.tool.proto.tool_pb2 import ProcessStepRequest, ToolDetailsRequest, ToolDetailsResponse, \
18
- ProcessStepResponse, ToolDetails, StepRecord, StepRecordFieldValue, ToolInputDetails, ToolOutputDetails
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 SapioConnectionInfo, SapioUserSecretType
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) -> StepEntryOutputData | list[StepRecord]:
33
+ def to_proto(self) -> StepOutputBatchPbo | list[FieldValueMapPbo]:
30
34
  """
31
- Convert this SapioToolResult object to a StepEntryOutputData or list of StepRecord proto objects.
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. Each entry in the list represents a separate binary
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) -> StepEntryOutputData | list[StepRecord]:
50
- return StepEntryOutputData(
51
- entry_data=StepEntryData(
52
- dataType=DataType.BINARY,
53
- binary_data=StepBinaryData(entries=self.binary_data)
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) -> StepEntryOutputData | list[StepRecord]:
72
- return StepEntryOutputData(
73
- entry_data=StepEntryData(
74
- dataType=DataType.CSV,
75
- csv_data=StepCsvData(
76
- header=StepCsvHeaderRow(cells=self.csv_data[0].keys()),
77
- entries=[StepCsvRow(cells=[str(x) for x in row.values()]) for row in self.csv_data]
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) -> StepEntryOutputData | list[StepRecord]:
97
- new_records: list[StepRecord] = []
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, StepRecordFieldValue] = {}
102
+ fields: dict[str, FieldValuePbo] = {}
100
103
  for field, value in field_map.items():
101
- field_value = StepRecordFieldValue()
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(StepRecord(fields=fields))
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) -> StepEntryOutputData | list[StepRecord]:
131
- return StepEntryOutputData(
132
- entry_data=StepEntryData(
133
- dataType=DataType.IMAGE,
134
- image_data=StepImageData(
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
- entries=self.image_data)
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(). A common JSON data type is a
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) -> StepEntryOutputData | list[StepRecord]:
156
- return StepEntryOutputData(
157
- entry_data=StepEntryData(
158
- dataType=DataType.JSON,
159
- json_data=StepJsonData(entries=[json.dumps(x) for x in self.json_data])
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. Each entry in the list represents a separate text entry.
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) -> StepEntryOutputData | list[StepRecord]:
177
- return StepEntryOutputData(
178
- entry_data=StepEntryData(
179
- dataType=DataType.TEXT,
180
- text_data=StepTextData(entries=self.text_data)
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 GetToolDetails(self, request: ToolDetailsRequest, context: ServicerContext) -> ToolDetailsResponse:
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[ToolDetails] = self.get_details()
194
- return ToolDetailsResponse(tool_framework_version=self.tool_version(), tool_details=details)
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 ToolDetailsResponse()
199
+ return ToolDetailsResponsePbo()
198
200
 
199
- def ProcessData(self, request: ProcessStepRequest, context: ServicerContext) -> ProcessStepResponse:
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
- entry_data: list[StepEntryOutputData] = []
205
- new_records: list[StepRecord] = []
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: StepEntryOutputData | list[StepRecord] = result.to_proto()
210
- if isinstance(data, StepEntryOutputData):
211
- entry_data.append(data)
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 ProcessStepResponse(entry_data=entry_data, log=logs, new_records=new_records)
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 ProcessStepResponse(log=[traceback.format_exc()])
220
+ return ProcessStepResponsePbo(log=[traceback.format_exc()])
219
221
 
220
222
  @staticmethod
221
- def create_user(info: SapioConnectionInfo, timeout_seconds: int = 60) -> SapioUser:
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 == SapioUserSecretType.SESSION_TOKEN:
233
+ if info.secret_type == SapioUserSecretTypePbo.SESSION_TOKEN:
232
234
  user.api_token = info.secret
233
- elif info.secret_type == SapioUserSecretType.PASSWORD:
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[ToolDetails]:
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[ToolDetails] = []
287
+ tool_details: list[ToolDetailsPbo] = []
286
288
  for tool in self._get_tools():
287
- tool_details.append(tool.to_proto())
289
+ tool_details.append(tool.to_pbo())
288
290
  return tool_details
289
291
 
290
- def run(self, user: SapioUser, request: ProcessStepRequest, context: ServicerContext) \
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
- results = tool.run(user, request, context)
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[ToolInputDetails]
318
- outputs: list[ToolOutputDetails]
319
- configs: list[VeloxFieldDefProto]
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 add_input(self, details: ToolInputDetails) -> None:
345
+ def setup(self, user: SapioUser, request: ProcessStepRequestPbo, context: ServicerContext) -> None:
338
346
  """
339
- Add an input configuration to the tool. This determines how many inputs this tool will accept in the plan
340
- manager, as well as what those inputs are.
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
- :param details: The input configuration details.
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
- self.inputs.append(details)
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, details: ToolOutputDetails) -> None:
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 outputs this tool will return in the plan
349
- manager, as well as what those outputs are.
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 details: The output configuration details.
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(details)
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: VeloxFieldDefProto) -> None:
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 add_integer_config_field(self, field_name: str, display_name: str, description: str,
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 an integer configuration field to the tool. This field will be used to configure the tool in the plan
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(VeloxFieldDefProto(
377
- data_field_type=FieldTypeProto.INTEGER,
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
- integer_properties=IntegerProperties(
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(VeloxFieldDefProto(
405
- data_field_type=FieldTypeProto.DOUBLE,
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=DoubleProperties(
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 add_list_config_field(self, field_name: str, display_name: str, description: str, default_value: str,
420
- allowed_values: list[str]) -> None:
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 a list configuration field to the tool. This field will be used to configure the tool in the plan
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 allowed_values: The list of allowed values for the field.
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(VeloxFieldDefProto(
432
- data_field_type=FieldTypeProto.SELECTION,
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
- selection_properties=SelectionProperties(
507
+ integer_properties=IntegerPropertiesPbo(
439
508
  default_value=default_value,
440
- static_list_values=allowed_values,
509
+ min_value=min_value,
510
+ max_value=max_value
441
511
  )
442
512
  ))
443
513
 
444
- def add_boolean_config_field(self, field_name: str, display_name: str, description: str, default_value: bool) \
445
- -> None:
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 boolean configuration field to the tool. This field will be used to configure the tool in the plan
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(VeloxFieldDefProto(
456
- data_field_type=FieldTypeProto.BOOLEAN,
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
- boolean_properties=BooleanProperties(
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 to_proto(self) -> ToolDetails:
539
+ def to_pbo(self) -> ToolDetailsPbo:
468
540
  """
469
- :return: The ToolDetails proto object representing this tool.
541
+ :return: The ToolDetailsPbo proto object representing this tool.
470
542
  """
471
- return ToolDetails(
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
- def log_message(self, message: str) -> None:
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.logs.append(f"{self.name}: {message}")
577
+ if not verbose or self.verbose_logging:
578
+ self.logs.append(f"{self.name}: {message}")
487
579
 
488
- @abstractmethod
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
- Execute this tool.
582
+ Get the binary data from the request object.
492
583
 
493
- :param user: A user object that can be used to initialize manager classes using DataMgmtServer to query the
494
- system.
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
- pass
587
+ return list(self.request.input[index].item_container.binary_container.items)
500
588
 
501
- @staticmethod
502
- def get_request_json(request: ProcessStepRequest, index: int = 0) -> list[Any]:
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 request: The request object containing the input data.
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
- entry_data = request.entry_data
511
- input_data: list[Any] = [json.loads(x) for x in entry_data[index].entry_data.json_data.entries]
512
- return input_data
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