Coverage for src/monadc/try_/try_.py: 73%

1 from typing import TypeVar, Generic, Callable, Union, Any, Optional, Type

2

3 T = TypeVar('T') # Success type

4 U = TypeVar('U') # Result type

5 E = TypeVar('E', bound=Exception) # Exception type

6

7

8 class Try(Generic[T]):

9 """

10 Scala inspired Try monad for representing computations that may throw exceptions.

11

12 Try[T] represents a computation that either:

13 - Success[T]: A successful computation with result of type T

14 - Failure: A failed computation that caught an exception

15

16 Try automatically catches exceptions and wraps them in Failure.

17 """

18

19 def __new__(cls, func: Callable[[], T]) -> 'Try[T]':

20 """Factory constructor that executes function and catches exceptions."""

21 if cls is not Try:

22 # Direct subclass instantiation (Success, Failure)

23 return super().__new__(cls)

24

25 try:

26 result = func()

27 from .success import Success

28 return Success(result)

29 except Exception as e:

30 from .failure import Failure

31 return Failure(e)

32

33 @classmethod

34 def of_value(cls, value: T) -> 'Try[T]':

35 """Create a Success directly from a value without function execution."""

36 from .success import Success

37 return Success(value)

38

39 # Type checking methods

40 def is_success(self) -> bool:

41 """Returns True if this Try is a Success, False otherwise."""

42 raise NotImplementedError("Use Success or Failure, not Try directly")

43

44 def is_failure(self) -> bool:

45 """Returns True if this Try is a Failure, False otherwise."""

46 raise NotImplementedError("Use Success or Failure, not Try directly")

47

48 # Value extraction

49 def get(self) -> T:

50 """Get the value. Raises the original exception if this is a Failure."""

51 raise NotImplementedError("Use Success or Failure, not Try directly")

52

53 def get_or_else(self, default: Union[T, Callable[[], T]]) -> T:

54 """Get the value or return default if this is a Failure."""

55 raise NotImplementedError("Use Success or Failure, not Try directly")

56

57 # Exception access

58 def exception(self) -> Optional[Exception]:

59 """Get the exception if this is a Failure, None otherwise."""

60 raise NotImplementedError("Use Success or Failure, not Try directly")

61

62 # Transformations

63 def map(self, func: Callable[[T], U]) -> 'Try[U]':

64 """Transform the Success value if present, otherwise return unchanged Failure."""

65 raise NotImplementedError("Use Success or Failure, not Try directly")

66

67 def flat_map(self, func: Callable[[T], 'Try[U]']) -> 'Try[U]':

68 """Transform Success value to Try if present, otherwise return unchanged Failure."""

69 raise NotImplementedError("Use Success or Failure, not Try directly")

70

71 def filter(self, predicate: Callable[[T], bool]) -> 'Try[T]':

72 """Keep Success if predicate matches, otherwise return Failure."""

73 raise NotImplementedError("Use Success or Failure, not Try directly")

74

75 # Recovery operations

76 def recover(self, func: Callable[[Exception], T]) -> 'Try[T]':

77 """Recover from Failure by applying function to exception."""

78 raise NotImplementedError("Use Success or Failure, not Try directly")

79

80 def recover_with(self, func: Callable[[Exception], 'Try[T]']) -> 'Try[T]':

81 """Recover from Failure by applying function that returns Try."""

82 raise NotImplementedError("Use Success or Failure, not Try directly")

83

84 def or_else(self, alternative: Union['Try[T]', Callable[[], 'Try[T]']]) -> 'Try[T]':

85 """Return self if Success, otherwise return alternative Try."""

86 raise NotImplementedError("Use Success or Failure, not Try directly")

87

88 def flatten(self) -> 'Try[Any]':

89 """Flatten nested Try[Try[T]] to Try[T]. Raises TypeError if not nested."""

90 raise NotImplementedError("Use Success or Failure, not Try directly")

91

92 def flatten_safe(self) -> 'Try[Any]':

93 """Safe flatten that returns self if not nested (idempotent behavior)."""

94 raise NotImplementedError("Use Success or Failure, not Try directly")

95

96 # Folding

97 def fold(self, if_failure: Callable[[Exception], U], if_success: Callable[[T], U]) -> U:

98 """Apply if_failure to exception or if_success to value."""

99 raise NotImplementedError("Use Success or Failure, not Try directly")

100

101 def transform(self, success_func: Callable[[T], 'Try[U]'],

102 failure_func: Callable[[Exception], 'Try[U]']) -> 'Try[U]':

103 """Transform both Success and Failure cases."""

104 raise NotImplementedError("Use Success or Failure, not Try directly")

105

106 # Side effects

107 def foreach(self, func: Callable[[T], Any]) -> None:

108 """Execute function on Success value, do nothing for Failure."""

109 raise NotImplementedError("Use Success or Failure, not Try directly")

110

111 # Conversions

112 def to_option(self) -> Any: # Returns Option[T]

113 """Convert Try to Option, losing exception information."""

114 raise NotImplementedError("Use Success or Failure, not Try directly")

115

116 def to_either(self) -> Any: # Returns Either[Exception, T]

117 """Convert Try to Either with exception as Left value."""

118 raise NotImplementedError("Use Success or Failure, not Try directly")