2023-11-27 15:16:35 +01:00
|
|
|
import typing
|
|
|
|
from typing import Any, Optional
|
|
|
|
|
|
|
|
import pytest
|
|
|
|
|
2023-11-28 09:58:56 +01:00
|
|
|
from haystack.core.component import component
|
|
|
|
from haystack.core.component.descriptions import find_component_inputs, find_component_outputs
|
|
|
|
from haystack.core.errors import ComponentError
|
|
|
|
from haystack.core.component import InputSocket, OutputSocket, Component
|
2023-11-27 15:16:35 +01:00
|
|
|
|
|
|
|
|
|
|
|
def test_correct_declaration():
|
|
|
|
@component
|
|
|
|
class MockComponent:
|
|
|
|
def to_dict(self):
|
|
|
|
return {}
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
def from_dict(cls, data):
|
|
|
|
return cls()
|
|
|
|
|
|
|
|
@component.output_types(output_value=int)
|
|
|
|
def run(self, input_value: int):
|
|
|
|
return {"output_value": input_value}
|
|
|
|
|
|
|
|
# Verifies also instantiation works with no issues
|
|
|
|
assert MockComponent()
|
|
|
|
assert component.registry["test_component.MockComponent"] == MockComponent
|
|
|
|
|
|
|
|
|
|
|
|
def test_correct_declaration_with_additional_readonly_property():
|
|
|
|
@component
|
|
|
|
class MockComponent:
|
|
|
|
@property
|
|
|
|
def store(self):
|
|
|
|
return "test_store"
|
|
|
|
|
|
|
|
def to_dict(self):
|
|
|
|
return {}
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
def from_dict(cls, data):
|
|
|
|
return cls()
|
|
|
|
|
|
|
|
@component.output_types(output_value=int)
|
|
|
|
def run(self, input_value: int):
|
|
|
|
return {"output_value": input_value}
|
|
|
|
|
|
|
|
# Verifies that instantiation works with no issues
|
|
|
|
assert MockComponent()
|
|
|
|
assert component.registry["test_component.MockComponent"] == MockComponent
|
|
|
|
assert MockComponent().store == "test_store"
|
|
|
|
|
|
|
|
|
|
|
|
def test_correct_declaration_with_additional_writable_property():
|
|
|
|
@component
|
|
|
|
class MockComponent:
|
|
|
|
@property
|
|
|
|
def store(self):
|
|
|
|
return "test_store"
|
|
|
|
|
|
|
|
@store.setter
|
|
|
|
def store(self, value):
|
|
|
|
self._store = value
|
|
|
|
|
|
|
|
def to_dict(self):
|
|
|
|
return {}
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
def from_dict(cls, data):
|
|
|
|
return cls()
|
|
|
|
|
|
|
|
@component.output_types(output_value=int)
|
|
|
|
def run(self, input_value: int):
|
|
|
|
return {"output_value": input_value}
|
|
|
|
|
|
|
|
# Verifies that instantiation works with no issues
|
|
|
|
assert component.registry["test_component.MockComponent"] == MockComponent
|
|
|
|
comp = MockComponent()
|
|
|
|
comp.store = "test_store"
|
|
|
|
assert comp.store == "test_store"
|
|
|
|
|
|
|
|
|
|
|
|
def test_missing_run():
|
2023-11-28 09:58:56 +01:00
|
|
|
with pytest.raises(ComponentError, match=r"must have a 'run\(\)' method"):
|
2023-11-27 15:16:35 +01:00
|
|
|
|
|
|
|
@component
|
|
|
|
class MockComponent:
|
|
|
|
def another_method(self, input_value: int):
|
|
|
|
return {"output_value": input_value}
|
|
|
|
|
|
|
|
|
|
|
|
def test_set_input_types():
|
|
|
|
class MockComponent:
|
|
|
|
def __init__(self):
|
|
|
|
component.set_input_types(self, value=Any)
|
|
|
|
|
|
|
|
def to_dict(self):
|
|
|
|
return {}
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
def from_dict(cls, data):
|
|
|
|
return cls()
|
|
|
|
|
|
|
|
@component.output_types(value=int)
|
|
|
|
def run(self, **kwargs):
|
|
|
|
return {"value": 1}
|
|
|
|
|
|
|
|
comp = MockComponent()
|
|
|
|
assert comp.__canals_input__ == {"value": InputSocket("value", Any)}
|
|
|
|
assert comp.run() == {"value": 1}
|
|
|
|
|
|
|
|
|
|
|
|
def test_set_output_types():
|
|
|
|
@component
|
|
|
|
class MockComponent:
|
|
|
|
def __init__(self):
|
|
|
|
component.set_output_types(self, value=int)
|
|
|
|
|
|
|
|
def to_dict(self):
|
|
|
|
return {}
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
def from_dict(cls, data):
|
|
|
|
return cls()
|
|
|
|
|
|
|
|
def run(self, value: int):
|
|
|
|
return {"value": 1}
|
|
|
|
|
|
|
|
comp = MockComponent()
|
|
|
|
assert comp.__canals_output__ == {"value": OutputSocket("value", int)}
|
|
|
|
|
|
|
|
|
|
|
|
def test_output_types_decorator_with_compatible_type():
|
|
|
|
@component
|
|
|
|
class MockComponent:
|
|
|
|
@component.output_types(value=int)
|
|
|
|
def run(self, value: int):
|
|
|
|
return {"value": 1}
|
|
|
|
|
|
|
|
def to_dict(self):
|
|
|
|
return {}
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
def from_dict(cls, data):
|
|
|
|
return cls()
|
|
|
|
|
|
|
|
comp = MockComponent()
|
|
|
|
assert comp.__canals_output__ == {"value": OutputSocket("value", int)}
|
|
|
|
|
|
|
|
|
|
|
|
def test_component_decorator_set_it_as_component():
|
|
|
|
@component
|
|
|
|
class MockComponent:
|
|
|
|
@component.output_types(value=int)
|
|
|
|
def run(self, value: int):
|
|
|
|
return {"value": 1}
|
|
|
|
|
|
|
|
def to_dict(self):
|
|
|
|
return {}
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
def from_dict(cls, data):
|
|
|
|
return cls()
|
|
|
|
|
|
|
|
comp = MockComponent()
|
|
|
|
assert isinstance(comp, Component)
|
|
|
|
|
|
|
|
|
|
|
|
def test_inputs_method_no_inputs():
|
|
|
|
@component
|
|
|
|
class MockComponent:
|
|
|
|
def run(self):
|
|
|
|
return {"value": 1}
|
|
|
|
|
|
|
|
comp = MockComponent()
|
|
|
|
assert find_component_inputs(comp) == {}
|
|
|
|
|
|
|
|
|
|
|
|
def test_inputs_method_one_input():
|
|
|
|
@component
|
|
|
|
class MockComponent:
|
|
|
|
def run(self, value: int):
|
|
|
|
return {"value": 1}
|
|
|
|
|
|
|
|
comp = MockComponent()
|
|
|
|
assert find_component_inputs(comp) == {"value": {"is_mandatory": True, "is_variadic": False, "type": int}}
|
|
|
|
|
|
|
|
|
|
|
|
def test_inputs_method_multiple_inputs():
|
|
|
|
@component
|
|
|
|
class MockComponent:
|
|
|
|
def run(self, value1: int, value2: str):
|
|
|
|
return {"value": 1}
|
|
|
|
|
|
|
|
comp = MockComponent()
|
|
|
|
assert find_component_inputs(comp) == {
|
|
|
|
"value1": {"is_mandatory": True, "is_variadic": False, "type": int},
|
|
|
|
"value2": {"is_mandatory": True, "is_variadic": False, "type": str},
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
def test_inputs_method_multiple_inputs_optional():
|
|
|
|
@component
|
|
|
|
class MockComponent:
|
|
|
|
def run(self, value1: int, value2: Optional[str]):
|
|
|
|
return {"value": 1}
|
|
|
|
|
|
|
|
comp = MockComponent()
|
|
|
|
assert find_component_inputs(comp) == {
|
|
|
|
"value1": {"is_mandatory": True, "is_variadic": False, "type": int},
|
|
|
|
"value2": {"is_mandatory": True, "is_variadic": False, "type": typing.Optional[str]},
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
def test_inputs_method_variadic_positional_args():
|
|
|
|
@component
|
|
|
|
class MockComponent:
|
|
|
|
def __init__(self):
|
|
|
|
component.set_input_types(self, value=Any)
|
|
|
|
|
|
|
|
def run(self, *args):
|
|
|
|
return {"value": 1}
|
|
|
|
|
|
|
|
comp = MockComponent()
|
|
|
|
assert find_component_inputs(comp) == {"value": {"is_mandatory": True, "is_variadic": False, "type": typing.Any}}
|
|
|
|
|
|
|
|
|
|
|
|
def test_inputs_method_variadic_keyword_positional_args():
|
|
|
|
@component
|
|
|
|
class MockComponent:
|
|
|
|
def __init__(self):
|
|
|
|
component.set_input_types(self, value=Any)
|
|
|
|
|
|
|
|
def run(self, **kwargs):
|
|
|
|
return {"value": 1}
|
|
|
|
|
|
|
|
comp = MockComponent()
|
|
|
|
assert find_component_inputs(comp) == {"value": {"is_mandatory": True, "is_variadic": False, "type": typing.Any}}
|
|
|
|
|
|
|
|
|
|
|
|
def test_inputs_dynamic_from_init():
|
|
|
|
@component
|
|
|
|
class MockComponent:
|
|
|
|
def __init__(self):
|
|
|
|
component.set_input_types(self, value=int)
|
|
|
|
|
|
|
|
def run(self, value: int, **kwargs):
|
|
|
|
return {"value": 1}
|
|
|
|
|
|
|
|
comp = MockComponent()
|
|
|
|
assert find_component_inputs(comp) == {"value": {"is_mandatory": True, "is_variadic": False, "type": int}}
|
|
|
|
|
|
|
|
|
|
|
|
def test_outputs_method_no_outputs():
|
|
|
|
@component
|
|
|
|
class MockComponent:
|
|
|
|
def run(self):
|
|
|
|
return {}
|
|
|
|
|
|
|
|
comp = MockComponent()
|
|
|
|
assert find_component_outputs(comp) == {}
|
|
|
|
|
|
|
|
|
|
|
|
def test_outputs_method_one_output():
|
|
|
|
@component
|
|
|
|
class MockComponent:
|
|
|
|
@component.output_types(value=int)
|
|
|
|
def run(self):
|
|
|
|
return {"value": 1}
|
|
|
|
|
|
|
|
comp = MockComponent()
|
|
|
|
assert find_component_outputs(comp) == {"value": {"type": int}}
|
|
|
|
|
|
|
|
|
|
|
|
def test_outputs_method_multiple_outputs():
|
|
|
|
@component
|
|
|
|
class MockComponent:
|
|
|
|
@component.output_types(value1=int, value2=str)
|
|
|
|
def run(self):
|
|
|
|
return {"value1": 1, "value2": "test"}
|
|
|
|
|
|
|
|
comp = MockComponent()
|
|
|
|
assert find_component_outputs(comp) == {"value1": {"type": int}, "value2": {"type": str}}
|
|
|
|
|
|
|
|
|
|
|
|
def test_outputs_dynamic_from_init():
|
|
|
|
@component
|
|
|
|
class MockComponent:
|
|
|
|
def __init__(self):
|
|
|
|
component.set_output_types(self, value=int)
|
|
|
|
|
|
|
|
def run(self):
|
|
|
|
return {"value": 1}
|
|
|
|
|
|
|
|
comp = MockComponent()
|
|
|
|
assert find_component_outputs(comp) == {"value": {"type": int}}
|