Files
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

192 lines
6.8 KiB
Python

# 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