sapiopycommons 2025.7.18a614__tar.gz → 2025.7.21a626__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 (115) hide show
  1. {sapiopycommons-2025.7.18a614 → sapiopycommons-2025.7.21a626}/PKG-INFO +2 -2
  2. {sapiopycommons-2025.7.18a614 → sapiopycommons-2025.7.21a626}/pyproject.toml +2 -2
  3. {sapiopycommons-2025.7.18a614 → sapiopycommons-2025.7.21a626}/src/sapiopycommons/ai/api/plan/tool/proto/tool_pb2.py +16 -14
  4. {sapiopycommons-2025.7.18a614 → sapiopycommons-2025.7.21a626}/src/sapiopycommons/ai/api/plan/tool/proto/tool_pb2.pyi +22 -4
  5. {sapiopycommons-2025.7.18a614 → sapiopycommons-2025.7.21a626}/src/sapiopycommons/ai/protobuf_utils.py +1 -0
  6. {sapiopycommons-2025.7.18a614 → sapiopycommons-2025.7.21a626}/src/sapiopycommons/ai/test_client.py +1 -0
  7. {sapiopycommons-2025.7.18a614 → sapiopycommons-2025.7.21a626}/src/sapiopycommons/ai/tool_service_base.py +148 -72
  8. {sapiopycommons-2025.7.18a614 → sapiopycommons-2025.7.21a626}/src/sapiopycommons/callbacks/callback_util.py +665 -332
  9. {sapiopycommons-2025.7.18a614 → sapiopycommons-2025.7.21a626}/src/sapiopycommons/callbacks/field_builder.py +2 -0
  10. {sapiopycommons-2025.7.18a614 → sapiopycommons-2025.7.21a626}/src/sapiopycommons/chem/IndigoMolecules.py +29 -1
  11. {sapiopycommons-2025.7.18a614 → sapiopycommons-2025.7.21a626}/src/sapiopycommons/chem/Molecules.py +3 -3
  12. {sapiopycommons-2025.7.18a614 → sapiopycommons-2025.7.21a626}/src/sapiopycommons/customreport/auto_pagers.py +26 -1
  13. {sapiopycommons-2025.7.18a614 → sapiopycommons-2025.7.21a626}/src/sapiopycommons/customreport/term_builder.py +1 -1
  14. {sapiopycommons-2025.7.18a614 → sapiopycommons-2025.7.21a626}/src/sapiopycommons/datatype/pseudo_data_types.py +349 -326
  15. sapiopycommons-2025.7.21a626/src/sapiopycommons/eln/experiment_cache.py +188 -0
  16. {sapiopycommons-2025.7.18a614 → sapiopycommons-2025.7.21a626}/src/sapiopycommons/eln/experiment_handler.py +408 -767
  17. {sapiopycommons-2025.7.18a614 → sapiopycommons-2025.7.21a626}/src/sapiopycommons/eln/experiment_report_util.py +11 -6
  18. sapiopycommons-2025.7.21a626/src/sapiopycommons/eln/experiment_step_factory.py +476 -0
  19. {sapiopycommons-2025.7.18a614 → sapiopycommons-2025.7.21a626}/src/sapiopycommons/eln/plate_designer.py +7 -2
  20. sapiopycommons-2025.7.21a626/src/sapiopycommons/eln/step_creation.py +236 -0
  21. {sapiopycommons-2025.7.18a614 → sapiopycommons-2025.7.21a626}/src/sapiopycommons/files/file_util.py +7 -5
  22. {sapiopycommons-2025.7.18a614 → sapiopycommons-2025.7.21a626}/src/sapiopycommons/general/accession_service.py +2 -2
  23. {sapiopycommons-2025.7.18a614 → sapiopycommons-2025.7.21a626}/src/sapiopycommons/general/aliases.py +3 -1
  24. {sapiopycommons-2025.7.18a614 → sapiopycommons-2025.7.21a626}/src/sapiopycommons/general/audit_log.py +7 -0
  25. {sapiopycommons-2025.7.18a614 → sapiopycommons-2025.7.21a626}/src/sapiopycommons/general/custom_report_util.py +12 -0
  26. sapiopycommons-2025.7.21a626/src/sapiopycommons/general/data_structure_util.py +115 -0
  27. {sapiopycommons-2025.7.18a614 → sapiopycommons-2025.7.21a626}/src/sapiopycommons/processtracking/custom_workflow_handler.py +11 -1
  28. {sapiopycommons-2025.7.18a614 → sapiopycommons-2025.7.21a626}/src/sapiopycommons/processtracking/endpoints.py +27 -0
  29. {sapiopycommons-2025.7.18a614 → sapiopycommons-2025.7.21a626}/src/sapiopycommons/recordmodel/record_handler.py +783 -389
  30. {sapiopycommons-2025.7.18a614 → sapiopycommons-2025.7.21a626}/src/sapiopycommons/rules/eln_rule_handler.py +8 -1
  31. {sapiopycommons-2025.7.18a614 → sapiopycommons-2025.7.21a626}/src/sapiopycommons/rules/on_save_rule_handler.py +8 -1
  32. {sapiopycommons-2025.7.18a614 → sapiopycommons-2025.7.21a626}/src/sapiopycommons/webhook/webhook_handlers.py +9 -4
  33. {sapiopycommons-2025.7.18a614 → sapiopycommons-2025.7.21a626}/src/sapiopycommons/webhook/webservice_handlers.py +2 -2
  34. {sapiopycommons-2025.7.18a614 → sapiopycommons-2025.7.21a626}/.gitignore +0 -0
  35. {sapiopycommons-2025.7.18a614 → sapiopycommons-2025.7.21a626}/LICENSE +0 -0
  36. {sapiopycommons-2025.7.18a614 → sapiopycommons-2025.7.21a626}/README.md +0 -0
  37. {sapiopycommons-2025.7.18a614 → sapiopycommons-2025.7.21a626}/src/sapiopycommons/__init__.py +0 -0
  38. {sapiopycommons-2025.7.18a614 → sapiopycommons-2025.7.21a626}/src/sapiopycommons/ai/__init__.py +0 -0
  39. {sapiopycommons-2025.7.18a614 → sapiopycommons-2025.7.21a626}/src/sapiopycommons/ai/api/fielddefinitions/proto/fields_pb2.py +0 -0
  40. {sapiopycommons-2025.7.18a614 → sapiopycommons-2025.7.21a626}/src/sapiopycommons/ai/api/fielddefinitions/proto/fields_pb2.pyi +0 -0
  41. {sapiopycommons-2025.7.18a614 → sapiopycommons-2025.7.21a626}/src/sapiopycommons/ai/api/fielddefinitions/proto/fields_pb2_grpc.py +0 -0
  42. {sapiopycommons-2025.7.18a614 → sapiopycommons-2025.7.21a626}/src/sapiopycommons/ai/api/fielddefinitions/proto/velox_field_def_pb2.py +0 -0
  43. {sapiopycommons-2025.7.18a614 → sapiopycommons-2025.7.21a626}/src/sapiopycommons/ai/api/fielddefinitions/proto/velox_field_def_pb2.pyi +0 -0
  44. {sapiopycommons-2025.7.18a614 → sapiopycommons-2025.7.21a626}/src/sapiopycommons/ai/api/fielddefinitions/proto/velox_field_def_pb2_grpc.py +0 -0
  45. {sapiopycommons-2025.7.18a614 → sapiopycommons-2025.7.21a626}/src/sapiopycommons/ai/api/plan/proto/step_output_pb2.py +0 -0
  46. {sapiopycommons-2025.7.18a614 → sapiopycommons-2025.7.21a626}/src/sapiopycommons/ai/api/plan/proto/step_output_pb2.pyi +0 -0
  47. {sapiopycommons-2025.7.18a614 → sapiopycommons-2025.7.21a626}/src/sapiopycommons/ai/api/plan/proto/step_output_pb2_grpc.py +0 -0
  48. {sapiopycommons-2025.7.18a614 → sapiopycommons-2025.7.21a626}/src/sapiopycommons/ai/api/plan/proto/step_pb2.py +0 -0
  49. {sapiopycommons-2025.7.18a614 → sapiopycommons-2025.7.21a626}/src/sapiopycommons/ai/api/plan/proto/step_pb2.pyi +0 -0
  50. {sapiopycommons-2025.7.18a614 → sapiopycommons-2025.7.21a626}/src/sapiopycommons/ai/api/plan/proto/step_pb2_grpc.py +0 -0
  51. {sapiopycommons-2025.7.18a614 → sapiopycommons-2025.7.21a626}/src/sapiopycommons/ai/api/plan/script/proto/script_pb2.py +0 -0
  52. {sapiopycommons-2025.7.18a614 → sapiopycommons-2025.7.21a626}/src/sapiopycommons/ai/api/plan/script/proto/script_pb2.pyi +0 -0
  53. {sapiopycommons-2025.7.18a614 → sapiopycommons-2025.7.21a626}/src/sapiopycommons/ai/api/plan/script/proto/script_pb2_grpc.py +0 -0
  54. {sapiopycommons-2025.7.18a614 → sapiopycommons-2025.7.21a626}/src/sapiopycommons/ai/api/plan/tool/proto/entry_pb2.py +0 -0
  55. {sapiopycommons-2025.7.18a614 → sapiopycommons-2025.7.21a626}/src/sapiopycommons/ai/api/plan/tool/proto/entry_pb2.pyi +0 -0
  56. {sapiopycommons-2025.7.18a614 → sapiopycommons-2025.7.21a626}/src/sapiopycommons/ai/api/plan/tool/proto/entry_pb2_grpc.py +0 -0
  57. {sapiopycommons-2025.7.18a614 → sapiopycommons-2025.7.21a626}/src/sapiopycommons/ai/api/plan/tool/proto/tool_pb2_grpc.py +0 -0
  58. {sapiopycommons-2025.7.18a614 → sapiopycommons-2025.7.21a626}/src/sapiopycommons/ai/api/session/proto/sapio_conn_info_pb2.py +0 -0
  59. {sapiopycommons-2025.7.18a614 → sapiopycommons-2025.7.21a626}/src/sapiopycommons/ai/api/session/proto/sapio_conn_info_pb2.pyi +0 -0
  60. {sapiopycommons-2025.7.18a614 → sapiopycommons-2025.7.21a626}/src/sapiopycommons/ai/api/session/proto/sapio_conn_info_pb2_grpc.py +0 -0
  61. {sapiopycommons-2025.7.18a614 → sapiopycommons-2025.7.21a626}/src/sapiopycommons/callbacks/__init__.py +0 -0
  62. {sapiopycommons-2025.7.18a614 → sapiopycommons-2025.7.21a626}/src/sapiopycommons/chem/__init__.py +0 -0
  63. {sapiopycommons-2025.7.18a614 → sapiopycommons-2025.7.21a626}/src/sapiopycommons/customreport/__init__.py +0 -0
  64. {sapiopycommons-2025.7.18a614 → sapiopycommons-2025.7.21a626}/src/sapiopycommons/customreport/column_builder.py +0 -0
  65. {sapiopycommons-2025.7.18a614 → sapiopycommons-2025.7.21a626}/src/sapiopycommons/customreport/custom_report_builder.py +0 -0
  66. {sapiopycommons-2025.7.18a614 → sapiopycommons-2025.7.21a626}/src/sapiopycommons/datatype/__init__.py +0 -0
  67. {sapiopycommons-2025.7.18a614 → sapiopycommons-2025.7.21a626}/src/sapiopycommons/datatype/attachment_util.py +0 -0
  68. {sapiopycommons-2025.7.18a614 → sapiopycommons-2025.7.21a626}/src/sapiopycommons/datatype/data_fields.py +0 -0
  69. {sapiopycommons-2025.7.18a614 → sapiopycommons-2025.7.21a626}/src/sapiopycommons/eln/__init__.py +0 -0
  70. {sapiopycommons-2025.7.18a614 → sapiopycommons-2025.7.21a626}/src/sapiopycommons/eln/experiment_tags.py +0 -0
  71. {sapiopycommons-2025.7.18a614 → sapiopycommons-2025.7.21a626}/src/sapiopycommons/files/__init__.py +0 -0
  72. {sapiopycommons-2025.7.18a614 → sapiopycommons-2025.7.21a626}/src/sapiopycommons/files/complex_data_loader.py +0 -0
  73. {sapiopycommons-2025.7.18a614 → sapiopycommons-2025.7.21a626}/src/sapiopycommons/files/file_bridge.py +0 -0
  74. {sapiopycommons-2025.7.18a614 → sapiopycommons-2025.7.21a626}/src/sapiopycommons/files/file_bridge_handler.py +0 -0
  75. {sapiopycommons-2025.7.18a614 → sapiopycommons-2025.7.21a626}/src/sapiopycommons/files/file_data_handler.py +0 -0
  76. {sapiopycommons-2025.7.18a614 → sapiopycommons-2025.7.21a626}/src/sapiopycommons/files/file_validator.py +0 -0
  77. {sapiopycommons-2025.7.18a614 → sapiopycommons-2025.7.21a626}/src/sapiopycommons/files/file_writer.py +0 -0
  78. {sapiopycommons-2025.7.18a614 → sapiopycommons-2025.7.21a626}/src/sapiopycommons/flowcyto/flow_cyto.py +0 -0
  79. {sapiopycommons-2025.7.18a614 → sapiopycommons-2025.7.21a626}/src/sapiopycommons/flowcyto/flowcyto_data.py +0 -0
  80. {sapiopycommons-2025.7.18a614 → sapiopycommons-2025.7.21a626}/src/sapiopycommons/general/__init__.py +0 -0
  81. {sapiopycommons-2025.7.18a614 → sapiopycommons-2025.7.21a626}/src/sapiopycommons/general/directive_util.py +0 -0
  82. {sapiopycommons-2025.7.18a614 → sapiopycommons-2025.7.21a626}/src/sapiopycommons/general/exceptions.py +0 -0
  83. {sapiopycommons-2025.7.18a614 → sapiopycommons-2025.7.21a626}/src/sapiopycommons/general/html_formatter.py +0 -0
  84. {sapiopycommons-2025.7.18a614 → sapiopycommons-2025.7.21a626}/src/sapiopycommons/general/popup_util.py +0 -0
  85. {sapiopycommons-2025.7.18a614 → sapiopycommons-2025.7.21a626}/src/sapiopycommons/general/sapio_links.py +0 -0
  86. {sapiopycommons-2025.7.18a614 → sapiopycommons-2025.7.21a626}/src/sapiopycommons/general/storage_util.py +0 -0
  87. {sapiopycommons-2025.7.18a614 → sapiopycommons-2025.7.21a626}/src/sapiopycommons/general/time_util.py +0 -0
  88. {sapiopycommons-2025.7.18a614 → sapiopycommons-2025.7.21a626}/src/sapiopycommons/multimodal/multimodal.py +0 -0
  89. {sapiopycommons-2025.7.18a614 → sapiopycommons-2025.7.21a626}/src/sapiopycommons/multimodal/multimodal_data.py +0 -0
  90. {sapiopycommons-2025.7.18a614 → sapiopycommons-2025.7.21a626}/src/sapiopycommons/processtracking/__init__.py +0 -0
  91. {sapiopycommons-2025.7.18a614 → sapiopycommons-2025.7.21a626}/src/sapiopycommons/recordmodel/__init__.py +0 -0
  92. {sapiopycommons-2025.7.18a614 → sapiopycommons-2025.7.21a626}/src/sapiopycommons/rules/__init__.py +0 -0
  93. {sapiopycommons-2025.7.18a614 → sapiopycommons-2025.7.21a626}/src/sapiopycommons/samples/aliquot.py +0 -0
  94. {sapiopycommons-2025.7.18a614 → sapiopycommons-2025.7.21a626}/src/sapiopycommons/sftpconnect/__init__.py +0 -0
  95. {sapiopycommons-2025.7.18a614 → sapiopycommons-2025.7.21a626}/src/sapiopycommons/sftpconnect/sftp_builder.py +0 -0
  96. {sapiopycommons-2025.7.18a614 → sapiopycommons-2025.7.21a626}/src/sapiopycommons/webhook/__init__.py +0 -0
  97. {sapiopycommons-2025.7.18a614 → sapiopycommons-2025.7.21a626}/src/sapiopycommons/webhook/webhook_context.py +0 -0
  98. {sapiopycommons-2025.7.18a614 → sapiopycommons-2025.7.21a626}/tests/AF-A0A009IHW8-F1-model_v4.cif +0 -0
  99. {sapiopycommons-2025.7.18a614 → sapiopycommons-2025.7.21a626}/tests/_do_not_add_init_py_here +0 -0
  100. {sapiopycommons-2025.7.18a614 → sapiopycommons-2025.7.21a626}/tests/accession_test.py +0 -0
  101. {sapiopycommons-2025.7.18a614 → sapiopycommons-2025.7.21a626}/tests/aliquot_test.py +0 -0
  102. {sapiopycommons-2025.7.18a614 → sapiopycommons-2025.7.21a626}/tests/bio_reg_test.py +0 -0
  103. {sapiopycommons-2025.7.18a614 → sapiopycommons-2025.7.21a626}/tests/chem_test.py +0 -0
  104. {sapiopycommons-2025.7.18a614 → sapiopycommons-2025.7.21a626}/tests/chem_test_curation_queue.py +0 -0
  105. {sapiopycommons-2025.7.18a614 → sapiopycommons-2025.7.21a626}/tests/curation_queue_test.sdf +0 -0
  106. {sapiopycommons-2025.7.18a614 → sapiopycommons-2025.7.21a626}/tests/data_type_models.py +0 -0
  107. {sapiopycommons-2025.7.18a614 → sapiopycommons-2025.7.21a626}/tests/flowcyto/101_DEN084Y5_15_E01_008_clean.fcs +0 -0
  108. {sapiopycommons-2025.7.18a614 → sapiopycommons-2025.7.21a626}/tests/flowcyto/101_DEN084Y5_15_E03_009_clean.fcs +0 -0
  109. {sapiopycommons-2025.7.18a614 → sapiopycommons-2025.7.21a626}/tests/flowcyto/101_DEN084Y5_15_E05_010_clean.fcs +0 -0
  110. {sapiopycommons-2025.7.18a614 → sapiopycommons-2025.7.21a626}/tests/flowcyto/8_color_ICS.wsp +0 -0
  111. {sapiopycommons-2025.7.18a614 → sapiopycommons-2025.7.21a626}/tests/flowcyto/COVID19_W_001_O.fcs +0 -0
  112. {sapiopycommons-2025.7.18a614 → sapiopycommons-2025.7.21a626}/tests/flowcyto_test.py +0 -0
  113. {sapiopycommons-2025.7.18a614 → sapiopycommons-2025.7.21a626}/tests/kappa.chains.fasta +0 -0
  114. {sapiopycommons-2025.7.18a614 → sapiopycommons-2025.7.21a626}/tests/mafft_test.py +0 -0
  115. {sapiopycommons-2025.7.18a614 → sapiopycommons-2025.7.21a626}/tests/test.gb +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: sapiopycommons
