Coverage for src/extratools_core/functools.py: 100%

48 statements  

« prev     ^ index     » next       coverage.py v7.8.1, created at 2025-06-24 04:41 -0700

1from collections import Counter 

2from collections.abc import Awaitable, Callable, Iterable 

3from decimal import Decimal 

4from inspect import Signature, signature 

5from statistics import mean 

6from typing import Any, cast 

7 

8 

9# Name it in this way to mimic as a function 

10class lazy_call[T: Any]: # noqa: N801 

11 __DEFAULT_OBJECT = object() 

12 

13 def __init__( 

14 self, 

15 func: Callable[..., T], 

16 *args: Any, 

17 **kwargs: Any, 

18 ) -> None: 

19 self.__func = func 

20 self.__args = args 

21 self.__kwargs = kwargs 

22 

23 self.__object: object | T = self.__DEFAULT_OBJECT 

24 

25 def __call__(self) -> T: 

26 if self.__object == self.__DEFAULT_OBJECT: 

27 self.__object = self.__func(*self.__args, **self.__kwargs) 

28 

29 return cast("T", self.__object) 

30 

31 

32# Name it in this way to mimic as a function 

33class multi_call[T: Any]: # noqa: N801 

34 def __init__( 

35 self, 

36 times: int, 

37 agg_func: Callable[[Iterable[T]], T], 

38 ) -> None: 

39 self.__times = times 

40 self.__agg_func = agg_func 

41 

42 def __call__( 

43 self, 

44 func: Callable[..., T], 

45 *args: Any, 

46 **kwargs: Any, 

47 ) -> T: 

48 results = ( 

49 func(*args, **kwargs) 

50 for i in range(self.__times) 

51 ) 

52 

53 return self.__agg_func(results) 

54 

55 

56# Name it in this way to mimic as a function 

57class multi_call_for_average[T: int | float | Decimal](multi_call[T]): # noqa: N801 

58 def __init__( 

59 self, 

60 times: int, 

61 ) -> None: 

62 super().__init__( 

63 times, 

64 mean, 

65 ) 

66 

67 

68# Name it in this way to mimic as a function 

69class multi_call_for_most_common[T: Any](multi_call[T]): # noqa: N801 

70 def __init__( 

71 self, 

72 times: int, 

73 ) -> None: 

74 def most_commmon(results: Iterable[T]) -> T: 

75 return Counter(results).most_common(1)[0][0] 

76 

77 super().__init__( 

78 times, 

79 most_commmon, 

80 ) 

81 

82 

83class Intercept[T: Any]: 

84 def __init__( 

85 self, 

86 replacement: Callable[[dict[str, Any]], T], 

87 ) -> None: 

88 self.__replacement = replacement 

89 

90 def __call__[**P](self, f: Callable[P, T]) -> Callable[P, T]: 

91 sig: Signature = signature(f) 

92 

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

94 return self.__replacement( 

95 sig.bind(*args, **kwargs).arguments, 

96 ) 

97 

98 return wrapper 

99 

100 

101class InterceptAsync[T: Any]: 

102 def __init__( 

103 self, 

104 replacement: Callable[[dict[str, Any]], Awaitable[T]], 

105 ) -> None: 

106 self.__replacement = replacement 

107 

108 def __call__[**P](self, f: Callable[P, Awaitable[T]]) -> Callable[P, Awaitable[T]]: 

109 sig: Signature = signature(f) 

110 

111 async def wrapper(*args: P.args, **kwargs: P.kwargs) -> T: 

112 return await self.__replacement( 

113 sig.bind(*args, **kwargs).arguments, 

114 ) 

115 

116 return wrapper