chore: import upstream snapshot with attribution
CodeQL / Analyze (python) (push) Has been cancelled
Update Platform Components Table / update (push) Has been cancelled
Docker image release / Build base image (push) Has been cancelled
Sync docs with Docusaurus / sync (push) Has been cancelled
Tests / Check if changed (push) Has been cancelled
Tests / format (push) Has been cancelled
Tests / check-imports (push) Has been cancelled
Tests / Unit / macos-latest (push) Has been cancelled
Tests / Unit / ubuntu-latest (push) Has been cancelled
Tests / Unit / windows-latest (push) Has been cancelled
Tests / mypy (push) Has been cancelled
Tests / Integration / ubuntu-latest (push) Has been cancelled
Tests / Integration / macos-latest (push) Has been cancelled
Tests / Integration / windows-latest (push) Has been cancelled
Tests / notify-slack-on-failure (push) Has been cancelled
Tests / Mark tests as completed (push) Has been cancelled
CodeQL / Analyze (python) (push) Has been cancelled
Update Platform Components Table / update (push) Has been cancelled
Docker image release / Build base image (push) Has been cancelled
Sync docs with Docusaurus / sync (push) Has been cancelled
Tests / Check if changed (push) Has been cancelled
Tests / format (push) Has been cancelled
Tests / check-imports (push) Has been cancelled
Tests / Unit / macos-latest (push) Has been cancelled
Tests / Unit / ubuntu-latest (push) Has been cancelled
Tests / Unit / windows-latest (push) Has been cancelled
Tests / mypy (push) Has been cancelled
Tests / Integration / ubuntu-latest (push) Has been cancelled
Tests / Integration / macos-latest (push) Has been cancelled
Tests / Integration / windows-latest (push) Has been cancelled
Tests / notify-slack-on-failure (push) Has been cancelled
Tests / Mark tests as completed (push) Has been cancelled
This commit is contained in:
@@ -0,0 +1,576 @@
|
||||
# SPDX-FileCopyrightText: 2022-present deepset GmbH <info@deepset.ai>
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
from functools import partial
|
||||
from typing import Any
|
||||
|
||||
import pytest
|
||||
|
||||
from haystack.core.component import Component, InputSocket, OutputSocket, component
|
||||
from haystack.core.component.component import _hook_component_init
|
||||
from haystack.core.errors import ComponentError
|
||||
from haystack.core.pipeline import Pipeline
|
||||
|
||||
|
||||
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) -> dict[str, int]:
|
||||
return {"output_value": input_value}
|
||||
|
||||
# Verifies also instantiation works with no issues
|
||||
assert MockComponent()
|
||||
assert component.registry["test_component.MockComponent"] == MockComponent
|
||||
assert isinstance(MockComponent(), Component)
|
||||
assert MockComponent().__haystack_supports_async__ is False # type: ignore[attr-defined]
|
||||
|
||||
|
||||
def test_correct_declaration_with_async():
|
||||
@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) -> dict[str, int]:
|
||||
return {"output_value": input_value}
|
||||
|
||||
@component.output_types(output_value=int)
|
||||
async def run_async(self, input_value: int) -> dict[str, int]:
|
||||
return {"output_value": input_value}
|
||||
|
||||
# Verifies also instantiation works with no issues
|
||||
assert MockComponent()
|
||||
assert component.registry["test_component.MockComponent"] == MockComponent
|
||||
assert isinstance(MockComponent(), Component)
|
||||
assert MockComponent().__haystack_supports_async__ is True # type: ignore[attr-defined]
|
||||
|
||||
|
||||
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) -> dict[str, 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) -> dict[str, 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():
|
||||
with pytest.raises(ComponentError, match=r"must have a 'run\(\)' method"):
|
||||
|
||||
@component
|
||||
class MockComponent: # type: ignore[type-var]
|
||||
def another_method(self, input_value: int) -> dict[str, int]:
|
||||
return {"output_value": input_value}
|
||||
|
||||
|
||||
def test_async_run_not_async():
|
||||
@component
|
||||
class MockComponent:
|
||||
@component.output_types(value=int)
|
||||
def run(self, value: int) -> dict[str, int]:
|
||||
return {"value": 1}
|
||||
|
||||
@component.output_types(value=int)
|
||||
def run_async(self, value: int) -> dict[str, int]:
|
||||
return {"value": 1}
|
||||
|
||||
with pytest.raises(ComponentError, match=r"must be a coroutine"):
|
||||
_ = MockComponent()
|
||||
|
||||
|
||||
def test_async_run_not_coroutine():
|
||||
@component
|
||||
class MockComponent:
|
||||
@component.output_types(value=int)
|
||||
def run(self, value: int) -> dict[str, int]:
|
||||
return {"value": 1}
|
||||
|
||||
@component.output_types(value=int)
|
||||
async def run_async(self, value: int) -> dict[str, int]: # type: ignore[misc]
|
||||
yield {"value": 1}
|
||||
|
||||
with pytest.raises(ComponentError, match=r"must be a coroutine"):
|
||||
_ = MockComponent()
|
||||
|
||||
|
||||
def test_parameters_mismatch_run_and_async_run():
|
||||
err_msg = r"Parameters of 'run' and 'run_async' methods must be the same"
|
||||
|
||||
@component
|
||||
class MockComponentMismatchingInputTypes:
|
||||
@component.output_types(value=int)
|
||||
def run(self, value: int) -> dict[str, int]:
|
||||
return {"value": 1}
|
||||
|
||||
@component.output_types(value=int)
|
||||
async def run_async(self, value: str) -> dict[str, int]:
|
||||
return {"value": 1}
|
||||
|
||||
with pytest.raises(ComponentError, match=err_msg):
|
||||
_ = MockComponentMismatchingInputTypes()
|
||||
|
||||
@component
|
||||
class MockComponentMismatchingInputs:
|
||||
@component.output_types(value=int)
|
||||
def run(self, value: int, **kwargs: Any) -> dict[str, int]:
|
||||
return {"value": 1}
|
||||
|
||||
@component.output_types(value=int)
|
||||
async def run_async(self, value: int) -> dict[str, int]:
|
||||
return {"value": 1}
|
||||
|
||||
with pytest.raises(ComponentError, match=err_msg):
|
||||
_ = MockComponentMismatchingInputs()
|
||||
|
||||
@component
|
||||
class MockComponentMismatchingInputOrder:
|
||||
@component.output_types(value=int)
|
||||
def run(self, value: int, another: str) -> dict[str, int]:
|
||||
return {"value": 1}
|
||||
|
||||
@component.output_types(value=int)
|
||||
async def run_async(self, another: str, value: int) -> dict[str, int]:
|
||||
return {"value": 1}
|
||||
|
||||
with pytest.raises(ComponentError, match=err_msg):
|
||||
_ = MockComponentMismatchingInputOrder()
|
||||
|
||||
|
||||
def test_set_input_types():
|
||||
@component
|
||||
class MockComponent:
|
||||
def __init__(self, flag: bool):
|
||||
component.set_input_types(self, value=Any)
|
||||
if flag:
|
||||
component.set_input_type(self, name="another", type=str)
|
||||
|
||||
@component.output_types(value=int)
|
||||
def run(self, **kwargs):
|
||||
return {"value": 1}
|
||||
|
||||
comp = MockComponent(False)
|
||||
assert comp.__haystack_input__._sockets_dict == {"value": InputSocket("value", Any)} # type: ignore[attr-defined]
|
||||
assert comp.run() == {"value": 1}
|
||||
|
||||
comp = MockComponent(True)
|
||||
assert comp.__haystack_input__._sockets_dict == { # type: ignore[attr-defined]
|
||||
"value": InputSocket("value", Any),
|
||||
"another": InputSocket("another", str),
|
||||
}
|
||||
assert comp.run() == {"value": 1}
|
||||
|
||||
|
||||
def test_set_input_types_no_kwarg():
|
||||
@component
|
||||
class MockComponent:
|
||||
def __init__(self, flag: bool):
|
||||
if flag:
|
||||
component.set_input_type(self, name="another", type=str)
|
||||
else:
|
||||
component.set_input_types(self, value=Any)
|
||||
|
||||
@component.output_types(value=int)
|
||||
def run(self, fini: bool) -> dict[str, int]:
|
||||
return {"value": 1}
|
||||
|
||||
with pytest.raises(ComponentError, match=r"doesn't have a kwargs parameter"):
|
||||
_ = MockComponent(False)
|
||||
|
||||
with pytest.raises(ComponentError, match=r"doesn't have a kwargs parameter"):
|
||||
_ = MockComponent(True)
|
||||
|
||||
|
||||
def test_set_input_types_overrides_run():
|
||||
@component
|
||||
class MockComponent:
|
||||
def __init__(self, state: bool):
|
||||
if state:
|
||||
component.set_input_type(self, name="fini", type=str)
|
||||
else:
|
||||
component.set_input_types(self, fini=Any)
|
||||
|
||||
@component.output_types(value=int)
|
||||
def run(self, fini: bool, **kwargs: Any) -> dict[str, int]:
|
||||
return {"value": 1}
|
||||
|
||||
err_msg = "cannot override the parameters of the 'run' method"
|
||||
with pytest.raises(ComponentError, match=err_msg):
|
||||
_ = MockComponent(False)
|
||||
|
||||
with pytest.raises(ComponentError, match=err_msg):
|
||||
_ = MockComponent(True)
|
||||
|
||||
|
||||
def test_set_input_types_postponed_annotations():
|
||||
# The component HelloUsingFutureAnnotations must live in a different module than the one where the test is defined,
|
||||
# so we can properly set up postponed evaluation of annotations using `from __future__ import annotations`.
|
||||
# For this reason, we define it in haystack.testing.sample_components.future_annotations and import it here.
|
||||
from haystack.testing.sample_components import HelloUsingFutureAnnotations
|
||||
|
||||
assert HelloUsingFutureAnnotations().__haystack_input__._sockets_dict == {"word": InputSocket("word", str)} # type: ignore[attr-defined]
|
||||
|
||||
|
||||
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) -> dict[str, int]:
|
||||
return {"value": 1}
|
||||
|
||||
comp = MockComponent()
|
||||
assert comp.__haystack_output__._sockets_dict == {"value": OutputSocket("value", int)} # type: ignore[attr-defined]
|
||||
|
||||
|
||||
def test_output_types_decorator_with_compatible_type():
|
||||
@component
|
||||
class MockComponent:
|
||||
@component.output_types(value=int)
|
||||
def run(self, value: int) -> dict[str, int]:
|
||||
return {"value": 1}
|
||||
|
||||
def to_dict(self) -> dict:
|
||||
return {}
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, data: dict) -> "MockComponent":
|
||||
return cls()
|
||||
|
||||
comp = MockComponent()
|
||||
assert comp.__haystack_output__._sockets_dict == {"value": OutputSocket("value", int)} # type: ignore[attr-defined]
|
||||
|
||||
|
||||
def test_output_types_decorator_wrong_method():
|
||||
with pytest.raises(ComponentError):
|
||||
|
||||
@component
|
||||
class MockComponent:
|
||||
def run(self, value: int) -> dict[str, int]:
|
||||
return {"value": 1}
|
||||
|
||||
@component.output_types(value=int)
|
||||
def to_dict(self):
|
||||
return {}
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, data):
|
||||
return cls()
|
||||
|
||||
|
||||
def test_output_types_decorator_and_set_output_types():
|
||||
@component
|
||||
class MockComponent:
|
||||
def __init__(self) -> None:
|
||||
component.set_output_types(self, value=int)
|
||||
|
||||
@component.output_types(value=int)
|
||||
def run(self, value: int) -> dict[str, int]:
|
||||
return {"value": 1}
|
||||
|
||||
with pytest.raises(ComponentError, match="Cannot call `set_output_types`"):
|
||||
_ = MockComponent()
|
||||
|
||||
|
||||
def test_output_types_decorator_and_set_output_types_async():
|
||||
@component
|
||||
class MockComponent:
|
||||
def __init__(self) -> None:
|
||||
component.set_output_types(self, value=int)
|
||||
|
||||
def run(self, value: int) -> dict[str, int]:
|
||||
return {"value": 1}
|
||||
|
||||
@component.output_types(value=int)
|
||||
async def run_async(self, value: int) -> dict[str, int]:
|
||||
return {"value": 1}
|
||||
|
||||
with pytest.raises(ComponentError, match="Cannot call `set_output_types`"):
|
||||
_ = MockComponent()
|
||||
|
||||
|
||||
def test_output_types_decorator_mismatch_run_async_run():
|
||||
@component
|
||||
class MockComponent:
|
||||
@component.output_types(value=int)
|
||||
def run(self, value: int) -> dict[str, str]:
|
||||
return {"value": "1"}
|
||||
|
||||
@component.output_types(value=str)
|
||||
async def run_async(self, value: int) -> dict[str, str]:
|
||||
return {"value": "1"}
|
||||
|
||||
with pytest.raises(ComponentError, match=r"Output type specifications .* must be the same"):
|
||||
_ = MockComponent()
|
||||
|
||||
|
||||
def test_output_types_decorator_missing_async_run():
|
||||
@component
|
||||
class MockComponent:
|
||||
@component.output_types(value=int)
|
||||
def run(self, value: int) -> dict[str, int]:
|
||||
return {"value": 1}
|
||||
|
||||
async def run_async(self, value: int) -> dict[str, int]:
|
||||
return {"value": 1}
|
||||
|
||||
with pytest.raises(ComponentError, match=r"Output type specifications .* must be the same"):
|
||||
_ = MockComponent()
|
||||
|
||||
|
||||
def test_component_decorator_set_it_as_component():
|
||||
@component
|
||||
class MockComponent:
|
||||
@component.output_types(value=int)
|
||||
def run(self, value: int) -> dict[str, int]:
|
||||
return {"value": 1}
|
||||
|
||||
def to_dict(self) -> dict:
|
||||
return {}
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, data: dict) -> "MockComponent":
|
||||
return cls()
|
||||
|
||||
comp = MockComponent()
|
||||
assert isinstance(comp, Component)
|
||||
|
||||
|
||||
def test_input_has_default_value():
|
||||
@component
|
||||
class MockComponent:
|
||||
@component.output_types(value=int)
|
||||
def run(self, value: int = 42) -> dict[str, int]:
|
||||
return {"value": value}
|
||||
|
||||
comp = MockComponent()
|
||||
assert comp.__haystack_input__._sockets_dict["value"].default_value == 42 # type: ignore[attr-defined]
|
||||
assert not comp.__haystack_input__._sockets_dict["value"].is_mandatory # type: ignore[attr-defined]
|
||||
|
||||
|
||||
def test_keyword_only_args():
|
||||
@component
|
||||
class MockComponent:
|
||||
def __init__(self):
|
||||
component.set_output_types(self, value=int)
|
||||
|
||||
def run(self, *, arg: int) -> dict[str, int]:
|
||||
return {"value": arg}
|
||||
|
||||
comp = MockComponent()
|
||||
component_inputs = {
|
||||
name: {"type": socket.type}
|
||||
for name, socket in comp.__haystack_input__._sockets_dict.items() # type: ignore[attr-defined]
|
||||
}
|
||||
assert component_inputs == {"arg": {"type": int}}
|
||||
|
||||
|
||||
def test_repr():
|
||||
@component
|
||||
class MockComponent:
|
||||
def __init__(self):
|
||||
component.set_output_types(self, value=int)
|
||||
|
||||
def run(self, value: int) -> dict[str, int]:
|
||||
return {"value": value}
|
||||
|
||||
comp = MockComponent()
|
||||
assert repr(comp) == f"{object.__repr__(comp)}\nInputs:\n - value: int\nOutputs:\n - value: int"
|
||||
|
||||
|
||||
def test_repr_added_to_pipeline():
|
||||
@component
|
||||
class MockComponent:
|
||||
def __init__(self):
|
||||
component.set_output_types(self, value=int)
|
||||
|
||||
def run(self, value: int) -> dict[str, int]:
|
||||
return {"value": value}
|
||||
|
||||
pipe = Pipeline()
|
||||
comp = MockComponent()
|
||||
pipe.add_component("my_component", comp)
|
||||
assert repr(comp) == f"{object.__repr__(comp)}\nmy_component\nInputs:\n - value: int\nOutputs:\n - value: int"
|
||||
|
||||
|
||||
def test_pre_init_hooking():
|
||||
@component
|
||||
class MockComponent:
|
||||
def __init__(self, pos_arg1, pos_arg2, pos_arg3=None, *, kwarg1=1, kwarg2="string"):
|
||||
self.pos_arg1 = pos_arg1
|
||||
self.pos_arg2 = pos_arg2
|
||||
self.pos_arg3 = pos_arg3
|
||||
self.kwarg1 = kwarg1
|
||||
self.kwarg2 = kwarg2
|
||||
|
||||
@component.output_types(output_value=int)
|
||||
def run(self, input_value: int) -> dict[str, int]:
|
||||
return {"output_value": input_value}
|
||||
|
||||
def pre_init_hook(component_class, init_params, expected_params):
|
||||
assert component_class == MockComponent
|
||||
assert init_params == expected_params
|
||||
|
||||
def pre_init_hook_modify(component_class, init_params, expected_params):
|
||||
assert component_class == MockComponent
|
||||
assert init_params == expected_params
|
||||
|
||||
init_params["pos_arg1"] = 2
|
||||
init_params["pos_arg2"] = 0
|
||||
init_params["pos_arg3"] = "modified"
|
||||
init_params["kwarg2"] = "modified string"
|
||||
|
||||
with _hook_component_init(partial(pre_init_hook, expected_params={"pos_arg1": 1, "pos_arg2": 2, "kwarg1": None})):
|
||||
_ = MockComponent(1, 2, kwarg1=None)
|
||||
|
||||
with _hook_component_init(partial(pre_init_hook, expected_params={"pos_arg1": 1, "pos_arg2": 2, "pos_arg3": 0.01})):
|
||||
_ = MockComponent(pos_arg1=1, pos_arg2=2, pos_arg3=0.01)
|
||||
|
||||
with _hook_component_init(
|
||||
partial(pre_init_hook_modify, expected_params={"pos_arg1": 0, "pos_arg2": 1, "pos_arg3": 0.01, "kwarg1": 0})
|
||||
):
|
||||
c = MockComponent(0, 1, pos_arg3=0.01, kwarg1=0)
|
||||
|
||||
assert c.pos_arg1 == 2
|
||||
assert c.pos_arg2 == 0
|
||||
assert c.pos_arg3 == "modified"
|
||||
assert c.kwarg1 == 0
|
||||
assert c.kwarg2 == "modified string"
|
||||
|
||||
|
||||
def test_pre_init_hooking_variadic_positional_args():
|
||||
@component
|
||||
class MockComponent:
|
||||
def __init__(self, *args, kwarg1=1, kwarg2="string"):
|
||||
self.args = args
|
||||
self.kwarg1 = kwarg1
|
||||
self.kwarg2 = kwarg2
|
||||
|
||||
@component.output_types(output_value=int)
|
||||
def run(self, input_value: int) -> dict[str, int]:
|
||||
return {"output_value": input_value}
|
||||
|
||||
def pre_init_hook(component_class, init_params, expected_params):
|
||||
assert component_class == MockComponent
|
||||
assert init_params == expected_params
|
||||
|
||||
c = MockComponent(1, 2, 3, kwarg1=None)
|
||||
assert c.args == (1, 2, 3)
|
||||
assert c.kwarg1 is None
|
||||
assert c.kwarg2 == "string"
|
||||
|
||||
with (
|
||||
pytest.raises(ComponentError),
|
||||
_hook_component_init(partial(pre_init_hook, expected_params={"args": (1, 2), "kwarg1": None})),
|
||||
):
|
||||
_ = MockComponent(1, 2, kwarg1=None)
|
||||
|
||||
|
||||
def test_pre_init_hooking_variadic_kwargs():
|
||||
@component
|
||||
class MockComponent:
|
||||
def __init__(self, pos_arg1, pos_arg2=None, **kwargs):
|
||||
self.pos_arg1 = pos_arg1
|
||||
self.pos_arg2 = pos_arg2
|
||||
self.kwargs = kwargs
|
||||
|
||||
@component.output_types(output_value=int)
|
||||
def run(self, input_value: int) -> dict[str, int]:
|
||||
return {"output_value": input_value}
|
||||
|
||||
def pre_init_hook(component_class, init_params, expected_params):
|
||||
assert component_class == MockComponent
|
||||
assert init_params == expected_params
|
||||
|
||||
with _hook_component_init(
|
||||
partial(pre_init_hook, expected_params={"pos_arg1": 1, "kwarg1": None, "kwarg2": 10, "kwarg3": "string"})
|
||||
):
|
||||
c = MockComponent(1, kwarg1=None, kwarg2=10, kwarg3="string")
|
||||
assert c.pos_arg1 == 1
|
||||
assert c.pos_arg2 is None
|
||||
assert c.kwargs == {"kwarg1": None, "kwarg2": 10, "kwarg3": "string"}
|
||||
|
||||
def pre_init_hook_modify(component_class, init_params, expected_params):
|
||||
assert component_class == MockComponent
|
||||
assert init_params == expected_params
|
||||
|
||||
init_params["pos_arg1"] = 2
|
||||
init_params["pos_arg2"] = 0
|
||||
init_params["some_kwarg"] = "modified string"
|
||||
|
||||
with _hook_component_init(
|
||||
partial(
|
||||
pre_init_hook_modify,
|
||||
expected_params={"pos_arg1": 0, "pos_arg2": 1, "kwarg1": 999, "some_kwarg": "some_value"},
|
||||
)
|
||||
):
|
||||
c = MockComponent(0, 1, kwarg1=999, some_kwarg="some_value")
|
||||
|
||||
assert c.pos_arg1 == 2
|
||||
assert c.pos_arg2 == 0
|
||||
assert c.kwargs == {"kwarg1": 999, "some_kwarg": "modified string"}
|
||||
@@ -0,0 +1,94 @@
|
||||
# SPDX-FileCopyrightText: 2022-present deepset GmbH <info@deepset.ai>
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
from typing import Any
|
||||
|
||||
import pytest
|
||||
|
||||
from haystack.core.component import component
|
||||
from haystack.core.component.component import ComponentError
|
||||
|
||||
|
||||
@component
|
||||
class ValidComponent:
|
||||
def run(self, text: str) -> dict[str, Any]:
|
||||
return {"result": text}
|
||||
|
||||
async def run_async(self, text: str) -> dict[str, Any]:
|
||||
return {"result": text}
|
||||
|
||||
|
||||
@component
|
||||
class DifferentParamNameComponent:
|
||||
def run(self, text: str) -> dict[str, Any]:
|
||||
return {"result": text}
|
||||
|
||||
async def run_async(self, input_text: str) -> dict[str, Any]:
|
||||
return {"result": input_text}
|
||||
|
||||
|
||||
@component
|
||||
class DifferentParamTypeComponent:
|
||||
def run(self, text: str) -> dict[str, Any]:
|
||||
return {"result": text}
|
||||
|
||||
async def run_async(self, text: list[str]) -> dict[str, Any]:
|
||||
return {"result": text[0]}
|
||||
|
||||
|
||||
@component
|
||||
class DifferentDefaultValueComponent:
|
||||
def run(self, text: str = "default") -> dict[str, Any]:
|
||||
return {"result": text}
|
||||
|
||||
async def run_async(self, text: str = "different") -> dict[str, Any]:
|
||||
return {"result": text}
|
||||
|
||||
|
||||
@component
|
||||
class DifferentParamKindComponent:
|
||||
def run(self, text: str) -> dict[str, Any]:
|
||||
return {"result": text}
|
||||
|
||||
async def run_async(self, *, text: str) -> dict[str, Any]:
|
||||
return {"result": text}
|
||||
|
||||
|
||||
@component
|
||||
class DifferentParamCountComponent:
|
||||
def run(self, text: str) -> dict[str, Any]:
|
||||
return {"result": text}
|
||||
|
||||
async def run_async(self, text: str, extra: str) -> dict[str, Any]:
|
||||
return {"result": text + extra}
|
||||
|
||||
|
||||
def test_valid_signatures():
|
||||
component = ValidComponent()
|
||||
assert component.run("test") == {"result": "test"}
|
||||
|
||||
|
||||
def test_different_param_names():
|
||||
with pytest.raises(ComponentError, match="name mismatch"):
|
||||
DifferentParamNameComponent()
|
||||
|
||||
|
||||
def test_different_param_types():
|
||||
with pytest.raises(ComponentError, match="type mismatch"):
|
||||
DifferentParamTypeComponent()
|
||||
|
||||
|
||||
def test_different_default_values():
|
||||
with pytest.raises(ComponentError, match="default value mismatch"):
|
||||
DifferentDefaultValueComponent()
|
||||
|
||||
|
||||
def test_different_param_kinds():
|
||||
with pytest.raises(ComponentError, match=r"kind \(POSITIONAL, KEYWORD, etc\.\) mismatch: "):
|
||||
DifferentParamKindComponent()
|
||||
|
||||
|
||||
def test_different_param_count():
|
||||
with pytest.raises(ComponentError, match="Different number of parameters"):
|
||||
DifferentParamCountComponent()
|
||||
@@ -0,0 +1,101 @@
|
||||
# SPDX-FileCopyrightText: 2022-present deepset GmbH <info@deepset.ai>
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import pytest
|
||||
|
||||
from haystack.core.component.sockets import InputSocket, OutputSocket, Sockets
|
||||
from haystack.testing.factory import component_class
|
||||
|
||||
|
||||
class TestSockets:
|
||||
def test_init(self):
|
||||
comp = component_class("SomeComponent", input_types={"input_1": int, "input_2": int})()
|
||||
sockets: dict[str, InputSocket | OutputSocket] = {
|
||||
"input_1": InputSocket("input_1", int),
|
||||
"input_2": InputSocket("input_2", int),
|
||||
}
|
||||
io = Sockets(component=comp, sockets_dict=sockets, sockets_io_type=InputSocket)
|
||||
assert io._component == comp
|
||||
assert "input_1" in io.__dict__
|
||||
assert io.__dict__["input_1"] == comp.__haystack_input__._sockets_dict["input_1"] # type: ignore[attr-defined]
|
||||
assert "input_2" in io.__dict__
|
||||
assert io.__dict__["input_2"] == comp.__haystack_input__._sockets_dict["input_2"] # type: ignore[attr-defined]
|
||||
|
||||
def test_init_with_empty_sockets(self):
|
||||
comp = component_class("SomeComponent")()
|
||||
io = Sockets(component=comp, sockets_dict={}, sockets_io_type=InputSocket)
|
||||
|
||||
assert io._component == comp
|
||||
assert io._sockets_dict == {}
|
||||
|
||||
def test_getattribute(self):
|
||||
comp = component_class("SomeComponent", input_types={"input_1": int, "input_2": int})()
|
||||
io = Sockets(
|
||||
component=comp,
|
||||
sockets_dict=comp.__haystack_input__._sockets_dict, # type: ignore[attr-defined]
|
||||
sockets_io_type=InputSocket,
|
||||
)
|
||||
|
||||
assert io.input_1 == comp.__haystack_input__._sockets_dict["input_1"] # type: ignore[attr-defined]
|
||||
assert io.input_2 == comp.__haystack_input__._sockets_dict["input_2"] # type: ignore[attr-defined]
|
||||
|
||||
def test_getattribute_non_existing_socket(self):
|
||||
comp = component_class("SomeComponent", input_types={"input_1": int, "input_2": int})()
|
||||
io = Sockets(
|
||||
component=comp,
|
||||
sockets_dict=comp.__haystack_input__._sockets_dict, # type: ignore[attr-defined]
|
||||
sockets_io_type=InputSocket,
|
||||
)
|
||||
|
||||
with pytest.raises(AttributeError):
|
||||
io.input_3
|
||||
|
||||
def test_repr(self):
|
||||
comp = component_class("SomeComponent", input_types={"input_1": int, "input_2": int})()
|
||||
io = Sockets(
|
||||
component=comp,
|
||||
sockets_dict=comp.__haystack_input__._sockets_dict, # type: ignore[attr-defined]
|
||||
sockets_io_type=InputSocket,
|
||||
)
|
||||
res = repr(io)
|
||||
assert res == "Inputs:\n - input_1: int\n - input_2: int"
|
||||
|
||||
def test_get(self):
|
||||
comp = component_class("SomeComponent", input_types={"input_1": int, "input_2": int})()
|
||||
io = Sockets(
|
||||
component=comp,
|
||||
sockets_dict=comp.__haystack_input__._sockets_dict, # type: ignore[attr-defined]
|
||||
sockets_io_type=InputSocket,
|
||||
)
|
||||
|
||||
assert io.get("input_1") == comp.__haystack_input__._sockets_dict["input_1"] # type: ignore[attr-defined]
|
||||
assert io.get("input_2") == comp.__haystack_input__._sockets_dict["input_2"] # type: ignore[attr-defined]
|
||||
assert io.get("invalid") is None
|
||||
assert io.get("invalid", InputSocket("input_2", int)) == InputSocket("input_2", int)
|
||||
|
||||
def test_contains(self):
|
||||
comp = component_class("SomeComponent", input_types={"input_1": int, "input_2": int})()
|
||||
io = Sockets(
|
||||
component=comp,
|
||||
sockets_dict=comp.__haystack_input__._sockets_dict, # type: ignore[attr-defined]
|
||||
sockets_io_type=InputSocket,
|
||||
)
|
||||
|
||||
assert "input_1" in io
|
||||
assert "input_2" in io
|
||||
assert "invalid" not in io
|
||||
|
||||
|
||||
def test_input_socket_variadic_without_type_arg_raises_component_error():
|
||||
"""Bare Variadic (no type argument) should raise ComponentError, not IndexError."""
|
||||
from collections.abc import Iterable
|
||||
from typing import Annotated
|
||||
|
||||
from haystack.core.component.types import HAYSTACK_VARIADIC_ANNOTATION
|
||||
from haystack.core.errors import ComponentError
|
||||
|
||||
bare_variadic = Annotated[Iterable, HAYSTACK_VARIADIC_ANNOTATION]
|
||||
|
||||
with pytest.raises(ComponentError, match="must have a type argument"):
|
||||
InputSocket("inputs", bare_variadic)
|
||||
Reference in New Issue
Block a user