sapiopycommons 2025.9.8a731__py3-none-any.whl → 2025.9.9a738__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/server.py +12 -2
- sapiopycommons/ai/test_client.py +48 -37
- sapiopycommons/ai/tool_service_base.py +68 -34
- sapiopycommons/files/temp_files.py +2 -0
- {sapiopycommons-2025.9.8a731.dist-info → sapiopycommons-2025.9.9a738.dist-info}/METADATA +1 -1
- {sapiopycommons-2025.9.8a731.dist-info → sapiopycommons-2025.9.9a738.dist-info}/RECORD +8 -8
- {sapiopycommons-2025.9.8a731.dist-info → sapiopycommons-2025.9.9a738.dist-info}/WHEEL +0 -0
- {sapiopycommons-2025.9.8a731.dist-info → sapiopycommons-2025.9.9a738.dist-info}/licenses/LICENSE +0 -0
sapiopycommons/ai/server.py
CHANGED
|
@@ -47,10 +47,10 @@ class SapioGrpcServer:
|
|
|
47
47
|
Initialize the gRPC server with the specified port and message size.
|
|
48
48
|
|
|
49
49
|
:param port: The port to listen on for incoming gRPC requests.
|
|
50
|
-
:param message_mb_size: The maximum size of a message in megabytes.
|
|
50
|
+
:param message_mb_size: The maximum size of a sent or received message in megabytes.
|
|
51
|
+
:param debug_mode: Sets the debug mode for services.
|
|
51
52
|
:param options: Additional gRPC server options to set. This should be a list of tuples where the first item is
|
|
52
53
|
the option name and the second item is the option value.
|
|
53
|
-
:param debug_mode: Sets the debug mode for services.
|
|
54
54
|
"""
|
|
55
55
|
if isinstance(port, str):
|
|
56
56
|
port = int(port)
|
|
@@ -68,6 +68,16 @@ class SapioGrpcServer:
|
|
|
68
68
|
self._script_services = []
|
|
69
69
|
self._tool_services = []
|
|
70
70
|
|
|
71
|
+
def update_message_size(self, message_mb_size: int) -> None:
|
|
72
|
+
"""
|
|
73
|
+
Update the maximum message size for the gRPC server.
|
|
74
|
+
|
|
75
|
+
:param message_mb_size: The new maximum message size in megabytes.
|
|
76
|
+
"""
|
|
77
|
+
for i, (option_name, _) in enumerate(self.options):
|
|
78
|
+
if option_name in ('grpc.max_send_message_length', 'grpc.max_receive_message_length'):
|
|
79
|
+
self.options[i] = (option_name, message_mb_size * 1024 * 1024)
|
|
80
|
+
|
|
71
81
|
def add_converter_service(self, service: ConverterServiceServicer) -> None:
|
|
72
82
|
"""
|
|
73
83
|
Add a converter service to the gRPC server.
|
sapiopycommons/ai/test_client.py
CHANGED
|
@@ -61,45 +61,49 @@ class ToolOutput:
|
|
|
61
61
|
self.new_records = []
|
|
62
62
|
self.logs = []
|
|
63
63
|
|
|
64
|
+
def __bool__(self):
|
|
65
|
+
return self.status == "Success"
|
|
66
|
+
|
|
64
67
|
def __str__(self):
|
|
65
68
|
ret_val: str = f"{self.tool_name} Output:\n"
|
|
66
69
|
ret_val += f"\tStatus: {self.status}\n"
|
|
67
70
|
ret_val += f"\tMessage: {self.message}\n"
|
|
68
71
|
ret_val += "-" * 25 + "\n"
|
|
69
72
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
output:
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
output
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
output:
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
output:
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
73
|
+
if self.status == "Success":
|
|
74
|
+
ret_val += f"Binary Output: {sum(len(x) for x in self.binary_output)} item(s) across {len(self.binary_output)} outputs\n"
|
|
75
|
+
for i, output in enumerate(self.binary_output, start=1):
|
|
76
|
+
output: list[bytes]
|
|
77
|
+
ret_val += f"\tBinary Output {i}:\n"
|
|
78
|
+
for binary in output:
|
|
79
|
+
ret_val += f"\t\t{len(binary)} byte(s): {binary[:50]}...\n"
|
|
80
|
+
|
|
81
|
+
ret_val += f"CSV Output: {sum(len(x) for x in self.csv_output)} item(s) across {len(self.csv_output)} outputs\n"
|
|
82
|
+
for i, output in enumerate(self.csv_output, start=1):
|
|
83
|
+
output: list[dict[str, Any]]
|
|
84
|
+
ret_val += f"\tCSV Output {i}:\n"
|
|
85
|
+
ret_val += f"\t\tHeaders: {', '.join(output[0].keys())}\n"
|
|
86
|
+
for j, csv_row in enumerate(output):
|
|
87
|
+
ret_val += f"\t\t{j}: {', '.join(f'{v}' for k, v in csv_row.items())}\n"
|
|
88
|
+
|
|
89
|
+
ret_val += f"JSON Output: {sum(len(x) for x in self.json_output)} item(s) across {len(self.json_output)} outputs\n"
|
|
90
|
+
for i, output in enumerate(self.json_output, start=1):
|
|
91
|
+
output: list[Any]
|
|
92
|
+
ret_val += f"\tJSON Output {i}:\n"
|
|
93
|
+
for json_obj in output:
|
|
94
|
+
ret_val += f"\t\t"
|
|
95
|
+
ret_val += json.dumps(json_obj, indent=2).replace("\n", "\n\t\t") + "\n"
|
|
96
|
+
|
|
97
|
+
ret_val += f"Text Output: {sum(len(x) for x in self.text_output)} item(s) across {len(self.text_output)} outputs\n"
|
|
98
|
+
for i, output in enumerate(self.text_output, start=1):
|
|
99
|
+
output: list[str]
|
|
100
|
+
ret_val += f"\tText Output {i}:\n"
|
|
101
|
+
for text in output:
|
|
102
|
+
ret_val += f"\t\t{text}\n"
|
|
103
|
+
|
|
104
|
+
ret_val += f"New Records: {len(self.new_records)} item(s)\n"
|
|
105
|
+
for record in self.new_records:
|
|
106
|
+
ret_val += f"{json.dumps(record, indent=2)}\n"
|
|
103
107
|
|
|
104
108
|
ret_val += f"Logs: {len(self.logs)} item(s)\n"
|
|
105
109
|
for log in self.logs:
|
|
@@ -117,16 +121,22 @@ class TestClient:
|
|
|
117
121
|
_request_inputs: list[StepItemContainerPbo]
|
|
118
122
|
_config_fields: dict[str, FieldValuePbo]
|
|
119
123
|
|
|
120
|
-
def __init__(self, grpc_server_url: str, user: SapioUser | None = None,
|
|
124
|
+
def __init__(self, grpc_server_url: str, message_mb_size: int = 1024, user: SapioUser | None = None,
|
|
121
125
|
options: list[tuple[str, Any]] | None = None):
|
|
122
126
|
"""
|
|
123
127
|
:param grpc_server_url: The URL of the gRPC server to connect to.
|
|
128
|
+
:param message_mb_size: The maximum size of a sent or received message in megabytes.
|
|
124
129
|
:param user: Optional SapioUser object to use for the connection. If not provided, a default connection
|
|
125
130
|
will be created with test credentials.
|
|
126
131
|
:param options: Optional list of gRPC channel options.
|
|
127
132
|
"""
|
|
128
133
|
self.grpc_server_url = grpc_server_url
|
|
129
|
-
self.options =
|
|
134
|
+
self.options = [
|
|
135
|
+
('grpc.max_send_message_length', message_mb_size * 1024 * 1024),
|
|
136
|
+
('grpc.max_receive_message_length', message_mb_size * 1024 * 1024)
|
|
137
|
+
]
|
|
138
|
+
if options:
|
|
139
|
+
self.options.extend(options)
|
|
130
140
|
self._create_connection(user)
|
|
131
141
|
self._request_inputs = []
|
|
132
142
|
self._config_fields = {}
|
|
@@ -248,12 +258,13 @@ class TestClient:
|
|
|
248
258
|
stub = ToolServiceStub(channel)
|
|
249
259
|
return stub.GetToolDetails(ToolDetailsRequestPbo(sapio_conn_info=self.connection))
|
|
250
260
|
|
|
251
|
-
def call_tool(self, tool_name: str, is_dry_run: bool = False) -> ToolOutput:
|
|
261
|
+
def call_tool(self, tool_name: str, is_verbose: bool = True, is_dry_run: bool = False) -> ToolOutput:
|
|
252
262
|
"""
|
|
253
263
|
Send the request to the tool service for a particular tool name. This will send all the inputs that have been
|
|
254
264
|
added using the add_X_input functions.
|
|
255
265
|
|
|
256
266
|
:param tool_name: The name of the tool to call on the server.
|
|
267
|
+
:param is_verbose: If True, the tool will log verbosely.
|
|
257
268
|
:param is_dry_run: If True, the tool will not be executed, but the request will be validated.
|
|
258
269
|
:return: A ToolOutput object containing the results of the tool service call.
|
|
259
270
|
"""
|
|
@@ -266,7 +277,7 @@ class TestClient:
|
|
|
266
277
|
tool_name=tool_name,
|
|
267
278
|
config_field_values=self._config_fields,
|
|
268
279
|
dry_run=is_dry_run,
|
|
269
|
-
verbose_logging=
|
|
280
|
+
verbose_logging=is_verbose,
|
|
270
281
|
input=[
|
|
271
282
|
StepInputBatchPbo(is_partial=False, item_container=item)
|
|
272
283
|
for item in self._request_inputs
|
|
@@ -8,7 +8,7 @@ import re
|
|
|
8
8
|
import traceback
|
|
9
9
|
from abc import abstractmethod, ABC
|
|
10
10
|
from logging import Logger
|
|
11
|
-
from typing import Any, Iterable,
|
|
11
|
+
from typing import Any, Iterable, Mapping
|
|
12
12
|
|
|
13
13
|
from grpc import ServicerContext
|
|
14
14
|
from sapiopylib.rest.User import SapioUser, ensure_logger_initialized
|
|
@@ -20,7 +20,7 @@ from sapiopycommons.ai.protoapi.fielddefinitions.velox_field_def_pb2 import Velo
|
|
|
20
20
|
from sapiopycommons.ai.protoapi.plan.item.item_container_pb2 import ContentTypePbo
|
|
21
21
|
from sapiopycommons.ai.protoapi.plan.tool.entry_pb2 import StepOutputBatchPbo, StepItemContainerPbo, \
|
|
22
22
|
StepBinaryContainerPbo, StepCsvContainerPbo, StepCsvHeaderRowPbo, StepCsvRowPbo, StepJsonContainerPbo, \
|
|
23
|
-
StepTextContainerPbo
|
|
23
|
+
StepTextContainerPbo
|
|
24
24
|
from sapiopycommons.ai.protoapi.plan.tool.tool_pb2 import ToolDetailsRequestPbo, ToolDetailsResponsePbo, \
|
|
25
25
|
ToolDetailsPbo, ProcessStepRequestPbo, ProcessStepResponsePbo, ToolOutputDetailsPbo, ToolIoConfigBasePbo, \
|
|
26
26
|
ToolInputDetailsPbo, ExampleContainerPbo, ProcessStepResponseStatusPbo
|
|
@@ -151,12 +151,12 @@ class JsonResult(SapioToolResult):
|
|
|
151
151
|
"""
|
|
152
152
|
A class representing JSON results from a Sapio tool.
|
|
153
153
|
"""
|
|
154
|
-
json_data: list[Any]
|
|
154
|
+
json_data: list[dict[str, Any]]
|
|
155
155
|
content_type: str
|
|
156
156
|
file_extensions: list[str]
|
|
157
157
|
name: str
|
|
158
158
|
|
|
159
|
-
def __init__(self, json_data: list[Any], content_type: str = "json", file_extensions: list[str] = None,
|
|
159
|
+
def __init__(self, json_data: list[dict[str, Any]], content_type: str = "json", file_extensions: list[str] = None,
|
|
160
160
|
name: str | None = None):
|
|
161
161
|
"""
|
|
162
162
|
:param json_data: The list of JSON data results. Each entry in the list represents a separate JSON object.
|
|
@@ -331,11 +331,13 @@ class ToolServiceBase(ToolServiceServicer, ABC):
|
|
|
331
331
|
# Setup the tool with details from the request.
|
|
332
332
|
tool.setup(user, request, context, self.debug_mode)
|
|
333
333
|
# Validate that the provided inputs match the tool's expected inputs.
|
|
334
|
+
msg: str = ""
|
|
334
335
|
if len(request.input) != len(tool.input_configs):
|
|
335
|
-
msg
|
|
336
|
-
f"instead.")
|
|
336
|
+
msg = f"Expected {len(tool.input_configs)} inputs for this tool, but got {len(request.input)} instead."
|
|
337
337
|
else:
|
|
338
|
-
|
|
338
|
+
errors: list[str] = tool.validate_input()
|
|
339
|
+
if errors:
|
|
340
|
+
msg = "\n".join(errors)
|
|
339
341
|
# If there is no error message, then the inputs are valid.
|
|
340
342
|
success: bool = not bool(msg)
|
|
341
343
|
# If this is a dry run, then provide the fixed dry run output.
|
|
@@ -368,9 +370,6 @@ class ToolBase(ABC):
|
|
|
368
370
|
"""
|
|
369
371
|
A base class for implementing a tool.
|
|
370
372
|
"""
|
|
371
|
-
_name: str
|
|
372
|
-
_description: str
|
|
373
|
-
_data_type_name: str | None
|
|
374
373
|
input_configs: list[ToolInputDetailsPbo]
|
|
375
374
|
_input_container_types: list[ContainerType]
|
|
376
375
|
output_configs: list[ToolOutputDetailsPbo]
|
|
@@ -380,13 +379,13 @@ class ToolBase(ABC):
|
|
|
380
379
|
logs: list[str]
|
|
381
380
|
logger: Logger
|
|
382
381
|
verbose_logging: bool
|
|
383
|
-
debug_mode: bool
|
|
384
382
|
|
|
385
383
|
temp_data: TempFileHandler
|
|
386
384
|
|
|
387
385
|
user: SapioUser
|
|
388
386
|
request: ProcessStepRequestPbo
|
|
389
387
|
context: ServicerContext
|
|
388
|
+
debug_mode: bool
|
|
390
389
|
|
|
391
390
|
@staticmethod
|
|
392
391
|
@abstractmethod
|
|
@@ -413,9 +412,6 @@ class ToolBase(ABC):
|
|
|
413
412
|
return None
|
|
414
413
|
|
|
415
414
|
def __init__(self):
|
|
416
|
-
self._name = self.name()
|
|
417
|
-
self._description = self.description()
|
|
418
|
-
self._data_type_name = self.data_type_name()
|
|
419
415
|
self.input_configs = []
|
|
420
416
|
self._input_container_types = []
|
|
421
417
|
self.output_configs = []
|
|
@@ -423,7 +419,7 @@ class ToolBase(ABC):
|
|
|
423
419
|
self.config_fields = []
|
|
424
420
|
self.temp_data = TempFileHandler()
|
|
425
421
|
self.logs = []
|
|
426
|
-
self.logger = logging.getLogger(f"ToolBase.{self.
|
|
422
|
+
self.logger = logging.getLogger(f"ToolBase.{self.name()}")
|
|
427
423
|
ensure_logger_initialized(self.logger)
|
|
428
424
|
|
|
429
425
|
def setup(self, user: SapioUser, request: ProcessStepRequestPbo, context: ServicerContext, debug_mode: bool) -> None:
|
|
@@ -460,7 +456,7 @@ class ToolBase(ABC):
|
|
|
460
456
|
:param description: The description of the input.
|
|
461
457
|
:param structure_example: An optional example of the structure of the input, such as how the structure of a
|
|
462
458
|
JSON output may look. This does not need to be an entirely valid example, and should often be truncated for
|
|
463
|
-
brevity.
|
|
459
|
+
brevity. This must be provided for any container type other than BINARY.
|
|
464
460
|
:param validation: An optional validation string for the input.
|
|
465
461
|
:param input_count: A tuple of the minimum and maximum number of inputs allowed for this tool.
|
|
466
462
|
:param is_paged: If true, this input will be paged. If false, this input will not be paged.
|
|
@@ -468,6 +464,8 @@ class ToolBase(ABC):
|
|
|
468
464
|
for this to have an effect.
|
|
469
465
|
:param max_request_bytes: The maximum request size in bytes for this tool.
|
|
470
466
|
"""
|
|
467
|
+
if container_type != ContainerType.BINARY and structure_example is None:
|
|
468
|
+
raise ValueError("structure_example must be provided for inputs with a container_type other than BINARY.")
|
|
471
469
|
structure: ExampleContainerPbo | None = None
|
|
472
470
|
if isinstance(structure_example, str):
|
|
473
471
|
structure = ExampleContainerPbo(text_example=structure_example)
|
|
@@ -509,14 +507,18 @@ class ToolBase(ABC):
|
|
|
509
507
|
or bytes, such as for representing binary outputs like images or files.
|
|
510
508
|
:param structure_example: An optional example of the structure of the input, such as how the structure of a
|
|
511
509
|
JSON output may look. This does not need to be an entirely valid example, and should often be truncated for
|
|
512
|
-
brevity.
|
|
510
|
+
brevity. This must be provided for any container type other than BINARY.
|
|
513
511
|
"""
|
|
512
|
+
if not testing_example:
|
|
513
|
+
raise ValueError("A testing_example must be provided for the output.")
|
|
514
514
|
testing: ExampleContainerPbo | None = None
|
|
515
515
|
if isinstance(testing_example, str):
|
|
516
516
|
testing = ExampleContainerPbo(text_example=testing_example)
|
|
517
517
|
elif isinstance(testing_example, bytes):
|
|
518
518
|
testing = ExampleContainerPbo(binary_example=testing_example)
|
|
519
519
|
|
|
520
|
+
if container_type != ContainerType.BINARY and structure_example is None:
|
|
521
|
+
raise ValueError("structure_example must be provided for inputs with a container_type other than BINARY.")
|
|
520
522
|
structure: ExampleContainerPbo | None = None
|
|
521
523
|
if isinstance(structure_example, str):
|
|
522
524
|
structure = ExampleContainerPbo(text_example=structure_example)
|
|
@@ -727,16 +729,16 @@ class ToolBase(ABC):
|
|
|
727
729
|
:return: The ToolDetailsPbo proto object representing this tool.
|
|
728
730
|
"""
|
|
729
731
|
return ToolDetailsPbo(
|
|
730
|
-
name=self.
|
|
731
|
-
description=self.
|
|
732
|
+
name=self.name(),
|
|
733
|
+
description=self.description(),
|
|
732
734
|
input_configs=self.input_configs,
|
|
733
735
|
output_configs=self.output_configs,
|
|
734
|
-
output_data_type_name=self.
|
|
736
|
+
output_data_type_name=self.data_type_name(),
|
|
735
737
|
config_fields=self.config_fields
|
|
736
738
|
)
|
|
737
739
|
|
|
738
740
|
@abstractmethod
|
|
739
|
-
def validate_input(self) -> str | None:
|
|
741
|
+
def validate_input(self) -> list[str] | None:
|
|
740
742
|
"""
|
|
741
743
|
Validate the request given to this tool. If the request is validly formatted, this method should return None.
|
|
742
744
|
If the request is not valid, this method should return an error message indicating what is wrong with the
|
|
@@ -749,8 +751,8 @@ class ToolBase(ABC):
|
|
|
749
751
|
The request settings can be accessed using the self.get_config_fields() method.
|
|
750
752
|
The request itself can be accessed using self.request.
|
|
751
753
|
|
|
752
|
-
:return: A
|
|
753
|
-
|
|
754
|
+
:return: A list of the error messages if the request is not valid. If the request is valid, return an empty
|
|
755
|
+
list or None.
|
|
754
756
|
"""
|
|
755
757
|
pass
|
|
756
758
|
|
|
@@ -788,7 +790,10 @@ class ToolBase(ABC):
|
|
|
788
790
|
import pandas as pd
|
|
789
791
|
with io.StringIO(example) as stream:
|
|
790
792
|
example: str = pd.read_json(path_or_buf=stream, lines=True).to_json()
|
|
791
|
-
|
|
793
|
+
data = json.loads(example)
|
|
794
|
+
if not isinstance(data, list):
|
|
795
|
+
data = [data]
|
|
796
|
+
results.append(JsonResult(json_data=data, content_type=content_type))
|
|
792
797
|
case ContainerType.TEXT:
|
|
793
798
|
example: str = example.text_example
|
|
794
799
|
results.append(TextResult(text_data=[example], content_type=content_type))
|
|
@@ -821,7 +826,7 @@ class ToolBase(ABC):
|
|
|
821
826
|
if not message:
|
|
822
827
|
return
|
|
823
828
|
if self.verbose_logging:
|
|
824
|
-
self.logs.append(f"INFO: {self.
|
|
829
|
+
self.logs.append(f"INFO: {self.name()}: {message}")
|
|
825
830
|
self.logger.info(message)
|
|
826
831
|
|
|
827
832
|
def log_warning(self, message: str) -> None:
|
|
@@ -833,7 +838,7 @@ class ToolBase(ABC):
|
|
|
833
838
|
"""
|
|
834
839
|
if not message:
|
|
835
840
|
return
|
|
836
|
-
self.logs.append(f"WARNING: {self.
|
|
841
|
+
self.logs.append(f"WARNING: {self.name()}: {message}")
|
|
837
842
|
self.logger.warning(message)
|
|
838
843
|
|
|
839
844
|
def log_error(self, message: str) -> None:
|
|
@@ -845,7 +850,7 @@ class ToolBase(ABC):
|
|
|
845
850
|
"""
|
|
846
851
|
if not message:
|
|
847
852
|
return
|
|
848
|
-
self.logs.append(f"ERROR: {self.
|
|
853
|
+
self.logs.append(f"ERROR: {self.name()}: {message}")
|
|
849
854
|
self.logger.error(message)
|
|
850
855
|
|
|
851
856
|
def log_exception(self, message: str, e: Exception) -> None:
|
|
@@ -858,9 +863,18 @@ class ToolBase(ABC):
|
|
|
858
863
|
"""
|
|
859
864
|
if not message and not e:
|
|
860
865
|
return
|
|
861
|
-
self.logs.append(f"EXCEPTION: {self.
|
|
866
|
+
self.logs.append(f"EXCEPTION: {self.name()}: {message} - {e}")
|
|
862
867
|
self.logger.error(f"{message}\n{traceback.format_exc()}")
|
|
863
868
|
|
|
869
|
+
def is_input_partial(self, index: int = 0) -> bool:
|
|
870
|
+
"""
|
|
871
|
+
Check if the input at the given index is marked as partial.
|
|
872
|
+
|
|
873
|
+
:param index: The index of the input to check. Defaults to 0. Used for tools that accept multiple inputs.
|
|
874
|
+
:return: True if the input is marked as partial, False otherwise.
|
|
875
|
+
"""
|
|
876
|
+
return self.request.input[index].is_partial
|
|
877
|
+
|
|
864
878
|
def get_input_name(self, index: int = 0) -> str | None:
|
|
865
879
|
"""
|
|
866
880
|
Get the name of the input from the request object.
|
|
@@ -870,6 +884,15 @@ class ToolBase(ABC):
|
|
|
870
884
|
"""
|
|
871
885
|
return self.request.input[index].item_container.container_name
|
|
872
886
|
|
|
887
|
+
def get_input_content_type(self, index: int = 0) -> ContentTypePbo:
|
|
888
|
+
"""
|
|
889
|
+
Get the content type of the input from the request object.
|
|
890
|
+
|
|
891
|
+
:param index: The index of the input to parse. Defaults to 0. Used for tools that accept multiple inputs.
|
|
892
|
+
:return: The content type of the input from the request object.
|
|
893
|
+
"""
|
|
894
|
+
return self.request.input[index].item_container.content_type
|
|
895
|
+
|
|
873
896
|
def get_input_binary(self, index: int = 0) -> list[bytes]:
|
|
874
897
|
"""
|
|
875
898
|
Get the binary data from the request object.
|
|
@@ -877,7 +900,10 @@ class ToolBase(ABC):
|
|
|
877
900
|
:param index: The index of the input to parse. Defaults to 0. Used for tools that accept multiple inputs.
|
|
878
901
|
:return: The binary data from the request object.
|
|
879
902
|
"""
|
|
880
|
-
|
|
903
|
+
container: StepItemContainerPbo = self.request.input[index].item_container
|
|
904
|
+
if not container.HasField("binary_container"):
|
|
905
|
+
raise Exception(f"Input {index} does not contain a binary container.")
|
|
906
|
+
return list(container.binary_container.items)
|
|
881
907
|
|
|
882
908
|
def get_input_csv(self, index: int = 0) -> tuple[list[str], list[dict[str, str]]]:
|
|
883
909
|
"""
|
|
@@ -888,10 +914,12 @@ class ToolBase(ABC):
|
|
|
888
914
|
the column names, and the data rows are a list of dictionaries where each dictionary represents a row in the
|
|
889
915
|
CSV with the column names as keys and the corresponding values as strings.
|
|
890
916
|
"""
|
|
891
|
-
|
|
917
|
+
container: StepItemContainerPbo = self.request.input[index].item_container
|
|
918
|
+
if not container.HasField("csv_container"):
|
|
919
|
+
raise Exception(f"Input {index} does not contain a CSV container.")
|
|
892
920
|
ret_val: list[dict[str, str]] = []
|
|
893
|
-
headers: Iterable[str] =
|
|
894
|
-
for row in
|
|
921
|
+
headers: Iterable[str] = container.csv_container.header.cells
|
|
922
|
+
for row in container.csv_container.items:
|
|
895
923
|
row_dict: dict[str, str] = {}
|
|
896
924
|
for header, value in zip(headers, row.cells):
|
|
897
925
|
row_dict[header] = value
|
|
@@ -906,7 +934,10 @@ class ToolBase(ABC):
|
|
|
906
934
|
:return: A list of parsed JSON objects. Each entry in the list represents a separate JSON entry from the input.
|
|
907
935
|
Depending on this tool, this may be a list of dictionaries or a list of lists.
|
|
908
936
|
"""
|
|
909
|
-
|
|
937
|
+
container: StepItemContainerPbo = self.request.input[index].item_container
|
|
938
|
+
if not container.HasField("json_container"):
|
|
939
|
+
raise Exception(f"Input {index} does not contain a JSON container.")
|
|
940
|
+
return [json.loads(x) for x in container.json_container.items]
|
|
910
941
|
|
|
911
942
|
def get_input_text(self, index: int = 0) -> list[str]:
|
|
912
943
|
"""
|
|
@@ -915,7 +946,10 @@ class ToolBase(ABC):
|
|
|
915
946
|
:param index: The index of the input to parse. Defaults to 0. Used for tools that accept multiple inputs.
|
|
916
947
|
:return: A list of text data as strings.
|
|
917
948
|
"""
|
|
918
|
-
|
|
949
|
+
container: StepItemContainerPbo = self.request.input[index].item_container
|
|
950
|
+
if not container.HasField("text_container"):
|
|
951
|
+
raise Exception(f"Input {index} does not contain a text container.")
|
|
952
|
+
return list(container.text_container.items)
|
|
919
953
|
|
|
920
954
|
def get_config_defs(self) -> dict[str, VeloxFieldDefPbo]:
|
|
921
955
|
"""
|
|
@@ -43,7 +43,9 @@ class TempFileHandler:
|
|
|
43
43
|
for directory in self.directories:
|
|
44
44
|
if os.path.exists(directory):
|
|
45
45
|
shutil.rmtree(directory)
|
|
46
|
+
self.directories.clear()
|
|
46
47
|
|
|
47
48
|
for file_path in self.files:
|
|
48
49
|
if os.path.exists(file_path):
|
|
49
50
|
os.remove(file_path)
|
|
51
|
+
self.files.clear()
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: sapiopycommons
|
|
3
|
-
Version: 2025.9.
|
|
3
|
+
Version: 2025.9.9a738
|
|
4
4
|
Summary: Official Sapio Python API Utilities Package
|
|
5
5
|
Project-URL: Homepage, https://github.com/sapiosciences
|
|
6
6
|
Author-email: Jonathan Steck <jsteck@sapiosciences.com>, Yechen Qiao <yqiao@sapiosciences.com>
|
|
@@ -2,9 +2,9 @@ sapiopycommons/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
|
2
2
|
sapiopycommons/ai/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
3
3
|
sapiopycommons/ai/converter_service_base.py,sha256=TMSyEekbbqMk9dRuAtLlSJ1sA1H8KpyCDlSOeqGFMWI,5115
|
|
4
4
|
sapiopycommons/ai/protobuf_utils.py,sha256=cBjbxoFAwU02kNUxEce95WnMU2CMuDD-qFaeWgvQJMQ,24599
|
|
5
|
-
sapiopycommons/ai/server.py,sha256=
|
|
6
|
-
sapiopycommons/ai/test_client.py,sha256
|
|
7
|
-
sapiopycommons/ai/tool_service_base.py,sha256=
|
|
5
|
+
sapiopycommons/ai/server.py,sha256=XDm_mj1yWHw-xQRFsFRHnsGw2JU0wsW2mR22P8PB09A,5744
|
|
6
|
+
sapiopycommons/ai/test_client.py,sha256=-kMXXU_f5FAB7n4UX66NT8I8G52M0eZjn-hpESN_io8,16330
|
|
7
|
+
sapiopycommons/ai/tool_service_base.py,sha256=G4gvZPG9lhlfOZNIpY306JDCyfP_IlOIdCpkN37ekGM,48621
|
|
8
8
|
sapiopycommons/ai/protoapi/fielddefinitions/fields_pb2.py,sha256=8tKXwLXcqFGdQHHSEBSi6Fd7dcaCFoOqmhjzqhenb_M,2372
|
|
9
9
|
sapiopycommons/ai/protoapi/fielddefinitions/fields_pb2.pyi,sha256=FwtXmNAf7iYGEFm4kbqb04v77jNHbZg18ZmEDhle_bU,1444
|
|
10
10
|
sapiopycommons/ai/protoapi/fielddefinitions/fields_pb2_grpc.py,sha256=uO25bcnfGqXpP4ggUur54Nr73Wj-DGWftExzLNcxdHI,931
|
|
@@ -66,7 +66,7 @@ sapiopycommons/files/file_data_handler.py,sha256=f96MlkMuQhUCi4oLnzJK5AiuElCp5jL
|
|
|
66
66
|
sapiopycommons/files/file_util.py,sha256=djouyGjsYgWzjz2OBRnSeMDgj6NrsJUm1a2J93J8Wco,31915
|
|
67
67
|
sapiopycommons/files/file_validator.py,sha256=ryg22-93csmRO_Pv0ZpWphNkB74xWZnHyJ23K56qLj0,28761
|
|
68
68
|
sapiopycommons/files/file_writer.py,sha256=hACVl0duCjP28gJ1NPljkjagNCLod0ygUlPbvUmRDNM,17605
|
|
69
|
-
sapiopycommons/files/temp_files.py,sha256=
|
|
69
|
+
sapiopycommons/files/temp_files.py,sha256=sw9Uw1ebhKzKcjE0VV7EcIA0UlySypGf90LlnJxYDiY,1602
|
|
70
70
|
sapiopycommons/flowcyto/flow_cyto.py,sha256=vs9WhXXKz3urpjL8QKSk56B-NSmQR3O3x_WFBKoeO10,3227
|
|
71
71
|
sapiopycommons/flowcyto/flowcyto_data.py,sha256=mYKFuLbtpJ-EsQxLGtu4tNHVlygTxKixgJxJqD68F58,2596
|
|
72
72
|
sapiopycommons/general/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
@@ -99,7 +99,7 @@ sapiopycommons/webhook/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3
|
|
|
99
99
|
sapiopycommons/webhook/webhook_context.py,sha256=D793uLsb1691SalaPnBUk3rOSxn_hYLhdvkaIxjNXss,1909
|
|
100
100
|
sapiopycommons/webhook/webhook_handlers.py,sha256=7o_wXOruhT9auNh8OfhJAh4WhhiPKij67FMBSpGPICc,39939
|
|
101
101
|
sapiopycommons/webhook/webservice_handlers.py,sha256=cvW6Mk_110BzYqkbk63Kg7jWrltBCDALOlkJRu8h4VQ,14300
|
|
102
|
-
sapiopycommons-2025.9.
|
|
103
|
-
sapiopycommons-2025.9.
|
|
104
|
-
sapiopycommons-2025.9.
|
|
105
|
-
sapiopycommons-2025.9.
|
|
102
|
+
sapiopycommons-2025.9.9a738.dist-info/METADATA,sha256=w4xrNnptiu4pINKxhPQylg8Kt_ybYEZ_sIXp48xYPDQ,3142
|
|
103
|
+
sapiopycommons-2025.9.9a738.dist-info/WHEEL,sha256=C2FUgwZgiLbznR-k0b_5k3Ai_1aASOXDss3lzCUsUug,87
|
|
104
|
+
sapiopycommons-2025.9.9a738.dist-info/licenses/LICENSE,sha256=HyVuytGSiAUQ6ErWBHTqt1iSGHhLmlC8fO7jTCuR8dU,16725
|
|
105
|
+
sapiopycommons-2025.9.9a738.dist-info/RECORD,,
|
|
File without changes
|
{sapiopycommons-2025.9.8a731.dist-info → sapiopycommons-2025.9.9a738.dist-info}/licenses/LICENSE
RENAMED
|
File without changes
|