Coverage for sources/absence/cell.py: 99%

74 statements  

« prev     ^ index     » next       coverage.py v7.15.4, created at 2026-08-08 06:42 +0000

1# vim: set filetype=python fileencoding=utf-8: 

2# -*- coding: utf-8 -*- 

3 

4#============================================================================# 

5# # 

6# Licensed under the Apache License, Version 2.0 (the "License"); # 

7# you may not use this file except in compliance with the License. # 

8# You may obtain a copy of the License at # 

9# # 

10# http://www.apache.org/licenses/LICENSE-2.0 # 

11# # 

12# Unless required by applicable law or agreed to in writing, software # 

13# distributed under the License is distributed on an "AS IS" BASIS, # 

14# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # 

15# See the License for the specific language governing permissions and # 

16# limitations under the License. # 

17# # 

18#============================================================================# 

19 

20 

21''' Immutable container wrapping Absential[T] with conditional API. ''' 

22 

23 

24from . import __ 

25from .exceptions import CellStateError as _CellStateError 

26from .objects import Absential as _Absential 

27from .objects import absent as _absent 

28from .objects import is_absent as _is_absent 

29 

30 

31_T = __.typx.TypeVar( '_T' ) 

32_R = __.typx.TypeVar( '_R' ) 

33 

34 

35class AbsenceCell( __.typx.Generic[ _T ] ): 

36 ''' Wraps an Absential[T] value with a rich conditional API. 

37 

38 Provides safe extraction, evaluation, transformation, and chaining 

39 for values that may be absent, without requiring manual boolean 

40 checks or repeated is_absent guards. 

41 ''' 

42 

43 __slots__ = ( '_value', ) 

44 

45 _value: _Absential[ _T ] 

46 

47 def __init__( 

48 self, 

49 value: __.typx.Annotated[ 

50 _Absential[ _T ], 

51 __.ddoc.Doc( ''' Value to wrap. Defaults to absent. ''' ), 

52 ] = _absent, 

53 ) -> None: 

54 object.__setattr__( self, '_value', value ) 

55 

56 

57 def __setattr__( self, name: str, value: object ) -> None: 

58 raise AttributeError( 'AbsenceCell is immutable.' ) # noqa: TRY003 

59 

60 

61 def __delattr__( self, name: str ) -> None: 

62 raise AttributeError( 'AbsenceCell is immutable.' ) # noqa: TRY003 

63 

64 

65 def __bool__( self ) -> bool: 

66 return self._value is not _absent 

67 

68 

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

70 if not isinstance( other, AbsenceCell ): return NotImplemented 70 ↛ exitline 70 didn't return from function '__eq__' because the return on line 70 wasn't executed

71 # isinstance narrows to AbsenceCell but drops the type parameter, 

72 # so other._value is Absential[Unknown]. The == comparison is 

73 # correct regardless: absent uses identity, values use __eq__. 

74 return self._value == other._value # pyright: ignore 

75 

76 

77 def __hash__( self ) -> int: 

78 return hash( self._value ) 

79 

80 

81 def __repr__( self ) -> str: 

82 if self._value is _absent: return 'AbsenceCell( )' 

83 return f'AbsenceCell( {self._value!r} )' 

84 

85 

86 def __str__( self ) -> str: 

87 if self._value is _absent: return 'absent' 

88 return str( self._value ) 

89 

90 

91 @classmethod 

92 def from_optional( 

93 cls, 

94 value: __.typx.Annotated[ 

95 _T | None, 

96 __.ddoc.Doc( ''' Optional value to bridge. ''' ), 

97 ], 

98 none_is_absent: __.typx.Annotated[ 

99 bool, 

100 __.ddoc.Doc( 

101 ''' Whether None produces empty cell. ''' ), 

102 ] = True, 

103 ) -> __.typx.Self: 

104 ''' Creates cell from Optional[T], bridging None semantics. 

105 

106 When none_is_absent is True (default), None produces an empty 

107 cell. When False, None is stored as an occupied value. 

108 ''' 

109 if none_is_absent and value is None: 

110 return cls( ) 

111 return cls( value ) # type: ignore[arg-type] 

112 

