sapiopycommons 2025.8.20a713__tar.gz → 2025.9.8a728__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 (124) hide show
  1. {sapiopycommons-2025.8.20a713 → sapiopycommons-2025.9.8a728}/PKG-INFO +1 -1
  2. {sapiopycommons-2025.8.20a713 → sapiopycommons-2025.9.8a728}/pyproject.toml +1 -1
  3. {sapiopycommons-2025.8.20a713 → sapiopycommons-2025.9.8a728}/src/sapiopycommons/ai/converter_service_base.py +2 -1
  4. {sapiopycommons-2025.8.20a713 → sapiopycommons-2025.9.8a728}/src/sapiopycommons/ai/protoapi/plan/item/item_container_pb2.py +4 -4
  5. {sapiopycommons-2025.8.20a713 → sapiopycommons-2025.9.8a728}/src/sapiopycommons/ai/protoapi/plan/item/item_container_pb2.pyi +4 -2
  6. {sapiopycommons-2025.8.20a713 → sapiopycommons-2025.9.8a728}/src/sapiopycommons/ai/server.py +6 -4
  7. {sapiopycommons-2025.8.20a713 → sapiopycommons-2025.9.8a728}/src/sapiopycommons/ai/test_client.py +47 -30
  8. {sapiopycommons-2025.8.20a713 → sapiopycommons-2025.9.8a728}/src/sapiopycommons/ai/tool_service_base.py +58 -19
  9. sapiopycommons-2025.9.8a728/src/sapiopycommons/files/temp_files.py +49 -0
  10. {sapiopycommons-2025.8.20a713 → sapiopycommons-2025.9.8a728}/.gitignore +0 -0
  11. {sapiopycommons-2025.8.20a713 → sapiopycommons-2025.9.8a728}/LICENSE +0 -0
  12. {sapiopycommons-2025.8.20a713 → sapiopycommons-2025.9.8a728}/README.md +0 -0
  13. {sapiopycommons-2025.8.20a713 → sapiopycommons-2025.9.8a728}/src/sapiopycommons/__init__.py +0 -0
  14. {sapiopycommons-2025.8.20a713 → sapiopycommons-2025.9.8a728}/src/sapiopycommons/ai/__init__.py +0 -0
  15. {sapiopycommons-2025.8.20a713 → sapiopycommons-2025.9.8a728}/src/sapiopycommons/ai/protoapi/fielddefinitions/fields_pb2.py +0 -0
  16. {sapiopycommons-2025.8.20a713 → sapiopycommons-2025.9.8a728}/src/sapiopycommons/ai/protoapi/fielddefinitions/fields_pb2.pyi +0 -0
  17. {sapiopycommons-2025.8.20a713 → sapiopycommons-2025.9.8a728}/src/sapiopycommons/ai/protoapi/fielddefinitions/fields_pb2_grpc.py +0 -0
  18. {sapiopycommons-2025.8.20a713 → sapiopycommons-2025.9.8a728}/src/sapiopycommons/ai/protoapi/fielddefinitions/velox_field_def_pb2.py +0 -0
  19. {sapiopycommons-2025.8.20a713 → sapiopycommons-2025.9.8a728}/src/sapiopycommons/ai/protoapi/fielddefinitions/velox_field_def_pb2.pyi +0 -0
  20. {sapiopycommons-2025.8.20a713 → sapiopycommons-2025.9.8a728}/src/sapiopycommons/ai/protoapi/fielddefinitions/velox_field_def_pb2_grpc.py +0 -0
  21. {sapiopycommons-2025.8.20a713 → sapiopycommons-2025.9.8a728}/src/sapiopycommons/ai/protoapi/plan/converter/converter_pb2.py +0 -0
  22. {sapiopycommons-2025.8.20a713 → sapiopycommons-2025.9.8a728}/src/sapiopycommons/ai/protoapi/plan/converter/converter_pb2.pyi +0 -0
  23. {sapiopycommons-2025.8.20a713 → sapiopycommons-2025.9.8a728}/src/sapiopycommons/ai/protoapi/plan/converter/converter_pb2_grpc.py +0 -0
  24. {sapiopycommons-2025.8.20a713 → sapiopycommons-2025.9.8a728}/src/sapiopycommons/ai/protoapi/plan/item/item_container_pb2_grpc.py +0 -0
  25. {sapiopycommons-2025.8.20a713 → sapiopycommons-2025.9.8a728}/src/sapiopycommons/ai/protoapi/plan/script/script_pb2.py +0 -0
  26. {sapiopycommons-2025.8.20a713 → sapiopycommons-2025.9.8a728}/src/sapiopycommons/ai/protoapi/plan/script/script_pb2.pyi +0 -0
  27. {sapiopycommons-2025.8.20a713 → sapiopycommons-2025.9.8a728}/src/sapiopycommons/ai/protoapi/plan/script/script_pb2_grpc.py +0 -0
  28. {sapiopycommons-2025.8.20a713 → sapiopycommons-2025.9.8a728}/src/sapiopycommons/ai/protoapi/plan/step_output_pb2.py +0 -0
  29. {sapiopycommons-2025.8.20a713 → sapiopycommons-2025.9.8a728}/src/sapiopycommons/ai/protoapi/plan/step_output_pb2.pyi +0 -0
  30. {sapiopycommons-2025.8.20a713 → sapiopycommons-2025.9.8a728}/src/sapiopycommons/ai/protoapi/plan/step_output_pb2_grpc.py +0 -0
  31. {sapiopycommons-2025.8.20a713 → sapiopycommons-2025.9.8a728}/src/sapiopycommons/ai/protoapi/plan/step_pb2.py +0 -0
  32. {sapiopycommons-2025.8.20a713 → sapiopycommons-2025.9.8a728}/src/sapiopycommons/ai/protoapi/plan/step_pb2.pyi +0 -0
  33. {sapiopycommons-2025.8.20a713 → sapiopycommons-2025.9.8a728}/src/sapiopycommons/ai/protoapi/plan/step_pb2_grpc.py +0 -0
  34. {sapiopycommons-2025.8.20a713 → sapiopycommons-2025.9.8a728}/src/sapiopycommons/ai/protoapi/plan/tool/entry_pb2.py +0 -0
  35. {sapiopycommons-2025.8.20a713 → sapiopycommons-2025.9.8a728}/src/sapiopycommons/ai/protoapi/plan/tool/entry_pb2.pyi +0 -0
  36. {sapiopycommons-2025.8.20a713 → sapiopycommons-2025.9.8a728}/src/sapiopycommons/ai/protoapi/plan/tool/entry_pb2_grpc.py +0 -0
  37. {sapiopycommons-2025.8.20a713 → sapiopycommons-2025.9.8a728}/src/sapiopycommons/ai/protoapi/plan/tool/tool_pb2.py +0 -0
  38. {sapiopycommons-2025.8.20a713 → sapiopycommons-2025.9.8a728}/src/sapiopycommons/ai/protoapi/plan/tool/tool_pb2.pyi +0 -0
  39. {sapiopycommons-2025.8.20a713 → sapiopycommons-2025.9.8a728}/src/sapiopycommons/ai/protoapi/plan/tool/tool_pb2_grpc.py +0 -0
  40. {sapiopycommons-2025.8.20a713 → sapiopycommons-2025.9.8a728}/src/sapiopycommons/ai/protoapi/session/sapio_conn_info_pb2.py +0 -0
  41. {sapiopycommons-2025.8.20a713 → sapiopycommons-2025.9.8a728}/src/sapiopycommons/ai/protoapi/session/sapio_conn_info_pb2.pyi +0 -0
  42. {sapiopycommons-2025.8.20a713 → sapiopycommons-2025.9.8a728}/src/sapiopycommons/ai/protoapi/session/sapio_conn_info_pb2_grpc.py +0 -0
  43. {sapiopycommons-2025.8.20a713 → sapiopycommons-2025.9.8a728}/src/sapiopycommons/ai/protobuf_utils.py +0 -0
  44. {sapiopycommons-2025.8.20a713 → sapiopycommons-2025.9.8a728}/src/sapiopycommons/callbacks/__init__.py +0 -0
  45. {sapiopycommons-2025.8.20a713 → sapiopycommons-2025.9.8a728}/src/sapiopycommons/callbacks/callback_util.py +0 -0
  46. {sapiopycommons-2025.8.20a713 → sapiopycommons-2025.9.8a728}/src/sapiopycommons/callbacks/field_builder.py +0 -0
  47. {sapiopycommons-2025.8.20a713 → sapiopycommons-2025.9.8a728}/src/sapiopycommons/chem/IndigoMolecules.py +0 -0
  48. {sapiopycommons-2025.8.20a713 → sapiopycommons-2025.9.8a728}/src/sapiopycommons/chem/Molecules.py +0 -0
  49. {sapiopycommons-2025.8.20a713 → sapiopycommons-2025.9.8a728}/src/sapiopycommons/chem/__init__.py +0 -0
  50. {sapiopycommons-2025.8.20a713 → sapiopycommons-2025.9.8a728}/src/sapiopycommons/customreport/__init__.py +0 -0
  51. {sapiopycommons-2025.8.20a713 → sapiopycommons-2025.9.8a728}/src/sapiopycommons/customreport/auto_pagers.py +0 -0
  52. {sapiopycommons-2025.8.20a713 → sapiopycommons-2025.9.8a728}/src/sapiopycommons/customreport/column_builder.py +0 -0
  53. {sapiopycommons-2025.8.20a713 → sapiopycommons-2025.9.8a728}/src/sapiopycommons/customreport/custom_report_builder.py +0 -0
  54. {sapiopycommons-2025.8.20a713 → sapiopycommons-2025.9.8a728}/src/sapiopycommons/customreport/term_builder.py +0 -0
  55. {sapiopycommons-2025.8.20a713 → sapiopycommons-2025.9.8a728}/src/sapiopycommons/datatype/__init__.py +0 -0
  56. {sapiopycommons-2025.8.20a713 → sapiopycommons-2025.9.8a728}/src/sapiopycommons/datatype/attachment_util.py +0 -0
  57. {sapiopycommons-2025.8.20a713 → sapiopycommons-2025.9.8a728}/src/sapiopycommons/datatype/data_fields.py +0 -0
  58. {sapiopycommons-2025.8.20a713 → sapiopycommons-2025.9.8a728}/src/sapiopycommons/datatype/pseudo_data_types.py +0 -0
  59. {sapiopycommons-2025.8.20a713 → sapiopycommons-2025.9.8a728}/src/sapiopycommons/eln/__init__.py +0 -0
  60. {sapiopycommons-2025.8.20a713 → sapiopycommons-2025.9.8a728}/src/sapiopycommons/eln/experiment_cache.py +0 -0
  61. {sapiopycommons-2025.8.20a713 → sapiopycommons-2025.9.8a728}/src/sapiopycommons/eln/experiment_handler.py +0 -0
  62. {sapiopycommons-2025.8.20a713 → sapiopycommons-2025.9.8a728}/src/sapiopycommons/eln/experiment_report_util.py +0 -0
  63. {sapiopycommons-2025.8.20a713 → sapiopycommons-2025.9.8a728}/src/sapiopycommons/eln/experiment_step_factory.py +0 -0
  64. {sapiopycommons-2025.8.20a713 → sapiopycommons-2025.9.8a728}/src/sapiopycommons/eln/experiment_tags.py +0 -0
  65. {sapiopycommons-2025.8.20a713 → sapiopycommons-2025.9.8a728}/src/sapiopycommons/eln/plate_designer.py +0 -0
  66. {sapiopycommons-2025.8.20a713 → sapiopycommons-2025.9.8a728}/src/sapiopycommons/eln/step_creation.py +0 -0
  67. {sapiopycommons-2025.8.20a713 → sapiopycommons-2025.9.8a728}/src/sapiopycommons/files/__init__.py +0 -0
  68. {sapiopycommons-2025.8.20a713 → sapiopycommons-2025.9.8a728}/src/sapiopycommons/files/complex_data_loader.py +0 -0
  69. {sapiopycommons-2025.8.20a713 → sapiopycommons-2025.9.8a728}/src/sapiopycommons/files/file_bridge.py +0 -0
  70. {sapiopycommons-2025.8.20a713 → sapiopycommons-2025.9.8a728}/src/sapiopycommons/files/file_bridge_handler.py +0 -0
  71. {sapiopycommons-2025.8.20a713 → sapiopycommons-2025.9.8a728}/src/sapiopycommons/files/file_data_handler.py +0 -0
  72. {sapiopycommons-2025.8.20a713 → sapiopycommons-2025.9.8a728}/src/sapiopycommons/files/file_util.py +0 -0
  73. {sapiopycommons-2025.8.20a713 → sapiopycommons-2025.9.8a728}/src/sapiopycommons/files/file_validator.py +0 -0
  74. {sapiopycommons-2025.8.20a713 → sapiopycommons-2025.9.8a728}/src/sapiopycommons/files/file_writer.py +0 -0
  75. {sapiopycommons-2025.8.20a713 → sapiopycommons-2025.9.8a728}/src/sapiopycommons/flowcyto/flow_cyto.py +0 -0
  76. {sapiopycommons-2025.8.20a713 → sapiopycommons-2025.9.8a728}/src/sapiopycommons/flowcyto/flowcyto_data.py +0 -0
  77. {sapiopycommons-2025.8.20a713 → sapiopycommons-2025.9.8a728}/src/sapiopycommons/general/__init__.py +0 -0
  78. {sapiopycommons-2025.8.20a713 → sapiopycommons-2025.9.8a728}/src/sapiopycommons/general/accession_service.py +0 -0
  79. {sapiopycommons-2025.8.20a713 → sapiopycommons-2025.9.8a728}/src/sapiopycommons/general/aliases.py +0 -0
  80. {sapiopycommons-2025.8.20a713 → sapiopycommons-2025.9.8a728}/src/sapiopycommons/general/audit_log.py +0 -0
  81. {sapiopycommons-2025.8.20a713 → sapiopycommons-2025.9.8a728}/src/sapiopycommons/general/custom_report_util.py +0 -0
  82. {sapiopycommons-2025.8.20a713 → sapiopycommons-2025.9.8a728}/src/sapiopycommons/general/data_structure_util.py +0 -0
  83. {sapiopycommons-2025.8.20a713 → sapiopycommons-2025.9.8a728}/src/sapiopycommons/general/directive_util.py +0 -0
  84. {sapiopycommons-2025.8.20a713 → sapiopycommons-2025.9.8a728}/src/sapiopycommons/general/exceptions.py +0 -0
  85. {sapiopycommons-2025.8.20a713 → sapiopycommons-2025.9.8a728}/src/sapiopycommons/general/html_formatter.py +0 -0
  86. {sapiopycommons-2025.8.20a713 → sapiopycommons-2025.9.8a728}/src/sapiopycommons/general/popup_util.py +0 -0
  87. {sapiopycommons-2025.8.20a713 → sapiopycommons-2025.9.8a728}/src/sapiopycommons/general/sapio_links.py +0 -0
  88. {sapiopycommons-2025.8.20a713 → sapiopycommons-2025.9.8a728}/src/sapiopycommons/general/storage_util.py +0 -0
  89. {sapiopycommons-2025.8.20a713 → sapiopycommons-2025.9.8a728}/src/sapiopycommons/general/time_util.py +0 -0
  90. {sapiopycommons-2025.8.20a713 → sapiopycommons-2025.9.8a728}/src/sapiopycommons/multimodal/multimodal.py +0 -0
  91. {sapiopycommons-2025.8.20a713 → sapiopycommons-2025.9.8a728}/src/sapiopycommons/multimodal/multimodal_data.py +0 -0
  92. {sapiopycommons-2025.8.20a713 → sapiopycommons-2025.9.8a728}/src/sapiopycommons/processtracking/__init__.py +0 -0
  93. {sapiopycommons-2025.8.20a713 → sapiopycommons-2025.9.8a728}/src/sapiopycommons/processtracking/custom_workflow_handler.py +0 -0
  94. {sapiopycommons-2025.8.20a713 → sapiopycommons-2025.9.8a728}/src/sapiopycommons/processtracking/endpoints.py +0 -0
  95. {sapiopycommons-2025.8.20a713 → sapiopycommons-2025.9.8a728}/src/sapiopycommons/recordmodel/__init__.py +0 -0
  96. {sapiopycommons-2025.8.20a713 → sapiopycommons-2025.9.8a728}/src/sapiopycommons/recordmodel/record_handler.py +0 -0
  97. {sapiopycommons-2025.8.20a713 → sapiopycommons-2025.9.8a728}/src/sapiopycommons/rules/__init__.py +0 -0
  98. {sapiopycommons-2025.8.20a713 → sapiopycommons-2025.9.8a728}/src/sapiopycommons/rules/eln_rule_handler.py +0 -0
  99. {sapiopycommons-2025.8.20a713 → sapiopycommons-2025.9.8a728}/src/sapiopycommons/rules/on_save_rule_handler.py +0 -0
  100. {sapiopycommons-2025.8.20a713 → sapiopycommons-2025.9.8a728}/src/sapiopycommons/samples/aliquot.py +0 -0
  101. {sapiopycommons-2025.8.20a713 → sapiopycommons-2025.9.8a728}/src/sapiopycommons/sftpconnect/__init__.py +0 -0
  102. {sapiopycommons-2025.8.20a713 → sapiopycommons-2025.9.8a728}/src/sapiopycommons/sftpconnect/sftp_builder.py +0 -0
  103. {sapiopycommons-2025.8.20a713 → sapiopycommons-2025.9.8a728}/src/sapiopycommons/webhook/__init__.py +0 -0
  104. {sapiopycommons-2025.8.20a713 → sapiopycommons-2025.9.8a728}/src/sapiopycommons/webhook/webhook_context.py +0 -0
  105. {sapiopycommons-2025.8.20a713 → sapiopycommons-2025.9.8a728}/src/sapiopycommons/webhook/webhook_handlers.py +0 -0
  106. {sapiopycommons-2025.8.20a713 → sapiopycommons-2025.9.8a728}/src/sapiopycommons/webhook/webservice_handlers.py +0 -0
  107. {sapiopycommons-2025.8.20a713 → sapiopycommons-2025.9.8a728}/tests/AF-A0A009IHW8-F1-model_v4.cif +0 -0
  108. {sapiopycommons-2025.8.20a713 → sapiopycommons-2025.9.8a728}/tests/_do_not_add_init_py_here +0 -0
  109. {sapiopycommons-2025.8.20a713 → sapiopycommons-2025.9.8a728}/tests/accession_test.py +0 -0
  110. {sapiopycommons-2025.8.20a713 → sapiopycommons-2025.9.8a728}/tests/aliquot_test.py +0 -0
  111. {sapiopycommons-2025.8.20a713 → sapiopycommons-2025.9.8a728}/tests/bio_reg_test.py +0 -0
  112. {sapiopycommons-2025.8.20a713 → sapiopycommons-2025.9.8a728}/tests/chem_test.py +0 -0
  113. {sapiopycommons-2025.8.20a713 → sapiopycommons-2025.9.8a728}/tests/chem_test_curation_queue.py +0 -0
  114. {sapiopycommons-2025.8.20a713 → sapiopycommons-2025.9.8a728}/tests/curation_queue_test.sdf +0 -0
  115. {sapiopycommons-2025.8.20a713 → sapiopycommons-2025.9.8a728}/tests/data_type_models.py +0 -0
  116. {sapiopycommons-2025.8.20a713 → sapiopycommons-2025.9.8a728}/tests/flowcyto/101_DEN084Y5_15_E01_008_clean.fcs +0 -0
  117. {sapiopycommons-2025.8.20a713 → sapiopycommons-2025.9.8a728}/tests/flowcyto/101_DEN084Y5_15_E03_009_clean.fcs +0 -0
  118. {sapiopycommons-2025.8.20a713 → sapiopycommons-2025.9.8a728}/tests/flowcyto/101_DEN084Y5_15_E05_010_clean.fcs +0 -0
  119. {sapiopycommons-2025.8.20a713 → sapiopycommons-2025.9.8a728}/tests/flowcyto/8_color_ICS.wsp +0 -0
  120. {sapiopycommons-2025.8.20a713 → sapiopycommons-2025.9.8a728}/tests/flowcyto/COVID19_W_001_O.fcs +0 -0
  121. {sapiopycommons-2025.8.20a713 → sapiopycommons-2025.9.8a728}/tests/flowcyto_test.py +0 -0
  122. {sapiopycommons-2025.8.20a713 → sapiopycommons-2025.9.8a728}/tests/kappa.chains.fasta +0 -0
  123. {sapiopycommons-2025.8.20a713 → sapiopycommons-2025.9.8a728}/tests/mafft_test.py +0 -0
  124. {sapiopycommons-2025.8.20a713 → sapiopycommons-2025.9.8a728}/tests/test.gb +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: sapiopycommons
