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
« 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 -*-
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#============================================================================#
21''' Immutable container wrapping Absential[T] with conditional API. '''
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
31_T = __.typx.TypeVar( '_T' )
32_R = __.typx.TypeVar( '_R' )
35class AbsenceCell( __.typx.Generic[ _T ] ):
36 ''' Wraps an Absential[T] value with a rich conditional API.
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 '''
43 __slots__ = ( '_value', )
45 _value: _Absential[ _T ]
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 )
57 def __setattr__( self, name: str, value: object ) -> None:
58 raise AttributeError( 'AbsenceCell is immutable.' ) # noqa: TRY003
61 def __delattr__( self, name: str ) -> None:
62 raise AttributeError( 'AbsenceCell is immutable.' ) # noqa: TRY003
65 def __bool__( self ) -> bool:
66 return self._value is not _absent
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
77 def __hash__( self ) -> int:
78 return hash( self._value )
81 def __repr__( self ) -> str:
82 if self._value is _absent: return 'AbsenceCell( )'
83 return f'AbsenceCell( {self._value!r} )'
86 def __str__( self ) -> str:
87 if self._value is _absent: return 'absent'
88 return str( self._value )
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.
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]
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 )
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 )
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 )
157 def extract( self ) -> _T:
158 ''' Extracts the contained value.
160 Raises CellStateError if cell is empty.
161 '''
162 value = self._value
163 if _is_absent( value ):
164 raise _CellStateError( )
165 return value
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
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
194 def is_absent( self ) -> bool:
195 ''' Checks if cell contains the absent sentinel. '''
196 return self._value is _absent
199 def is_present( self ) -> bool:
200 ''' Checks if cell contains a present value. '''
201 return self._value is not _absent
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
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
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 ) )