sapiopycommons 2025.6.30a570__tar.gz → 2025.6.30a571__tar.gz

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 (111) hide show
  1. {sapiopycommons-2025.6.30a570 → sapiopycommons-2025.6.30a571}/PKG-INFO +1 -1
  2. {sapiopycommons-2025.6.30a570 → sapiopycommons-2025.6.30a571}/pyproject.toml +1 -1
  3. sapiopycommons-2025.6.30a571/src/sapiopycommons/ai/test_client.py +197 -0
  4. {sapiopycommons-2025.6.30a570 → sapiopycommons-2025.6.30a571}/src/sapiopycommons/ai/tool_service_base.py +6 -3
  5. {sapiopycommons-2025.6.30a570 → sapiopycommons-2025.6.30a571}/.gitignore +0 -0
  6. {sapiopycommons-2025.6.30a570 → sapiopycommons-2025.6.30a571}/LICENSE +0 -0
  7. {sapiopycommons-2025.6.30a570 → sapiopycommons-2025.6.30a571}/README.md +0 -0
  8. {sapiopycommons-2025.6.30a570 → sapiopycommons-2025.6.30a571}/src/sapiopycommons/__init__.py +0 -0
  9. {sapiopycommons-2025.6.30a570 → sapiopycommons-2025.6.30a571}/src/sapiopycommons/ai/__init__.py +0 -0
  10. {sapiopycommons-2025.6.30a570 → sapiopycommons-2025.6.30a571}/src/sapiopycommons/ai/api/fielddefinitions/proto/fields_pb2.py +0 -0
  11. {sapiopycommons-2025.6.30a570 → sapiopycommons-2025.6.30a571}/src/sapiopycommons/ai/api/fielddefinitions/proto/fields_pb2.pyi +0 -0
  12. {sapiopycommons-2025.6.30a570 → sapiopycommons-2025.6.30a571}/src/sapiopycommons/ai/api/fielddefinitions/proto/fields_pb2_grpc.py +0 -0
  13. {sapiopycommons-2025.6.30a570 → sapiopycommons-2025.6.30a571}/src/sapiopycommons/ai/api/fielddefinitions/proto/velox_field_def_pb2.py +0 -0
  14. {sapiopycommons-2025.6.30a570 → sapiopycommons-2025.6.30a571}/src/sapiopycommons/ai/api/fielddefinitions/proto/velox_field_def_pb2.pyi +0 -0
  15. {sapiopycommons-2025.6.30a570 → sapiopycommons-2025.6.30a571}/src/sapiopycommons/ai/api/fielddefinitions/proto/velox_field_def_pb2_grpc.py +0 -0
  16. {sapiopycommons-2025.6.30a570 → sapiopycommons-2025.6.30a571}/src/sapiopycommons/ai/api/plan/proto/step_output_pb2.py +0 -0
  17. {sapiopycommons-2025.6.30a570 → sapiopycommons-2025.6.30a571}/src/sapiopycommons/ai/api/plan/proto/step_output_pb2.pyi +0 -0
  18. {sapiopycommons-2025.6.30a570 → sapiopycommons-2025.6.30a571}/src/sapiopycommons/ai/api/plan/proto/step_output_pb2_grpc.py +0 -0
  19. {sapiopycommons-2025.6.30a570 → sapiopycommons-2025.6.30a571}/src/sapiopycommons/ai/api/plan/proto/step_pb2.py +0 -0
  20. {sapiopycommons-2025.6.30a570 → sapiopycommons-2025.6.30a571}/src/sapiopycommons/ai/api/plan/proto/step_pb2.pyi +0 -0
  21. {sapiopycommons-2025.6.30a570 → sapiopycommons-2025.6.30a571}/src/sapiopycommons/ai/api/plan/proto/step_pb2_grpc.py +0 -0
  22. {sapiopycommons-2025.6.30a570 → sapiopycommons-2025.6.30a571}/src/sapiopycommons/ai/api/plan/script/proto/script_pb2.py +0 -0
  23. {sapiopycommons-2025.6.30a570 → sapiopycommons-2025.6.30a571}/src/sapiopycommons/ai/api/plan/script/proto/script_pb2.pyi +0 -0
  24. {sapiopycommons-2025.6.30a570 → sapiopycommons-2025.6.30a571}/src/sapiopycommons/ai/api/plan/script/proto/script_pb2_grpc.py +0 -0
  25. {sapiopycommons-2025.6.30a570 → sapiopycommons-2025.6.30a571}/src/sapiopycommons/ai/api/plan/tool/proto/entry_pb2.py +0 -0
  26. {sapiopycommons-2025.6.30a570 → sapiopycommons-2025.6.30a571}/src/sapiopycommons/ai/api/plan/tool/proto/entry_pb2.pyi +0 -0
  27. {sapiopycommons-2025.6.30a570 → sapiopycommons-2025.6.30a571}/src/sapiopycommons/ai/api/plan/tool/proto/entry_pb2_grpc.py +0 -0
  28. {sapiopycommons-2025.6.30a570 → sapiopycommons-2025.6.30a571}/src/sapiopycommons/ai/api/plan/tool/proto/tool_pb2.py +0 -0
  29. {sapiopycommons-2025.6.30a570 → sapiopycommons-2025.6.30a571}/src/sapiopycommons/ai/api/plan/tool/proto/tool_pb2.pyi +0 -0
  30. {sapiopycommons-2025.6.30a570 → sapiopycommons-2025.6.30a571}/src/sapiopycommons/ai/api/plan/tool/proto/tool_pb2_grpc.py +0 -0
  31. {sapiopycommons-2025.6.30a570 → sapiopycommons-2025.6.30a571}/src/sapiopycommons/ai/api/session/proto/sapio_conn_info_pb2.py +0 -0
  32. {sapiopycommons-2025.6.30a570 → sapiopycommons-2025.6.30a571}/src/sapiopycommons/ai/api/session/proto/sapio_conn_info_pb2.pyi +0 -0
  33. {sapiopycommons-2025.6.30a570 → sapiopycommons-2025.6.30a571}/src/sapiopycommons/ai/api/session/proto/sapio_conn_info_pb2_grpc.py +0 -0
  34. {sapiopycommons-2025.6.30a570 → sapiopycommons-2025.6.30a571}/src/sapiopycommons/ai/protobuf_utils.py +0 -0
  35. {sapiopycommons-2025.6.30a570 → sapiopycommons-2025.6.30a571}/src/sapiopycommons/callbacks/__init__.py +0 -0
  36. {sapiopycommons-2025.6.30a570 → sapiopycommons-2025.6.30a571}/src/sapiopycommons/callbacks/callback_util.py +0 -0
  37. {sapiopycommons-2025.6.30a570 → sapiopycommons-2025.6.30a571}/src/sapiopycommons/callbacks/field_builder.py +0 -0
  38. {sapiopycommons-2025.6.30a570 → sapiopycommons-2025.6.30a571}/src/sapiopycommons/chem/IndigoMolecules.py +0 -0
  39. {sapiopycommons-2025.6.30a570 → sapiopycommons-2025.6.30a571}/src/sapiopycommons/chem/Molecules.py +0 -0
  40. {sapiopycommons-2025.6.30a570 → sapiopycommons-2025.6.30a571}/src/sapiopycommons/chem/__init__.py +0 -0
  41. {sapiopycommons-2025.6.30a570 → sapiopycommons-2025.6.30a571}/src/sapiopycommons/customreport/__init__.py +0 -0
  42. {sapiopycommons-2025.6.30a570 → sapiopycommons-2025.6.30a571}/src/sapiopycommons/customreport/auto_pagers.py +0 -0
  43. {sapiopycommons-2025.6.30a570 → sapiopycommons-2025.6.30a571}/src/sapiopycommons/customreport/column_builder.py +0 -0
  44. {sapiopycommons-2025.6.30a570 → sapiopycommons-2025.6.30a571}/src/sapiopycommons/customreport/custom_report_builder.py +0 -0
  45. {sapiopycommons-2025.6.30a570 → sapiopycommons-2025.6.30a571}/src/sapiopycommons/customreport/term_builder.py +0 -0
  46. {sapiopycommons-2025.6.30a570 → sapiopycommons-2025.6.30a571}/src/sapiopycommons/datatype/__init__.py +0 -0
  47. {sapiopycommons-2025.6.30a570 → sapiopycommons-2025.6.30a571}/src/sapiopycommons/datatype/attachment_util.py +0 -0
  48. {sapiopycommons-2025.6.30a570 → sapiopycommons-2025.6.30a571}/src/sapiopycommons/datatype/data_fields.py +0 -0
  49. {sapiopycommons-2025.6.30a570 → sapiopycommons-2025.6.30a571}/src/sapiopycommons/datatype/pseudo_data_types.py +0 -0
  50. {sapiopycommons-2025.6.30a570 → sapiopycommons-2025.6.30a571}/src/sapiopycommons/eln/__init__.py +0 -0
  51. {sapiopycommons-2025.6.30a570 → sapiopycommons-2025.6.30a571}/src/sapiopycommons/eln/experiment_handler.py +0 -0
  52. {sapiopycommons-2025.6.30a570 → sapiopycommons-2025.6.30a571}/src/sapiopycommons/eln/experiment_report_util.py +0 -0
  53. {sapiopycommons-2025.6.30a570 → sapiopycommons-2025.6.30a571}/src/sapiopycommons/eln/experiment_tags.py +0 -0
  54. {sapiopycommons-2025.6.30a570 → sapiopycommons-2025.6.30a571}/src/sapiopycommons/eln/plate_designer.py +0 -0
  55. {sapiopycommons-2025.6.30a570 → sapiopycommons-2025.6.30a571}/src/sapiopycommons/files/__init__.py +0 -0
  56. {sapiopycommons-2025.6.30a570 → sapiopycommons-2025.6.30a571}/src/sapiopycommons/files/complex_data_loader.py +0 -0
  57. {sapiopycommons-2025.6.30a570 → sapiopycommons-2025.6.30a571}/src/sapiopycommons/files/file_bridge.py +0 -0
  58. {sapiopycommons-2025.6.30a570 → sapiopycommons-2025.6.30a571}/src/sapiopycommons/files/file_bridge_handler.py +0 -0
  59. {sapiopycommons-2025.6.30a570 → sapiopycommons-2025.6.30a571}/src/sapiopycommons/files/file_data_handler.py +0 -0
  60. {sapiopycommons-2025.6.30a570 → sapiopycommons-2025.6.30a571}/src/sapiopycommons/files/file_util.py +0 -0
  61. {sapiopycommons-2025.6.30a570 → sapiopycommons-2025.6.30a571}/src/sapiopycommons/files/file_validator.py +0 -0
  62. {sapiopycommons-2025.6.30a570 → sapiopycommons-2025.6.30a571}/src/sapiopycommons/files/file_writer.py +0 -0
  63. {sapiopycommons-2025.6.30a570 → sapiopycommons-2025.6.30a571}/src/sapiopycommons/flowcyto/flow_cyto.py +0 -0
  64. {sapiopycommons-2025.6.30a570 → sapiopycommons-2025.6.30a571}/src/sapiopycommons/flowcyto/flowcyto_data.py +0 -0
  65. {sapiopycommons-2025.6.30a570 → sapiopycommons-2025.6.30a571}/src/sapiopycommons/general/__init__.py +0 -0
  66. {sapiopycommons-2025.6.30a570 → sapiopycommons-2025.6.30a571}/src/sapiopycommons/general/accession_service.py +0 -0
  67. {sapiopycommons-2025.6.30a570 → sapiopycommons-2025.6.30a571}/src/sapiopycommons/general/aliases.py +0 -0
  68. {sapiopycommons-2025.6.30a570 → sapiopycommons-2025.6.30a571}/src/sapiopycommons/general/audit_log.py +0 -0
  69. {sapiopycommons-2025.6.30a570 → sapiopycommons-2025.6.30a571}/src/sapiopycommons/general/custom_report_util.py +0 -0
  70. {sapiopycommons-2025.6.30a570 → sapiopycommons-2025.6.30a571}/src/sapiopycommons/general/directive_util.py +0 -0
  71. {sapiopycommons-2025.6.30a570 → sapiopycommons-2025.6.30a571}/src/sapiopycommons/general/exceptions.py +0 -0
  72. {sapiopycommons-2025.6.30a570 → sapiopycommons-2025.6.30a571}/src/sapiopycommons/general/html_formatter.py +0 -0
  73. {sapiopycommons-2025.6.30a570 → sapiopycommons-2025.6.30a571}/src/sapiopycommons/general/popup_util.py +0 -0
  74. {sapiopycommons-2025.6.30a570 → sapiopycommons-2025.6.30a571}/src/sapiopycommons/general/sapio_links.py +0 -0
  75. {sapiopycommons-2025.6.30a570 → sapiopycommons-2025.6.30a571}/src/sapiopycommons/general/storage_util.py +0 -0
  76. {sapiopycommons-2025.6.30a570 → sapiopycommons-2025.6.30a571}/src/sapiopycommons/general/time_util.py +0 -0
  77. {sapiopycommons-2025.6.30a570 → sapiopycommons-2025.6.30a571}/src/sapiopycommons/multimodal/multimodal.py +0 -0
  78. {sapiopycommons-2025.6.30a570 → sapiopycommons-2025.6.30a571}/src/sapiopycommons/multimodal/multimodal_data.py +0 -0
  79. {sapiopycommons-2025.6.30a570 → sapiopycommons-2025.6.30a571}/src/sapiopycommons/processtracking/__init__.py +0 -0
  80. {sapiopycommons-2025.6.30a570 → sapiopycommons-2025.6.30a571}/src/sapiopycommons/processtracking/custom_workflow_handler.py +0 -0
  81. {sapiopycommons-2025.6.30a570 → sapiopycommons-2025.6.30a571}/src/sapiopycommons/processtracking/endpoints.py +0 -0
  82. {sapiopycommons-2025.6.30a570 → sapiopycommons-2025.6.30a571}/src/sapiopycommons/recordmodel/__init__.py +0 -0
  83. {sapiopycommons-2025.6.30a570 → sapiopycommons-2025.6.30a571}/src/sapiopycommons/recordmodel/record_handler.py +0 -0
  84. {sapiopycommons-2025.6.30a570 → sapiopycommons-2025.6.30a571}/src/sapiopycommons/rules/__init__.py +0 -0
  85. {sapiopycommons-2025.6.30a570 → sapiopycommons-2025.6.30a571}/src/sapiopycommons/rules/eln_rule_handler.py +0 -0
  86. {sapiopycommons-2025.6.30a570 → sapiopycommons-2025.6.30a571}/src/sapiopycommons/rules/on_save_rule_handler.py +0 -0
  87. {sapiopycommons-2025.6.30a570 → sapiopycommons-2025.6.30a571}/src/sapiopycommons/samples/aliquot.py +0 -0
  88. {sapiopycommons-2025.6.30a570 → sapiopycommons-2025.6.30a571}/src/sapiopycommons/sftpconnect/__init__.py +0 -0
  89. {sapiopycommons-2025.6.30a570 → sapiopycommons-2025.6.30a571}/src/sapiopycommons/sftpconnect/sftp_builder.py +0 -0
  90. {sapiopycommons-2025.6.30a570 → sapiopycommons-2025.6.30a571}/src/sapiopycommons/webhook/__init__.py +0 -0
  91. {sapiopycommons-2025.6.30a570 → sapiopycommons-2025.6.30a571}/src/sapiopycommons/webhook/webhook_context.py +0 -0
  92. {sapiopycommons-2025.6.30a570 → sapiopycommons-2025.6.30a571}/src/sapiopycommons/webhook/webhook_handlers.py +0 -0
  93. {sapiopycommons-2025.6.30a570 → sapiopycommons-2025.6.30a571}/src/sapiopycommons/webhook/webservice_handlers.py +0 -0
  94. {sapiopycommons-2025.6.30a570 → sapiopycommons-2025.6.30a571}/tests/AF-A0A009IHW8-F1-model_v4.cif +0 -0
  95. {sapiopycommons-2025.6.30a570 → sapiopycommons-2025.6.30a571}/tests/_do_not_add_init_py_here +0 -0
  96. {sapiopycommons-2025.6.30a570 → sapiopycommons-2025.6.30a571}/tests/accession_test.py +0 -0
  97. {sapiopycommons-2025.6.30a570 → sapiopycommons-2025.6.30a571}/tests/aliquot_test.py +0 -0
  98. {sapiopycommons-2025.6.30a570 → sapiopycommons-2025.6.30a571}/tests/bio_reg_test.py +0 -0
  99. {sapiopycommons-2025.6.30a570 → sapiopycommons-2025.6.30a571}/tests/chem_test.py +0 -0
  100. {sapiopycommons-2025.6.30a570 → sapiopycommons-2025.6.30a571}/tests/chem_test_curation_queue.py +0 -0
  101. {sapiopycommons-2025.6.30a570 → sapiopycommons-2025.6.30a571}/tests/curation_queue_test.sdf +0 -0
  102. {sapiopycommons-2025.6.30a570 → sapiopycommons-2025.6.30a571}/tests/data_type_models.py +0 -0
  103. {sapiopycommons-2025.6.30a570 → sapiopycommons-2025.6.30a571}/tests/flowcyto/101_DEN084Y5_15_E01_008_clean.fcs +0 -0
  104. {sapiopycommons-2025.6.30a570 → sapiopycommons-2025.6.30a571}/tests/flowcyto/101_DEN084Y5_15_E03_009_clean.fcs +0 -0
  105. {sapiopycommons-2025.6.30a570 → sapiopycommons-2025.6.30a571}/tests/flowcyto/101_DEN084Y5_15_E05_010_clean.fcs +0 -0
  106. {sapiopycommons-2025.6.30a570 → sapiopycommons-2025.6.30a571}/tests/flowcyto/8_color_ICS.wsp +0 -0
  107. {sapiopycommons-2025.6.30a570 → sapiopycommons-2025.6.30a571}/tests/flowcyto/COVID19_W_001_O.fcs +0 -0
  108. {sapiopycommons-2025.6.30a570 → sapiopycommons-2025.6.30a571}/tests/flowcyto_test.py +0 -0
  109. {sapiopycommons-2025.6.30a570 → sapiopycommons-2025.6.30a571}/tests/kappa.chains.fasta +0 -0
  110. {sapiopycommons-2025.6.30a570 → sapiopycommons-2025.6.30a571}/tests/mafft_test.py +0 -0
  111. {sapiopycommons-2025.6.30a570 → sapiopycommons-2025.6.30a571}/tests/test.gb +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: sapiopycommons
