feat: Change Pipeline.add_component to fail when reusing Component instances (#6847)

* Change Pipeline.add_component to fail when reusing Component instances

* Change variable name and store Pipeline instance in it

* Fix tests
This commit is contained in:
Silvano Cerza 2024-01-30 11:15:26 +01:00 committed by GitHub
parent d90b0de124
commit 76d324a149
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 36 additions and 5 deletions

View File

@ -147,6 +147,12 @@ class ComponentMeta(type):
if run_signature.parameters[param].default != inspect.Parameter.empty:
socket_kwargs["default_value"] = run_signature.parameters[param].default
instance.__haystack_input__[param] = InputSocket(**socket_kwargs)
# Since a Component can't be used in multiple Pipelines at the same time
# we need to know if it's already owned by a Pipeline when adding it to one.
# We use this flag to check that.
instance.__haystack_added_to_pipeline__ = None
return instance

View File

@ -190,10 +190,19 @@ class Pipeline:
f"'{type(instance)}' doesn't seem to be a component. Is this class decorated with @component?"
)
if getattr(instance, "__haystack_added_to_pipeline__", None):
msg = (
"Component has already been added in another Pipeline. "
"Components can't be shared between Pipelines. Create a new instance instead."
)
raise PipelineError(msg)
# Create the component's input and output sockets
input_sockets = getattr(instance, "__haystack_input__", {})
output_sockets = getattr(instance, "__haystack_output__", {})
setattr(instance, "__haystack_added_to_pipeline__", self)
# Add component to the graph, disconnected
logger.debug("Adding component '%s' (%s)", name, instance)
self.graph.add_node(

View File

@ -0,0 +1,4 @@
---
enhancements:
- |
Change `Pipeline.add_component()` to fail if the `Component` instance has already been added in another `Pipeline`.

View File

@ -338,11 +338,12 @@ def test_connect_receiver_socket_does_not_exist():
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_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")

View File

@ -24,8 +24,6 @@ def test_pipeline_equally_long_branches():
pipeline.connect("add_two", "multiplexer.value")
pipeline.connect("add_one", "multiplexer.value")
pipeline.draw(Path(__file__).parent / Path(__file__).name.replace(".py", ".png"))
results = pipeline.run({"multiplexer": {"value": 0}})
assert results == {"remainder": {"remainder_is_0": 0}}

View File

@ -7,14 +7,27 @@ from typing import Optional
import pytest
from haystack.core.component.sockets import InputSocket, OutputSocket
from haystack.core.errors import PipelineError, PipelineMaxLoops, PipelineRuntimeError
from haystack.core.errors import PipelineError, PipelineRuntimeError
from haystack.core.pipeline import Pipeline
from haystack.testing.factory import component_class
from haystack.testing.sample_components import AddFixedValue, Double, Sum, Threshold
from haystack.testing.sample_components import AddFixedValue, Double
logging.basicConfig(level=logging.DEBUG)
def test_add_component_to_different_pipelines():
first_pipe = Pipeline()
second_pipe = Pipeline()
some_component = component_class("Some")()
assert some_component.__haystack_added_to_pipeline__ is None
first_pipe.add_component("some", some_component)
assert some_component.__haystack_added_to_pipeline__ is first_pipe
with pytest.raises(PipelineError):
second_pipe.add_component("some", some_component)
def test_run_with_component_that_does_not_return_dict():
BrokenComponent = component_class(
"BrokenComponent", input_types={"a": int}, output_types={"b": int}, output=1 # type:ignore