3
- Version: 2025.8.20a713
3
+ Version: 2025.9.8a728
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.08.20a713'
7
+ version='2025.09.08a728'
8
8
  authors = [
9
9
  { name="Jonathan Steck", email="jsteck@sapiosciences.com" },
10
10
  { name="Yechen Qiao", email="yqiao@sapiosciences.com" },
@@ -118,7 +118,8 @@ class ConverterBase(ABC):
118
118
  :param target_type: The target content type.
119
119
  :return: True if this converter can convert from the input type to the target type, False otherwise.
120
120
  """
121
- return self.input_type() == input_type.name and self.output_type() == target_type.name
121
+ return (self.input_type().lower() == input_type.name.lower()
122
+ and self.output_type().lower() == target_type.name.lower())
122
123
 
123
124
  @abstractmethod
124
125
  def convert(self, content: StepItemContainerPbo) -> StepItemContainerPbo:
@@ -24,7 +24,7 @@ _sym_db = _symbol_database.Default()
24
24
 
25
25
 
26
26
 
27
- DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n9sapiopycommons/ai/protoapi/plan/item/item_container.proto\"2\n\x0e\x43ontentTypePbo\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\x12\n\nextensions\x18\x02 \x03(\t\"$\n\x13StepCsvHeaderRowPbo\x12\r\n\x05\x63\x65lls\x18\x01 \x03(\t\"\x1e\n\rStepCsvRowPbo\x12\r\n\x05\x63\x65lls\x18\x01 \x03(\t\"Z\n\x13StepCsvContainerPbo\x12$\n\x06header\x18\x0e \x01(\x0b\x32\x14.StepCsvHeaderRowPbo\x12\x1d\n\x05items\x18\x0f \x03(\x0b\x32\x0e.StepCsvRowPbo\"%\n\x14StepJsonContainerPbo\x12\r\n\x05items\x18\x0f \x03(\t\"%\n\x14StepTextContainerPbo\x12\r\n\x05items\x18\x0f \x03(\t\"\'\n\x16StepBinaryContainerPbo\x12\r\n\x05items\x18\x0f \x03(\x0c\"<\n\x15StepImageContainerPbo\x12\x14\n\x0cimage_format\x18\x01 \x01(\t\x12\r\n\x05items\x18\x0f \x03(\x0c\"\x94\x02\n\x14StepItemContainerPbo\x12%\n\x0c\x63ontent_type\x18\x02 \x01(\x0b\x32\x0f.ContentTypePbo\x12\x34\n\x10\x62inary_container\x18\xff\x0f \x01(\x0b\x32\x17.StepBinaryContainerPboH\x00\x12.\n\rcsv_container\x18\xfe\x0f \x01(\x0b\x32\x14.StepCsvContainerPboH\x00\x12\x30\n\x0ejson_container\x18\xfd\x0f \x01(\x0b\x32\x15.StepJsonContainerPboH\x00\x12\x30\n\x0etext_container\x18\xfc\x0f \x01(\x0b\x32\x15.StepTextContainerPboH\x00\x42\x0b\n\tcontainer*A\n\x0b\x44\x61taTypePbo\x12\n\n\x06\x42INARY\x10\x00\x12\x08\n\x04JSON\x10\x01\x12\x07\n\x03\x43SV\x10\x02\x12\x08\n\x04TEXT\x10\x03\x12\t\n\x05IMAGE\x10\x04\x42 \n\x1c\x63om.velox.protoapi.plan.itemP\x01\x62\x06proto3')
27
+ DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n9sapiopycommons/ai/protoapi/plan/item/item_container.proto\"2\n\x0e\x43ontentTypePbo\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\x12\n\nextensions\x18\x02 \x03(\t\"$\n\x13StepCsvHeaderRowPbo\x12\r\n\x05\x63\x65lls\x18\x01 \x03(\t\"\x1e\n\rStepCsvRowPbo\x12\r\n\x05\x63\x65lls\x18\x01 \x03(\t\"Z\n\x13StepCsvContainerPbo\x12$\n\x06header\x18\x0e \x01(\x0b\x32\x14.StepCsvHeaderRowPbo\x12\x1d\n\x05items\x18\x0f \x03(\x0b\x32\x0e.StepCsvRowPbo\"%\n\x14StepJsonContainerPbo\x12\r\n\x05items\x18\x0f \x03(\t\"%\n\x14StepTextContainerPbo\x12\r\n\x05items\x18\x0f \x03(\t\"\'\n\x16StepBinaryContainerPbo\x12\r\n\x05items\x18\x0f \x03(\x0c\"<\n\x15StepImageContainerPbo\x12\x14\n\x0cimage_format\x18\x01 \x01(\t\x12\r\n\x05items\x18\x0f \x03(\x0c\"\xac\x02\n\x14StepItemContainerPbo\x12%\n\x0c\x63ontent_type\x18\x02 \x01(\x0b\x32\x0f.ContentTypePbo\x12\x16\n\x0e\x63ontainer_name\x18\x03 \x01(\t\x12\x34\n\x10\x62inary_container\x18\xff\x0f \x01(\x0b\x32\x17.StepBinaryContainerPboH\x00\x12.\n\rcsv_container\x18\xfe\x0f \x01(\x0b\x32\x14.StepCsvContainerPboH\x00\x12\x30\n\x0ejson_container\x18\xfd\x0f \x01(\x0b\x32\x15.StepJsonContainerPboH\x00\x12\x30\n\x0etext_container\x18\xfc\x0f \x01(\x0b\x32\x15.StepTextContainerPboH\x00\x42\x0b\n\tcontainer*A\n\x0b\x44\x61taTypePbo\x12\n\n\x06\x42INARY\x10\x00\x12\x08\n\x04JSON\x10\x01\x12\x07\n\x03\x43SV\x10\x02\x12\x08\n\x04TEXT\x10\x03\x12\t\n\x05IMAGE\x10\x04\x42 \n\x1c\x63om.velox.protoapi.plan.itemP\x01\x62\x06proto3')
28
28
 
29
29
  _globals = globals()
30
30
  _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
@@ -32,8 +32,8 @@ _builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'sapiopycommons.ai.protoapi.
32
32
  if not _descriptor._USE_C_DESCRIPTORS:
33
33
  _globals['DESCRIPTOR']._loaded_options = None
34
34
  _globals['DESCRIPTOR']._serialized_options = b'\n\034com.velox.protoapi.plan.itemP\001'
35
- _globals['_DATATYPEPBO']._serialized_start=735
36
- _globals['_DATATYPEPBO']._serialized_end=800
35
+ _globals['_DATATYPEPBO']._serialized_start=759
36
+ _globals['_DATATYPEPBO']._serialized_end=824
37
37
  _globals['_CONTENTTYPEPBO']._serialized_start=61
38
38
  _globals['_CONTENTTYPEPBO']._serialized_end=111
39
39
  _globals['_STEPCSVHEADERROWPBO']._serialized_start=113
@@ -51,5 +51,5 @@ if not _descriptor._USE_C_DESCRIPTORS:
51
51
  _globals['_STEPIMAGECONTAINERPBO']._serialized_start=394
52
52
  _globals['_STEPIMAGECONTAINERPBO']._serialized_end=454
53
53
  _globals['_STEPITEMCONTAINERPBO']._serialized_start=457
54
- _globals['_STEPITEMCONTAINERPBO']._serialized_end=733
54
+ _globals['_STEPITEMCONTAINERPBO']._serialized_end=757
55
55
  # @@protoc_insertion_point(module_scope)
@@ -74,15 +74,17 @@ class StepImageContainerPbo(_message.Message):
74
74
  def __init__(self, image_format: _Optional[str] = ..., items: _Optional[_Iterable[bytes]] = ...) -> None: ...
75
75
 
76
76
  class StepItemContainerPbo(_message.Message):
77
- __slots__ = ("content_type", "binary_container", "csv_container", "json_container", "text_container")
77
+ __slots__ = ("content_type", "container_name", "binary_container", "csv_container", "json_container", "text_container")
78
78
  CONTENT_TYPE_FIELD_NUMBER: _ClassVar[int]
79
+ CONTAINER_NAME_FIELD_NUMBER: _ClassVar[int]
79
80
  BINARY_CONTAINER_FIELD_NUMBER: _ClassVar[int]
80
81
  CSV_CONTAINER_FIELD_NUMBER: _ClassVar[int]
81
82
  JSON_CONTAINER_FIELD_NUMBER: _ClassVar[int]
82
83
  TEXT_CONTAINER_FIELD_NUMBER: _ClassVar[int]
83
84
  content_type: ContentTypePbo
85
+ container_name: str
84
86
  binary_container: StepBinaryContainerPbo
85
87
  csv_container: StepCsvContainerPbo
86
88
  json_container: StepJsonContainerPbo
87
89
  text_container: StepTextContainerPbo
88
- def __init__(self, content_type: _Optional[_Union[ContentTypePbo, _Mapping]] = ..., binary_container: _Optional[_Union[StepBinaryContainerPbo, _Mapping]] = ..., csv_container: _Optional[_Union[StepCsvContainerPbo, _Mapping]] = ..., json_container: _Optional[_Union[StepJsonContainerPbo, _Mapping]] = ..., text_container: _Optional[_Union[StepTextContainerPbo, _Mapping]] = ...) -> None: ...
90
+ def __init__(self, content_type: _Optional[_Union[ContentTypePbo, _Mapping]] = ..., container_name: _Optional[str] = ..., binary_container: _Optional[_Union[StepBinaryContainerPbo, _Mapping]] = ..., csv_container: _Optional[_Union[StepCsvContainerPbo, _Mapping]] = ..., json_container: _Optional[_Union[StepJsonContainerPbo, _Mapping]] = ..., text_container: _Optional[_Union[StepTextContainerPbo, _Mapping]] = ...) -> None: ...
@@ -14,13 +14,13 @@ class SapioGrpcServer:
14
14
  """
15
15
  A gRPC server for handling the various Sapio gRPC services.
16
16
  """
17
- port: str
17
+ port: int
18
18
  options: list[tuple[str, Any]]
19
19
  _converter_services: list[ConverterServiceServicer]
20
20
  _script_services: list[ScriptServiceServicer]
21
21
  _tool_services: list[ToolServiceServicer]
22
22
 
23
- def __init__(self, port: str = "50051", message_mb_size: int = 100, options: list[tuple[str, Any]] | None = None) \
23
+ def __init__(self, port: int = 50051, message_mb_size: int = 1024, options: list[tuple[str, Any]] | None = None) \
24
24
  -> None:
25
25
  """
26
26
  Initialize the gRPC server with the specified port and message size.
@@ -30,6 +30,8 @@ class SapioGrpcServer:
30
30
  :param options: Additional gRPC server options to set. This should be a list of tuples where the first item is
31
31
  the option name and the second item is the option value.
32
32
  """
33
+ if isinstance(port, str):
34
+ port = int(port)
33
35
  self.port = port
34
36
  self.options = [
35
37
  ('grpc.max_send_message_length', message_mb_size * 1024 * 1024),
@@ -86,9 +88,9 @@ class SapioGrpcServer:
86
88
  print(f"Registering Tool service: {service.__class__.__name__}")
87
89
  add_ToolServiceServicer_to_server(service, server)
88
90
 
89
- server.add_insecure_port("[::]:" + self.port)
91
+ server.add_insecure_port(f"[::]:{self.port}")
90
92
  await server.start()
91
- print("Server started, listening on " + self.port)
93
+ print(f"Server started, listening on {self.port}")
92
94
  try:
93
95
  await server.wait_for_termination()
94
96
  finally:
@@ -43,10 +43,10 @@ class ToolOutput:
43
43
  status: str
44
44
  message: str
45
45
 
46
- binary_output: list[bytes]
47
- csv_output: list[dict[str, Any]]
48
- json_output: list[Any]
49
- text_output: list[str]
46
+ binary_output: list[list[bytes]]
47
+ csv_output: list[list[dict[str, Any]]]
48
+ json_output: list[list[Any]]
49
+ text_output: list[list[str]]
50
50
 
51
51
  new_records: list[dict[str, FieldValue]]
52
52
 
@@ -67,24 +67,35 @@ class ToolOutput:
67
67
  ret_val += f"\tMessage: {self.message}\n"
68
68
  ret_val += "-" * 25 + "\n"
69
69
 
70
- ret_val += f"Binary Output: {len(self.binary_output)} item(s)\n"
71
- for binary in self.binary_output:
72
- ret_val += f"\t{len(binary)} byte(s)\n"
73
- ret_val += f"\t{binary[:50]}...\n"
74
-
75
- ret_val += f"CSV Output: {len(self.csv_output)} item(s)\n"
76
- if self.csv_output:
77
- ret_val += f"\tHeaders: {', '.join(self.csv_output[0].keys())}\n"
78
- for i, csv_row in enumerate(self.csv_output):
79
- ret_val += f"\t{i}: {', '.join(f'{v}' for k, v in csv_row.items())}\n"
80
-
81
- ret_val += f"JSON Output: {len(self.json_output)} item(s)\n"
82
- if self.json_output:
83
- ret_val += f"{json.dumps(self.json_output, indent=2)}\n"
84
-
85
- ret_val += f"Text Output: {len(self.text_output)} item(s)\n"
86
- for text in self.text_output:
87
- ret_val += f"\t{text}\n"
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"
88
99
 
89
100
  ret_val += f"New Records: {len(self.new_records)} item(s)\n"
90
101
  for record in self.new_records:
@@ -277,14 +288,20 @@ class TestClient:
277
288
  for item in response.output:
278
289
  container = item.item_container
279
290
 
280
- results.binary_output.extend(container.binary_container.items)
281
- for header in container.csv_container.header.cells:
282
- output_row: dict[str, Any] = {}
283
- for i, row in enumerate(container.csv_container.items):
284
- output_row[header] = row.cells[i]
285
- results.csv_output.append(output_row)
286
- results.json_output.extend([json.loads(x) for x in container.json_container.items])
287
- results.text_output.extend(container.text_container.items)
291
+ if container.HasField("binary_container"):
292
+ results.binary_output.append(list(container.binary_container.items))
293
+ elif container.HasField("csv_container"):
294
+ csv_output: list[dict[str, Any]] = []
295
+ for header in container.csv_container.header.cells:
296
+ output_row: dict[str, Any] = {}
297
+ for i, row in enumerate(container.csv_container.items):
298
+ output_row[header] = row.cells[i]
299
+ csv_output.append(output_row)
300
+ results.csv_output.append(csv_output)
301
+ elif container.HasField("json_container"):
302
+ results.json_output.append([json.loads(x) for x in container.json_container.items])
303
+ elif container.HasField("text_container"):
304
+ results.text_output.append(list(container.text_container.items))
288
305
 
289
306
  for record in response.new_records:
290
307
  field_map: dict[str, Any] = {x: ProtobufUtils.field_pbo_to_value(y) for x, y in record.fields.items()}
@@ -29,6 +29,7 @@ from sapiopycommons.ai.protoapi.session.sapio_conn_info_pb2 import SapioUserSecr
29
29
  from sapiopycommons.ai.protobuf_utils import ProtobufUtils
30
30
  from sapiopycommons.ai.test_client import ContainerType
31
31
  from sapiopycommons.files.file_util import FileUtil
32
+ from sapiopycommons.files.temp_files import TempFileHandler
32
33
  from sapiopycommons.general.aliases import FieldMap, FieldValue
33
34
 
34
35
 
@@ -53,21 +54,26 @@ class BinaryResult(SapioToolResult):
53
54
  binary_data: list[bytes]
54
55
  content_type: str
55
56
  file_extensions: list[str]
57
+ name: str
56
58
 
57
- def __init__(self, binary_data: list[bytes], content_type: str = "binary", file_extensions: list[str] = None):
59
+ def __init__(self, binary_data: list[bytes], content_type: str = "binary", file_extensions: list[str] = None,
60
+ name: str | None = None):
58
61
  """
59
62
  :param binary_data: The binary data as a list of bytes.
60
63
  :param content_type: The content type of the data.
61
64
  :param file_extensions: A list of file extensions that this binary data can be saved as.
65
+ :param name: An optional identifying name for this result that will be accessible to the next tool.
62
66
  """
63
67
  self.binary_data = binary_data
64
68
  self.content_type = content_type
65
69
  self.file_extensions = file_extensions if file_extensions else []
70
+ self.name = name
66
71
 
67
72
  def to_proto(self) -> StepOutputBatchPbo | list[FieldValueMapPbo]:
68
73
  return StepOutputBatchPbo(
69
74
  item_container=StepItemContainerPbo(
70
75
  content_type=ContentTypePbo(name=self.content_type, extensions=self.file_extensions),
76
+ container_name=self.name,
71
77
  binary_container=StepBinaryContainerPbo(items=self.binary_data)
72
78
  )
73
79
  )
@@ -80,21 +86,26 @@ class CsvResult(SapioToolResult):
80
86
  csv_data: list[dict[str, Any]]
81
87
  content_type: str
82
88
  file_extensions: list[str]
89
+ name: str
83
90
 
84
- def __init__(self, csv_data: list[dict[str, Any]], content_type: str = "csv", file_extensions: list[str] = None):
91
+ def __init__(self, csv_data: list[dict[str, Any]], content_type: str = "csv", file_extensions: list[str] = None,
92
+ name: str | None = None):
85
93
  """
86
94
  :param csv_data: The list of CSV data results, provided as a list of dictionaries of column name to value.
87
95
  :param content_type: The content type of the data.
88
96
  :param file_extensions: A list of file extensions that this binary data can be saved as.
97
+ :param name: An optional identifying name for this result that will be accessible to the next tool.
89
98
  """
90
99
  self.csv_data = csv_data
91
100
  self.content_type = content_type
92
101
  self.file_extensions = file_extensions if file_extensions else ["csv"]
102
+ self.name = name
93
103
 
94
104
  def to_proto(self) -> StepOutputBatchPbo | list[FieldValueMapPbo]:
95
105
  return StepOutputBatchPbo(
96
106
  item_container=StepItemContainerPbo(
97
107
  content_type=ContentTypePbo(name=self.content_type, extensions=self.file_extensions),
108
+ container_name=self.name,
98
109
  csv_container=StepCsvContainerPbo(
99
110
  header=StepCsvHeaderRowPbo(cells=self.csv_data[0].keys()),
100
111
  items=[StepCsvRowPbo(cells=[str(x) for x in row.values()]) for row in self.csv_data]
@@ -143,22 +154,27 @@ class JsonResult(SapioToolResult):
143
154
  json_data: list[Any]
144
155
  content_type: str
145
156
  file_extensions: list[str]
157
+ name: str
146
158
 
147
- def __init__(self, json_data: list[Any], content_type: str = "json", file_extensions: list[str] = None):
159
+ def __init__(self, json_data: list[Any], content_type: str = "json", file_extensions: list[str] = None,
160
+ name: str | None = None):
148
161
  """
149
162
  :param json_data: The list of JSON data results. Each entry in the list represents a separate JSON object.
150
163
  These entries must be able to be serialized to JSON using json.dumps().
151
164
  :param content_type: The content type of the data.
152
165
  :param file_extensions: A list of file extensions that this binary data can be saved as.
166
+ :param name: An optional identifying name for this result that will be accessible to the next tool.
153
167
  """
154
168
  self.json_data = json_data
155
169
  self.content_type = content_type
156
170
  self.file_extensions = file_extensions if file_extensions else ["json"]
171
+ self.name = name
157
172
 
158
173
  def to_proto(self) -> StepOutputBatchPbo | list[FieldValueMapPbo]:
159
174
  return StepOutputBatchPbo(
160
175
  item_container=StepItemContainerPbo(
161
176
  content_type=ContentTypePbo(name=self.content_type, extensions=self.file_extensions),
177
+ container_name=self.name,
162
178
  json_container=StepJsonContainerPbo(items=[json.dumps(x) for x in self.json_data])
163
179
  )
164
180
  )
@@ -171,21 +187,26 @@ class TextResult(SapioToolResult):
171
187
  text_data: list[str]
172
188
  content_type: str
173
189
  file_extensions: list[str]
190
+ name: str
174
191
 
175
- def __init__(self, text_data: list[str], content_type: str = "text", file_extensions: list[str] = None):
192
+ def __init__(self, text_data: list[str], content_type: str = "text", file_extensions: list[str] = None,
193
+ name: str | None = None):
176
194
  """
177
195
  :param text_data: The text data as a list of strings.
178
196
  :param content_type: The content type of the data.
179
197
  :param file_extensions: A list of file extensions that this binary data can be saved as.
198
+ :param name: An optional identifying name for this result that will be accessible to the next tool.
180
199
  """
181
200
  self.text_data = text_data
182
201
  self.content_type = content_type
183
202
  self.file_extensions = file_extensions if file_extensions else ["txt"]
203
+ self.name = name
184
204
 
185
205
  def to_proto(self) -> StepOutputBatchPbo | list[FieldValueMapPbo]:
186
206
  return StepOutputBatchPbo(
187
207
  item_container=StepItemContainerPbo(
188
208
  content_type=ContentTypePbo(name=self.content_type, extensions=self.file_extensions),
209
+ container_name=self.name,
189
210
  text_container=StepTextContainerPbo(items=self.text_data)
190
211
  )
191
212
  )
@@ -298,8 +319,8 @@ class ToolServiceBase(ToolServiceServicer, ABC):
298
319
  # If the tool is not found, list all of the registered tools for this service so that the LLM can correct
299
320
  # the tool it is requesting.
300
321
  all_tool_names: str = "\n".join(registered_tools.keys())
301
- msg: str = (f"Tool \"{find_tool}\" not found in the registered tools for this service. The registered tools "
302
- f"for this service are: \n{all_tool_names}")
322
+ msg: str = (f"Tool \"{find_tool}\" not found in the registered tools for this service. The registered "
323
+ f"tools for this service are: \n{all_tool_names}")
303
324
  return False, msg, [], []
304
325
 
305
326
  # Instantiate the tool class.
@@ -309,7 +330,8 @@ class ToolServiceBase(ToolServiceServicer, ABC):
309
330
  tool.setup(user, request, context)
310
331
  # Validate that the provided inputs match the tool's expected inputs.
311
332
  if len(request.input) != len(tool.input_configs):
312
- msg: str = f"Expected {len(tool.input_configs)} inputs for this tool, but got {len(request.input)} instead."
333
+ msg: str = (f"Expected {len(tool.input_configs)} inputs for this tool, but got {len(request.input)} "
334
+ f"instead.")
313
335
  else:
314
336
  msg: str = tool.validate_input()
315
337
  # If there is no error message, then the inputs are valid.
@@ -327,6 +349,9 @@ class ToolServiceBase(ToolServiceServicer, ABC):
327
349
  except Exception as e:
328
350
  tool.log_exception("Exception occurred during tool execution.", e)
329
351
  return False, str(e), [], tool.logs
352
+ finally:
353
+ # Clean up any temporary files created by the tool.
354
+ tool.temp_data.cleanup()
330
355
 
331
356
 
332
357
  class ToolBase(ABC):
@@ -346,6 +371,8 @@ class ToolBase(ABC):
346
371
  logger: Logger
347
372
  verbose_logging: bool
348
373
 
374
+ temp_data: TempFileHandler
375
+
349
376
  user: SapioUser
350
377
  request: ProcessStepRequestPbo
351
378
  context: ServicerContext
@@ -383,6 +410,7 @@ class ToolBase(ABC):
383
410
  self.output_configs = []
384
411
  self._output_container_types = []
385
412
  self.config_fields = []
413
+ self.temp_data = TempFileHandler()
386
414
  self.logs = []
387
415
  self.logger = logging.getLogger(f"ToolBase.{self._name}")
388
416
  ensure_logger_initialized(self.logger)
@@ -508,8 +536,8 @@ class ToolBase(ABC):
508
536
  """
509
537
  self.config_fields.append(ProtobufUtils.field_def_to_pbo(field))
510
538
 
511
- def add_boolean_config_field(self, field_name: str, display_name: str, description: str, default_value: bool,
512
- optional: bool = False) -> None:
539
+ def add_boolean_config_field(self, field_name: str, display_name: str, description: str,
540
+ default_value: bool | None = None, optional: bool = False) -> None:
513
541
  """
514
542
  Add a boolean configuration field to the tool. This field will be used to configure the tool in the plan
515
543
  manager.
@@ -532,9 +560,9 @@ class ToolBase(ABC):
532
560
  )
533
561
  ))
534
562
 
535
- def add_double_config_field(self, field_name: str, display_name: str, description: str, default_value: float,
536
- min_value: float = -10.**120, max_value: float = 10.**120, precision: int = 2,
537
- optional: bool = False) -> None:
563
+ def add_double_config_field(self, field_name: str, display_name: str, description: str,
564
+ default_value: float | None = None, min_value: float = -10.**120,
565
+ max_value: float = 10.**120, precision: int = 2, optional: bool = False) -> None:
538
566
  """
539
567
  Add a double configuration field to the tool. This field will be used to configure the tool in the plan
540
568
  manager.
@@ -564,7 +592,7 @@ class ToolBase(ABC):
564
592
  ))
