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,191 @@
|
||||
# SPDX-FileCopyrightText: 2022-present deepset GmbH <info@deepset.ai>
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
from types import UnionType
|
||||
from typing import Annotated, Any, get_args, get_origin
|
||||
|
||||
from haystack.core.component.types import HAYSTACK_GREEDY_VARIADIC_ANNOTATION, HAYSTACK_VARIADIC_ANNOTATION
|
||||
from haystack.utils.type_serialization import _build_pep604_union_type, _is_union_type
|
||||
|
||||
|
||||
class _delegate_default:
|
||||
"""Custom object for delegating filling of default values to the underlying components."""
|
||||
|
||||
|
||||
def _is_compatible(
|
||||
type1: type | UnionType, type2: type | UnionType, unwrap_nested: bool = True
|
||||
) -> tuple[bool, type | UnionType | None]:
|
||||
"""
|
||||
Check if two types are compatible (bidirectional/symmetric check).
|
||||
|
||||
:param type1: First type to compare
|
||||
:param type2: Second type to compare
|
||||
:param unwrap_nested: If True, recursively unwraps nested Optional and Variadic types.
|
||||
If False, only unwraps at the top level.
|
||||
:return: Tuple of (True if types are compatible, common type if compatible)
|
||||
"""
|
||||
type1_unwrapped = _unwrap_all(type1, recursive=unwrap_nested)
|
||||
type2_unwrapped = _unwrap_all(type2, recursive=unwrap_nested)
|
||||
|
||||
return _types_are_compatible(type1_unwrapped, type2_unwrapped)
|
||||
|
||||
|
||||
def _types_are_compatible(type1: type | UnionType, type2: type | UnionType) -> tuple[bool, type | UnionType | None]:
|
||||
"""
|
||||
Core type compatibility check implementing symmetric matching.
|
||||
|
||||
:param type1: First unwrapped type to compare
|
||||
:param type2: Second unwrapped type to compare
|
||||
:return: True if types are compatible, False otherwise
|
||||
"""
|
||||
# Handle Any type
|
||||
if type1 is Any:
|
||||
return True, type2
|
||||
if type2 is Any:
|
||||
return True, type1
|
||||
|
||||
# Direct equality
|
||||
if type1 == type2:
|
||||
return True, type1
|
||||
|
||||
type1_origin = get_origin(type1)
|
||||
type2_origin = get_origin(type2)
|
||||
|
||||
# Handle Union types (including X | Y syntax)
|
||||
if _is_union_type(type1_origin) or _is_union_type(type2_origin):
|
||||
return _check_union_compatibility(type1, type2, type1_origin, type2_origin)
|
||||
|
||||
# Handle non-Union types
|
||||
return _check_non_union_compatibility(type1, type2, type1_origin, type2_origin)
|
||||
|
||||
|
||||
def _check_union_compatibility(
|
||||
type1: type | UnionType, type2: type | UnionType, type1_origin: Any, type2_origin: Any
|
||||
) -> tuple[bool, type | UnionType | None]:
|
||||
"""Handle all Union type compatibility cases (including X | Y syntax)."""
|
||||
if _is_union_type(type1_origin) and not _is_union_type(type2_origin):
|
||||
# Find all compatible types from the union
|
||||
compatible_types = []
|
||||
for union_arg in get_args(type1):
|
||||
is_compat, common = _types_are_compatible(union_arg, type2)
|
||||
if is_compat and common is not None:
|
||||
compatible_types.append(common)
|
||||
if compatible_types:
|
||||
result_type = _build_pep604_union_type(compatible_types)
|
||||
return True, result_type
|
||||
return False, None
|
||||
|
||||
if _is_union_type(type2_origin) and not _is_union_type(type1_origin):
|
||||
# Find all compatible types from the union
|
||||
compatible_types = []
|
||||
for union_arg in get_args(type2):
|
||||
is_compat, common = _types_are_compatible(type1, union_arg)
|
||||
if is_compat and common is not None:
|
||||
compatible_types.append(common)
|
||||
if compatible_types:
|
||||
result_type = _build_pep604_union_type(compatible_types)
|
||||
return True, result_type
|
||||
return False, None
|
||||
|
||||
# Both are Union types
|
||||
compatible_types = []
|
||||
for arg1 in get_args(type1):
|
||||
for arg2 in get_args(type2):
|
||||
is_compat, common = _types_are_compatible(arg1, arg2)
|
||||
if is_compat and common is not None:
|
||||
compatible_types.append(common)
|
||||
|
||||
if compatible_types:
|
||||
result_type = _build_pep604_union_type(compatible_types)
|
||||
return True, result_type
|
||||
return False, None
|
||||
|
||||
|
||||
def _check_non_union_compatibility(
|
||||
type1: type | UnionType, type2: type | UnionType, type1_origin: Any, type2_origin: Any
|
||||
) -> tuple[bool, type | UnionType | None]:
|
||||
"""Handle non-Union type compatibility cases."""
|
||||
# If no origin, compare types directly
|
||||
if not type1_origin and not type2_origin:
|
||||
if type1 == type2:
|
||||
return True, type1
|
||||
return False, None
|
||||
|
||||
# Both must have origins and they must be equal
|
||||
if not (type1_origin and type2_origin and type1_origin == type2_origin):
|
||||
return False, None
|
||||
|
||||
# Compare generic type arguments
|
||||
type1_args = get_args(type1)
|
||||
type2_args = get_args(type2)
|
||||
|
||||
if len(type1_args) != len(type2_args):
|
||||
return False, None
|
||||
|
||||
# Check if all arguments are compatible
|
||||
common_args = []
|
||||
for t1_arg, t2_arg in zip(type1_args, type2_args, strict=True):
|
||||
is_compat, common = _types_are_compatible(t1_arg, t2_arg)
|
||||
if not is_compat:
|
||||
return False, None
|
||||
common_args.append(common)
|
||||
|
||||
# Reconstruct the type with common arguments
|
||||
return True, type1_origin[tuple(common_args)]
|
||||
|
||||
|
||||
def _unwrap_all(t: type | UnionType, recursive: bool) -> type | UnionType:
|
||||
"""
|
||||
Unwrap a type until no more unwrapping is possible.
|
||||
|
||||
:param t: Type to unwrap
|
||||
:param recursive: If True, recursively unwraps nested types
|
||||
:return: The fully unwrapped type
|
||||
"""
|
||||
# First handle top-level Variadic/GreedyVariadic
|
||||
if _is_variadic_type(t):
|
||||
t = _unwrap_variadics(t, recursive=recursive)
|
||||
else:
|
||||
# If it's a generic type and we're unwrapping recursively
|
||||
origin = get_origin(t)
|
||||
if recursive and origin is not None and (args := get_args(t)):
|
||||
unwrapped_args = tuple(_unwrap_all(arg, recursive) for arg in args)
|
||||
# types.UnionType (PEP 604 X | Y) is not subscriptable, so we use _build_pep604_union_type
|
||||
if origin is UnionType:
|
||||
t = _build_pep604_union_type(list(unwrapped_args))
|
||||
else:
|
||||
t = origin[unwrapped_args]
|
||||
|
||||
return t
|
||||
|
||||
|
||||
def _is_variadic_type(t: type | UnionType) -> bool:
|
||||
"""Check if type is a Variadic or GreedyVariadic type."""
|
||||
origin = get_origin(t)
|
||||
if origin is Annotated:
|
||||
args = get_args(t)
|
||||
return len(args) >= 2 and args[1] in (HAYSTACK_VARIADIC_ANNOTATION, HAYSTACK_GREEDY_VARIADIC_ANNOTATION) # noqa: PLR2004
|
||||
return False
|
||||
|
||||
|
||||
def _unwrap_variadics(t: type | UnionType, recursive: bool) -> type | UnionType:
|
||||
"""
|
||||
Unwrap Variadic or GreedyVariadic annotated types.
|
||||
|
||||
:param t: Type to unwrap
|
||||
:param recursive: If True, recursively unwraps nested types
|
||||
:return: Unwrapped type if it was a variadic type, original type otherwise
|
||||
"""
|
||||
if not _is_variadic_type(t):
|
||||
return t
|
||||
|
||||
args = get_args(t)
|
||||
# Get the Iterable[X] type and extract X
|
||||
iterable_type = args[0]
|
||||
inner_type = get_args(iterable_type)[0]
|
||||
|
||||
# Only recursively unwrap if requested
|
||||
if recursive:
|
||||
return _unwrap_all(inner_type, recursive)
|
||||
return inner_type
|
||||
Reference in New Issue
Block a user