3
- Version: 2025.6.30a570
3
+ Version: 2025.6.30a571
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>
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "sapiopycommons"
7
- version='2025.06.30a570'
7
+ version='2025.06.30a571'
8
8
  authors = [
9
9
  { name="Jonathan Steck", email="jsteck@sapiosciences.com" },
10
10
  { name="Yechen Qiao", email="yqiao@sapiosciences.com" },
@@ -0,0 +1,197 @@
1
+ import json
2
+ from typing import Mapping, Any
3
+
4
+ import grpc
5
+
6
+ from sapiopycommons.ai.api.fielddefinitions.proto.fields_pb2 import FieldValuePbo
7
+ from sapiopycommons.ai.api.plan.tool.proto.entry_pb2 import DataTypePbo, StepBinaryContainerPbo, StepCsvRowPbo, \
8
+ StepCsvHeaderRowPbo, StepCsvContainerPbo, StepJsonContainerPbo, StepImageContainerPbo, StepTextContainerPbo, \
9
+ StepItemContainerPbo, StepInputBatchPbo
10
+ from sapiopycommons.ai.api.plan.tool.proto.tool_pb2 import ProcessStepResponsePbo, ProcessStepRequestPbo
11
+ from sapiopycommons.ai.api.plan.tool.proto.tool_pb2_grpc import ToolServiceStub
12
+ from sapiopycommons.ai.api.session.proto.sapio_conn_info_pb2 import SapioConnectionInfoPbo, SapioUserSecretTypePbo
13
+
14
+
15
+ class TestOutput:
16
+ """
17
+ A class for holding the output of a TestClient that calls a ToolService. TestOutput objects an be
18
+ printed to show the output of the tool in a human-readable format.
19
+ """
20
+ binary_output: list[bytes]
21
+ csv_output: list[dict[str, Any]]
22
+ json_output: list[Any]
23
+ image_output: list[bytes]
24
+ text_output: list[str]
25
+
26
+ new_records: list[Mapping[str, FieldValuePbo]]
27
+
28
+ logs: list[str]
29
+
30
+ def __init__(self):
31
+ self.binary_output = []
32
+ self.csv_output = []
33
+ self.json_output = []
34
+ self.image_output = []
35
+ self.text_output = []
36
+ self.new_records = []
37
+ self.logs = []
38
+
39
+ def __str__(self):
40
+ ret_val: str = ""
41
+ ret_val += f"Binary Output: {len(self.binary_output)} item(s)\n"
42
+ for binary in self.binary_output:
43
+ ret_val += f"\t{len(binary)} byte(s)\n"
44
+ ret_val += f"\t{binary[:50]}...\n"
45
+ ret_val += f"CSV Output: {len(self.csv_output)} item(s)\n"
46
+ if self.csv_output:
47
+ ret_val += f"\tHeaders: {', '.join(self.csv_output[0].keys())}\n"
48
+ for i, csv_row in enumerate(self.csv_output):
49
+ ret_val += f"\t{i}: {', '.join(f'{v}' for k, v in csv_row.items())}\n"
50
+ ret_val += f"JSON Output: {len(self.json_output)} item(s)\n"
51
+ if self.json_output:
52
+ ret_val += f"\t{json.dumps(self.json_output, indent=2)}\n"
53
+ ret_val += f"Image Output: {len(self.image_output)} item(s)\n"
54
+ for image in self.image_output:
55
+ ret_val += f"\t{len(image)} bytes\n"
56
+ ret_val += f"\t{image[:50]}...\n"
57
+ ret_val += f"Text Output: {len(self.text_output)} item(s)\n"
58
+ for text in self.text_output:
59
+ ret_val += f"\t{text}...\n"
60
+ ret_val += f"New Records: {len(self.new_records)} item(s)\n"
61
+ for record in self.new_records:
62
+ ret_val += f"\t{json.dumps(record, indent=2)}\n"
63
+ ret_val += f"Logs: {len(self.logs)} item(s)\n"
64
+ for log in self.logs:
65
+ ret_val += f"\t{log}\n"
66
+ return ret_val
67
+
68
+
69
+ class TestClient:
70
+ """
71
+ A client for testing a ToolService. This client can be used to send requests to a tool and receive
72
+ responses.
73
+ """
74
+ server_url: str
75
+ tool_name: str
76
+ connection: SapioConnectionInfoPbo
77
+ request_inputs: list[Any]
78
+
79
+ def __init__(self, server_url: str, tool_name: str):
80
+ """
81
+ :param server_url: The URL of the gRPC server to connect to.
82
+ :param tool_name: The name of the tool to call on the server.
83
+ """
84
+ self.create_user()
85
+ self.server_url = server_url
86
+ self.tool_name = tool_name
87
+ self.request_inputs = []
88
+
89
+ def create_user(self):
90
+ """
91
+ Create a SapioConnectionInfoPbo object with test credentials. This method can be overridden to
92
+ create a user with specific credentials for testing.
93
+ """
94
+ self.connection = SapioConnectionInfoPbo()
95
+ self.connection.username = "Testing"
96
+ self.connection.webservice_url = "https://localhost:8080/webservice/api"
97
+ self.connection.app_guid = "1234567890"
98
+ self.connection.secret_type = SapioUserSecretTypePbo.PASSWORD
99
+ self.connection.rmi_host.append("Testing")
100
+ self.connection.rmi_port = 9001
101
+ self.connection.secret = "password"
102
+
103
+ def add_input_input(self, input_data: list[bytes]) -> None:
104
+ """
105
+ Add a binary input to the the next request.
106
+ """
107
+ self._add_input(DataTypePbo.BINARY, StepBinaryContainerPbo(items=input_data))
108
+
109
+ def add_csv_input(self, input_data: list[dict[str, Any]]) -> None:
110
+ """
111
+ Add a CSV input to the next request.
112
+ """
113
+ csv_items = []
114
+ for row in input_data:
115
+ csv_items.append(StepCsvRowPbo(cells=[str(value) for value in row.values()]))
116
+ header = StepCsvHeaderRowPbo(cells=list(input_data[0].keys()))
117
+ self._add_input(DataTypePbo.CSV, StepCsvContainerPbo(header=header, items=csv_items))
118
+
119
+ def add_json_input(self, input_data: list[dict[str, Any]]) -> None:
120
+ """
121
+ Add a JSON input to the next request.
122
+ """
123
+ self._add_input(DataTypePbo.JSON, StepJsonContainerPbo(items=[json.dumps(x) for x in input_data]))
124
+
125
+ def add_image_input(self, input_data: list[bytes], image_format: str = "png") -> None:
126
+ """
127
+ Add an image input to the next request.
128
+ """
129
+ self._add_input(DataTypePbo.IMAGE, StepImageContainerPbo(items=input_data, image_format=image_format))
130
+
131
+ def add_text_input(self, input_data: list[str]) -> None:
132
+ """
133
+ Add a text input to the next request.
134
+ """
135
+ self._add_input(DataTypePbo.TEXT, StepTextContainerPbo(items=input_data))
136
+
137
+ def _add_input(self, data_type: DataTypePbo, items: Any) -> None:
138
+ """
139
+ Helper method for adding inputs to the next request.
140
+ """
141
+ match data_type:
142
+ case DataTypePbo.BINARY:
143
+ container = StepItemContainerPbo(dataType=data_type, binary_container=items)
144
+ case DataTypePbo.CSV:
145
+ container = StepItemContainerPbo(dataType=data_type, csv_container=items)
146
+ case DataTypePbo.JSON:
147
+ container = StepItemContainerPbo(dataType=data_type, json_container=items)
148
+ case DataTypePbo.IMAGE:
149
+ container = StepItemContainerPbo(dataType=data_type, image_container=items)
150
+ case DataTypePbo.TEXT:
151
+ container = StepItemContainerPbo(dataType=data_type, text_container=items)
152
+ case _:
153
+ raise ValueError(f"Unsupported data type: {data_type}")
154
+ self.request_inputs.append(container)
155
+
156
+ def send_request(self) -> TestOutput:
157
+ """
158
+ Send the request to the tool service. This will send all the inputs that have been added using the
159
+ add_X_input functions.
160
+
161
+ :return: A TestOutput object containing the results of the tool service call.
162
+ """
163
+ with grpc.insecure_channel(self.server_url) as channel:
164
+ stub = ToolServiceStub(channel)
165
+
166
+ response: ProcessStepResponsePbo = stub.ProcessData(
167
+ ProcessStepRequestPbo(
168
+ sapio_user=self.connection,
169
+ tool_name=self.tool_name,
170
+ input=[
171
+ StepInputBatchPbo(is_partial=False, item_container=item)
172
+ for item in self.request_inputs
173
+ ]
174
+ )
175
+ )
176
+
177
+ results = TestOutput()
178
+
179
+ for item in response.output:
180
+ container = item.item_container
181
+
182
+ results.binary_output.extend(container.binary_container.items)
183
+ for header in container.csv_container.header.cells:
184
+ output_row: dict[str, Any] = {}
185
+ for i, row in enumerate(container.csv_container.items):
186
+ output_row[header] = row.cells[i]
187
+ results.csv_output.append(output_row)
188
+ results.json_output.extend([json.loads(x) for x in container.json_container.items])
189
+ results.image_output.extend(container.image_container.items)
190
+ results.text_output.extend(container.text_container.items)
191
+
192
+ for record in response.new_records:
193
+ results.new_records.append(record.fields)
194
+
195
+ results.logs.extend(response.log)
196
+
197
+ return results
@@ -643,8 +643,7 @@ class ToolBase(ABC):
643
643
  def log_info(self, message: str) -> None:
644
644
  """
645
645
  Log an info message for this tool. If verbose logging is enabled, this message will be included in the logs
646
- returned to the caller.
647
- .
646
+ returned to the caller. Empty/None inputs will not be logged.
648
647
 
649
648
  :param message: The message to log.
650
649
  """
@@ -657,6 +656,7 @@ class ToolBase(ABC):
657
656
  def log_warning(self, message: str) -> None:
658
657
  """
659
658
  Log a warning message for this tool. This message will be included in the logs returned to the caller.
659
+ Empty/None inputs will not be logged.
660
660
 
661
661
  :param message: The message to log.
662
662
  """
@@ -668,6 +668,7 @@ class ToolBase(ABC):
668
668
  def log_error(self, message: str) -> None:
669
669
  """
670
670
  Log an error message for this tool. This message will be included in the logs returned to the caller.
671
+ Empty/None inputs will not be logged.
671
672
 
672
673
  :param message: The message to log.
673
674
  """
@@ -679,6 +680,7 @@ class ToolBase(ABC):
679
680
  def log_exception(self, message: str, e: Exception) -> None:
680
681
  """
681
682
  Log an exception for this tool. This message will be included in the logs returned to the caller.
683
+ Empty/None inputs will not be logged.
682
684
 
683
685
  :param message: The message to log.
684
686
  :param e: The exception to log.
@@ -761,7 +763,8 @@ class ToolBase(ABC):
761
763
 
762
764
  def get_config_fields(self) -> dict[str, FieldValue]:
763
765
  """
764
- Get the configuration fields from the request object.
766
+ Get the configuration field values from the request object. If a field is not present in the request,
767
+ the default value from the config definition will be returned.
765
768
 
766
769
  :return: A dictionary of configuration field names and their values.
767
770
  """