565
593
 
566
594
  def add_integer_config_field(self, field_name: str, display_name: str, description: str,
567
- default_value: int, min_value: int = -2**31, max_value: int = 2**31-1,
595
+ default_value: int | None = None, min_value: int = -2**31, max_value: int = 2**31-1,
568
596
  optional: bool = False) -> None:
569
597
  """
570
598
  Add an integer configuration field to the tool. This field will be used to configure the tool in the plan
@@ -593,7 +621,8 @@ class ToolBase(ABC):
593
621
  ))
594
622
 
595
623
  def add_string_config_field(self, field_name: str, display_name: str, description: str,
596
- default_value: str, max_length: int = 1000, optional: bool = False) -> None:
624
+ default_value: str | None = None, max_length: int = 1000, optional: bool = False) \
625
+ -> None:
597
626
  """
598
627
  Add a string configuration field to the tool. This field will be used to configure the tool in the plan
599
628
  manager.
@@ -618,8 +647,9 @@ class ToolBase(ABC):
618
647
  )
619
648
  ))
620
649
 
621
- def add_list_config_field(self, field_name: str, display_name: str, description: str, default_value: str,
622
- allowed_values: list[str], direct_edit: bool = False, optional: bool = False) -> None:
650
+ def add_list_config_field(self, field_name: str, display_name: str, description: str,
651
+ default_value: str | None = None, allowed_values: list[str] | None = None,
652
+ direct_edit: bool = False, optional: bool = False) -> None:
623
653
  """
624
654
  Add a list configuration field to the tool. This field will be used to configure the tool in the plan
625
655
  manager.
@@ -648,8 +678,8 @@ class ToolBase(ABC):
648
678
  ))
649
679
 
650
680
  def add_multi_list_config_field(self, field_name: str, display_name: str, description: str,
651
- default_value: list[str], allowed_values: list[str], direct_edit: bool = False,
652
- optional: bool = False) -> None:
681
+ default_value: list[str] | None = None, allowed_values: list[str] | None = None,
682
+ direct_edit: bool = False, optional: bool = False) -> None:
653
683
  """
