Coverage for src/pqlattice/typing/_types_validator.py: 100%

1 import logging

2 from collections.abc import Callable

3 from inspect import signature

4 from typing import Any, TypeAliasType, TypeGuard

5

6 import numpy as np

7 from numpy.typing import NDArray

8

9 from ._types import Matrix, SquareMatrix, Vector

10

11 logger = logging.getLogger(__name__)

12

13

14 def _is_nparray(obj: Any) -> TypeGuard[NDArray[Any]]:

15 return isinstance(obj, np.ndarray)

16

17

18 def is_Vector(obj: Any) -> TypeGuard[Vector]:

19 return _is_nparray(obj) and len(obj.shape) == 1

20

21

22 def is_Matrix(obj: Any) -> TypeGuard[Matrix]:

23 return _is_nparray(obj) and len(obj.shape) == 2

24

25

26 def is_SquareMatrix(obj: Any) -> TypeGuard[SquareMatrix]:

27 return is_Matrix(obj) and obj.shape[0] == obj.shape[1]

28

29

30 def _get_predicate_for_alias[T: TypeAliasType](type_name: T) -> Callable[[T], bool] | None:

31 # Bare

32 if type_name == Vector:

33 return is_Vector

34

35 if type_name == Matrix:

36 return is_Matrix

37

38 if type_name == SquareMatrix:

39 return is_SquareMatrix

40

41 return None

42

43

44 def validate_aliases[**P, T](func: Callable[P, T]) -> Callable[P, T]:

45 def wrapper(*args: P.args, **kwds: P.kwargs) -> T:

46 sig = signature(func)

47 bounded_args = sig.bind(*args, **kwds)

48 bounded_args.apply_defaults()

49 for arg_name, arg_value in bounded_args.arguments.items():

50 if expected_type := func.__annotations__.get(arg_name): # There is a type annotation for the argument

51 pred = _get_predicate_for_alias(expected_type)

52 if pred is not None and not pred(arg_value): # type annotations has a predicate to be checked and predicate is not fullfilled

53 raise TypeError(f"func <{func.__name__}>, arg <{arg_name}> val <{arg_value}> arg's type <{type(arg_value)}> predicate for <{expected_type}> failed")

54

55 return func(*args, **kwds)

56

57 return wrapper