Files
deepset-ai--haystack/haystack/core/pipeline/utils.py
T
wehub-resource-sync c56bef871b
Sync docs with Docusaurus / sync (push) Waiting to run
Tests / Check if changed (push) Waiting to run
Tests / format (push) Blocked by required conditions
Tests / check-imports (push) Blocked by required conditions
Tests / Unit / macos-latest (push) Blocked by required conditions
Tests / Unit / ubuntu-latest (push) Blocked by required conditions
Tests / Unit / windows-latest (push) Blocked by required conditions
Tests / mypy (push) Blocked by required conditions
Tests / Integration / ubuntu-latest (push) Blocked by required conditions
Tests / Integration / macos-latest (push) Blocked by required conditions
Tests / Integration / windows-latest (push) Blocked by required conditions
Tests / notify-slack-on-failure (push) Blocked by required conditions
Tests / Mark tests as completed (push) Blocked by required conditions
Docker image release / Build base image (push) Waiting to run
CodeQL / Analyze (python) (push) Has been cancelled
Update Platform Components Table / update (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:22:28 +08:00

208 lines
6.9 KiB
Python

# SPDX-FileCopyrightText: 2022-present deepset GmbH <info@deepset.ai>
#
# SPDX-License-Identifier: Apache-2.0
import heapq
from collections.abc import Callable
from copy import deepcopy
from functools import wraps
from itertools import count
from typing import Any
from haystack import logging
from haystack.core.component import Component
logger = logging.getLogger(__name__)
def _deepcopy_with_exceptions(obj: Any) -> Any:
"""
Attempts to perform a deep copy of the given object.
This function recursively handles common container types (lists, tuples, sets, and dicts) to ensure deep copies
of nested structures. For specific object types that are known to be problematic for deepcopying-such as
instances of `Component`, `Tool`, or `Toolset` - the original object is returned as-is.
If `deepcopy` fails for any other reason, the original object is returned and a log message is recorded.
:param obj: The object to be deep-copied.
:returns:
A deep-copied version of the object, or the original object if deepcopying fails.
"""
# Import here to avoid circular imports
from haystack.tools.tool import Tool
from haystack.tools.toolset import Toolset
if isinstance(obj, (list, tuple, set)):
return type(obj)(_deepcopy_with_exceptions(v) for v in obj)
if isinstance(obj, dict):
return {k: _deepcopy_with_exceptions(v) for k, v in obj.items()}
# Components and Tools often contain objects that we do not want to deepcopy or are not deepcopyable
# (e.g. models, clients, etc.). In this case we return the object as-is.
if isinstance(obj, (Component, Tool, Toolset)):
return obj
try:
return deepcopy(obj)
except Exception as e:
logger.info(
"Deepcopy failed for object of type '{obj_type}'. Error: {error}. Returning original object instead.",
obj_type=type(obj).__name__,
error=e,
)
return obj
def parse_connect_string(connection: str) -> tuple[str, str | None]:
"""
Returns component-connection pairs from a connect_to/from string.
:param connection:
The connection string.
:returns:
A tuple containing the component name and the connection name.
"""
if "." in connection:
split_str = connection.split(".", maxsplit=1)
return (split_str[0], split_str[1])
return connection, None
class FIFOPriorityQueue:
"""
A priority queue that maintains FIFO order for items of equal priority.
Items with the same priority are processed in the order they were added.
This queue ensures that when multiple items share the same priority level,
they are dequeued in the same order they were enqueued (First-In-First-Out).
"""
def __init__(self) -> None:
"""
Initialize a new FIFO priority queue.
"""
# List of tuples (priority, count, item) where count ensures FIFO order
self._queue: list[tuple[int, int, Any]] = []
# Counter to maintain insertion order for equal priorities
self._counter = count()
def push(self, item: Any, priority: int) -> None:
"""
Push an item into the queue with a given priority.
Items with equal priority maintain FIFO ordering based on insertion time.
Lower priority numbers are dequeued first.
:param item:
The item to insert into the queue.
:param priority:
Priority level for the item. Lower numbers indicate higher priority.
"""
next_count = next(self._counter)
entry = (priority, next_count, item)
heapq.heappush(self._queue, entry)
def pop(self) -> tuple[int, Any]:
"""
Remove and return the highest priority item from the queue.
For items with equal priority, returns the one that was inserted first.
:returns:
A tuple containing (priority, item) with the lowest priority number.
:raises IndexError:
If the queue is empty.
"""
if not self._queue:
raise IndexError("pop from empty queue")
priority, _, item = heapq.heappop(self._queue)
return priority, item
def peek(self) -> tuple[int, Any]:
"""
Return but don't remove the highest priority item from the queue.
For items with equal priority, returns the one that was inserted first.
:returns:
A tuple containing (priority, item) with the lowest priority number.
:raises IndexError:
If the queue is empty.
"""
if not self._queue:
raise IndexError("peek at empty queue")
priority, _, item = self._queue[0]
return priority, item
def get(self) -> tuple[int, Any] | None:
"""
Remove and return the highest priority item from the queue.
For items with equal priority, returns the one that was inserted first.
Unlike pop(), returns None if the queue is empty instead of raising an exception.
:returns:
A tuple containing (priority, item), or None if the queue is empty.
"""
if not self._queue:
return None
priority, _, item = heapq.heappop(self._queue)
return priority, item
def __len__(self) -> int:
"""
Return the number of items in the queue.
:returns:
The number of items currently in the queue.
"""
return len(self._queue)
def __bool__(self) -> bool:
"""
Return True if the queue has items, False if empty.
:returns:
True if the queue contains items, False otherwise.
"""
return bool(self._queue)
def args_deprecated(func: Callable[..., Any]) -> Callable[..., Any]:
"""
Decorator to warn about the use of positional arguments in a function.
Adapted from https://stackoverflow.com/questions/68432070/
:param func:
"""
def _positional_arg_warning() -> None:
"""
Triggers a warning message if positional arguments are used in a function
"""
import warnings
msg = (
"Warning: In an upcoming release, this method will require keyword arguments for all parameters. "
"Please update your code to use keyword arguments to ensure future compatibility. "
)
warnings.warn(msg, DeprecationWarning, stacklevel=2)
@wraps(func)
def wrapper(*args: Any, **kwargs: Any) -> Any:
# call the function first, to make sure the signature matches
ret_value = func(*args, **kwargs)
# A Pipeline instance is always the first argument - remove it from the args to check for positional arguments
# We check the class name as strings to avoid circular imports
if args and isinstance(args, tuple) and args[0].__class__.__name__ in ["Pipeline", "PipelineBase"]:
args = args[1:]
if args:
_positional_arg_warning()
return ret_value
return wrapper