654
684
  Add a multi-select list configuration field to the tool. This field will be used to configure the tool in the
655
685
  plan manager.
@@ -671,7 +701,7 @@ class ToolBase(ABC):
671
701
  required=not optional,
672
702
  editable=True,
673
703
  selection_properties=SelectionPropertiesPbo(
674
- default_value=",".join(default_value),
704
+ default_value=",".join(default_value) if default_value else None,
675
705
  static_list_values=allowed_values,
676
706
  multi_select=True,
677
707
  direct_edit=direct_edit,
@@ -817,6 +847,15 @@ class ToolBase(ABC):
817
847
  self.logs.append(f"EXCEPTION: {self._name}: {message} - {e}")
818
848
  self.logger.error(f"{message}\n{traceback.format_exc()}")
819
849
 
850
+ def get_input_name(self, index: int = 0) -> str | None:
851
+ """
852
+ Get the name of the input from the request object.
853
+
854
+ :param index: The index of the input to parse. Defaults to 0. Used for tools that accept multiple inputs.
855
+ :return: The name of the input from the request object, or None if no name is set.
856
+ """
857
+ return self.request.input[index].item_container.container_name
858
+
820
859
  def get_input_binary(self, index: int = 0) -> list[bytes]:
821
860
  """
822
861
  Get the binary data from the request object.
@@ -0,0 +1,49 @@
1
+ import os
2
+ import shutil
3
+ import tempfile
4
+
5
+
6
+ # FR-47422: Created class.
7
+ class TempFileHandler:
8
+ """
9
+ A utility class to manage temporary files and directories.
10
+ """
11
+ directories: list[str]
12
+ files: list[str]
13
+
14
+ def __init__(self) -> None:
15
+ self.directories = []
16
+ self.files = []
17
+
18
+ def create_temp_directory(self) -> str:
19
+ """
20
+ :return: The path to a newly created temporary directory.
21
+ """
22
+ directory: str = tempfile.mkdtemp()
23
+ self.directories.append(directory)
24
+ return directory
25
+
26
+ def create_temp_file(self, data: str | bytes, suffix: str = "") -> str:
27
+ """
28
+ :param data: The data to write to the temporary file.
29
+ :param suffix: An optional suffix for the temporary file.
30
+ :return: The path to a newly created temporary file containing the provided data.
31
+ """
32
+ mode: str = 'w' if isinstance(data, str) else 'wb'
33
+ with tempfile.NamedTemporaryFile(mode=mode, suffix=suffix, delete=False) as tmp_file:
34
+ tmp_file.write(data)
35
+ file_path: str = tmp_file.name
36
+ self.files.append(file_path)
37
+ return file_path
38
+
39
+ def cleanup(self) -> None:
40
+ """
41
+ Delete all temporary files and directories created by this handler.
42
+ """
43
+ for directory in self.directories:
44
+ if os.path.exists(directory):
45
+ shutil.rmtree(directory)
46
+
47
+ for file_path in self.files:
48
+ if os.path.exists(file_path):
49
+ os.remove(file_path)