Coverage for src / pyfplib / option.py: 94%

56 statements  

« prev     ^ index     » next       coverage.py v7.13.2, created at 2026-02-04 23:48 +0300

1"""This module provides Result[T] type 

2and two constructors of Option: Ok[T] and Err[T]. 

3""" 

4 

5from typing import Callable, Generic, Optional, TypeVar, cast 

6 

7from pyfplib.errors import ExpectedError, UnwrapError 

8 

9T = TypeVar("T") 

10U = TypeVar("U") 

11 

12 

13class Option(Generic[T]): 

14 """ 

15 Rust-inspired Option<T> monad for Python. 

16 

17 Represents a value that is either Some(T) containing data, 

18 or Nothing() representing absence of value. 

19 

20 Provides safe null-handling through method chaining 

21 instead of explicit None checks. 

22 """ 

23 

24 # Support for structural pattern matching (Python 3.10+) 

25 __match_args__ = ("value",) 

26 

27 def __init__(self, value: Optional[T]): 

28 """ 

29 Internal constructor - use Some(value) or Nothing() instead. 

30 

31 Args: 

32 value: The contained value or None for Nothing 

33 """ 

34 self.__value = value 

35 

36 @property 

37 def value(self) -> Optional[T]: 

38 """Returns the raw contained value (use with caution - may be None).""" 

39 return self.__value 

40 

41 def is_some(self) -> bool: 

42 """Returns True if this Option contains a value (Some[T]).""" 

43 return self.__value is not None 

44 

45 def is_none(self) -> bool: 

46 """Returns True if this Option contains no value (Nothing).""" 

47 return self.__value is None 

48 

49 def if_some(self, fn: Callable[[T], None]) -> "Option[T]": 

50 """ 

51 Executes callback if Some[T], otherwise does nothing. 

52 Returns self for method chaining. 

53 

54 Args: 

55 fn: callback to execute with contained value 

56 """ 

57 if self.__value is not None: 

58 fn(self.__value) 

59 return self 

60 

61 def if_none(self, fn: Callable[[], None]) -> "Option[T]": 

62 """ 

63 Executes callback if Nothing, otherwise does nothing. 

64 Returns self for method chaining. 

65 

66 Args: 

67 fn: callback to execute when no value present 

68 """ 

69 if self.__value is None: 

70 fn() 

71 return self 

72 

73 def unwrap(self) -> T: 

74 """ 

75 Extracts the contained value, raising UnwrapError if Nothing. 

76 

77 Raises: 

78 UnwrapError: When attempting to unwrap Nothing 

79 """ 

80 if self.__value is None: 

81 msg = "called `Option.unwrap()` on a `Nothing` value" 

82 raise UnwrapError(msg) 

83 return self.__value 

84 

85 def expect(self, message: str) -> T: 

86 """Returns contained value or raises ExpectedError""" 

87 if self.__value is None: 

88 raise ExpectedError(message) 

89 return self.__value 

90 

91 def unwrap_or(self, value: T) -> T: 

92 """Returns contained value or provided default if Nothing.""" 

93 return value if self.__value is None else self.__value 

94 

95 def map(self, fn: Callable[[T], U]) -> "Option[U]": 

96 """ 

97 Maps contained value T -> U, returns Nothing() if no value. 

98 Preserves Option structure for method chaining. 

99 

100 Args: 

101 fn: a pure function mapping T to U 

102 """ 

103 return Some(fn(self.__value)) if self.__value is not None else Nothing() 

104 

105 def map_from(self, fn: Callable[[T], "Option[U]"]) -> "Option[U]": 

106 """ 

107 Maps contained value through a function returning Option[U]. 

108 FlatMaps Option[T] -> Option[U], returns Nothing() if no value. 

109 

110 Args: 

111 fn: the function returning Option[U] 

112 """ 

113 return fn(self.__value) if self.__value is not None else Nothing() 

114 

115 def __eq__(self, other: object) -> bool: 

116 """Equality comparison based on contained values.""" 

117 ret: bool = False 

118 if isinstance(other, Option): 118 ↛ 120line 118 didn't jump to line 120 because the condition on line 118 was always true

119 ret = self.__value == other.value 

120 return ret 

121 

122 def __hash__(self): 

123 return hash(self.__value) 

124 

125 def __bool__(self) -> bool: 

126 """Boolean conversion - True if Some[T], False if Nothing.""" 

127 return self.is_some() 

128 

129 @staticmethod 

130 def from_optional(value: Optional[T] = None) -> "Option[T]": 

131 """Creates Option[T] from Optional[T].""" 

132 return Nothing() if value is None else Some(cast(T, value)) 

133 

134 

135class Some(Option[T]): 

136 """ 

137 Some extends Option[T]. 

138 It is used to create Option[T] containing a value 

139 

140 Usage: 

141 Some(123) 

142 Some("hello") 

143 """ 

144 

145 def __init__(self, value: T): 

146 super().__init__(value) 

147 

148 

149class Nothing(Option[T]): 

150 """ 

151 Nothing extends Option[T]. 

152 It is used to create empty Option[T] 

153 

154 Usage: 

155 Nothing() - no value contained. 

156 """ 

157 

158 def __init__(self): 

159 super().__init__(None)