113 

114 def evaluate_or( 

115 self, 

116 func: __.typx.Annotated[ 

117 __.cabc.Callable[ [ _T ], _R ], 

118 __.ddoc.Doc( ''' Function applied to contained value. ''' ), 

119 ], 

120 default: __.typx.Annotated[ 

121 _R, 

122 __.ddoc.Doc( ''' Result for empty cells. ''' ), 

123 ], 

124 ) -> _R: 

125 ''' Applies func to value, or returns default if cell is empty. ''' 

126 value = self._value 

127 if _is_absent( value ): return default 

128 return func( value ) 

129 

130 

131 def evaluate_or_false( 

132 self, 

133 predicate: __.typx.Annotated[ 

134 __.cabc.Callable[ [ _T ], bool ], 

135 __.ddoc.Doc( ''' Predicate applied to contained value. ''' ), 

136 ], 

137 ) -> bool: 

138 ''' Applies predicate, returning False if cell is empty. ''' 

139 value = self._value 

140 if _is_absent( value ): return False 

141 return predicate( value ) 

142 

143 

144 def evaluate_or_true( 

145 self, 

146 predicate: __.typx.Annotated[ 

147 __.cabc.Callable[ [ _T ], bool ], 

148 __.ddoc.Doc( ''' Predicate applied to contained value. ''' ), 

149 ], 

150 ) -> bool: 

151 ''' Applies predicate, returning True if cell is empty. ''' 

152 value = self._value 

153 if _is_absent( value ): return True 

154 return predicate( value ) 

155 

156 

157 def extract( self ) -> _T: 

158 ''' Extracts the contained value. 

159 

160 Raises CellStateError if cell is empty. 

161 ''' 

162 value = self._value 

163 if _is_absent( value ): 

164 raise _CellStateError( ) 

165 return value 

166 

167 

168 def extract_or( 

169 self, 

170 default: __.typx.Annotated[ 

171 _T, 

172 __.ddoc.Doc( ''' Fallback for empty cells. ''' ), 

173 ], 

174 ) -> _T: 

175 ''' Extracts value, or returns default if cell is empty. ''' 

176 value = self._value 

177 if _is_absent( value ): return default 

178 return value 

179 

180 

181 def extract_or_compute( 

182 self, 

183 factory: __.typx.Annotated[ 

184 __.cabc.Callable[ [ ], _T ], 

185 __.ddoc.Doc( ''' Factory called for empty cells. ''' ), 

186 ], 

187 ) -> _T: 

188 ''' Extracts value, or returns factory() if cell is empty. ''' 

189 value = self._value 

190 if _is_absent( value ): return factory( ) 

191 return value 

192 

193 

194 def is_absent( self ) -> bool: 

195 ''' Checks if cell contains the absent sentinel. ''' 

196 return self._value is _absent 

197 

198 

199 def is_present( self ) -> bool: 

200 ''' Checks if cell contains a present value. ''' 

201 return self._value is not _absent 

202 

203 

204 def or_else( 

205 self, 

206 alternative: __.typx.Annotated[ 

207 'AbsenceCell[ _T ]', 

208 __.ddoc.Doc( ''' Cell returned if self is empty. ''' ), 

209 ], 

210 ) -> 'AbsenceCell[ _T ]': 

211 ''' Returns self if occupied, or alternative if empty. ''' 

212 if self._value is _absent: return alternative 

213 return self 

214 

215 

216 def to_optional( self ) -> __.typx.Optional[ _T ]: 

217 ''' Returns value if occupied, or None if cell is empty. ''' 

218 value = self._value 

219 if _is_absent( value ): return None 

220 return value 

221 

222 

223 def transform( 

224 self, 

225 func: __.typx.Annotated[ 

226 __.cabc.Callable[ [ _T ], _R ], 

227 __.ddoc.Doc( ''' Function applied to contained value. ''' ), 

228 ], 

229 ) -> 'AbsenceCell[ _R ]': 

230 ''' Returns new cell with func applied, or empty cell. ''' 

231 value = self._value 

232 if _is_absent( value ): return AbsenceCell( ) 

233 return AbsenceCell( func( value ) )