Rework Pipeline.connect() tests and structure (#6917)

This commit is contained in:
Silvano Cerza 2024-02-05 18:16:02 +01:00 committed by GitHub
parent 0191b1e6e4
commit b9d7a98359
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 526 additions and 504 deletions

View File

@ -0,0 +1,200 @@
import pytest
from haystack.core.component.types import Variadic
from haystack.core.errors import PipelineConnectError
from haystack.core.pipeline import Pipeline
from haystack.core.pipeline.pipeline import parse_connect_string
from haystack.testing import factory
def test_parse_connection():
assert parse_connect_string("foobar") == ("foobar", None)
assert parse_connect_string("foo.bar") == ("foo", "bar")
def test_connect():
comp1 = factory.component_class("Comp1", output_types={"value": int})()
comp2 = factory.component_class("Comp2", input_types={"value": int})()
pipe = Pipeline()
pipe.add_component("comp1", comp1)
pipe.add_component("comp2", comp2)
pipe.connect("comp1.value", "comp2.value")
assert comp1.__haystack_output__.value.receivers == ["comp2"]
assert comp2.__haystack_input__.value.senders == ["comp1"]
assert list(pipe.graph.edges) == [("comp1", "comp2", "value/value")]
def test_reconnection():
comp1 = factory.component_class("Comp1", output_types={"value": int})()
comp2 = factory.component_class("Comp2", input_types={"value": int})()
pipe = Pipeline()
pipe.add_component("comp1", comp1)
pipe.add_component("comp2", comp2)
pipe.connect("comp1.value", "comp2.value")
pipe.connect("comp1.value", "comp2.value")
assert comp1.__haystack_output__.value.receivers == ["comp2"]
assert comp2.__haystack_input__.value.senders == ["comp1"]
assert list(pipe.graph.edges) == [("comp1", "comp2", "value/value")]
def test_with_sender_component_name():
comp1 = factory.component_class("Comp1", output_types={"value": int})()
comp2 = factory.component_class("Comp2", input_types={"value": int})()
pipe = Pipeline()
pipe.add_component("comp1", comp1)
pipe.add_component("comp2", comp2)
pipe.connect("comp1", "comp2.value")
assert comp1.__haystack_output__.value.receivers == ["comp2"]
assert comp2.__haystack_input__.value.senders == ["comp1"]
assert list(pipe.graph.edges) == [("comp1", "comp2", "value/value")]
def test_with_receiver_component_name():
comp1 = factory.component_class("Comp1", output_types={"value": int})()
comp2 = factory.component_class("Comp2", input_types={"value": int})()
pipe = Pipeline()
pipe.add_component("comp1", comp1)
pipe.add_component("comp2", comp2)
pipe.connect("comp1.value", "comp2")
assert comp1.__haystack_output__.value.receivers == ["comp2"]
assert comp2.__haystack_input__.value.senders == ["comp1"]
assert list(pipe.graph.edges) == [("comp1", "comp2", "value/value")]
def test_with_sender_and_receiver_component_name():
comp1 = factory.component_class("Comp1", output_types={"value": int})()
comp2 = factory.component_class("Comp2", input_types={"value": int})()
pipe = Pipeline()
pipe.add_component("comp1", comp1)
pipe.add_component("comp2", comp2)
pipe.connect("comp1", "comp2")
assert comp1.__haystack_output__.value.receivers == ["comp2"]
assert comp2.__haystack_input__.value.senders == ["comp1"]
assert list(pipe.graph.edges) == [("comp1", "comp2", "value/value")]
def test_with_sender_not_in_pipeline():
comp2 = factory.component_class("Comp2", input_types={"value": int})()
pipe = Pipeline()
pipe.add_component("comp2", comp2)
with pytest.raises(ValueError):
pipe.connect("comp1.value", "comp2.value")
def test_with_receiver_not_in_pipeline():
comp1 = factory.component_class("Comp1", output_types={"value": int})()
pipe = Pipeline()
pipe.add_component("comp1", comp1)
with pytest.raises(ValueError):
pipe.connect("comp1.value", "comp2.value")
def test_with_sender_socket_name_not_in_pipeline():
comp1 = factory.component_class("Comp1", output_types={"value": int})()
comp2 = factory.component_class("Comp2", input_types={"value": int})()
pipe = Pipeline()
pipe.add_component("comp1", comp1)
pipe.add_component("comp2", comp2)
with pytest.raises(PipelineConnectError):
pipe.connect("comp1.non_existing", "comp2.value")
def test_with_receiver_socket_name_not_in_pipeline():
comp1 = factory.component_class("Comp1", output_types={"value": int})()
comp2 = factory.component_class("Comp2", input_types={"value": int})()
pipe = Pipeline()
pipe.add_component("comp1", comp1)
pipe.add_component("comp2", comp2)
with pytest.raises(PipelineConnectError):
pipe.connect("comp1.value", "comp2.non_existing")
def test_with_no_matching_types_and_same_names():
comp1 = factory.component_class("Comp1", output_types={"value": int})()
comp2 = factory.component_class("Comp2", input_types={"value": str})()
pipe = Pipeline()
pipe.add_component("comp1", comp1)
pipe.add_component("comp2", comp2)
with pytest.raises(PipelineConnectError):
pipe.connect("comp1", "comp2")
def test_with_multiple_sender_connections_with_same_type_and_differing_name():
comp1 = factory.component_class("Comp1", output_types={"val1": int, "val2": int})()
comp2 = factory.component_class("Comp2", input_types={"value": int})()
pipe = Pipeline()
pipe.add_component("comp1", comp1)
pipe.add_component("comp2", comp2)
with pytest.raises(PipelineConnectError):
pipe.connect("comp1", "comp2")
def test_with_multiple_receiver_connections_with_same_type_and_differing_name():
comp1 = factory.component_class("Comp1", output_types={"value": int})()
comp2 = factory.component_class("Comp2", input_types={"val1": int, "val2": int})()
pipe = Pipeline()
pipe.add_component("comp1", comp1)
pipe.add_component("comp2", comp2)
with pytest.raises(PipelineConnectError):
pipe.connect("comp1", "comp2")
def test_with_multiple_sender_connections_with_same_type_and_same_name():
comp1 = factory.component_class("Comp1", output_types={"value": int, "other": int})()
comp2 = factory.component_class("Comp2", input_types={"value": int})()
pipe = Pipeline()
pipe.add_component("comp1", comp1)
pipe.add_component("comp2", comp2)
pipe.connect("comp1", "comp2")
assert comp1.__haystack_output__.value.receivers == ["comp2"]
assert comp2.__haystack_input__.value.senders == ["comp1"]
assert list(pipe.graph.edges) == [("comp1", "comp2", "value/value")]
def test_with_multiple_receiver_connections_with_same_type_and_same_name():
comp1 = factory.component_class("Comp1", output_types={"value": int})()
comp2 = factory.component_class("Comp2", input_types={"value": int, "other": int})()
pipe = Pipeline()
pipe.add_component("comp1", comp1)
pipe.add_component("comp2", comp2)
pipe.connect("comp1", "comp2")
assert comp1.__haystack_output__.value.receivers == ["comp2"]
assert comp2.__haystack_input__.value.senders == ["comp1"]
assert list(pipe.graph.edges) == [("comp1", "comp2", "value/value")]
def test_multiple_outputs_to_non_variadic_input():
comp1 = factory.component_class("Comp1", output_types={"value": int})()
comp2 = factory.component_class("Comp2", output_types={"value": int})()
comp3 = factory.component_class("Comp3", input_types={"value": int})()
pipe = Pipeline()
pipe.add_component("comp1", comp1)
pipe.add_component("comp2", comp2)
pipe.add_component("comp3", comp3)
pipe.connect("comp1.value", "comp3.value")
with pytest.raises(PipelineConnectError):
pipe.connect("comp2.value", "comp3.value")
def test_multiple_outputs_to_variadic_input():
comp1 = factory.component_class("Comp1", output_types={"value": int})()
comp2 = factory.component_class("Comp2", output_types={"value": int})()
comp3 = factory.component_class("Comp3", input_types={"value": Variadic[int]})()
pipe = Pipeline()
pipe.add_component("comp1", comp1)
pipe.add_component("comp2", comp2)
pipe.add_component("comp3", comp3)
pipe.connect("comp1.value", "comp3.value")
pipe.connect("comp2.value", "comp3.value")
assert comp1.__haystack_output__.value.receivers == ["comp3"]
assert comp2.__haystack_output__.value.receivers == ["comp3"]
assert comp3.__haystack_input__.value.senders == ["comp1", "comp2"]
assert list(pipe.graph.edges) == [("comp1", "comp3", "value/value"), ("comp2", "comp3", "value/value")]

View File

@ -1,404 +0,0 @@
# SPDX-FileCopyrightText: 2022-present deepset GmbH <info@deepset.ai>
#
# SPDX-License-Identifier: Apache-2.0
import re
from enum import Enum
from pathlib import Path
from typing import Any, Dict, List, Literal, Mapping, Optional, Sequence, Set, Tuple, Union
import pytest
from haystack.core.errors import PipelineConnectError
from haystack.core.pipeline import Pipeline
from haystack.core.pipeline.pipeline import parse_connect_string
from haystack.testing import factory
from haystack.testing.sample_components import AddFixedValue
class Class1:
...
class Class2:
...
class Class3(Class1):
...
class Enum1(Enum):
TEST1 = Class1
TEST2 = Class2
@pytest.mark.parametrize(
"from_type,to_type",
[
pytest.param(str, str, id="same-primitives"),
pytest.param(str, Optional[str], id="receiving-primitive-is-optional"),
pytest.param(str, Union[int, str], id="receiving-type-is-union-of-primitives"),
pytest.param(Union[int, str], Union[int, str], id="identical-unions"),
pytest.param(Union[int, str], Union[int, str, bool], id="receiving-union-is-superset-of-sender"),
pytest.param(str, Any, id="primitive-to-any"),
pytest.param(Class1, Class1, id="same-class"),
pytest.param(Class1, Optional[Class1], id="receiving-class-is-optional"),
pytest.param(Class1, Class1, id="class-to-any"),
pytest.param(Class3, Class1, id="subclass-to-class"),
pytest.param(Class1, Union[int, Class1], id="receiving-type-is-union-of-classes"),
pytest.param(Class3, Union[int, Class1], id="receiving-type-is-union-of-superclasses"),
pytest.param(List[int], List[int], id="same-lists"),
pytest.param(List[int], Optional[List[int]], id="receiving-list-is-optional"),
pytest.param(List[int], List[Any], id="list-of-primitive-to-list-of-any"),
pytest.param(List[Class1], List[Class1], id="list-of-same-classes"),
pytest.param(List[Class3], List[Class1], id="list-of-subclass-to-list-of-class"),
pytest.param(List[Class1], List[Any], id="list-of-classes-to-list-of-any"),
pytest.param(List[Set[Sequence[bool]]], List[Set[Sequence[bool]]], id="nested-sequences-of-same-primitives"),
pytest.param(
List[Set[Sequence[bool]]],
List[Set[Sequence[Any]]],
id="nested-sequences-of-primitives-to-nested-sequences-of-any",
),
pytest.param(List[Set[Sequence[Class1]]], List[Set[Sequence[Class1]]], id="nested-sequences-of-same-classes"),
pytest.param(
List[Set[Sequence[Class3]]],
List[Set[Sequence[Class1]]],
id="nested-sequences-of-subclasses-to-nested-sequences-of-classes",
),
pytest.param(
List[Set[Sequence[Class1]]],
List[Set[Sequence[Any]]],
id="nested-sequences-of-classes-to-nested-sequences-of-any",
),
pytest.param(Dict[str, int], Dict[str, int], id="same-dicts-of-primitives"),
pytest.param(Dict[str, int], Dict[Any, int], id="dict-of-primitives-to-dict-of-any-keys"),
pytest.param(Dict[str, int], Dict[str, Any], id="dict-of-primitives-to-dict-of-any-values"),
pytest.param(Dict[str, int], Dict[Any, Any], id="dict-of-primitives-to-dict-of-any-key-and-values"),
pytest.param(Dict[str, Class1], Dict[str, Class1], id="same-dicts-of-classes-values"),
pytest.param(Dict[str, Class3], Dict[str, Class1], id="dict-of-subclasses-to-dict-of-classes"),
pytest.param(Dict[str, Class1], Dict[Any, Class1], id="dict-of-classes-to-dict-of-any-keys"),
pytest.param(Dict[str, Class1], Dict[str, Any], id="dict-of-classes-to-dict-of-any-values"),
pytest.param(Dict[str, Class1], Dict[Any, Any], id="dict-of-classes-to-dict-of-any-key-and-values"),
pytest.param(
Dict[str, Mapping[str, Dict[str, int]]],
Dict[str, Mapping[str, Dict[str, int]]],
id="nested-mappings-of-same-primitives",
),
pytest.param(
Dict[str, Mapping[str, Dict[str, int]]],
Dict[str, Mapping[str, Dict[Any, int]]],
id="nested-mapping-of-primitives-to-nested-mapping-of-any-keys",
),
pytest.param(
Dict[str, Mapping[str, Dict[str, int]]],
Dict[str, Mapping[Any, Dict[str, int]]],
id="nested-mapping-of-primitives-to-nested-mapping-of-higher-level-any-keys",
),
pytest.param(
Dict[str, Mapping[str, Dict[str, int]]],
Dict[str, Mapping[str, Dict[str, Any]]],
id="nested-mapping-of-primitives-to-nested-mapping-of-any-values",
),
pytest.param(
Dict[str, Mapping[str, Dict[str, int]]],
Dict[str, Mapping[Any, Dict[Any, Any]]],
id="nested-mapping-of-primitives-to-nested-mapping-of-any-keys-and-values",
),
pytest.param(
Dict[str, Mapping[str, Dict[str, Class1]]],
Dict[str, Mapping[str, Dict[str, Class1]]],
id="nested-mappings-of-same-classes",
),
pytest.param(
Dict[str, Mapping[str, Dict[str, Class3]]],
Dict[str, Mapping[str, Dict[str, Class1]]],
id="nested-mapping-of-subclasses-to-nested-mapping-of-classes",
),
pytest.param(
Dict[str, Mapping[str, Dict[str, Class1]]],
Dict[str, Mapping[str, Dict[Any, Class1]]],
id="nested-mapping-of-classes-to-nested-mapping-of-any-keys",
),
pytest.param(
Dict[str, Mapping[str, Dict[str, Class1]]],
Dict[str, Mapping[Any, Dict[str, Class1]]],
id="nested-mapping-of-classes-to-nested-mapping-of-higher-level-any-keys",
),
pytest.param(
Dict[str, Mapping[str, Dict[str, Class1]]],
Dict[str, Mapping[str, Dict[str, Any]]],
id="nested-mapping-of-classes-to-nested-mapping-of-any-values",
),
pytest.param(
Dict[str, Mapping[str, Dict[str, Class1]]],
Dict[str, Mapping[Any, Dict[Any, Any]]],
id="nested-mapping-of-classes-to-nested-mapping-of-any-keys-and-values",
),
pytest.param(Literal["a", "b", "c"], Literal["a", "b", "c"], id="same-primitive-literal"),
pytest.param(Literal[Enum1.TEST1], Literal[Enum1.TEST1], id="same-enum-literal"),
pytest.param(
Tuple[Optional[Literal["a", "b", "c"]], Union[Path, Dict[int, Class1]]],
Tuple[Optional[Literal["a", "b", "c"]], Union[Path, Dict[int, Class1]]],
id="identical-deeply-nested-complex-type",
),
],
)
def test_connect_compatible_types(from_type, to_type):
comp1 = factory.component_class("Comp1", output_types={"value": from_type})()
comp2 = factory.component_class("Comp2", input_types={"value": to_type})()
pipe = Pipeline()
pipe.add_component("c1", comp1)
pipe.add_component("c2", comp2)
pipe.connect("c1", "c2")
assert list(pipe.graph.edges) == [("c1", "c2", "value/value")]
@pytest.mark.parametrize(
"from_type, to_type",
[
pytest.param(int, bool, id="different-primitives"),
pytest.param(Class1, Class2, id="different-classes"),
pytest.param(Class1, Class3, id="class-to-subclass"),
pytest.param(Any, int, id="any-to-primitive"),
pytest.param(Any, Class2, id="any-to-class"),
pytest.param(Optional[str], str, id="sending-primitive-is-optional"),
pytest.param(Optional[Class1], Class1, id="sending-class-is-optional"),
pytest.param(Optional[List[int]], List[int], id="sending-list-is-optional"),
pytest.param(Union[int, str], str, id="sending-type-is-union"),
pytest.param(Union[int, str, bool], Union[int, str], id="sending-union-is-superset-of-receiver"),
pytest.param(Union[int, bool], Union[int, str], id="partially-overlapping-unions-with-primitives"),
pytest.param(Union[int, Class1], Union[int, Class2], id="partially-overlapping-unions-with-classes"),
pytest.param(List[int], List[str], id="different-lists-of-primitives"),
pytest.param(List[int], List, id="list-of-primitive-to-bare-list"), # is "correct", but we don't support it
pytest.param(List[int], list, id="list-of-primitive-to-list-object"), # is "correct", but we don't support it
pytest.param(List[Class1], List[Class2], id="different-lists-of-classes"),
pytest.param(List[Class1], List[Class3], id="lists-of-classes-to-subclasses"),
pytest.param(List[Any], List[str], id="list-of-any-to-list-of-primitives"),
pytest.param(List[Any], List[Class2], id="list-of-any-to-list-of-classes"),
pytest.param(
List[Set[Sequence[str]]], List[Set[Sequence[bool]]], id="nested-sequences-of-different-primitives"
),
pytest.param(
List[Set[Sequence[str]]], Set[List[Sequence[str]]], id="different-nested-sequences-of-same-primitives"
),
pytest.param(
List[Set[Sequence[Class1]]], List[Set[Sequence[Class2]]], id="nested-sequences-of-different-classes"
),
pytest.param(
List[Set[Sequence[Class1]]], List[Set[Sequence[Class3]]], id="nested-sequences-of-classes-to-subclasses"
),
pytest.param(
List[Set[Sequence[Class1]]], Set[List[Sequence[Class1]]], id="different-nested-sequences-of-same-class"
),
pytest.param(
List[Set[Sequence[Any]]], List[Set[Sequence[bool]]], id="nested-list-of-Any-to-nested-list-of-primitives"
),
pytest.param(
List[Set[Sequence[Any]]], List[Set[Sequence[Class2]]], id="nested-list-of-Any-to-nested-list-of-classes"
),
pytest.param(Dict[str, int], Dict[int, int], id="different-dict-of-primitive-keys"),
pytest.param(Dict[str, int], Dict[str, bool], id="different-dict-of-primitive-values"),
pytest.param(Dict[str, Class1], Dict[str, Class2], id="different-dict-of-class-values"),
pytest.param(Dict[str, Class1], Dict[str, Class3], id="different-dict-of-class-to-subclass-values"),
pytest.param(Dict[Any, int], Dict[int, int], id="dict-of-Any-keys-to-dict-of-primitives"),
pytest.param(Dict[str, Any], Dict[int, int], id="dict-of-Any-values-to-dict-of-primitives"),
pytest.param(Dict[str, Any], Dict[int, Class1], id="dict-of-Any-values-to-dict-of-classes"),
pytest.param(Dict[Any, Any], Dict[int, int], id="dict-of-Any-keys-and-values-to-dict-of-primitives"),
pytest.param(Dict[Any, Any], Dict[int, Class1], id="dict-of-Any-keys-and-values-to-dict-of-classes"),
pytest.param(
Dict[str, Mapping[str, Dict[str, int]]],
Mapping[str, Dict[str, Dict[str, int]]],
id="different-nested-mappings-of-same-primitives",
),
pytest.param(
Dict[str, Mapping[str, Dict[str, int]]],
Dict[str, Mapping[str, Dict[int, int]]],
id="same-nested-mappings-of-different-primitive-keys",
),
pytest.param(
Dict[str, Mapping[str, Dict[str, int]]],
Dict[str, Mapping[int, Dict[str, int]]],
id="same-nested-mappings-of-different-higer-level-primitive-keys",
),
pytest.param(
Dict[str, Mapping[str, Dict[str, int]]],
Dict[str, Mapping[str, Dict[str, bool]]],
id="same-nested-mappings-of-different-primitive-values",
),
pytest.param(
Dict[str, Mapping[str, Dict[str, Class1]]],
Dict[str, Mapping[str, Dict[str, Class2]]],
id="same-nested-mappings-of-different-class-values",
),
pytest.param(
Dict[str, Mapping[str, Dict[str, Class1]]],
Dict[str, Mapping[str, Dict[str, Class2]]],
id="same-nested-mappings-of-class-to-subclass-values",
),
pytest.param(
Dict[str, Mapping[str, Dict[Any, int]]],
Dict[str, Mapping[str, Dict[str, int]]],
id="nested-mapping-of-Any-keys-to-nested-mapping-of-primitives",
),
pytest.param(
Dict[str, Mapping[Any, Dict[Any, int]]],
Dict[str, Mapping[str, Dict[str, int]]],
id="nested-mapping-of-higher-level-Any-keys-to-nested-mapping-of-primitives",
),
pytest.param(
Dict[str, Mapping[str, Dict[str, Any]]],
Dict[str, Mapping[str, Dict[str, int]]],
id="nested-mapping-of-Any-values-to-nested-mapping-of-primitives",
),
pytest.param(
Dict[str, Mapping[str, Dict[str, Any]]],
Dict[str, Mapping[str, Dict[str, Class1]]],
id="nested-mapping-of-Any-values-to-nested-mapping-of-classes",
),
pytest.param(
Dict[str, Mapping[str, Dict[Any, Any]]],
Dict[str, Mapping[str, Dict[str, int]]],
id="nested-mapping-of-Any-keys-and-values-to-nested-mapping-of-primitives",
),
pytest.param(
Dict[str, Mapping[str, Dict[Any, Any]]],
Dict[str, Mapping[str, Dict[str, Class1]]],
id="nested-mapping-of-Any-keys-and-values-to-nested-mapping-of-classes",
),
pytest.param(Literal["a", "b", "c"], Literal["x", "y"], id="different-literal-of-same-primitive"),
pytest.param(Literal["a", "b", "c"], Literal["a", "b"], id="subset-literal"),
pytest.param(Literal[Enum1.TEST1], Literal[Enum1.TEST2], id="different-literal-of-same-enum"),
pytest.param(
Tuple[Optional[Literal["a", "b", "c"]], Union[Path, Dict[int, Class1]]],
Tuple[Literal["a", "b", "c"], Union[Path, Dict[int, Class1]]],
id="deeply-nested-complex-type-is-compatible-but-cannot-be-checked",
),
],
)
def test_connect_non_compatible_types(from_type, to_type):
comp1 = factory.component_class("Comp1", output_types={"value": from_type})()
comp2 = factory.component_class("Comp2", input_types={"value": to_type})()
pipe = Pipeline()
pipe.add_component("c1", comp1)
pipe.add_component("c2", comp2)
with pytest.raises(
PipelineConnectError,
match="Cannot connect 'c1.value' with 'c2.value': their declared input and output types do not match.",
):
pipe.connect("c1", "c2")
def test_connect_sender_component_does_not_exist():
add_1 = AddFixedValue()
add_2 = AddFixedValue()
pipe = Pipeline()
pipe.add_component("first", add_1)
pipe.add_component("second", add_2)
with pytest.raises(ValueError, match="Component named third not found in the pipeline"):
pipe.connect("third", "second")
def test_connect_receiver_component_does_not_exist():
add_1 = AddFixedValue()
add_2 = AddFixedValue()
pipe = Pipeline()
pipe.add_component("first", add_1)
pipe.add_component("second", add_2)
with pytest.raises(ValueError, match="Component named third not found in the pipeline"):
pipe.connect("first", "third")
def test_connect_sender_socket_does_not_exist():
add_1 = AddFixedValue()
add_2 = AddFixedValue()
pipe = Pipeline()
pipe.add_component("first", add_1)
pipe.add_component("second", add_2)
with pytest.raises(PipelineConnectError, match="first.wrong does not exist"):
pipe.connect("first.wrong", "second")
def test_connect_receiver_socket_does_not_exist():
add_1 = AddFixedValue()
add_2 = AddFixedValue()
pipe = Pipeline()
pipe.add_component("first", add_1)
pipe.add_component("second", add_2)
with pytest.raises(PipelineConnectError, match="second.wrong does not exist"):
pipe.connect("first", "second.wrong")
def test_connect_many_outputs_to_the_same_input():
add_1 = AddFixedValue()
add_2 = AddFixedValue()
add_3 = AddFixedValue()
pipe = Pipeline()
pipe.add_component("first", add_1)
pipe.add_component("second", add_2)
pipe.add_component("third", add_3)
pipe.connect("first.result", "second.value")
with pytest.raises(PipelineConnectError, match=r"second.value is already connected to \['first'\]"):
pipe.connect("third.result", "second.value")
def test_connect_many_connections_possible_name_matches():
Component1 = factory.component_class("Component1", output_types={"value": str})
Component2 = factory.component_class(
"Component2", input_types={"value": str, "othervalue": str, "yetanothervalue": str}
)
pipe = Pipeline()
pipe.add_component("c1", Component1())
pipe.add_component("c2", Component2())
pipe.connect("c1", "c2")
assert list(pipe.graph.edges) == [("c1", "c2", "value/value")]
def test_connect_many_connections_possible_no_name_matches():
Component1 = factory.component_class("Component1", output_types={"value": str})
Component2 = factory.component_class("Component2", input_types={"value1": str, "value2": str, "value3": str})
expected_message = re.escape(
"""Cannot connect 'c1' with 'c2': more than one connection is possible between these components. Please specify the connection name, like: pipeline.connect('c1.value', 'c2.value1').
'c1':
- value: str
'c2':
- value1: str (available)
- value2: str (available)
- value3: str (available)"""
)
pipe = Pipeline()
pipe.add_component("c1", Component1())
pipe.add_component("c2", Component2())
with pytest.raises(PipelineConnectError, match=expected_message):
pipe.connect("c1", "c2")
def test_parse_connection():
assert parse_connect_string("foobar") == ("foobar", None)
assert parse_connect_string("foo.bar") == ("foo", "bar")
assert parse_connect_string("foo.bar.baz") == ("foo", "bar.baz")
def test_connect_with_same_socket_names():
SimpleComponent = factory.component_class("SimpleComponent", output_types={"documents": List})
ComponentWithMultipleInputs = factory.component_class(
"ComponentWithMultipleInputs", input_types={"question": Any, "documents": Any}
)
pipe = Pipeline()
pipe.add_component("simple", SimpleComponent())
pipe.add_component("multiple", ComponentWithMultipleInputs())
pipe.connect("simple", "multiple")
assert list(pipe.graph.edges) == [("simple", "multiple", "documents/documents")]

View File

@ -1,21 +0,0 @@
import pytest
from haystack import Pipeline
from haystack.core.errors import PipelineConnectError
from haystack.testing.sample_components import Double
def test_connect_component_twice():
pipe = Pipeline()
c1 = Double()
c2 = Double()
c3 = Double()
pipe.add_component("c1", c1)
pipe.add_component("c2", c2)
pipe.add_component("c3", c3)
pipe.connect("c1.value", "c2.value")
# the following should be a no-op
pipe.connect("c1.value", "c2.value")
# this should fail instead
with pytest.raises(PipelineConnectError):
pipe.connect("c3.value", "c2.value")

View File

@ -0,0 +1,326 @@
from enum import Enum
from pathlib import Path
from typing import Any, Dict, List, Literal, Mapping, Optional, Sequence, Set, Tuple, Union
import pytest
from haystack.core.type_utils import _type_name, _types_are_compatible
class Class1:
pass
class Class2:
pass
class Class3(Class1):
pass
class Enum1(Enum):
TEST1 = Class1
TEST2 = Class2
@pytest.mark.parametrize(
"type_,repr",
[
pytest.param(str, "str", id="primitive-types"),
pytest.param(Any, "Any", id="any"),
pytest.param(Class1, "Class1", id="class"),
pytest.param((Optional[int]), "Optional[int]", id="shallow-optional-with-primitive"),
pytest.param((Optional[Any]), "Optional[Any]", id="shallow-optional-with-any"),
pytest.param((Optional[Class1]), "Optional[Class1]", id="shallow-optional-with-class"),
pytest.param((Union[(bool, Class1)]), "Union[bool, Class1]", id="shallow-union"),
pytest.param((List[str]), "List[str]", id="shallow-sequence-of-primitives"),
pytest.param((List[Set[Sequence[str]]]), "List[Set[Sequence[str]]]", id="nested-sequence-of-primitives"),
pytest.param(
(Optional[List[Set[Sequence[str]]]]),
"Optional[List[Set[Sequence[str]]]]",
id="optional-nested-sequence-of-primitives",
),
pytest.param(
(List[Set[Sequence[Optional[str]]]]),
"List[Set[Sequence[Optional[str]]]]",
id="nested-optional-sequence-of-primitives",
),
pytest.param((List[Class1]), "List[Class1]", id="shallow-sequence-of-classes"),
pytest.param((List[Set[Sequence[Class1]]]), "List[Set[Sequence[Class1]]]", id="nested-sequence-of-classes"),
pytest.param((Dict[(str, int)]), "Dict[str, int]", id="shallow-mapping-of-primitives"),
pytest.param(
(Dict[(str, Mapping[(str, Dict[(str, int)])])]),
"Dict[str, Mapping[str, Dict[str, int]]]",
id="nested-mapping-of-primitives",
),
pytest.param(
(Dict[(str, Mapping[(Any, Dict[(str, int)])])]),
"Dict[str, Mapping[Any, Dict[str, int]]]",
id="nested-mapping-of-primitives-with-any",
),
pytest.param((Dict[(str, Class1)]), "Dict[str, Class1]", id="shallow-mapping-of-classes"),
pytest.param(
(Dict[(str, Mapping[(str, Dict[(str, Class1)])])]),
"Dict[str, Mapping[str, Dict[str, Class1]]]",
id="nested-mapping-of-classes",
),
pytest.param((Literal["a", "b", "c"]), "Literal['a', 'b', 'c']", id="string-literal"),
pytest.param((Literal[1, 2, 3]), "Literal[1, 2, 3]", id="primitive-literal"),
pytest.param((Literal[Enum1.TEST1]), "Literal[Enum1.TEST1]", id="enum-literal"),
pytest.param(
(Tuple[(Optional[Literal["a", "b", "c"]], Union[(Path, Dict[(int, Class1)])])]),
"Tuple[Optional[Literal['a', 'b', 'c']], Union[Path, Dict[int, Class1]]]",
id="deeply-nested-complex-type",
),
],
)
def test_type_name(type_, repr):
assert _type_name(type_) == repr
@pytest.mark.parametrize(
"sender_type,receiver_type",
[
pytest.param(str, str, id="same-primitives"),
pytest.param(str, (Optional[str]), id="receiving-primitive-is-optional"),
pytest.param(str, (Union[(int, str)]), id="receiving-type-is-union-of-primitives"),
pytest.param((Union[(int, str)]), (Union[(int, str)]), id="identical-unions"),
pytest.param((Union[(int, str)]), (Union[(int, str, bool)]), id="receiving-union-is-superset-of-sender"),
pytest.param(str, Any, id="primitive-to-any"),
pytest.param(Class1, Class1, id="same-class"),
pytest.param(Class1, (Optional[Class1]), id="receiving-class-is-optional"),
pytest.param(Class1, Class1, id="class-to-any"),
pytest.param(Class3, Class1, id="subclass-to-class"),
pytest.param(Class1, (Union[(int, Class1)]), id="receiving-type-is-union-of-classes"),
pytest.param(Class3, (Union[(int, Class1)]), id="receiving-type-is-union-of-superclasses"),
pytest.param((List[int]), (List[int]), id="same-lists"),
pytest.param((List[int]), (Optional[List[int]]), id="receiving-list-is-optional"),
pytest.param((List[int]), (List[Any]), id="list-of-primitive-to-list-of-any"),
pytest.param((List[Class1]), (List[Class1]), id="list-of-same-classes"),
pytest.param((List[Class3]), (List[Class1]), id="list-of-subclass-to-list-of-class"),
pytest.param((List[Class1]), (List[Any]), id="list-of-classes-to-list-of-any"),
pytest.param(
(List[Set[Sequence[bool]]]), (List[Set[Sequence[bool]]]), id="nested-sequences-of-same-primitives"
),
pytest.param(
(List[Set[Sequence[bool]]]),
(List[Set[Sequence[Any]]]),
id="nested-sequences-of-primitives-to-nested-sequences-of-any",
),
pytest.param(
(List[Set[Sequence[Class1]]]), (List[Set[Sequence[Class1]]]), id="nested-sequences-of-same-classes"
),
pytest.param(
(List[Set[Sequence[Class3]]]),
(List[Set[Sequence[Class1]]]),
id="nested-sequences-of-subclasses-to-nested-sequences-of-classes",
),
pytest.param(
(List[Set[Sequence[Class1]]]),
(List[Set[Sequence[Any]]]),
id="nested-sequences-of-classes-to-nested-sequences-of-any",
),
pytest.param((Dict[(str, int)]), (Dict[(str, int)]), id="same-dicts-of-primitives"),
pytest.param((Dict[(str, int)]), (Dict[(Any, int)]), id="dict-of-primitives-to-dict-of-any-keys"),
pytest.param((Dict[(str, int)]), (Dict[(str, Any)]), id="dict-of-primitives-to-dict-of-any-values"),
pytest.param((Dict[(str, int)]), (Dict[(Any, Any)]), id="dict-of-primitives-to-dict-of-any-key-and-values"),
pytest.param((Dict[(str, Class1)]), (Dict[(str, Class1)]), id="same-dicts-of-classes-values"),
pytest.param((Dict[(str, Class3)]), (Dict[(str, Class1)]), id="dict-of-subclasses-to-dict-of-classes"),
pytest.param((Dict[(str, Class1)]), (Dict[(Any, Class1)]), id="dict-of-classes-to-dict-of-any-keys"),
pytest.param((Dict[(str, Class1)]), (Dict[(str, Any)]), id="dict-of-classes-to-dict-of-any-values"),
pytest.param((Dict[(str, Class1)]), (Dict[(Any, Any)]), id="dict-of-classes-to-dict-of-any-key-and-values"),
pytest.param(
(Dict[(str, Mapping[(str, Dict[(str, int)])])]),
(Dict[(str, Mapping[(str, Dict[(str, int)])])]),
id="nested-mappings-of-same-primitives",
),
pytest.param(
(Dict[(str, Mapping[(str, Dict[(str, int)])])]),
(Dict[(str, Mapping[(str, Dict[(Any, int)])])]),
id="nested-mapping-of-primitives-to-nested-mapping-of-any-keys",
),
pytest.param(
(Dict[(str, Mapping[(str, Dict[(str, int)])])]),
(Dict[(str, Mapping[(Any, Dict[(str, int)])])]),
id="nested-mapping-of-primitives-to-nested-mapping-of-higher-level-any-keys",
),
pytest.param(
(Dict[(str, Mapping[(str, Dict[(str, int)])])]),
(Dict[(str, Mapping[(str, Dict[(str, Any)])])]),
id="nested-mapping-of-primitives-to-nested-mapping-of-any-values",
),
pytest.param(
(Dict[(str, Mapping[(str, Dict[(str, int)])])]),
(Dict[(str, Mapping[(Any, Dict[(Any, Any)])])]),
id="nested-mapping-of-primitives-to-nested-mapping-of-any-keys-and-values",
),
pytest.param(
(Dict[(str, Mapping[(str, Dict[(str, Class1)])])]),
(Dict[(str, Mapping[(str, Dict[(str, Class1)])])]),
id="nested-mappings-of-same-classes",
),
pytest.param(
(Dict[(str, Mapping[(str, Dict[(str, Class3)])])]),
(Dict[(str, Mapping[(str, Dict[(str, Class1)])])]),
id="nested-mapping-of-subclasses-to-nested-mapping-of-classes",
),
pytest.param(
(Dict[(str, Mapping[(str, Dict[(str, Class1)])])]),
(Dict[(str, Mapping[(str, Dict[(Any, Class1)])])]),
id="nested-mapping-of-classes-to-nested-mapping-of-any-keys",
),
pytest.param(
(Dict[(str, Mapping[(str, Dict[(str, Class1)])])]),
(Dict[(str, Mapping[(Any, Dict[(str, Class1)])])]),
id="nested-mapping-of-classes-to-nested-mapping-of-higher-level-any-keys",
),
pytest.param(
(Dict[(str, Mapping[(str, Dict[(str, Class1)])])]),
(Dict[(str, Mapping[(str, Dict[(str, Any)])])]),
id="nested-mapping-of-classes-to-nested-mapping-of-any-values",
),
pytest.param(
(Dict[(str, Mapping[(str, Dict[(str, Class1)])])]),
(Dict[(str, Mapping[(Any, Dict[(Any, Any)])])]),
id="nested-mapping-of-classes-to-nested-mapping-of-any-keys-and-values",
),
pytest.param((Literal["a", "b", "c"]), (Literal["a", "b", "c"]), id="same-primitive-literal"),
pytest.param((Literal[Enum1.TEST1]), (Literal[Enum1.TEST1]), id="same-enum-literal"),
pytest.param(
(Tuple[(Optional[Literal["a", "b", "c"]], Union[(Path, Dict[(int, Class1)])])]),
(Tuple[(Optional[Literal["a", "b", "c"]], Union[(Path, Dict[(int, Class1)])])]),
id="identical-deeply-nested-complex-type",
),
],
)
def test_types_are_compatible(sender_type, receiver_type):
assert _types_are_compatible(sender_type, receiver_type)
@pytest.mark.parametrize(
"sender_type,receiver_type",
[
pytest.param(int, bool, id="different-primitives"),
pytest.param(Class1, Class2, id="different-classes"),
pytest.param(Class1, Class3, id="class-to-subclass"),
pytest.param(Any, int, id="any-to-primitive"),
pytest.param(Any, Class2, id="any-to-class"),
pytest.param((Optional[str]), str, id="sending-primitive-is-optional"),
pytest.param((Optional[Class1]), Class1, id="sending-class-is-optional"),
pytest.param((Optional[List[int]]), (List[int]), id="sending-list-is-optional"),
pytest.param((Union[(int, str)]), str, id="sending-type-is-union"),
pytest.param((Union[(int, str, bool)]), (Union[(int, str)]), id="sending-union-is-superset-of-receiver"),
pytest.param((Union[(int, bool)]), (Union[(int, str)]), id="partially-overlapping-unions-with-primitives"),
pytest.param((Union[(int, Class1)]), (Union[(int, Class2)]), id="partially-overlapping-unions-with-classes"),
pytest.param((List[int]), (List[str]), id="different-lists-of-primitives"),
pytest.param((List[int]), List, id="list-of-primitive-to-bare-list"),
pytest.param((List[int]), list, id="list-of-primitive-to-list-object"),
pytest.param((List[Class1]), (List[Class2]), id="different-lists-of-classes"),
pytest.param((List[Class1]), (List[Class3]), id="lists-of-classes-to-subclasses"),
pytest.param((List[Any]), (List[str]), id="list-of-any-to-list-of-primitives"),
pytest.param((List[Any]), (List[Class2]), id="list-of-any-to-list-of-classes"),
pytest.param(
(List[Set[Sequence[str]]]), (List[Set[Sequence[bool]]]), id="nested-sequences-of-different-primitives"
),
pytest.param(
(List[Set[Sequence[str]]]), (Set[List[Sequence[str]]]), id="different-nested-sequences-of-same-primitives"
),
pytest.param(
(List[Set[Sequence[Class1]]]), (List[Set[Sequence[Class2]]]), id="nested-sequences-of-different-classes"
),
pytest.param(
(List[Set[Sequence[Class1]]]), (List[Set[Sequence[Class3]]]), id="nested-sequences-of-classes-to-subclasses"
),
pytest.param(
(List[Set[Sequence[Class1]]]), (Set[List[Sequence[Class1]]]), id="different-nested-sequences-of-same-class"
),
pytest.param(
(List[Set[Sequence[Any]]]),
(List[Set[Sequence[bool]]]),
id="nested-list-of-Any-to-nested-list-of-primitives",
),
pytest.param(
(List[Set[Sequence[Any]]]), (List[Set[Sequence[Class2]]]), id="nested-list-of-Any-to-nested-list-of-classes"
),
pytest.param((Dict[(str, int)]), (Dict[(int, int)]), id="different-dict-of-primitive-keys"),
pytest.param((Dict[(str, int)]), (Dict[(str, bool)]), id="different-dict-of-primitive-values"),
pytest.param((Dict[(str, Class1)]), (Dict[(str, Class2)]), id="different-dict-of-class-values"),
pytest.param((Dict[(str, Class1)]), (Dict[(str, Class3)]), id="different-dict-of-class-to-subclass-values"),
pytest.param((Dict[(Any, int)]), (Dict[(int, int)]), id="dict-of-Any-keys-to-dict-of-primitives"),
pytest.param((Dict[(str, Any)]), (Dict[(int, int)]), id="dict-of-Any-values-to-dict-of-primitives"),
pytest.param((Dict[(str, Any)]), (Dict[(int, Class1)]), id="dict-of-Any-values-to-dict-of-classes"),
pytest.param((Dict[(Any, Any)]), (Dict[(int, int)]), id="dict-of-Any-keys-and-values-to-dict-of-primitives"),
pytest.param((Dict[(Any, Any)]), (Dict[(int, Class1)]), id="dict-of-Any-keys-and-values-to-dict-of-classes"),
pytest.param(
(Dict[(str, Mapping[(str, Dict[(str, int)])])]),
(Mapping[(str, Dict[(str, Dict[(str, int)])])]),
id="different-nested-mappings-of-same-primitives",
),
pytest.param(
(Dict[(str, Mapping[(str, Dict[(str, int)])])]),
(Dict[(str, Mapping[(str, Dict[(int, int)])])]),
id="same-nested-mappings-of-different-primitive-keys",
),
pytest.param(
(Dict[(str, Mapping[(str, Dict[(str, int)])])]),
(Dict[(str, Mapping[(int, Dict[(str, int)])])]),
id="same-nested-mappings-of-different-higer-level-primitive-keys",
),
pytest.param(
(Dict[(str, Mapping[(str, Dict[(str, int)])])]),
(Dict[(str, Mapping[(str, Dict[(str, bool)])])]),
id="same-nested-mappings-of-different-primitive-values",
),
pytest.param(
(Dict[(str, Mapping[(str, Dict[(str, Class1)])])]),
(Dict[(str, Mapping[(str, Dict[(str, Class2)])])]),
id="same-nested-mappings-of-different-class-values",
),
pytest.param(
(Dict[(str, Mapping[(str, Dict[(str, Class1)])])]),
(Dict[(str, Mapping[(str, Dict[(str, Class2)])])]),
id="same-nested-mappings-of-class-to-subclass-values",
),
pytest.param(
(Dict[(str, Mapping[(str, Dict[(Any, int)])])]),
(Dict[(str, Mapping[(str, Dict[(str, int)])])]),
id="nested-mapping-of-Any-keys-to-nested-mapping-of-primitives",
),
pytest.param(
(Dict[(str, Mapping[(Any, Dict[(Any, int)])])]),
(Dict[(str, Mapping[(str, Dict[(str, int)])])]),
id="nested-mapping-of-higher-level-Any-keys-to-nested-mapping-of-primitives",
),
pytest.param(
(Dict[(str, Mapping[(str, Dict[(str, Any)])])]),
(Dict[(str, Mapping[(str, Dict[(str, int)])])]),
id="nested-mapping-of-Any-values-to-nested-mapping-of-primitives",
),
pytest.param(
(Dict[(str, Mapping[(str, Dict[(str, Any)])])]),
(Dict[(str, Mapping[(str, Dict[(str, Class1)])])]),
id="nested-mapping-of-Any-values-to-nested-mapping-of-classes",
),
pytest.param(
(Dict[(str, Mapping[(str, Dict[(Any, Any)])])]),
(Dict[(str, Mapping[(str, Dict[(str, int)])])]),
id="nested-mapping-of-Any-keys-and-values-to-nested-mapping-of-primitives",
),
pytest.param(
(Dict[(str, Mapping[(str, Dict[(Any, Any)])])]),
(Dict[(str, Mapping[(str, Dict[(str, Class1)])])]),
id="nested-mapping-of-Any-keys-and-values-to-nested-mapping-of-classes",
),
pytest.param((Literal["a", "b", "c"]), (Literal["x", "y"]), id="different-literal-of-same-primitive"),
pytest.param((Literal["a", "b", "c"]), (Literal["a", "b"]), id="subset-literal"),
pytest.param((Literal[Enum1.TEST1]), (Literal[Enum1.TEST2]), id="different-literal-of-same-enum"),
pytest.param(
(Tuple[(Optional[Literal["a", "b", "c"]], Union[(Path, Dict[(int, Class1)])])]),
(Tuple[(Literal["a", "b", "c"], Union[(Path, Dict[(int, Class1)])])]),
id="deeply-nested-complex-type-is-compatible-but-cannot-be-checked",
),
],
)
def test_types_are_not_compatible(sender_type, receiver_type):
assert not _types_are_compatible(sender_type, receiver_type)

View File

@ -1,79 +0,0 @@
from typing import List, Set, Sequence, Tuple, Dict, Mapping, Literal, Union, Optional, Any
from enum import Enum
from pathlib import Path
import pytest
from haystack.core.type_utils import _type_name
class Class1:
...
class Class2:
...
class Class3(Class1):
...
class Enum1(Enum):
TEST1 = Class1
TEST2 = Class2
@pytest.mark.parametrize(
"type_,repr",
[
pytest.param(str, "str", id="primitive-types"),
pytest.param(Any, "Any", id="any"),
pytest.param(Class1, "Class1", id="class"),
pytest.param(Optional[int], "Optional[int]", id="shallow-optional-with-primitive"),
pytest.param(Optional[Any], "Optional[Any]", id="shallow-optional-with-any"),
pytest.param(Optional[Class1], "Optional[Class1]", id="shallow-optional-with-class"),
pytest.param(Union[bool, Class1], "Union[bool, Class1]", id="shallow-union"),
pytest.param(List[str], "List[str]", id="shallow-sequence-of-primitives"),
pytest.param(List[Set[Sequence[str]]], "List[Set[Sequence[str]]]", id="nested-sequence-of-primitives"),
pytest.param(
Optional[List[Set[Sequence[str]]]],
"Optional[List[Set[Sequence[str]]]]",
id="optional-nested-sequence-of-primitives",
),
pytest.param(
List[Set[Sequence[Optional[str]]]],
"List[Set[Sequence[Optional[str]]]]",
id="nested-optional-sequence-of-primitives",
),
pytest.param(List[Class1], "List[Class1]", id="shallow-sequence-of-classes"),
pytest.param(List[Set[Sequence[Class1]]], "List[Set[Sequence[Class1]]]", id="nested-sequence-of-classes"),
pytest.param(Dict[str, int], "Dict[str, int]", id="shallow-mapping-of-primitives"),
pytest.param(
Dict[str, Mapping[str, Dict[str, int]]],
"Dict[str, Mapping[str, Dict[str, int]]]",
id="nested-mapping-of-primitives",
),
pytest.param(
Dict[str, Mapping[Any, Dict[str, int]]],
"Dict[str, Mapping[Any, Dict[str, int]]]",
id="nested-mapping-of-primitives-with-any",
),
pytest.param(Dict[str, Class1], "Dict[str, Class1]", id="shallow-mapping-of-classes"),
pytest.param(
Dict[str, Mapping[str, Dict[str, Class1]]],
"Dict[str, Mapping[str, Dict[str, Class1]]]",
id="nested-mapping-of-classes",
),
pytest.param(Literal["a", "b", "c"], "Literal['a', 'b', 'c']", id="string-literal"),
pytest.param(Literal[1, 2, 3], "Literal[1, 2, 3]", id="primitive-literal"),
pytest.param(Literal[Enum1.TEST1], "Literal[Enum1.TEST1]", id="enum-literal"),
pytest.param(
Tuple[Optional[Literal["a", "b", "c"]], Union[Path, Dict[int, Class1]]],
"Tuple[Optional[Literal['a', 'b', 'c']], Union[Path, Dict[int, Class1]]]",
id="deeply-nested-complex-type",
),
],
)
def test_type_name(type_, repr):
assert _type_name(type_) == repr