3
- Version: 2025.7.18a614
3
+ Version: 2025.7.21a626
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>
@@ -17,7 +17,7 @@ Classifier: Topic :: Scientific/Engineering :: Bio-Informatics
17
17
  Classifier: Topic :: Software Development :: Libraries :: Python Modules
18
18
  Requires-Python: >=3.10
19
19
  Requires-Dist: databind>=4.5
20
- Requires-Dist: sapiopylib>=2024.5.24.210
20
+ Requires-Dist: sapiopylib>=2025.4.17.264
21
21
  Description-Content-Type: text/markdown
22
22
 
23
23
 
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "sapiopycommons"
7
- version='2025.07.18a614'
7
+ version='2025.07.21a626'
8
8
  authors = [
9
9
  { name="Jonathan Steck", email="jsteck@sapiosciences.com" },
10
10
  { name="Yechen Qiao", email="yqiao@sapiosciences.com" },
@@ -14,7 +14,7 @@ license = "MPL-2.0"
14
14
  readme = "README.md"
15
15
  requires-python = ">=3.10"
16
16
  dependencies = [
17
- 'sapiopylib>=2024.5.24.210', 'databind>=4.5'
17
+ 'sapiopylib>=2025.4.17.264', 'databind>=4.5'
18
18
  ]
19
19
  classifiers = [
20
20
  "Intended Audience :: Developers",
@@ -34,7 +34,7 @@ from sapiopycommons.ai.api.plan.tool.proto.entry_pb2 import *
34
34
  from sapiopycommons.ai.api.plan.proto.step_pb2 import *
35
35
  from sapiopycommons.ai.api.session.proto.sapio_conn_info_pb2 import *
36
36
 
37
- DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n0sapiopycommons/ai/api/plan/tool/proto/tool.proto\x1a\x39sapiopycommons/ai/api/fielddefinitions/proto/fields.proto\x1a\x42sapiopycommons/ai/api/fielddefinitions/proto/velox_field_def.proto\x1a\x31sapiopycommons/ai/api/plan/tool/proto/entry.proto\x1a+sapiopycommons/ai/api/plan/proto/step.proto\x1a\x39sapiopycommons/ai/api/session/proto/sapio_conn_info.proto\"R\n\x13\x45xampleContainerPbo\x12\x16\n\x0ctext_example\x18\x01 \x01(\tH\x00\x12\x18\n\x0e\x62inary_example\x18\x02 \x01(\x0cH\x00\x42\t\n\x07\x65xample\"\xe9\x01\n\x13ToolIoConfigBasePbo\x12\x14\n\x0c\x63ontent_type\x18\x01 \x01(\t\x12\x11\n\tio_number\x18\x02 \x01(\x05\x12\x14\n\x0c\x64isplay_name\x18\x03 \x01(\t\x12\x13\n\x0b\x64\x65scription\x18\x04 \x01(\t\x12\x1e\n\x16\x64\x65precated_old_example\x18\x05 \x01(\t\x12/\n\x11structure_example\x18\x06 \x01(\x0b\x32\x14.ExampleContainerPbo\x12-\n\x0ftesting_example\x18\x07 \x01(\x0b\x32\x14.ExampleContainerPbo\"\xd9\x02\n\x13ToolInputDetailsPbo\x12)\n\x0b\x62\x61se_config\x18\x01 \x01(\x0b\x32\x14.ToolIoConfigBasePbo\x12\x12\n\nvalidation\x18\x02 \x01(\t\x12\x1c\n\x0fmin_input_count\x18\x03 \x01(\x05H\x00\x88\x01\x01\x12\x1c\n\x0fmax_input_count\x18\x04 \x01(\x05H\x01\x88\x01\x01\x12\r\n\x05paged\x18\x05 \x01(\x08\x12\x1a\n\rmin_page_size\x18\x06 \x01(\x05H\x02\x88\x01\x01\x12\x1a\n\rmax_page_size\x18\x07 \x01(\x05H\x03\x88\x01\x01\x12\x1e\n\x11max_request_bytes\x18\x08 \x01(\x05H\x04\x88\x01\x01\x42\x12\n\x10_min_input_countB\x12\n\x10_max_input_countB\x10\n\x0e_min_page_sizeB\x10\n\x0e_max_page_sizeB\x14\n\x12_max_request_bytes\"A\n\x14ToolOutputDetailsPbo\x12)\n\x0b\x62\x61se_config\x18\x01 \x01(\x0b\x32\x14.ToolIoConfigBasePbo\"\xab\x03\n\x15ProcessStepRequestPbo\x12+\n\nsapio_user\x18\x01 \x01(\x0b\x32\x17.SapioConnectionInfoPbo\x12\x11\n\ttool_name\x18\x02 \x01(\t\x12\x18\n\x10plan_instance_id\x18\x03 \x01(\x03\x12\x18\n\x10step_instance_id\x18\x04 \x01(\x03\x12\x15\n\rinvocation_id\x18\x05 \x01(\x03\x12%\n\rinput_configs\x18\x06 \x03(\x0b\x32\x0e.StepIoInfoPbo\x12&\n\x0eoutput_configs\x18\x07 \x03(\x0b\x32\x0e.StepIoInfoPbo\x12J\n\x13\x63onfig_field_values\x18\x08 \x03(\x0b\x32-.ProcessStepRequestPbo.ConfigFieldValuesEntry\x12\"\n\x05input\x18\xff\x0f \x03(\x0b\x32\x12.StepInputBatchPbo\x1aH\n\x16\x43onfigFieldValuesEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\x1d\n\x05value\x18\x02 \x01(\x0b\x32\x0e.FieldValuePbo:\x02\x38\x01\"u\n\x16ProcessStepResponsePbo\x12\'\n\x0bnew_records\x18\xfd\x0f \x03(\x0b\x32\x11.FieldValueMapPbo\x12\x0c\n\x03log\x18\xfe\x0f \x03(\t\x12$\n\x06output\x18\xff\x0f \x03(\x0b\x32\x13.StepOutputBatchPbo\"I\n\x15ToolDetailsRequestPbo\x12\x30\n\x0fsapio_conn_info\x18\x01 \x01(\x0b\x32\x17.SapioConnectionInfoPbo\"\xd8\x01\n\x0eToolDetailsPbo\x12\x0c\n\x04name\x18\x02 \x01(\t\x12\x13\n\x0b\x64\x65scription\x18\x03 \x01(\t\x12\x1d\n\x15output_data_type_name\x18\x04 \x01(\t\x12+\n\rinput_configs\x18\x05 \x03(\x0b\x32\x14.ToolInputDetailsPbo\x12-\n\x0eoutput_configs\x18\x06 \x03(\x0b\x32\x15.ToolOutputDetailsPbo\x12(\n\rconfig_fields\x18\x07 \x03(\x0b\x32\x11.VeloxFieldDefPbo\"_\n\x16ToolDetailsResponsePbo\x12\x1e\n\x16tool_framework_version\x18\x01 \x01(\x05\x12%\n\x0ctool_details\x18\x02 \x03(\x0b\x32\x0f.ToolDetailsPbo2\x90\x01\n\x0bToolService\x12\x41\n\x0eGetToolDetails\x12\x16.ToolDetailsRequestPbo\x1a\x17.ToolDetailsResponsePbo\x12>\n\x0bProcessData\x12\x16.ProcessStepRequestPbo\x1a\x17.ProcessStepResponsePboB!\n\x1d\x63om.velox.api.plan.tool.protoP\x01P\x00P\x01P\x02P\x03P\x04\x62\x06proto3')
37
+ DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n0sapiopycommons/ai/api/plan/tool/proto/tool.proto\x1a\x39sapiopycommons/ai/api/fielddefinitions/proto/fields.proto\x1a\x42sapiopycommons/ai/api/fielddefinitions/proto/velox_field_def.proto\x1a\x31sapiopycommons/ai/api/plan/tool/proto/entry.proto\x1a+sapiopycommons/ai/api/plan/proto/step.proto\x1a\x39sapiopycommons/ai/api/session/proto/sapio_conn_info.proto\"R\n\x13\x45xampleContainerPbo\x12\x16\n\x0ctext_example\x18\x01 \x01(\tH\x00\x12\x18\n\x0e\x62inary_example\x18\x02 \x01(\x0cH\x00\x42\t\n\x07\x65xample\"\xe9\x01\n\x13ToolIoConfigBasePbo\x12\x14\n\x0c\x63ontent_type\x18\x01 \x01(\t\x12\x11\n\tio_number\x18\x02 \x01(\x05\x12\x14\n\x0c\x64isplay_name\x18\x03 \x01(\t\x12\x13\n\x0b\x64\x65scription\x18\x04 \x01(\t\x12\x1e\n\x16\x64\x65precated_old_example\x18\x05 \x01(\t\x12/\n\x11structure_example\x18\x06 \x01(\x0b\x32\x14.ExampleContainerPbo\x12-\n\x0ftesting_example\x18\x07 \x01(\x0b\x32\x14.ExampleContainerPbo\"\xd9\x02\n\x13ToolInputDetailsPbo\x12)\n\x0b\x62\x61se_config\x18\x01 \x01(\x0b\x32\x14.ToolIoConfigBasePbo\x12\x12\n\nvalidation\x18\x02 \x01(\t\x12\x1c\n\x0fmin_input_count\x18\x03 \x01(\x05H\x00\x88\x01\x01\x12\x1c\n\x0fmax_input_count\x18\x04 \x01(\x05H\x01\x88\x01\x01\x12\r\n\x05paged\x18\x05 \x01(\x08\x12\x1a\n\rmin_page_size\x18\x06 \x01(\x05H\x02\x88\x01\x01\x12\x1a\n\rmax_page_size\x18\x07 \x01(\x05H\x03\x88\x01\x01\x12\x1e\n\x11max_request_bytes\x18\x08 \x01(\x05H\x04\x88\x01\x01\x42\x12\n\x10_min_input_countB\x12\n\x10_max_input_countB\x10\n\x0e_min_page_sizeB\x10\n\x0e_max_page_sizeB\x14\n\x12_max_request_bytes\"A\n\x14ToolOutputDetailsPbo\x12)\n\x0b\x62\x61se_config\x18\x01 \x01(\x0b\x32\x14.ToolIoConfigBasePbo\"\xd5\x03\n\x15ProcessStepRequestPbo\x12+\n\nsapio_user\x18\x01 \x01(\x0b\x32\x17.SapioConnectionInfoPbo\x12\x11\n\ttool_name\x18\x02 \x01(\t\x12\x18\n\x10plan_instance_id\x18\x03 \x01(\x03\x12\x18\n\x10step_instance_id\x18\x04 \x01(\x03\x12\x15\n\rinvocation_id\x18\x05 \x01(\x03\x12%\n\rinput_configs\x18\x06 \x03(\x0b\x32\x0e.StepIoInfoPbo\x12&\n\x0eoutput_configs\x18\x07 \x03(\x0b\x32\x0e.StepIoInfoPbo\x12J\n\x13\x63onfig_field_values\x18\x08 \x03(\x0b\x32-.ProcessStepRequestPbo.ConfigFieldValuesEntry\x12\x0f\n\x07\x64ry_run\x18\t \x01(\x08\x12\x17\n\x0fverbose_logging\x18\n \x01(\x08\x12\"\n\x05input\x18\xff\x0f \x03(\x0b\x32\x12.StepInputBatchPbo\x1aH\n\x16\x43onfigFieldValuesEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\x1d\n\x05value\x18\x02 \x01(\x0b\x32\x0e.FieldValuePbo:\x02\x38\x01\"\xbc\x01\n\x16ProcessStepResponsePbo\x12-\n\x06status\x18\x01 \x01(\x0e\x32\x1d.ProcessStepResponseStatusPbo\x12\x16\n\x0estatus_message\x18\x02 \x01(\t\x12\'\n\x0bnew_records\x18\xfd\x0f \x03(\x0b\x32\x11.FieldValueMapPbo\x12\x0c\n\x03log\x18\xfe\x0f \x03(\t\x12$\n\x06output\x18\xff\x0f \x03(\x0b\x32\x13.StepOutputBatchPbo\"I\n\x15ToolDetailsRequestPbo\x12\x30\n\x0fsapio_conn_info\x18\x01 \x01(\x0b\x32\x17.SapioConnectionInfoPbo\"\xd8\x01\n\x0eToolDetailsPbo\x12\x0c\n\x04name\x18\x02 \x01(\t\x12\x13\n\x0b\x64\x65scription\x18\x03 \x01(\t\x12\x1d\n\x15output_data_type_name\x18\x04 \x01(\t\x12+\n\rinput_configs\x18\x05 \x03(\x0b\x32\x14.ToolInputDetailsPbo\x12-\n\x0eoutput_configs\x18\x06 \x03(\x0b\x32\x15.ToolOutputDetailsPbo\x12(\n\rconfig_fields\x18\x07 \x03(\x0b\x32\x11.VeloxFieldDefPbo\"_\n\x16ToolDetailsResponsePbo\x12\x1e\n\x16tool_framework_version\x18\x01 \x01(\x05\x12%\n\x0ctool_details\x18\x02 \x03(\x0b\x32\x0f.ToolDetailsPbo*E\n\x1cProcessStepResponseStatusPbo\x12\x0b\n\x07UNKNOWN\x10\x00\x12\x0b\n\x07SUCCESS\x10\x01\x12\x0b\n\x07\x46\x41ILURE\x10\x02\x32\x90\x01\n\x0bToolService\x12\x41\n\x0eGetToolDetails\x12\x16.ToolDetailsRequestPbo\x1a\x17.ToolDetailsResponsePbo\x12>\n\x0bProcessData\x12\x16.ProcessStepRequestPbo\x1a\x17.ProcessStepResponsePboB!\n\x1d\x63om.velox.api.plan.tool.protoP\x01P\x00P\x01P\x02P\x03P\x04\x62\x06proto3')
38
38
 
39
39
  _globals = globals()
40
40
  _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
@@ -44,6 +44,8 @@ if not _descriptor._USE_C_DESCRIPTORS:
44
44
  _globals['DESCRIPTOR']._serialized_options = b'\n\035com.velox.api.plan.tool.protoP\001'
45
45
  _globals['_PROCESSSTEPREQUESTPBO_CONFIGFIELDVALUESENTRY']._loaded_options = None
46
46
  _globals['_PROCESSSTEPREQUESTPBO_CONFIGFIELDVALUESENTRY']._serialized_options = b'8\001'
47
+ _globals['_PROCESSSTEPRESPONSESTATUSPBO']._serialized_start=2123
48
+ _globals['_PROCESSSTEPRESPONSESTATUSPBO']._serialized_end=2192
47
49
  _globals['_EXAMPLECONTAINERPBO']._serialized_start=334
48
50
  _globals['_EXAMPLECONTAINERPBO']._serialized_end=416
49
51
  _globals['_TOOLIOCONFIGBASEPBO']._serialized_start=419
@@ -53,17 +55,17 @@ if not _descriptor._USE_C_DESCRIPTORS:
53
55
  _globals['_TOOLOUTPUTDETAILSPBO']._serialized_start=1002
54
56
  _globals['_TOOLOUTPUTDETAILSPBO']._serialized_end=1067
55
57
  _globals['_PROCESSSTEPREQUESTPBO']._serialized_start=1070
56
- _globals['_PROCESSSTEPREQUESTPBO']._serialized_end=1497
57
- _globals['_PROCESSSTEPREQUESTPBO_CONFIGFIELDVALUESENTRY']._serialized_start=1425
58
- _globals['_PROCESSSTEPREQUESTPBO_CONFIGFIELDVALUESENTRY']._serialized_end=1497
59
- _globals['_PROCESSSTEPRESPONSEPBO']._serialized_start=1499
60
- _globals['_PROCESSSTEPRESPONSEPBO']._serialized_end=1616
61
- _globals['_TOOLDETAILSREQUESTPBO']._serialized_start=1618
62
- _globals['_TOOLDETAILSREQUESTPBO']._serialized_end=1691
63
- _globals['_TOOLDETAILSPBO']._serialized_start=1694
64
- _globals['_TOOLDETAILSPBO']._serialized_end=1910
65
- _globals['_TOOLDETAILSRESPONSEPBO']._serialized_start=1912
66
- _globals['_TOOLDETAILSRESPONSEPBO']._serialized_end=2007
67
- _globals['_TOOLSERVICE']._serialized_start=2010
68
- _globals['_TOOLSERVICE']._serialized_end=2154
58
+ _globals['_PROCESSSTEPREQUESTPBO']._serialized_end=1539
59
+ _globals['_PROCESSSTEPREQUESTPBO_CONFIGFIELDVALUESENTRY']._serialized_start=1467
60
+ _globals['_PROCESSSTEPREQUESTPBO_CONFIGFIELDVALUESENTRY']._serialized_end=1539
61
+ _globals['_PROCESSSTEPRESPONSEPBO']._serialized_start=1542
62
+ _globals['_PROCESSSTEPRESPONSEPBO']._serialized_end=1730
63
+ _globals['_TOOLDETAILSREQUESTPBO']._serialized_start=1732
64
+ _globals['_TOOLDETAILSREQUESTPBO']._serialized_end=1805
65
+ _globals['_TOOLDETAILSPBO']._serialized_start=1808
66
+ _globals['_TOOLDETAILSPBO']._serialized_end=2024
67
+ _globals['_TOOLDETAILSRESPONSEPBO']._serialized_start=2026
68
+ _globals['_TOOLDETAILSRESPONSEPBO']._serialized_end=2121
69
+ _globals['_TOOLSERVICE']._serialized_start=2195
70
+ _globals['_TOOLSERVICE']._serialized_end=2339
69
71
  # @@protoc_insertion_point(module_scope)
@@ -4,6 +4,7 @@ from sapiopycommons.ai.api.plan.tool.proto import entry_pb2 as _entry_pb2
4
4
  from sapiopycommons.ai.api.plan.proto import step_pb2 as _step_pb2
5
5
  from sapiopycommons.ai.api.session.proto import sapio_conn_info_pb2 as _sapio_conn_info_pb2
6
6
  from google.protobuf.internal import containers as _containers
7
+ from google.protobuf.internal import enum_type_wrapper as _enum_type_wrapper
7
8
  from google.protobuf import descriptor as _descriptor
8
9
  from google.protobuf import message as _message
9
10
  from typing import ClassVar as _ClassVar, Iterable as _Iterable, Mapping as _Mapping, Optional as _Optional, Union as _Union
@@ -110,6 +111,15 @@ IMAGE: _entry_pb2.DataTypePbo
110
111
  SESSION_TOKEN: _sapio_conn_info_pb2.SapioUserSecretTypePbo
111
112
  PASSWORD: _sapio_conn_info_pb2.SapioUserSecretTypePbo
112
113
 
114
+ class ProcessStepResponseStatusPbo(int, metaclass=_enum_type_wrapper.EnumTypeWrapper):
115
+ __slots__ = ()
116
+ UNKNOWN: _ClassVar[ProcessStepResponseStatusPbo]
117
+ SUCCESS: _ClassVar[ProcessStepResponseStatusPbo]
118
+ FAILURE: _ClassVar[ProcessStepResponseStatusPbo]
119
+ UNKNOWN: ProcessStepResponseStatusPbo
120
+ SUCCESS: ProcessStepResponseStatusPbo
121
+ FAILURE: ProcessStepResponseStatusPbo
122
+
113
123
  class ExampleContainerPbo(_message.Message):
114
124
  __slots__ = ("text_example", "binary_example")
115
125
  TEXT_EXAMPLE_FIELD_NUMBER: _ClassVar[int]
@@ -163,7 +173,7 @@ class ToolOutputDetailsPbo(_message.Message):
163
173
  def __init__(self, base_config: _Optional[_Union[ToolIoConfigBasePbo, _Mapping]] = ...) -> None: ...
164
174
 
165
175
  class ProcessStepRequestPbo(_message.Message):
166
- __slots__ = ("sapio_user", "tool_name", "plan_instance_id", "step_instance_id", "invocation_id", "input_configs", "output_configs", "config_field_values", "input")
176
+ __slots__ = ("sapio_user", "tool_name", "plan_instance_id", "step_instance_id", "invocation_id", "input_configs", "output_configs", "config_field_values", "dry_run", "verbose_logging", "input")
167
177
  class ConfigFieldValuesEntry(_message.Message):
168
178
  __slots__ = ("key", "value")
169
179
  KEY_FIELD_NUMBER: _ClassVar[int]
@@ -179,6 +189,8 @@ class ProcessStepRequestPbo(_message.Message):
179
189
  INPUT_CONFIGS_FIELD_NUMBER: _ClassVar[int]
180
190
  OUTPUT_CONFIGS_FIELD_NUMBER: _ClassVar[int]
181
191
  CONFIG_FIELD_VALUES_FIELD_NUMBER: _ClassVar[int]
192
+ DRY_RUN_FIELD_NUMBER: _ClassVar[int]
193
+ VERBOSE_LOGGING_FIELD_NUMBER: _ClassVar[int]
182
194
  INPUT_FIELD_NUMBER: _ClassVar[int]
183
195
  sapio_user: _sapio_conn_info_pb2.SapioConnectionInfoPbo
184
196
  tool_name: str
@@ -188,18 +200,24 @@ class ProcessStepRequestPbo(_message.Message):
188
200
  input_configs: _containers.RepeatedCompositeFieldContainer[_step_pb2.StepIoInfoPbo]
189
201
  output_configs: _containers.RepeatedCompositeFieldContainer[_step_pb2.StepIoInfoPbo]
190
202
  config_field_values: _containers.MessageMap[str, _fields_pb2.FieldValuePbo]
203
+ dry_run: bool
204
+ verbose_logging: bool
191
205
  input: _containers.RepeatedCompositeFieldContainer[_entry_pb2.StepInputBatchPbo]
192
- def __init__(self, sapio_user: _Optional[_Union[_sapio_conn_info_pb2.SapioConnectionInfoPbo, _Mapping]] = ..., tool_name: _Optional[str] = ..., plan_instance_id: _Optional[int] = ..., step_instance_id: _Optional[int] = ..., invocation_id: _Optional[int] = ..., input_configs: _Optional[_Iterable[_Union[_step_pb2.StepIoInfoPbo, _Mapping]]] = ..., output_configs: _Optional[_Iterable[_Union[_step_pb2.StepIoInfoPbo, _Mapping]]] = ..., config_field_values: _Optional[_Mapping[str, _fields_pb2.FieldValuePbo]] = ..., input: _Optional[_Iterable[_Union[_entry_pb2.StepInputBatchPbo, _Mapping]]] = ...) -> None: ...
206
+ def __init__(self, sapio_user: _Optional[_Union[_sapio_conn_info_pb2.SapioConnectionInfoPbo, _Mapping]] = ..., tool_name: _Optional[str] = ..., plan_instance_id: _Optional[int] = ..., step_instance_id: _Optional[int] = ..., invocation_id: _Optional[int] = ..., input_configs: _Optional[_Iterable[_Union[_step_pb2.StepIoInfoPbo, _Mapping]]] = ..., output_configs: _Optional[_Iterable[_Union[_step_pb2.StepIoInfoPbo, _Mapping]]] = ..., config_field_values: _Optional[_Mapping[str, _fields_pb2.FieldValuePbo]] = ..., dry_run: bool = ..., verbose_logging: bool = ..., input: _Optional[_Iterable[_Union[_entry_pb2.StepInputBatchPbo, _Mapping]]] = ...) -> None: ...
193
207
 
194
208
  class ProcessStepResponsePbo(_message.Message):
195
- __slots__ = ("new_records", "log", "output")
209
+ __slots__ = ("status", "status_message", "new_records", "log", "output")
210
+ STATUS_FIELD_NUMBER: _ClassVar[int]
211
+ STATUS_MESSAGE_FIELD_NUMBER: _ClassVar[int]
196
212
  NEW_RECORDS_FIELD_NUMBER: _ClassVar[int]
197
213
  LOG_FIELD_NUMBER: _ClassVar[int]
198
214
  OUTPUT_FIELD_NUMBER: _ClassVar[int]
215
+ status: ProcessStepResponseStatusPbo
216
+ status_message: str
199
217
  new_records: _containers.RepeatedCompositeFieldContainer[_fields_pb2.FieldValueMapPbo]
200
218
  log: _containers.RepeatedScalarFieldContainer[str]
201
219
  output: _containers.RepeatedCompositeFieldContainer[_entry_pb2.StepOutputBatchPbo]
202
- def __init__(self, new_records: _Optional[_Iterable[_Union[_fields_pb2.FieldValueMapPbo, _Mapping]]] = ..., log: _Optional[_Iterable[str]] = ..., output: _Optional[_Iterable[_Union[_entry_pb2.StepOutputBatchPbo, _Mapping]]] = ...) -> None: ...
220
+ def __init__(self, status: _Optional[_Union[ProcessStepResponseStatusPbo, str]] = ..., status_message: _Optional[str] = ..., new_records: _Optional[_Iterable[_Union[_fields_pb2.FieldValueMapPbo, _Mapping]]] = ..., log: _Optional[_Iterable[str]] = ..., output: _Optional[_Iterable[_Union[_entry_pb2.StepOutputBatchPbo, _Mapping]]] = ...) -> None: ...
203
221
 
204
222
  class ToolDetailsRequestPbo(_message.Message):
205
223
  __slots__ = ("sapio_conn_info",)
@@ -22,6 +22,7 @@ from sapiopycommons.ai.api.plan.tool.proto.entry_pb2 import DataTypePbo
22
22
  from sapiopycommons.general.aliases import FieldValue
23
23
 
24
24
 
25
+ # FR-47422: Created class.
25
26
  class ProtobufUtils:
26
27
  @staticmethod
27
28
  def content_type_str(content_type: DataTypePbo) -> str:
@@ -13,6 +13,7 @@ from sapiopycommons.ai.api.plan.tool.proto.tool_pb2_grpc import ToolServiceStub
13
13
  from sapiopycommons.ai.api.session.proto.sapio_conn_info_pb2 import SapioConnectionInfoPbo, SapioUserSecretTypePbo
14
14
 
15
15
 
16
+ # FR-47422: Created class.
16
17
  class ToolOutput:
17
18
  """
18
19
  A class for holding the output of a TestClient that calls a ToolService. ToolOutput objects an be
@@ -1,7 +1,10 @@
1
1
  from __future__ import annotations
2
2
 
3
+ import base64
4
+ import io
3
5
  import json
4
6
  import logging
7
+ import re
5
8
  import traceback
6
9
  from abc import abstractmethod, ABC
7
10
  from logging import Logger
@@ -19,13 +22,15 @@ from sapiopycommons.ai.api.plan.tool.proto.entry_pb2 import StepOutputBatchPbo,
19
22
  StepJsonContainerPbo, StepTextContainerPbo, StepInputBatchPbo
20
23
  from sapiopycommons.ai.api.plan.tool.proto.tool_pb2 import ToolDetailsRequestPbo, ToolDetailsResponsePbo, \
21
24
  ToolDetailsPbo, ProcessStepRequestPbo, ProcessStepResponsePbo, ToolOutputDetailsPbo, ToolIoConfigBasePbo, \
22
- ToolInputDetailsPbo, ExampleContainerPbo
25
+ ToolInputDetailsPbo, ExampleContainerPbo, ProcessStepResponseStatusPbo
23
26
  from sapiopycommons.ai.api.plan.tool.proto.tool_pb2_grpc import ToolServiceServicer
24
27
  from sapiopycommons.ai.api.session.proto.sapio_conn_info_pb2 import SapioUserSecretTypePbo, SapioConnectionInfoPbo
25
28
  from sapiopycommons.ai.protobuf_utils import ProtobufUtils
29
+ from sapiopycommons.files.file_util import FileUtil
26
30
  from sapiopycommons.general.aliases import FieldMap, FieldValue
27
31
 
28
32
 
33
+ # FR-47422: Created classes.
29
34
  class SapioToolResult(ABC):
30
35
  """
31
36
  A class representing a result from a Sapio tool. Instantiate one of the subclasses to create a result object.
@@ -194,35 +199,43 @@ class ToolServiceBase(ToolServiceServicer, ABC):
194
199
  def GetToolDetails(self, request: ToolDetailsRequestPbo, context: ServicerContext) -> ToolDetailsResponsePbo:
195
200
  try:
196
201
  # Get the tool details from the registered tools.
197
- details: list[ToolDetailsPbo] = self.get_details()
202
+ details: list[ToolDetailsPbo] = []
203
+ for tool in self._initialize_tools():
204
+ details.append(tool.to_pbo())
198
205
  return ToolDetailsResponsePbo(tool_framework_version=self.tool_version(), tool_details=details)
199
206
  except Exception:
200
- # TODO: This response doesn't even allow logs. What should we do if an exception occurs in this case?
207
+ # Woe to you if you somehow cause an exception to be raised when just initializing your tools.
208
+ # There's no way to log this.
201
209
  return ToolDetailsResponsePbo()
202
210
 
203
211
  def ProcessData(self, request: ProcessStepRequestPbo, context: ServicerContext) -> ProcessStepResponsePbo:
204
212
  try:
205
213
  # Convert the SapioConnectionInfo proto object to a SapioUser object.
206
- user = self.create_user(request.sapio_user)
207
- # Get the tool results from the registered tool matching the request and convert them to proto objects.
214
+ user = self._create_user(request.sapio_user)
215
+ # Get the tool results from the registered tool matching the request.
216
+ success, msg, results, logs = self.run(user, request, context)
217
+ # Convert the results to protobuf objects.
208
218
  output_data: list[StepOutputBatchPbo] = []
209
219
  new_records: list[FieldValueMapPbo] = []
210
- # TODO: Make use of the success value after the response object has a field for it.
211
- success, results, logs = self.run(user, request, context)
212
220
  for result in results:
213
221
  data: StepOutputBatchPbo | list[FieldValueMapPbo] = result.to_proto()
214
222
  if isinstance(data, StepOutputBatchPbo):
215
223
  output_data.append(data)
216
224
  else:
217
225
  new_records.extend(data)
218
- # Return a ProcessStepResponse proto object containing the output data and new records to the caller.
219
- return ProcessStepResponsePbo(output=output_data, log=logs, new_records=new_records)
220
- except Exception:
221
- # TODO: Return a False success result after the response object has a field for it.
222
- return ProcessStepResponsePbo(log=[traceback.format_exc()])
226
+ # Return a ProcessStepResponse proto object containing the results to the caller.
227
+ status = ProcessStepResponseStatusPbo.SUCCESS if success else ProcessStepResponseStatusPbo.FAILURE
228
+ return ProcessStepResponsePbo(status=status, status_message=msg, output=output_data, log=logs,
229
+ new_records=new_records)
230
+ except Exception as e:
231
+ # This try/except should never be needed, as the tool should handle its own exceptions, but better safe
232
+ # than sorry.
233
+ return ProcessStepResponsePbo(status=ProcessStepResponseStatusPbo.FAILURE,
234
+ status_message=f"CRITICAL ERROR: {e}",
235
+ log=[traceback.format_exc()])
223
236
 
224
237
  @staticmethod
225
- def create_user(info: SapioConnectionInfoPbo, timeout_seconds: int = 60) -> SapioUser:
238
+ def _create_user(info: SapioConnectionInfoPbo, timeout_seconds: int = 60) -> SapioUser:
226
239
  """
227
240
  Create a SapioUser object from the given SapioConnectionInfo proto object.
228
241
 
@@ -230,48 +243,28 @@ class ToolServiceBase(ToolServiceServicer, ABC):
230
243
  :param timeout_seconds: The request timeout for calls made from this user object.
231
244
  """
232
245
  # TODO: Have a customizable request timeout? Would need to be added to the request object.
233
- # TODO: How should the RMI hosts and port be used in the connection info?
234
246
  user = SapioUser(info.webservice_url, True, timeout_seconds, guid=info.app_guid)
235
- if info.secret_type == SapioUserSecretTypePbo.SESSION_TOKEN:
236
- user.api_token = info.secret
237
- elif info.secret_type == SapioUserSecretTypePbo.PASSWORD:
238
- # TODO: Will the secret be base64 encoded if it's a password? That's how basic auth is normally handled.
239
- user.password = info.secret
240
- else:
241
- raise Exception(f"Unexpected secret type: {info.secret_type}")
247
+ match info.secret_type:
248
+ case SapioUserSecretTypePbo.SESSION_TOKEN:
249
+ user.api_token = info.secret
250
+ case SapioUserSecretTypePbo.PASSWORD:
251
+ secret: str = info.secret
252
+ if secret.startswith("Basic "):
253
+ secret = secret[6:]
254
+ credentials: list[str] = base64.b64decode(secret).decode().split(":")
255
+ user.username = credentials[0]
256
+ user.password = credentials[1]
257
+ case _:
258
+ raise Exception(f"Unexpected secret type: {info.secret_type}")
242
259
  return user
243
260
 
244
261
  @staticmethod
245
262
  def tool_version() -> int:
246
263
  """
247
- :return: The version of this tool.
264
+ :return: The version of this set of tools.
248
265
  """
249
266
  return 1
250
267
 
251
- def _get_tools(self) -> list[ToolBase]:
252
- """
253
- return: Get instances of the tools registered with this service.
254
- """
255
- # This is complaining about the name and description not being filled from ToolBase,
256
- # but none of the provided tools should have any init parameters.
257
- # noinspection PyArgumentList
258
- tools: list[ToolBase] = [x() for x in self.register_tools()]
259
- if not tools:
260
- raise Exception("No tools registered with this service.")
261
- return tools
262
-
263
- def _get_tool(self, name: str) -> ToolBase:
264
- """
265
- Get a specific tool instance by its name.
266
-
267
- :param name: The name of the tool to retrieve.
268
- :return: The tool object corresponding to the given name.
269
- """
270
- tools: dict[str, ToolBase] = {x.name: x for x in self._get_tools()}
271
- if name not in tools:
272
- raise Exception(f"Tool \"{name}\" not found in registered tools.")
273
- return tools[name]
274
-
275
268
  @abstractmethod
276
269
  def register_tools(self) -> list[type[ToolBase]]:
277
270
  """
@@ -282,19 +275,20 @@ class ToolServiceBase(ToolServiceServicer, ABC):
282
275
  """
283
276
  pass
284
277
 
285
- def get_details(self) -> list[ToolDetailsPbo]:
278
+ def _initialize_tools(self) -> list[ToolBase]:
286
279
  """
287
- Get the details of the tool.
288
-
289
- :return: A ToolDetailsResponse object containing the tool details.
280
+ return: Get instances of the tools registered with this service.
290
281
  """
291
- tool_details: list[ToolDetailsPbo] = []
292
- for tool in self._get_tools():
293
- tool_details.append(tool.to_pbo())
294
- return tool_details
282
+ # This is complaining about the name and description not being filled from ToolBase,
283
+ # but none of the provided tools should have any init parameters.
284
+ # noinspection PyArgumentList
285
+ tools: list[ToolBase] = [x() for x in self.register_tools()]
286
+ if not tools:
287
+ raise Exception("No tools registered with this service.")
288
+ return tools
295
289
 
296
290
  def run(self, user: SapioUser, request: ProcessStepRequestPbo, context: ServicerContext) \
297
- -> tuple[bool, list[SapioToolResult], list[str]]:
291
+ -> tuple[bool, str, list[SapioToolResult], list[str]]:
298
292
  """
299
293
  Execute a tool from this service.
300
294
 
@@ -302,16 +296,36 @@ class ToolServiceBase(ToolServiceServicer, ABC):
302
296
  system.
303
297
  :param request: The request object containing the input data.
304
298
  :param context: The gRPC context.
305
- :return: Whether or not the tool succeeded, the results of the tool, and any logs generated by the tool.
306
- """
307
- tool = self._get_tool(request.tool_name)
299
+ :return: Whether or not the tool succeeded, the status message, the results of the tool, and any logs
300
+ generated by the tool.
301
+ """
302
+ # Locate the tool named in the request.
303
+ tool: ToolBase | None = None
304
+ for t in self._initialize_tools():
305
+ if t.name == request.tool_name:
306
+ tool = t
307
+ break
308
+ if not tool:
309
+ return False, f"Tool \"{request.tool_name}\" not found in the registered tools for this service.", [], []
310
+
308
311
  try:
312
+ # Setup the tool with details from the request.
309
313
  tool.setup(user, request, context)
310
- results: list[SapioToolResult] = tool.run(user)
311
- return True, results, tool.logs
314
+ # Validate that the provided inputs match the tool's expected inputs.
315
+ success, msg = tool.validate_input()
316
+ # If this is a dry run, then provide the fixed dry run output.
317
+ # Otherwise, if the inputs were successfully validated, then the tool is executed normally.
318
+ results: list[SapioToolResult] = []
319
+ if request.dry_run:
320
+ results = tool.dry_run_output()
321
+ elif success:
322
+ results = tool.run(user)
323
+ # Update the status message to reflect the successful execution of the tool.
324
+ msg = f"{tool.name} successfully completed."
325
+ return success, msg, results, tool.logs
312
326
  except Exception as e:
313
327
  tool.log_exception("Exception occurred during tool execution.", e)
314
- return False, [], tool.logs
328
+ return False, str(e), [], tool.logs
315
329
 
316
330
 
317
331
  class ToolBase(ABC):
@@ -363,8 +377,7 @@ class ToolBase(ABC):
363
377
  self.user = user
364
378
  self.request = request
365
379
  self.context = context
366
- # TODO: Determine verbose logging from the request.
367
- self.verbose_logging = False
380
+ self.verbose_logging = request.verbose_logging
368
381
 
369
382
  def add_input(self, content_type: DataTypePbo, display_name: str, description: str,
370
383
  structure_example: str | bytes | None = None, validation: str | None = None,
@@ -412,7 +425,7 @@ class ToolBase(ABC):
412
425
  ))
413
426
 
414
427
  def add_output(self, content_type: DataTypePbo, display_name: str, description: str,
415
- testing_example: str | bytes | None = None, structure_example: str | bytes | None = None) -> None:
428
+ testing_example: str | bytes, structure_example: str | bytes | None = None) -> None:
416
429
  """
417
430
  Add an output configuration to the tool. This determines how many inputs this tool will accept in the plan
418
431
  manager, as well as what those inputs are. The IO number of the output will be set to the current number of
@@ -430,18 +443,18 @@ class ToolBase(ABC):
430
443
  JSON output may look. This does not need to be an entirely valid example, and should often be truncated for
431
444
  brevity.
432
445
  """
433
- structure: ExampleContainerPbo | None = None
434
- if isinstance(structure_example, str):
435
- structure = ExampleContainerPbo(text_example=structure_example)
436
- elif isinstance(structure_example, bytes):
437
- structure = ExampleContainerPbo(binary_example=structure_example)
438
-
439
446
  testing: ExampleContainerPbo | None = None
440
447
  if isinstance(testing_example, str):
441
448
  testing = ExampleContainerPbo(text_example=testing_example)
442
449
  elif isinstance(testing_example, bytes):
443
450
  testing = ExampleContainerPbo(binary_example=testing_example)
444
451
 
452
+ structure: ExampleContainerPbo | None = None
453
+ if isinstance(structure_example, str):
454
+ structure = ExampleContainerPbo(text_example=structure_example)
455
+ elif isinstance(structure_example, bytes):
456
+ structure = ExampleContainerPbo(binary_example=structure_example)
457
+
445
458
  self.outputs.append(ToolOutputDetailsPbo(
446
459
  base_config=ToolIoConfigBasePbo(
447
460
  io_number=len(self.outputs),
@@ -651,6 +664,69 @@ class ToolBase(ABC):
651
664
  config_fields=self.configs
652
665
  )
653
666
 
667
+ @abstractmethod
668
+ def validate_input(self) -> tuple[bool, str]:
669
+ """
670
+ Validate the request given to this tool. If the request is validly formatted, this method should return True
671
+ and a success message. If the request is not valid, this method should return False and an error message
672
+ indicating what is wrong with the request.
673
+
674
+ This method should not perform any actual processing of the request. It should only validate the inputs and
675
+ configurations provided in the request.
676
+
677
+ The request inputs can be accessed using the self.get_input_*() methods.
678
+ The request settings can be accessed using the self.get_config_fields() method.
679
+ The request itself can be accessed using self.request.
680
+
681
+ :return: A tuple containing a boolean indicating whether the request is valid and a message describing the
682
+ result of the validation.
683
+ """
684
+ pass
685
+
686
+ def dry_run_output(self) -> list[SapioToolResult]:
687
+ """
688
+ Provide fixed results for a dry run of this tool. This method should not perform any actual processing of the
689
+ request. It should only return example outputs that can be used to test the next tool in the plan.
690
+
691
+ The default implementation of this method looks at the testing_example field of each output configuration
692
+ and returns a SapioToolResult object based on the content type of the output.
693
+
694
+ :return: A list of SapioToolResult objects containing example outputs for this tool. Each result in the list
695
+ corresponds to a separate output from the tool.
696
+ """
697
+ results: list[SapioToolResult] = []
698
+ for output in self.outputs:
699
+ example: Any = output.base_config.testing_example
700
+ match output.base_config.content_type:
701
+ case DataTypePbo.BINARY:
702
+ example: bytes
703
+ results.append(BinaryResult(binary_data=[example]))
704
+ case DataTypePbo.CSV:
705
+ example: str
706
+ results.append(CsvResult(FileUtil.tokenize_csv(example.encode())[0]))
707
+ case DataTypePbo.IMAGE:
708
+ example: bytes
709
+ # We can't know what the actual image format is, so just assume PNG. If this is wrong and having
710
+ # the correct image format is necessary for testing, then the tool should override this method to
711
+ # provide the correct format.
712
+ results.append(ImageResult(image_format="PNG", image_data=[example]))
713
+ case DataTypePbo.JSON:
714
+ # The example may be in the JSONL format instead of plain JSON, so we need to use Pandas to parse
715
+ # the example into plain JSON.
716
+ example: str
717
+ # Format the JSONL in a way that Pandas likes. Collapse everything into a single line, and then
718
+ # split it back into multiple lines where each line is a single JSON list or dictionary.
719
+ example = re.sub("([]}])\s*([\[{])", r"\1\n\2", example.replace("\n", "")).strip()
720
+ # Read the JSONL into a Pandas DataFrame and convert it back to plain JSON.
721
+ import pandas as pd
722
+ with io.StringIO(example) as stream:
723
+ example = pd.read_json(path_or_buf=stream, lines=True).to_json()
724
+ results.append(JsonResult(json_data=[json.loads(example)]))
725
+ case DataTypePbo.TEXT:
726
+ example: str
727
+ results.append(TextResult(text_data=[example]))
728
+ return results
729
+
654
730
  @abstractmethod
655
731
  def run(self, user: SapioUser) -> list[SapioToolResult]:
656
732
  """
@@ -662,8 +738,8 @@ class ToolBase(ABC):
662
738
 
663
739
  :param user: A user object that can be used to initialize manager classes using DataMgmtServer to query the
664
740
  system.
665
- :return: A SapioToolResults object containing the response data. Each result in the list corresponds to a
666
- separate output from the tool. Field map results do not appear as tool output in the plan manager, instead
741
+ :return: A list of SapioToolResult objects containing the response data. Each result in the list corresponds to
742
+ a separate output from the tool. Field map results do not appear as tool output in the plan manager, instead
667
743
  appearing as records related to the plan step during the run.
668
744
  """
669
745
  pass