def is_registration_compatible_with_requested_key( self, requested_key: RegistrationKey, registered_key: RegistrationKey ): requested_converter: Converter = requested_key.bean_contract registered_converter: Converter = registered_key.bean_contract return can_substitute(requested_converter.generic_TFrom, registered_converter.generic_TFrom) \ and can_substitute(registered_converter.generic_TTo, requested_converter.generic_TTo)
def test_can_substitute_to_generic_class_bound(self): TB = typing.TypeVar("TB", bound=BaseClass1) class GenericClassBound(typing.Generic[TB]): pass assert can_substitute(GenericClassBound[DerivedClass1], GenericClassBound[TB]) assert not can_substitute(GenericClassBound[TB], GenericClassBound[DerivedClass1]) assert can_substitute(GenericClassBound[DerivedClass1], GenericClassBound) assert not can_substitute(GenericClassBound, GenericClassBound[DerivedClass1])
def identity(cls) -> "Converter[TFrom, TTo]": """ :return: An identity converter that maps ``x`` to ``x``. :raises ValueError: If ``type_from`` or ``type_to`` is a :py:class:`~typing.TypeVar`. :raises ValueError: If ``type_from`` can't substitute ``type_to`` according to the Liskov Substitution \ Principle. More specifically, a ``ValueError`` is raised if \ ``not can_substitute(type_from, type_to)``. See \ :py:func:`~grundzeug.reflection.types.can_substitute` for more details. """ type_args = get_type_arguments(cls) type_from = type_args[TFrom] type_to = type_args[TTo] if isinstance(type_to, typing.TypeVar) or isinstance( type_from, typing.TypeVar): raise ValueError( f"Attempt to call 'identity' on a generic Converter class. Please provide concrete type " f"arguments to resolve this error.") if not can_substitute(type_from, type_to): raise ValueError( f"TFrom={type_from} must be a subclass of TTo={type_to} for an identity mapping to exist " f"from TFrom to TTo.") class _IdentityConverter(cls): def __call__(self, value: TFrom) -> TTo: return value return _IdentityConverter()
def test_can_substitute_to_tuples(self): assert can_substitute(Tuple[DerivedClass1], Tuple[BaseClass1]) assert can_substitute(Tuple[DerivedClass1], tuple) assert can_substitute(Tuple[DerivedClass1, DerivedClass2], Tuple[BaseClass1, BaseClass2]) assert can_substitute(Tuple[DerivedClass1, BaseClass2], Tuple[BaseClass1, BaseClass2]) assert not can_substitute(Tuple[BaseClass1, BaseClass2], Tuple[DerivedClass1, DerivedClass2]) assert not can_substitute(Tuple[BaseClass1, BaseClass2], Tuple[DerivedClass1, BaseClass2])
def test_can_substitute_to_callable_callable(self): assert can_substitute( typing.Callable[[BaseClass1], BaseClass2], typing.Callable[[DerivedClass1], BaseClass2] ) assert not can_substitute( typing.Callable[[DerivedClass1], BaseClass2], typing.Callable[[BaseClass1], BaseClass2] ) assert can_substitute( typing.Callable[[BaseClass1], DerivedClass2], typing.Callable[[BaseClass1], BaseClass2] ) assert not can_substitute( typing.Callable[[BaseClass1], BaseClass2], typing.Callable[[BaseClass1], DerivedClass2] ) assert can_substitute( typing.Callable[[BaseClass1], DerivedClass2], typing.Callable[[DerivedClass1], BaseClass2] ) assert not can_substitute( typing.Callable[[DerivedClass1], BaseClass2], typing.Callable[[BaseClass1], DerivedClass2] )
def choose_best_candidate( self, requested_key: RegistrationKey, candidates: typing.OrderedDict[RegistrationKey, typing.Tuple[ContainerRegistration, IContainer]] ) -> typing.Optional[typing.Tuple[ContainerRegistration, IContainer]]: # Eliminate candidates which have more specific overrides. # For example, if the candidate list consists of Converter[Any, str] and Converter[int, str], then # Converter[Any, str] will be considered to be dominated by Converter[int, str], because it is less specific. # However, if we request a Converter[int, BaseClass] and the candidate list consists of # Converter[int, BaseClass] and Converter[int, DerivedClass], Converter[int, BaseClass] will be returned # because BaseClass is "closer" to the requested return type. is_dominated = [False for _ in candidates] for i, registration_key in enumerate(candidates): if is_dominated[i]: continue for j, other_registration_key in enumerate(candidates): if i == j or is_dominated[j]: continue arg1 = registration_key.bean_contract.generic_TFrom arg2 = other_registration_key.bean_contract.generic_TFrom ret1 = registration_key.bean_contract.generic_TTo ret2 = other_registration_key.bean_contract.generic_TTo if can_substitute(arg1, arg2) \ and can_substitute(ret2, ret1): is_dominated[j] = True result = [ resolved for resolved, is_dominated in zip(candidates, is_dominated) if not is_dominated ] if len(result) < 1: return None elif len(result) > 1: raise Exception("Ambiguous resolution!") else: return candidates[result[0]]
def test_can_substitute_to_generic_class_constraint(self): TC = typing.TypeVar("TC", BaseClass1, int) class GenericClassConstraint(typing.Generic[TC]): pass assert can_substitute(GenericClassConstraint[BaseClass1], GenericClassConstraint[TC]) assert not can_substitute(GenericClassConstraint[DerivedClass1], GenericClassConstraint[TC]) assert not can_substitute(GenericClassConstraint[TC], GenericClassConstraint[DerivedClass1]) assert can_substitute(GenericClassConstraint[BaseClass1], GenericClassConstraint) assert not can_substitute(GenericClassConstraint[DerivedClass1], GenericClassConstraint) assert not can_substitute(GenericClassConstraint, GenericClassConstraint[DerivedClass1])
def test_can_substitute_to_generic_subclass(self): assert can_substitute(DerivedGenericClass[str, int], GenericClass[int]) assert not can_substitute(DerivedGenericClass[int, str], GenericClass[int])
def test_can_substitute_to_generic_class(self): assert can_substitute(GenericClass[int], GenericClass[T1]) assert not can_substitute(GenericClass[T1], GenericClass[int]) assert can_substitute(GenericClass[int], GenericClass) assert not can_substitute(GenericClass, GenericClass[int])
def test_can_substitute_to_none_optional(self): assert can_substitute(None, Optional[str])
def test_can_substitute_to_any(self): assert can_substitute(str, Any) assert can_substitute(type, Any) assert can_substitute(None, Any) assert can_substitute(Tuple[DerivedClass1, DerivedClass2], Any)
def test_can_substitute_to_callable_class(self): assert can_substitute(CallableClass, typing.Callable[[str, float], int])
def test_can_substitute_to_optional(self): assert can_substitute(DerivedClass1, Optional[DerivedClass1]) assert can_substitute(DerivedClass1, Optional[BaseClass1]) assert not can_substitute(Optional[DerivedClass1], DerivedClass1) assert not can_substitute(Optional[BaseClass1], DerivedClass1)
def is_registration_key_supported(self, registration_key: RegistrationKey): if registration_key.bean_name is not None: return False return can_substitute(registration_key.bean_contract, Converter, assume_cant_substitute=True)