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.

@@ -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.
@@ -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
- ret_val += f"Binary Output: {sum(len(x) for x in self.binary_output)} item(s) across {len(self.binary_output)} outputs\n"
71
- for i, output in enumerate(self.binary_output, start=1):
72
- output: list[bytes]
73
- ret_val += f"\tBinary Output {i}:\n"
74
- for binary in output:
75
- ret_val += f"\t\t{len(binary)} byte(s): {binary[:50]}...\n"
76
-
77
- ret_val += f"CSV Output: {sum(len(x) for x in self.csv_output)} item(s) across {len(self.csv_output)} outputs\n"
78
- for i, output in enumerate(self.csv_output, start=1):
79
- output: list[dict[str, Any]]
80
- ret_val += f"\tCSV Output {i}:\n"
81
- ret_val += f"\t\tHeaders: {', '.join(output[0].keys())}\n"
82
- for j, csv_row in enumerate(output):
83
- ret_val += f"\t\t{j}: {', '.join(f'{v}' for k, v in csv_row.items())}\n"
84
-
85
- ret_val += f"JSON Output: {sum(len(x) for x in self.json_output)} item(s) across {len(self.json_output)} outputs\n"
86
- for i, output in enumerate(self.json_output, start=1):
87
- output: list[Any]
88
- ret_val += f"\tJSON Output {i}:\n"
89
- for json_obj in output:
90
- ret_val += f"\t\t"
91
- ret_val += json.dumps(json_obj, indent=2).replace("\n", "\n\t\t") + "\n"
92
-
93
- ret_val += f"Text Output: {sum(len(x) for x in self.text_output)} item(s) across {len(self.text_output)} outputs\n"
94
- for i, output in enumerate(self.text_output, start=1):
95
- output: list[str]
96
- ret_val += f"\tText Output {i}:\n"
97
- for text in output:
98
- ret_val += f"\t\t{text}\n"
99
-
100
- ret_val += f"New Records: {len(self.new_records)} item(s)\n"
101
- for record in self.new_records:
102
- ret_val += f"{json.dumps(record, indent=2)}\n"
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 = 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=True,
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, Sequence, Mapping
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, StepInputBatchPbo
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: str = (f"Expected {len(tool.input_configs)} inputs for this tool, but got {len(request.input)} "
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
- msg: str = tool.validate_input()
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._name}")
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._name,
731
- description=self._description,
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._data_type_name,
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 tuple containing a boolean indicating whether the request is valid and a message describing the
753
- result of the validation.
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
- results.append(JsonResult(json_data=[json.loads(example)], content_type=content_type))
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._name}: {message}")
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._name}: {message}")
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._name}: {message}")
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._name}: {message} - {e}")
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
- return list(self.request.input[index].item_container.binary_container.items)
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
- input_data: Sequence[StepInputBatchPbo] = self.request.input
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] = input_data[index].item_container.csv_container.header.cells
894
- for row in input_data[index].item_container.csv_container.items:
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
- return [json.loads(x) for x in self.request.input[index].item_container.json_container.items]
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
- return list(self.request.input[index].item_container.text_container.items)
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.8a731
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=jy81xjtt7uCY3sHG9iL_-0dy8st5J4fWSZ5Pwq3L1_s,5262
6
- sapiopycommons/ai/test_client.py,sha256=iPhn7cvKNLmDAXrjpmIkZpW2pDWlUhJZHDLHJbEoWsg,15673
7
- sapiopycommons/ai/tool_service_base.py,sha256=s6NJaOLUN3Ewkn1yoByKqHn7jn40yopD0ohySnKaOB0,46526
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=Puv59qtGwiXVJnTm4YuyeZPKw_leXDW906Uz_xbIt6A,1542
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.8a731.dist-info/METADATA,sha256=pe2sML9LAOWEXV-Hd8RtSS9VgCoorYA-aVG7_a9_f1Y,3142
103
- sapiopycommons-2025.9.8a731.dist-info/WHEEL,sha256=C2FUgwZgiLbznR-k0b_5k3Ai_1aASOXDss3lzCUsUug,87
104
- sapiopycommons-2025.9.8a731.dist-info/licenses/LICENSE,sha256=HyVuytGSiAUQ6ErWBHTqt1iSGHhLmlC8fO7jTCuR8dU,16725
105
- sapiopycommons-2025.9.8a731.dist-info/RECORD,,
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,,