sapiopycommons 2025.10.1a770__tar.gz → 2025.10.2a773__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.
- {sapiopycommons-2025.10.1a770 → sapiopycommons-2025.10.2a773}/PKG-INFO +1 -1
- {sapiopycommons-2025.10.1a770 → sapiopycommons-2025.10.2a773}/pyproject.toml +1 -1
- sapiopycommons-2025.10.2a773/src/sapiopycommons/ai/request_validation.py +451 -0
- {sapiopycommons-2025.10.1a770 → sapiopycommons-2025.10.2a773}/src/sapiopycommons/ai/tool_service_base.py +7 -7
- {sapiopycommons-2025.10.1a770 → sapiopycommons-2025.10.2a773}/.gitignore +0 -0
- {sapiopycommons-2025.10.1a770 → sapiopycommons-2025.10.2a773}/LICENSE +0 -0
- {sapiopycommons-2025.10.1a770 → sapiopycommons-2025.10.2a773}/README.md +0 -0
- {sapiopycommons-2025.10.1a770 → sapiopycommons-2025.10.2a773}/src/sapiopycommons/__init__.py +0 -0
- {sapiopycommons-2025.10.1a770 → sapiopycommons-2025.10.2a773}/src/sapiopycommons/ai/__init__.py +0 -0
- {sapiopycommons-2025.10.1a770 → sapiopycommons-2025.10.2a773}/src/sapiopycommons/ai/converter_service_base.py +0 -0
- {sapiopycommons-2025.10.1a770 → sapiopycommons-2025.10.2a773}/src/sapiopycommons/ai/protoapi/fielddefinitions/fields_pb2.py +0 -0
- {sapiopycommons-2025.10.1a770 → sapiopycommons-2025.10.2a773}/src/sapiopycommons/ai/protoapi/fielddefinitions/fields_pb2.pyi +0 -0
- {sapiopycommons-2025.10.1a770 → sapiopycommons-2025.10.2a773}/src/sapiopycommons/ai/protoapi/fielddefinitions/fields_pb2_grpc.py +0 -0
- {sapiopycommons-2025.10.1a770 → sapiopycommons-2025.10.2a773}/src/sapiopycommons/ai/protoapi/fielddefinitions/velox_field_def_pb2.py +0 -0
- {sapiopycommons-2025.10.1a770 → sapiopycommons-2025.10.2a773}/src/sapiopycommons/ai/protoapi/fielddefinitions/velox_field_def_pb2.pyi +0 -0
- {sapiopycommons-2025.10.1a770 → sapiopycommons-2025.10.2a773}/src/sapiopycommons/ai/protoapi/fielddefinitions/velox_field_def_pb2_grpc.py +0 -0
- {sapiopycommons-2025.10.1a770 → sapiopycommons-2025.10.2a773}/src/sapiopycommons/ai/protoapi/plan/converter/converter_pb2.py +0 -0
- {sapiopycommons-2025.10.1a770 → sapiopycommons-2025.10.2a773}/src/sapiopycommons/ai/protoapi/plan/converter/converter_pb2.pyi +0 -0
- {sapiopycommons-2025.10.1a770 → sapiopycommons-2025.10.2a773}/src/sapiopycommons/ai/protoapi/plan/converter/converter_pb2_grpc.py +0 -0
- {sapiopycommons-2025.10.1a770 → sapiopycommons-2025.10.2a773}/src/sapiopycommons/ai/protoapi/plan/item/item_container_pb2.py +0 -0
- {sapiopycommons-2025.10.1a770 → sapiopycommons-2025.10.2a773}/src/sapiopycommons/ai/protoapi/plan/item/item_container_pb2.pyi +0 -0
- {sapiopycommons-2025.10.1a770 → sapiopycommons-2025.10.2a773}/src/sapiopycommons/ai/protoapi/plan/item/item_container_pb2_grpc.py +0 -0
- {sapiopycommons-2025.10.1a770 → sapiopycommons-2025.10.2a773}/src/sapiopycommons/ai/protoapi/plan/script/script_pb2.py +0 -0
- {sapiopycommons-2025.10.1a770 → sapiopycommons-2025.10.2a773}/src/sapiopycommons/ai/protoapi/plan/script/script_pb2.pyi +0 -0
- {sapiopycommons-2025.10.1a770 → sapiopycommons-2025.10.2a773}/src/sapiopycommons/ai/protoapi/plan/script/script_pb2_grpc.py +0 -0
- {sapiopycommons-2025.10.1a770 → sapiopycommons-2025.10.2a773}/src/sapiopycommons/ai/protoapi/plan/step_output_pb2.py +0 -0
- {sapiopycommons-2025.10.1a770 → sapiopycommons-2025.10.2a773}/src/sapiopycommons/ai/protoapi/plan/step_output_pb2.pyi +0 -0
- {sapiopycommons-2025.10.1a770 → sapiopycommons-2025.10.2a773}/src/sapiopycommons/ai/protoapi/plan/step_output_pb2_grpc.py +0 -0
- {sapiopycommons-2025.10.1a770 → sapiopycommons-2025.10.2a773}/src/sapiopycommons/ai/protoapi/plan/step_pb2.py +0 -0
- {sapiopycommons-2025.10.1a770 → sapiopycommons-2025.10.2a773}/src/sapiopycommons/ai/protoapi/plan/step_pb2.pyi +0 -0
- {sapiopycommons-2025.10.1a770 → sapiopycommons-2025.10.2a773}/src/sapiopycommons/ai/protoapi/plan/step_pb2_grpc.py +0 -0
- {sapiopycommons-2025.10.1a770 → sapiopycommons-2025.10.2a773}/src/sapiopycommons/ai/protoapi/plan/tool/entry_pb2.py +0 -0
- {sapiopycommons-2025.10.1a770 → sapiopycommons-2025.10.2a773}/src/sapiopycommons/ai/protoapi/plan/tool/entry_pb2.pyi +0 -0
- {sapiopycommons-2025.10.1a770 → sapiopycommons-2025.10.2a773}/src/sapiopycommons/ai/protoapi/plan/tool/entry_pb2_grpc.py +0 -0
- {sapiopycommons-2025.10.1a770 → sapiopycommons-2025.10.2a773}/src/sapiopycommons/ai/protoapi/plan/tool/tool_pb2.py +0 -0
- {sapiopycommons-2025.10.1a770 → sapiopycommons-2025.10.2a773}/src/sapiopycommons/ai/protoapi/plan/tool/tool_pb2.pyi +0 -0
- {sapiopycommons-2025.10.1a770 → sapiopycommons-2025.10.2a773}/src/sapiopycommons/ai/protoapi/plan/tool/tool_pb2_grpc.py +0 -0
- {sapiopycommons-2025.10.1a770 → sapiopycommons-2025.10.2a773}/src/sapiopycommons/ai/protoapi/session/sapio_conn_info_pb2.py +0 -0
- {sapiopycommons-2025.10.1a770 → sapiopycommons-2025.10.2a773}/src/sapiopycommons/ai/protoapi/session/sapio_conn_info_pb2.pyi +0 -0
- {sapiopycommons-2025.10.1a770 → sapiopycommons-2025.10.2a773}/src/sapiopycommons/ai/protoapi/session/sapio_conn_info_pb2_grpc.py +0 -0
- {sapiopycommons-2025.10.1a770 → sapiopycommons-2025.10.2a773}/src/sapiopycommons/ai/protobuf_utils.py +0 -0
- {sapiopycommons-2025.10.1a770 → sapiopycommons-2025.10.2a773}/src/sapiopycommons/ai/server.py +0 -0
- {sapiopycommons-2025.10.1a770 → sapiopycommons-2025.10.2a773}/src/sapiopycommons/ai/test_client.py +0 -0
- {sapiopycommons-2025.10.1a770 → sapiopycommons-2025.10.2a773}/src/sapiopycommons/callbacks/__init__.py +0 -0
- {sapiopycommons-2025.10.1a770 → sapiopycommons-2025.10.2a773}/src/sapiopycommons/callbacks/callback_util.py +0 -0
- {sapiopycommons-2025.10.1a770 → sapiopycommons-2025.10.2a773}/src/sapiopycommons/callbacks/field_builder.py +0 -0
- {sapiopycommons-2025.10.1a770 → sapiopycommons-2025.10.2a773}/src/sapiopycommons/chem/IndigoMolecules.py +0 -0
- {sapiopycommons-2025.10.1a770 → sapiopycommons-2025.10.2a773}/src/sapiopycommons/chem/Molecules.py +0 -0
- {sapiopycommons-2025.10.1a770 → sapiopycommons-2025.10.2a773}/src/sapiopycommons/chem/__init__.py +0 -0
- {sapiopycommons-2025.10.1a770 → sapiopycommons-2025.10.2a773}/src/sapiopycommons/customreport/__init__.py +0 -0
- {sapiopycommons-2025.10.1a770 → sapiopycommons-2025.10.2a773}/src/sapiopycommons/customreport/auto_pagers.py +0 -0
- {sapiopycommons-2025.10.1a770 → sapiopycommons-2025.10.2a773}/src/sapiopycommons/customreport/column_builder.py +0 -0
- {sapiopycommons-2025.10.1a770 → sapiopycommons-2025.10.2a773}/src/sapiopycommons/customreport/custom_report_builder.py +0 -0
- {sapiopycommons-2025.10.1a770 → sapiopycommons-2025.10.2a773}/src/sapiopycommons/customreport/term_builder.py +0 -0
- {sapiopycommons-2025.10.1a770 → sapiopycommons-2025.10.2a773}/src/sapiopycommons/datatype/__init__.py +0 -0
- {sapiopycommons-2025.10.1a770 → sapiopycommons-2025.10.2a773}/src/sapiopycommons/datatype/attachment_util.py +0 -0
- {sapiopycommons-2025.10.1a770 → sapiopycommons-2025.10.2a773}/src/sapiopycommons/datatype/data_fields.py +0 -0
- {sapiopycommons-2025.10.1a770 → sapiopycommons-2025.10.2a773}/src/sapiopycommons/datatype/pseudo_data_types.py +0 -0
- {sapiopycommons-2025.10.1a770 → sapiopycommons-2025.10.2a773}/src/sapiopycommons/eln/__init__.py +0 -0
- {sapiopycommons-2025.10.1a770 → sapiopycommons-2025.10.2a773}/src/sapiopycommons/eln/experiment_cache.py +0 -0
- {sapiopycommons-2025.10.1a770 → sapiopycommons-2025.10.2a773}/src/sapiopycommons/eln/experiment_handler.py +0 -0
- {sapiopycommons-2025.10.1a770 → sapiopycommons-2025.10.2a773}/src/sapiopycommons/eln/experiment_report_util.py +0 -0
- {sapiopycommons-2025.10.1a770 → sapiopycommons-2025.10.2a773}/src/sapiopycommons/eln/experiment_step_factory.py +0 -0
- {sapiopycommons-2025.10.1a770 → sapiopycommons-2025.10.2a773}/src/sapiopycommons/eln/experiment_tags.py +0 -0
- {sapiopycommons-2025.10.1a770 → sapiopycommons-2025.10.2a773}/src/sapiopycommons/eln/plate_designer.py +0 -0
- {sapiopycommons-2025.10.1a770 → sapiopycommons-2025.10.2a773}/src/sapiopycommons/eln/step_creation.py +0 -0
- {sapiopycommons-2025.10.1a770 → sapiopycommons-2025.10.2a773}/src/sapiopycommons/files/__init__.py +0 -0
- {sapiopycommons-2025.10.1a770 → sapiopycommons-2025.10.2a773}/src/sapiopycommons/files/assay_plate_reader.py +0 -0
- {sapiopycommons-2025.10.1a770 → sapiopycommons-2025.10.2a773}/src/sapiopycommons/files/complex_data_loader.py +0 -0
- {sapiopycommons-2025.10.1a770 → sapiopycommons-2025.10.2a773}/src/sapiopycommons/files/file_bridge.py +0 -0
- {sapiopycommons-2025.10.1a770 → sapiopycommons-2025.10.2a773}/src/sapiopycommons/files/file_bridge_handler.py +0 -0
- {sapiopycommons-2025.10.1a770 → sapiopycommons-2025.10.2a773}/src/sapiopycommons/files/file_data_handler.py +0 -0
- {sapiopycommons-2025.10.1a770 → sapiopycommons-2025.10.2a773}/src/sapiopycommons/files/file_text_converter.py +0 -0
- {sapiopycommons-2025.10.1a770 → sapiopycommons-2025.10.2a773}/src/sapiopycommons/files/file_util.py +0 -0
- {sapiopycommons-2025.10.1a770 → sapiopycommons-2025.10.2a773}/src/sapiopycommons/files/file_validator.py +0 -0
- {sapiopycommons-2025.10.1a770 → sapiopycommons-2025.10.2a773}/src/sapiopycommons/files/file_writer.py +0 -0
- {sapiopycommons-2025.10.1a770 → sapiopycommons-2025.10.2a773}/src/sapiopycommons/files/temp_files.py +0 -0
- {sapiopycommons-2025.10.1a770 → sapiopycommons-2025.10.2a773}/src/sapiopycommons/flowcyto/flow_cyto.py +0 -0
- {sapiopycommons-2025.10.1a770 → sapiopycommons-2025.10.2a773}/src/sapiopycommons/flowcyto/flowcyto_data.py +0 -0
- {sapiopycommons-2025.10.1a770 → sapiopycommons-2025.10.2a773}/src/sapiopycommons/general/__init__.py +0 -0
- {sapiopycommons-2025.10.1a770 → sapiopycommons-2025.10.2a773}/src/sapiopycommons/general/accession_service.py +0 -0
- {sapiopycommons-2025.10.1a770 → sapiopycommons-2025.10.2a773}/src/sapiopycommons/general/aliases.py +0 -0
- {sapiopycommons-2025.10.1a770 → sapiopycommons-2025.10.2a773}/src/sapiopycommons/general/audit_log.py +0 -0
- {sapiopycommons-2025.10.1a770 → sapiopycommons-2025.10.2a773}/src/sapiopycommons/general/custom_report_util.py +0 -0
- {sapiopycommons-2025.10.1a770 → sapiopycommons-2025.10.2a773}/src/sapiopycommons/general/data_structure_util.py +0 -0
- {sapiopycommons-2025.10.1a770 → sapiopycommons-2025.10.2a773}/src/sapiopycommons/general/directive_util.py +0 -0
- {sapiopycommons-2025.10.1a770 → sapiopycommons-2025.10.2a773}/src/sapiopycommons/general/exceptions.py +0 -0
- {sapiopycommons-2025.10.1a770 → sapiopycommons-2025.10.2a773}/src/sapiopycommons/general/html_formatter.py +0 -0
- {sapiopycommons-2025.10.1a770 → sapiopycommons-2025.10.2a773}/src/sapiopycommons/general/popup_util.py +0 -0
- {sapiopycommons-2025.10.1a770 → sapiopycommons-2025.10.2a773}/src/sapiopycommons/general/sapio_links.py +0 -0
- {sapiopycommons-2025.10.1a770 → sapiopycommons-2025.10.2a773}/src/sapiopycommons/general/storage_util.py +0 -0
- {sapiopycommons-2025.10.1a770 → sapiopycommons-2025.10.2a773}/src/sapiopycommons/general/time_util.py +0 -0
- {sapiopycommons-2025.10.1a770 → sapiopycommons-2025.10.2a773}/src/sapiopycommons/multimodal/multimodal.py +0 -0
- {sapiopycommons-2025.10.1a770 → sapiopycommons-2025.10.2a773}/src/sapiopycommons/multimodal/multimodal_data.py +0 -0
- {sapiopycommons-2025.10.1a770 → sapiopycommons-2025.10.2a773}/src/sapiopycommons/processtracking/__init__.py +0 -0
- {sapiopycommons-2025.10.1a770 → sapiopycommons-2025.10.2a773}/src/sapiopycommons/processtracking/custom_workflow_handler.py +0 -0
- {sapiopycommons-2025.10.1a770 → sapiopycommons-2025.10.2a773}/src/sapiopycommons/processtracking/endpoints.py +0 -0
- {sapiopycommons-2025.10.1a770 → sapiopycommons-2025.10.2a773}/src/sapiopycommons/recordmodel/__init__.py +0 -0
- {sapiopycommons-2025.10.1a770 → sapiopycommons-2025.10.2a773}/src/sapiopycommons/recordmodel/record_handler.py +0 -0
- {sapiopycommons-2025.10.1a770 → sapiopycommons-2025.10.2a773}/src/sapiopycommons/rules/__init__.py +0 -0
- {sapiopycommons-2025.10.1a770 → sapiopycommons-2025.10.2a773}/src/sapiopycommons/rules/eln_rule_handler.py +0 -0
- {sapiopycommons-2025.10.1a770 → sapiopycommons-2025.10.2a773}/src/sapiopycommons/rules/on_save_rule_handler.py +0 -0
- {sapiopycommons-2025.10.1a770 → sapiopycommons-2025.10.2a773}/src/sapiopycommons/samples/aliquot.py +0 -0
- {sapiopycommons-2025.10.1a770 → sapiopycommons-2025.10.2a773}/src/sapiopycommons/sftpconnect/__init__.py +0 -0
- {sapiopycommons-2025.10.1a770 → sapiopycommons-2025.10.2a773}/src/sapiopycommons/sftpconnect/sftp_builder.py +0 -0
- {sapiopycommons-2025.10.1a770 → sapiopycommons-2025.10.2a773}/src/sapiopycommons/webhook/__init__.py +0 -0
- {sapiopycommons-2025.10.1a770 → sapiopycommons-2025.10.2a773}/src/sapiopycommons/webhook/webhook_context.py +0 -0
- {sapiopycommons-2025.10.1a770 → sapiopycommons-2025.10.2a773}/src/sapiopycommons/webhook/webhook_handlers.py +0 -0
- {sapiopycommons-2025.10.1a770 → sapiopycommons-2025.10.2a773}/src/sapiopycommons/webhook/webservice_handlers.py +0 -0
- {sapiopycommons-2025.10.1a770 → sapiopycommons-2025.10.2a773}/tests/AF-A0A009IHW8-F1-model_v4.cif +0 -0
- {sapiopycommons-2025.10.1a770 → sapiopycommons-2025.10.2a773}/tests/_do_not_add_init_py_here +0 -0
- {sapiopycommons-2025.10.1a770 → sapiopycommons-2025.10.2a773}/tests/accession_test.py +0 -0
- {sapiopycommons-2025.10.1a770 → sapiopycommons-2025.10.2a773}/tests/aliquot_test.py +0 -0
- {sapiopycommons-2025.10.1a770 → sapiopycommons-2025.10.2a773}/tests/assay_plate_reader/BMGLabtech96.txt +0 -0
- {sapiopycommons-2025.10.1a770 → sapiopycommons-2025.10.2a773}/tests/assay_plate_reader/assay_plate_processing_test.py +0 -0
- {sapiopycommons-2025.10.1a770 → sapiopycommons-2025.10.2a773}/tests/bio_reg_test.py +0 -0
- {sapiopycommons-2025.10.1a770 → sapiopycommons-2025.10.2a773}/tests/chem_test.py +0 -0
- {sapiopycommons-2025.10.1a770 → sapiopycommons-2025.10.2a773}/tests/chem_test_curation_queue.py +0 -0
- {sapiopycommons-2025.10.1a770 → sapiopycommons-2025.10.2a773}/tests/curation_queue_test.sdf +0 -0
- {sapiopycommons-2025.10.1a770 → sapiopycommons-2025.10.2a773}/tests/data_type_models.py +0 -0
- {sapiopycommons-2025.10.1a770 → sapiopycommons-2025.10.2a773}/tests/file_compression_tests.py +0 -0
- {sapiopycommons-2025.10.1a770 → sapiopycommons-2025.10.2a773}/tests/flowcyto/8_color_ICS.wsp +0 -0
- {sapiopycommons-2025.10.1a770 → sapiopycommons-2025.10.2a773}/tests/flowcyto_test.py +0 -0
- {sapiopycommons-2025.10.1a770 → sapiopycommons-2025.10.2a773}/tests/kappa.chains.fasta +0 -0
- {sapiopycommons-2025.10.1a770 → sapiopycommons-2025.10.2a773}/tests/mafft_test.py +0 -0
- {sapiopycommons-2025.10.1a770 → sapiopycommons-2025.10.2a773}/tests/test.gb +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: sapiopycommons
|
|
3
|
-
Version: 2025.10.
|
|
3
|
+
Version: 2025.10.2a773
|
|
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>
|
|
@@ -0,0 +1,451 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from abc import ABC
|
|
4
|
+
from typing import Any, Callable
|
|
5
|
+
|
|
6
|
+
from sapiopycommons.ai.test_client import ContainerType
|
|
7
|
+
from sapiopycommons.ai.tool_service_base import ToolBase
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class InputValidation(ABC):
|
|
11
|
+
"""
|
|
12
|
+
A base class for validating the input to a tool.
|
|
13
|
+
"""
|
|
14
|
+
index: int
|
|
15
|
+
max_entries: int | None
|
|
16
|
+
allow_empty_input: bool
|
|
17
|
+
allow_empty_entries: bool
|
|
18
|
+
|
|
19
|
+
def __init__(self, index: int, max_entries: int | None = None,
|
|
20
|
+
allow_empty_input: bool = False, allow_empty_entries: bool = False):
|
|
21
|
+
"""
|
|
22
|
+
:param index: The index of the input to validate.
|
|
23
|
+
:param max_entries: The maximum number of entries allowed for this input. If None, then there is no limit.
|
|
24
|
+
:param allow_empty_input: If true, then the input can be completely empty.
|
|
25
|
+
:param allow_empty_entries: If true, then individual entries in the input can be empty.
|
|
26
|
+
"""
|
|
27
|
+
self.index = index
|
|
28
|
+
self.max_entries = max_entries
|
|
29
|
+
self.allow_empty_input = allow_empty_input
|
|
30
|
+
self.allow_empty_entries = allow_empty_entries
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class BinaryValidation(InputValidation):
|
|
34
|
+
"""
|
|
35
|
+
A class representing a validation requirement for a binary input.
|
|
36
|
+
"""
|
|
37
|
+
func: Callable[[int, bytes], list[str]] | None
|
|
38
|
+
|
|
39
|
+
def __init__(self, index: int, max_entries: int | None = None,
|
|
40
|
+
allow_empty_input: bool = False, allow_empty_entries: bool = False,
|
|
41
|
+
func: Callable[[int, bytes], list[str]] | None = None):
|
|
42
|
+
"""
|
|
43
|
+
:param index: The index of the input to validate.
|
|
44
|
+
:param max_entries: The maximum number of entries allowed for this input. If None, then there is no limit.
|
|
45
|
+
:param allow_empty_input: If true, then the input can be completely empty.
|
|
46
|
+
:param allow_empty_entries: If true, then individual entries in the input can be empty
|
|
47
|
+
:param func: An optional function to run on each entry in the input. The function should take the index of
|
|
48
|
+
the input and the entry as arguments, and return a list of error messages if the entry is not valid. If the
|
|
49
|
+
entry is valid, the function should return an empty list. This function will not be called if the input or
|
|
50
|
+
entry are empty.
|
|
51
|
+
"""
|
|
52
|
+
super().__init__(index, max_entries, allow_empty_input, allow_empty_entries)
|
|
53
|
+
self.func = func
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
class CsvValidation(InputValidation):
|
|
57
|
+
"""
|
|
58
|
+
A class representing a validation requirement for a CSV input.
|
|
59
|
+
"""
|
|
60
|
+
required_headers: list[str] | None = None
|
|
61
|
+
|
|
62
|
+
func: Callable[[int, dict[str, Any]], list[str]] | None
|
|
63
|
+
|
|
64
|
+
def __init__(self, index: int, max_entries: int | None = None,
|
|
65
|
+
allow_empty_input: bool = False, allow_empty_entries: bool = False,
|
|
66
|
+
required_headers: list[str] | None = None,
|
|
67
|
+
func: Callable[[int, dict[str, Any]], list[str]] | None = None):
|
|
68
|
+
"""
|
|
69
|
+
:param index: The index of the input to validate.
|
|
70
|
+
:param max_entries: The maximum number of entries allowed for this input. If None, then there is no limit.
|
|
71
|
+
:param allow_empty_input: If true, then the input can be completely empty.
|
|
72
|
+
:param allow_empty_entries: If true, then individual entries in the input can be empty.
|
|
73
|
+
:param required_headers: A list of headers that must be present in the CSV input. If None, then no header
|
|
74
|
+
validation will be performed.
|
|
75
|
+
:param func: An optional function to run on each entry in the input. The function should take the index of
|
|
76
|
+
the input and the entry as arguments, and return a list of error messages if the entry is not valid. If the
|
|
77
|
+
entry is valid, the function should return an empty list. This function will not be called if the input or
|
|
78
|
+
entry are empty.
|
|
79
|
+
"""
|
|
80
|
+
super().__init__(index, max_entries, allow_empty_input, allow_empty_entries)
|
|
81
|
+
self.required_headers = required_headers
|
|
82
|
+
self.func = func
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
class JsonValidation(InputValidation):
|
|
86
|
+
"""
|
|
87
|
+
A class representing a validation requirement for a JSON input.
|
|
88
|
+
"""
|
|
89
|
+
json_requirements: dict[str, JsonKeyValidation]
|
|
90
|
+
|
|
91
|
+
func: Callable[[int, dict[str, Any]], list[str]] | None
|
|
92
|
+
|
|
93
|
+
def __init__(self, index: int, max_entries: int | None = None,
|
|
94
|
+
allow_empty_input: bool = False, allow_empty_entries: bool = False,
|
|
95
|
+
json_requirements: list[JsonKeyValidation] | None = None,
|
|
96
|
+
func: Callable[[int, dict[str, Any]], list[str]] | None = None):
|
|
97
|
+
"""
|
|
98
|
+
:param index: The index of the input to validate.
|
|
99
|
+
:param max_entries: The maximum number of entries allowed for this input. If None, then there is no limit.
|
|
100
|
+
:param allow_empty_input: If true, then the input can be completely empty.
|
|
101
|
+
:param allow_empty_entries: If true, then individual entries in the input can be empty.
|
|
102
|
+
:param json_requirements: A list of JSON requirements to validate for JSON inputs. Each requirement
|
|
103
|
+
specifies a key to validate, the expected type of the value for that key, and any nested requirements
|
|
104
|
+
for that key. Only applicable to JSON inputs.
|
|
105
|
+
:param func: An optional function to run on each entry in the input. The function should take the index of
|
|
106
|
+
the input and the entry as arguments, and return a list of error messages if the entry is not valid. If the
|
|
107
|
+
entry is valid, the function should return an empty list. This function will not be called if the input or
|
|
108
|
+
entry are empty.
|
|
109
|
+
"""
|
|
110
|
+
super().__init__(index, max_entries, allow_empty_input, allow_empty_entries)
|
|
111
|
+
self.json_requirements = {}
|
|
112
|
+
if json_requirements:
|
|
113
|
+
for req in json_requirements:
|
|
114
|
+
if req.key in self.json_requirements:
|
|
115
|
+
raise ValueError(f"Duplicate JSON requirement key {req.key} for input index {index}.")
|
|
116
|
+
self.json_requirements[req.key] = req
|
|
117
|
+
|
|
118
|
+
self.func = func
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
class JsonKeyValidation:
|
|
122
|
+
"""
|
|
123
|
+
A class representing a validation requirement for a specific key in a JSON input.
|
|
124
|
+
"""
|
|
125
|
+
key: str
|
|
126
|
+
json_type: type
|
|
127
|
+
required: bool
|
|
128
|
+
allow_empty: bool
|
|
129
|
+
|
|
130
|
+
list_type: type | None = None
|
|
131
|
+
nested_requirements: dict[str, JsonKeyValidation]
|
|
132
|
+
|
|
133
|
+
func: Callable[[str, Any], list[str]] | None = None
|
|
134
|
+
|
|
135
|
+
def __init__(self, key: str, json_type: type, required: bool = True, allow_empty: bool = False,
|
|
136
|
+
list_type: type | None = None, nested_requirements: list[JsonKeyValidation] | None = None,
|
|
137
|
+
func: Callable[[str, Any], list[str]] | None = None):
|
|
138
|
+
"""
|
|
139
|
+
:param key: The key in the JSON input to validate.
|
|
140
|
+
:param json_type: The expected type of the value for this key. This should be one of: str, int, float, bool,
|
|
141
|
+
list, or dict.
|
|
142
|
+
:param required: If true, then this key must be present in the JSON input. If false, then the key is optional,
|
|
143
|
+
but if present, it must still match the other expected criteria.
|
|
144
|
+
:param allow_empty: If true, then the value for this key can be empty (e.g., an empty string, list, or dict).
|
|
145
|
+
If false, then the value must not be empty.
|
|
146
|
+
:param list_type: The expected type of the entries in the list if json_type is list.
|
|
147
|
+
:param nested_requirements: A list of nested JSON requirements to validate for this key if it is a dict. Each
|
|
148
|
+
requirement specifies a key to validate, the expected type of the value for that key, and any nested
|
|
149
|
+
requirements for that key. Only applicable if json_type is dict, or if json_type is list and list_type is
|
|
150
|
+
dict.
|
|
151
|
+
:param func: An optional function to run on the value for this key. The function should take the path and the
|
|
152
|
+
value as arguments, and return a list of error messages if the value is not valid. If the value is valid,
|
|
153
|
+
the function should return an empty list. This function will not be called if the key is missing,
|
|
154
|
+
the value is of the wrong type, or the value is an empty str/list/dict and allow_empty is false.
|
|
155
|
+
"""
|
|
156
|
+
self.key = key
|
|
157
|
+
self.json_type = json_type
|
|
158
|
+
self.required = required
|
|
159
|
+
|
|
160
|
+
self.list_type = list_type
|
|
161
|
+
self.nested_requirements = {}
|
|
162
|
+
if nested_requirements:
|
|
163
|
+
for req in nested_requirements:
|
|
164
|
+
if req.key in self.nested_requirements:
|
|
165
|
+
raise ValueError(f"Duplicate nested requirement key {req.key} for JSON key {key}.")
|
|
166
|
+
self.nested_requirements[req.key] = req
|
|
167
|
+
|
|
168
|
+
self.func = func
|
|
169
|
+
|
|
170
|
+
allowed_types: set[type] = {str, int, float, bool, list, dict}
|
|
171
|
+
if self.json_type not in allowed_types:
|
|
172
|
+
raise ValueError(f"Invalid json_type {self.json_type} for key {key}. Must be one of: "
|
|
173
|
+
f"{', '.join([t.__name__ for t in allowed_types])}.")
|
|
174
|
+
if self.list_type is not None and self.list_type not in allowed_types:
|
|
175
|
+
raise ValueError(f"Invalid list_type {self.list_type} for key {key}. Must be one of: "
|
|
176
|
+
f"{', '.join([t.__name__ for t in allowed_types])}.")
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
class TextValidation(InputValidation):
|
|
180
|
+
"""
|
|
181
|
+
A class representing a validation requirement for a text input.
|
|
182
|
+
"""
|
|
183
|
+
flatten: bool
|
|
184
|
+
regex: str | None = None
|
|
185
|
+
|
|
186
|
+
func: Callable[[int, str], list[str]] | None = None
|
|
187
|
+
|
|
188
|
+
def __init__(self, index: int, max_entries: int | None = None,
|
|
189
|
+
allow_empty_input: bool = False, allow_empty_entries: bool = False, flatten: bool = False,
|
|
190
|
+
regex: str | None = None, func: Callable[[int, str], list[str]] | None = None):
|
|
191
|
+
"""
|
|
192
|
+
:param index: The index of the input to validate.
|
|
193
|
+
:param max_entries: The maximum number of entries allowed for this input. If None, then there is no limit.
|
|
194
|
+
:param allow_empty_input: If true, then the input can be completely empty.
|
|
195
|
+
:param allow_empty_entries: If true, then individual entries in the input can be empty.
|
|
196
|
+
:param flatten: If true, then the input will be flattened before validation
|
|
197
|
+
:param regex: An optional regular expression that each entry in the input must fully match. If None, then no
|
|
198
|
+
regex validation will be performed. This function will not be called if the input or entry are empty.
|
|
199
|
+
:param func: An optional function to run on each entry in the input. The function should take the index of
|
|
200
|
+
the input and the entry as arguments, and return a list of error messages if the entry is not valid. If the
|
|
201
|
+
entry is valid, the function should return an empty list. This function will not be called if the input or
|
|
202
|
+
entry are empty. If a regex is provided, the function will only be called if the entry matches the regex.
|
|
203
|
+
"""
|
|
204
|
+
super().__init__(index, max_entries, allow_empty_input, allow_empty_entries)
|
|
205
|
+
self.flatten = flatten
|
|
206
|
+
self.regex = regex
|
|
207
|
+
self.func = func
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
class InputValidator:
|
|
212
|
+
"""
|
|
213
|
+
A class for validating the inputs to a tool based on their container types and specified validation requirements.
|
|
214
|
+
"""
|
|
215
|
+
tool: ToolBase
|
|
216
|
+
requirements: dict[int, InputValidation]
|
|
217
|
+
|
|
218
|
+
def __init__(self, tool: ToolBase, requirements: list[InputValidation] | None = None):
|
|
219
|
+
"""
|
|
220
|
+
:param tool: The tool to validate the request of.
|
|
221
|
+
:param requirements: A list of validation requirements to apply to the request. If a validation object is
|
|
222
|
+
not provided for a given input, then default validation will be applied. Default validation requires that
|
|
223
|
+
the input is not empty, and that the entries in the input are not empty.
|
|
224
|
+
"""
|
|
225
|
+
self.tool = tool
|
|
226
|
+
self.requirements = {}
|
|
227
|
+
for req in requirements:
|
|
228
|
+
if req.index < 0 or req.index >= len(tool.input_configs):
|
|
229
|
+
raise ValueError(f"Validation requirement index {req.index} is out of range for tool "
|
|
230
|
+
f"{tool.name()} with {len(tool.input_configs)} inputs.")
|
|
231
|
+
if req.index in self.requirements:
|
|
232
|
+
raise ValueError(f"Duplicate validation requirement index {req.index} for tool {tool.name()}.")
|
|
233
|
+
self.requirements[req.index] = req
|
|
234
|
+
|
|
235
|
+
def run(self) -> list[str]:
|
|
236
|
+
"""
|
|
237
|
+
Run simple validation on all the inputs based on their container types. This requires the following:
|
|
238
|
+
- The input may not be empty.
|
|
239
|
+
- The entries in the input may not be empty, unless allow_empty is set to true.
|
|
240
|
+
- If provided, the number of entries in the input may not exceed a maximum size.
|
|
241
|
+
- If provided, certain keys must be present in the JSON input, and they must match the above behavior.
|
|
242
|
+
|
|
243
|
+
:return: A list of the error messages if the request is not valid. If the request is valid, return an empty
|
|
244
|
+
list.
|
|
245
|
+
"""
|
|
246
|
+
errors: list[str] = []
|
|
247
|
+
for i, input_type in enumerate(self.tool.input_container_types):
|
|
248
|
+
match input_type:
|
|
249
|
+
case ContainerType.BINARY:
|
|
250
|
+
r: InputValidation = self.requirements.get(i, BinaryValidation(i))
|
|
251
|
+
if not isinstance(r, BinaryValidation):
|
|
252
|
+
raise ValueError(f"Validation requirement for binary input at index {i} must be a "
|
|
253
|
+
f"BinaryValidation object. Got {type(r)} instead.")
|
|
254
|
+
errors.extend(self.validate_input_binary(i, r))
|
|
255
|
+
case ContainerType.CSV:
|
|
256
|
+
r: InputValidation = self.requirements.get(i, CsvValidation(i))
|
|
257
|
+
if not isinstance(r, CsvValidation):
|
|
258
|
+
raise ValueError(f"Validation requirement for CSV input at index {i} must be a "
|
|
259
|
+
f"CsvValidation object. Got {type(r)} instead.")
|
|
260
|
+
errors.extend(self.validate_input_csv(i, r))
|
|
261
|
+
case ContainerType.JSON:
|
|
262
|
+
r: InputValidation = self.requirements.get(i, JsonValidation(i))
|
|
263
|
+
if not isinstance(r, JsonValidation):
|
|
264
|
+
raise ValueError(f"Validation requirement for JSON input at index {i} must be a "
|
|
265
|
+
f"JsonValidation object. Got {type(r)} instead.")
|
|
266
|
+
errors.extend(self.validate_input_json(i, r))
|
|
267
|
+
case ContainerType.TEXT:
|
|
268
|
+
r: InputValidation = self.requirements.get(i, TextValidation(i))
|
|
269
|
+
if not isinstance(r, TextValidation):
|
|
270
|
+
raise ValueError(f"Validation requirement for text input at index {i} must be a "
|
|
271
|
+
f"TextValidation object. Got {type(r)} instead.")
|
|
272
|
+
errors.extend(self.validate_input_text(i, r))
|
|
273
|
+
return errors
|
|
274
|
+
|
|
275
|
+
def validate_input_binary(self, index: int, r: BinaryValidation) -> list[str]:
|
|
276
|
+
"""
|
|
277
|
+
Run simple validation on the binary input at the given index.
|
|
278
|
+
|
|
279
|
+
:param index: The index of the input to validate.
|
|
280
|
+
:param r: The validation requirement to use for this input.
|
|
281
|
+
:return: A list of error messages if the input is not valid. If the input is valid, return an empty list.
|
|
282
|
+
"""
|
|
283
|
+
input_files: list[bytes] = self.tool.get_input_binary(index)
|
|
284
|
+
errors: list[str] = []
|
|
285
|
+
if not input_files:
|
|
286
|
+
if not r.allow_empty_input:
|
|
287
|
+
errors.append(f"Input {index} is empty.")
|
|
288
|
+
elif r.max_entries is not None and len(input_files) > r.max_entries:
|
|
289
|
+
errors.append(f"Input {index} contains {len(input_files)} entries, which exceeds the maximum allowed "
|
|
290
|
+
f"number of {r.max_entries}.")
|
|
291
|
+
elif not r.allow_empty_entries or r.func:
|
|
292
|
+
for i, entry in enumerate(input_files):
|
|
293
|
+
if not entry.strip():
|
|
294
|
+
if not r.allow_empty_entries:
|
|
295
|
+
errors.append(f"Entry {i} of input {index} is empty or contains only whitespace.")
|
|
296
|
+
elif r.func:
|
|
297
|
+
errors.extend(r.func(i, entry))
|
|
298
|
+
return errors
|
|
299
|
+
|
|
300
|
+
def validate_input_csv(self, index: int, r: CsvValidation) -> list[str]:
|
|
301
|
+
"""
|
|
302
|
+
Run simple validation on the CSV input at the given index.
|
|
303
|
+
|
|
304
|
+
:param index: The index of the input to validate.
|
|
305
|
+
:param r: The validation requirement to use for this input.
|
|
306
|
+
:return: A list of error messages if the input is not valid. If the input is valid, return an empty list.
|
|
307
|
+
"""
|
|
308
|
+
headers, csv = self.tool.get_input_csv(index)
|
|
309
|
+
headers: list[str]
|
|
310
|
+
csv: list[dict[str, Any]]
|
|
311
|
+
|
|
312
|
+
errors: list[str] = []
|
|
313
|
+
if r.required_headers:
|
|
314
|
+
missing_headers: list[str] = [h for h in r.required_headers if h not in headers]
|
|
315
|
+
if missing_headers:
|
|
316
|
+
errors.append(f"Input {index} is missing required headers: {', '.join(missing_headers)}.")
|
|
317
|
+
|
|
318
|
+
if not csv:
|
|
319
|
+
if not r.allow_empty_input:
|
|
320
|
+
errors.append(f"Input {index} is empty.")
|
|
321
|
+
elif r.max_entries is not None and len(csv) > r.max_entries:
|
|
322
|
+
errors.append(f"Input {index} contains {len(csv)} entries, which exceeds the maximum allowed "
|
|
323
|
+
f"number of {r.max_entries}.")
|
|
324
|
+
elif not r.allow_empty_entries or r.func:
|
|
325
|
+
for i, entry in enumerate(csv):
|
|
326
|
+
if not entry or all(not cell.strip() for cell in entry):
|
|
327
|
+
if not r.allow_empty_entries:
|
|
328
|
+
errors.append(f"Entry {i} of input {index} is empty or contains only whitespace.")
|
|
329
|
+
elif r.func:
|
|
330
|
+
errors.extend(r.func(i, entry))
|
|
331
|
+
return errors
|
|
332
|
+
|
|
333
|
+
def validate_input_json(self, index: int, r: JsonValidation) -> list[str]:
|
|
334
|
+
"""
|
|
335
|
+
Run simple validation on the JSON input at the given index.
|
|
336
|
+
|
|
337
|
+
:param index: The index of the input to validate.
|
|
338
|
+
:param r: The validation requirement to use for this input.
|
|
339
|
+
:return: A list of error messages if the input is not valid. If the input is valid, return an empty list.
|
|
340
|
+
"""
|
|
341
|
+
input_json: list[dict[str, Any]] = self.tool.get_input_json(index)
|
|
342
|
+
errors: list[str] = []
|
|
343
|
+
if not input_json:
|
|
344
|
+
if not r.allow_empty_input:
|
|
345
|
+
errors.append(f"Input {index} is empty.")
|
|
346
|
+
elif r.max_entries is not None and len(input_json) > r.max_entries:
|
|
347
|
+
errors.append(f"Input {index} contains {len(input_json)} entries, which exceeds the maximum allowed "
|
|
348
|
+
f"number of {r.max_entries}.")
|
|
349
|
+
elif not r.allow_empty_entries or r.func:
|
|
350
|
+
for i, entry in enumerate(input_json):
|
|
351
|
+
if not entry:
|
|
352
|
+
if not r.allow_empty_entries:
|
|
353
|
+
errors.append(f"Entry {i} of input {index} is empty.")
|
|
354
|
+
elif r.func:
|
|
355
|
+
errors.extend(r.func(i, entry))
|
|
356
|
+
|
|
357
|
+
for key, rk in r.json_requirements.items():
|
|
358
|
+
for i, entry in enumerate(input_json):
|
|
359
|
+
errors.extend(self._validate_input_json_key(entry, rk, f"input[{index}][{i}]"))
|
|
360
|
+
|
|
361
|
+
return errors
|
|
362
|
+
|
|
363
|
+
def _validate_input_json_key(self, data: dict[str, Any], rk: JsonKeyValidation, path: str) -> list[str]:
|
|
364
|
+
"""
|
|
365
|
+
Recursively validate a JSON key in a JSON object.
|
|
366
|
+
|
|
367
|
+
:param data: The JSON object to validate.
|
|
368
|
+
:param rk: The JSON key validation requirement to use.
|
|
369
|
+
:param path: The path to the current JSON object, for error reporting.
|
|
370
|
+
:return: A list of error messages if the JSON object is not valid. If the JSON object is valid, return an empty
|
|
371
|
+
list.
|
|
372
|
+
"""
|
|
373
|
+
errors: list[str] = []
|
|
374
|
+
if rk.key not in data:
|
|
375
|
+
if rk.required:
|
|
376
|
+
errors.append(f"Missing required key '{rk.key}' at path '{path}'.")
|
|
377
|
+
return errors
|
|
378
|
+
|
|
379
|
+
value: Any = data[rk.key]
|
|
380
|
+
if not isinstance(value, rk.json_type):
|
|
381
|
+
errors.append(f"Key '{rk.key}' at path '{path}' is expected to be of type "
|
|
382
|
+
f"{rk.json_type.__name__}, but got {type(value).__name__}.")
|
|
383
|
+
return errors
|
|
384
|
+
|
|
385
|
+
if isinstance(value, (str, list, dict)) and not value:
|
|
386
|
+
if not rk.allow_empty:
|
|
387
|
+
errors.append(f"Key '{rk.key}' at path '{path}' is empty, but empty values are not allowed.")
|
|
388
|
+
return errors
|
|
389
|
+
|
|
390
|
+
correct_type: bool = True
|
|
391
|
+
if rk.json_type is list and rk.list_type is not None:
|
|
392
|
+
if not isinstance(value, list):
|
|
393
|
+
raise RuntimeError("This should never happen; value was already checked to be of type list.")
|
|
394
|
+
for i, item in enumerate(value):
|
|
395
|
+
if not isinstance(item, rk.list_type):
|
|
396
|
+
errors.append(f"Entry {i} of list key '{rk.key}' at path '{path}' is expected to be of type "
|
|
397
|
+
f"{rk.list_type.__name__}, but got {type(item).__name__}.")
|
|
398
|
+
correct_type = False
|
|
399
|
+
elif rk.list_type is dict and rk.nested_requirements:
|
|
400
|
+
if not isinstance(item, dict):
|
|
401
|
+
raise RuntimeError("This should never happen; item was already checked to be of type dict.")
|
|
402
|
+
for nk, nrk in rk.nested_requirements.items():
|
|
403
|
+
errors.extend(self._validate_input_json_key(item, nrk, f"{path}.{rk.key}[{i}]"))
|
|
404
|
+
|
|
405
|
+
elif rk.json_type is dict and rk.nested_requirements:
|
|
406
|
+
if not isinstance(value, dict):
|
|
407
|
+
raise RuntimeError("This should never happen; value was already checked to be of type dict.")
|
|
408
|
+
for nk, nrk in rk.nested_requirements.items():
|
|
409
|
+
errors.extend(self._validate_input_json_key(value, nrk, f"{path}.{rk.key}"))
|
|
410
|
+
|
|
411
|
+
if rk.func and correct_type:
|
|
412
|
+
errors.extend(rk.func(f"{path}.{rk.key}", value))
|
|
413
|
+
|
|
414
|
+
return errors
|
|
415
|
+
|
|
416
|
+
def validate_input_text(self, index: int, r: TextValidation) -> list[str]:
|
|
417
|
+
"""
|
|
418
|
+
Run simple validation on the binary input at the given index.
|
|
419
|
+
|
|
420
|
+
:param index: The index of the input to validate.
|
|
421
|
+
:param r: The validation requirement to use for this input.
|
|
422
|
+
:return: A list of error messages if the input is not valid. If the input is valid, return an empty list.
|
|
423
|
+
"""
|
|
424
|
+
input_text: list[str] = self.tool.get_input_text(index)
|
|
425
|
+
if r.flatten:
|
|
426
|
+
input_text = self.tool.flatten_text(input_text)
|
|
427
|
+
|
|
428
|
+
errors: list[str] = []
|
|
429
|
+
if not input_text:
|
|
430
|
+
if not r.allow_empty_input:
|
|
431
|
+
errors.append(f"Input {index} is empty.")
|
|
432
|
+
elif r.max_entries is not None and len(input_text) > r.max_entries:
|
|
433
|
+
errors.append(f"Input {index} contains {len(input_text)} entries, which exceeds the maximum allowed "
|
|
434
|
+
f"number of {r.max_entries}.")
|
|
435
|
+
elif not r.allow_empty_entries or r.regex or r.func:
|
|
436
|
+
for i, entry in enumerate(input_text):
|
|
437
|
+
if not entry.strip():
|
|
438
|
+
if not r.allow_empty_entries:
|
|
439
|
+
errors.append(f"Entry {i} of input {index} is empty or contains only whitespace.")
|
|
440
|
+
elif r.regex:
|
|
441
|
+
import re
|
|
442
|
+
if not re.fullmatch(r.regex, entry):
|
|
443
|
+
errors.append(f"Entry {i} of input {index} does not fully match the expected regex format "
|
|
444
|
+
f"{r.regex}.")
|
|
445
|
+
elif r.func:
|
|
446
|
+
errors.extend(r.func(i, entry))
|
|
447
|
+
if errors and r.flatten:
|
|
448
|
+
errors.append(f"Note that input flattening is enabled for input {index}, which may increase the number "
|
|
449
|
+
f"of entries reported in the above errors. Flattening splits each entry on newlines, removes "
|
|
450
|
+
f"empty lines, and iterates over every line in the input as opposed to each entry as a whole.")
|
|
451
|
+
return errors
|
|
@@ -378,9 +378,9 @@ class ToolBase(ABC):
|
|
|
378
378
|
A base class for implementing a tool.
|
|
379
379
|
"""
|
|
380
380
|
input_configs: list[ToolInputDetailsPbo]
|
|
381
|
-
|
|
381
|
+
input_container_types: list[ContainerType]
|
|
382
382
|
output_configs: list[ToolOutputDetailsPbo]
|
|
383
|
-
|
|
383
|
+
output_container_types: list[ContainerType]
|
|
384
384
|
config_fields: list[VeloxFieldDefPbo]
|
|
385
385
|
|
|
386
386
|
logs: list[str]
|
|
@@ -454,9 +454,9 @@ class ToolBase(ABC):
|
|
|
454
454
|
|
|
455
455
|
def __init__(self):
|
|
456
456
|
self.input_configs = []
|
|
457
|
-
self.
|
|
457
|
+
self.input_container_types = []
|
|
458
458
|
self.output_configs = []
|
|
459
|
-
self.
|
|
459
|
+
self.output_container_types = []
|
|
460
460
|
self.config_fields = []
|
|
461
461
|
self.temp_data = TempFileHandler()
|
|
462
462
|
self.logs = []
|
|
@@ -532,7 +532,7 @@ class ToolBase(ABC):
|
|
|
532
532
|
max_page_size=page_size[1] if page_size else None,
|
|
533
533
|
max_request_bytes=max_request_bytes,
|
|
534
534
|
))
|
|
535
|
-
self.
|
|
535
|
+
self.input_container_types.append(container_type)
|
|
536
536
|
|
|
537
537
|
def add_output(self, container_type: ContainerType, content_type: str, display_name: str, description: str,
|
|
538
538
|
testing_example: str | bytes, structure_example: str | bytes | None = None) -> None:
|
|
@@ -579,7 +579,7 @@ class ToolBase(ABC):
|
|
|
579
579
|
structure_example=structure,
|
|
580
580
|
testing_example=testing
|
|
581
581
|
)))
|
|
582
|
-
self.
|
|
582
|
+
self.output_container_types.append(container_type)
|
|
583
583
|
|
|
584
584
|
def add_config_field(self, field: VeloxFieldDefPbo) -> None:
|
|
585
585
|
"""
|
|
@@ -828,7 +828,7 @@ class ToolBase(ABC):
|
|
|
828
828
|
corresponds to a separate output from the tool.
|
|
829
829
|
"""
|
|
830
830
|
results: list[SapioToolResult] = []
|
|
831
|
-
for output, container_type in zip(self.output_configs, self.
|
|
831
|
+
for output, container_type in zip(self.output_configs, self.output_container_types):
|
|
832
832
|
config: ToolIoConfigBasePbo = output.base_config
|
|
833
833
|
example: ExampleContainerPbo = config.testing_example
|
|
834
834
|
content_type: str = config.content_type
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sapiopycommons-2025.10.1a770 → sapiopycommons-2025.10.2a773}/src/sapiopycommons/__init__.py
RENAMED
|
File without changes
|
{sapiopycommons-2025.10.1a770 → sapiopycommons-2025.10.2a773}/src/sapiopycommons/ai/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sapiopycommons-2025.10.1a770 → sapiopycommons-2025.10.2a773}/src/sapiopycommons/ai/server.py
RENAMED
|
File without changes
|
{sapiopycommons-2025.10.1a770 → sapiopycommons-2025.10.2a773}/src/sapiopycommons/ai/test_client.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sapiopycommons-2025.10.1a770 → sapiopycommons-2025.10.2a773}/src/sapiopycommons/chem/Molecules.py
RENAMED
|
File without changes
|
{sapiopycommons-2025.10.1a770 → sapiopycommons-2025.10.2a773}/src/sapiopycommons/chem/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sapiopycommons-2025.10.1a770 → sapiopycommons-2025.10.2a773}/src/sapiopycommons/eln/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sapiopycommons-2025.10.1a770 → sapiopycommons-2025.10.2a773}/src/sapiopycommons/files/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sapiopycommons-2025.10.1a770 → sapiopycommons-2025.10.2a773}/src/sapiopycommons/files/file_util.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sapiopycommons-2025.10.1a770 → sapiopycommons-2025.10.2a773}/src/sapiopycommons/files/temp_files.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sapiopycommons-2025.10.1a770 → sapiopycommons-2025.10.2a773}/src/sapiopycommons/general/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
{sapiopycommons-2025.10.1a770 → sapiopycommons-2025.10.2a773}/src/sapiopycommons/general/aliases.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sapiopycommons-2025.10.1a770 → sapiopycommons-2025.10.2a773}/src/sapiopycommons/rules/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sapiopycommons-2025.10.1a770 → sapiopycommons-2025.10.2a773}/src/sapiopycommons/samples/aliquot.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sapiopycommons-2025.10.1a770 → sapiopycommons-2025.10.2a773}/src/sapiopycommons/webhook/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sapiopycommons-2025.10.1a770 → sapiopycommons-2025.10.2a773}/tests/AF-A0A009IHW8-F1-model_v4.cif
RENAMED
|
File without changes
|
{sapiopycommons-2025.10.1a770 → sapiopycommons-2025.10.2a773}/tests/_do_not_add_init_py_here
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sapiopycommons-2025.10.1a770 → sapiopycommons-2025.10.2a773}/tests/chem_test_curation_queue.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sapiopycommons-2025.10.1a770 → sapiopycommons-2025.10.2a773}/tests/file_compression_tests.py
RENAMED
|
File without changes
|
{sapiopycommons-2025.10.1a770 → sapiopycommons-2025.10.2a773}/tests/flowcyto/8_color_ICS.wsp
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|