1
1
from __future__ import annotations
2
2
3
+ import enum
3
4
import abc
4
5
from typing import (
5
6
TYPE_CHECKING ,
12
13
TypeVar ,
13
14
Union ,
14
15
overload ,
16
+ Literal ,
15
17
)
16
18
17
19
import attr
18
20
19
21
from ._util import AlreadyUsedError , remove_tb_frames
20
22
21
23
if TYPE_CHECKING :
22
- from typing_extensions import ParamSpec , final
24
+ from typing_extensions import ParamSpec , final , Final
23
25
ArgsT = ParamSpec ("ArgsT" )
24
26
else :
25
27
28
+ Final = object
29
+
26
30
def final (func ):
27
31
return func
28
32
@@ -106,6 +110,14 @@ async def acapture(
106
110
return Error (exc )
107
111
108
112
113
+ class _Unwrapped (enum .Enum ):
114
+ IS_UNWRAPPED = enum .auto ()
115
+
116
+
117
+ IS_UNWRAPPED : Final = _Unwrapped .IS_UNWRAPPED
118
+ IsUnwrapped : TypeAlias = Literal [_Unwrapped .IS_UNWRAPPED ]
119
+
120
+
109
121
@attr .s (repr = False , init = False , slots = True )
110
122
class Outcome (abc .ABC , Generic [ValueT ]):
111
123
"""An abstract class representing the result of a Python computation.
@@ -122,13 +134,6 @@ class Outcome(abc.ABC, Generic[ValueT]):
122
134
hashable.
123
135
124
136
"""
125
- _unwrapped : bool = attr .ib (default = False , eq = False , init = False )
126
-
127
- def _set_unwrapped (self ) -> None :
128
- if self ._unwrapped :
129
- raise AlreadyUsedError
130
- object .__setattr__ (self , '_unwrapped' , True )
131
-
132
137
@abc .abstractmethod
133
138
def unwrap (self ) -> ValueT :
134
139
"""Return or raise the contained value or exception.
@@ -170,23 +175,30 @@ class Value(Outcome[ValueT], Generic[ValueT]):
170
175
171
176
"""
172
177
173
- value : ValueT = attr .ib ()
178
+ _value : IsUnwrapped | ValueT = attr .ib ()
174
179
"""The contained value."""
175
180
181
+ @property
182
+ def value (self ) -> ValueT :
183
+ return self .unwrap ()
184
+
176
185
def __repr__ (self ) -> str :
177
186
return f'Value({ self .value !r} )'
178
187
179
188
def unwrap (self ) -> ValueT :
180
- self ._set_unwrapped ()
181
- return self .value
189
+ if self ._value is IS_UNWRAPPED :
190
+ raise AlreadyUsedError
191
+
192
+ try :
193
+ return self ._value
194
+ finally :
195
+ object .__setattr__ (self , "_value" , IS_UNWRAPPED )
182
196
183
197
def send (self , gen : Generator [ResultT , ValueT , object ]) -> ResultT :
184
- self ._set_unwrapped ()
185
- return gen .send (self .value )
198
+ return gen .send (self .unwrap ())
186
199
187
200
async def asend (self , agen : AsyncGenerator [ResultT , ValueT ]) -> ResultT :
188
- self ._set_unwrapped ()
189
- return await agen .asend (self .value )
201
+ return await agen .asend (self .unwrap ())
190
202
191
203
192
204
@final
@@ -196,16 +208,24 @@ class Error(Outcome[NoReturn]):
196
208
197
209
"""
198
210
199
- error : BaseException = attr .ib (
211
+ _error : IsUnwrapped | BaseException = attr .ib (
200
212
validator = attr .validators .instance_of (BaseException )
201
213
)
202
214
"""The contained exception object."""
203
215
216
+ @property
217
+ def error (self ):
218
+ if self ._error is IS_UNWRAPPED :
219
+ raise AlreadyUsedError
220
+ try :
221
+ return self ._error
222
+ finally :
223
+ object .__setattr__ (self , "_error" , IsUnwrapped )
224
+
204
225
def __repr__ (self ) -> str :
205
226
return f'Error({ self .error !r} )'
206
227
207
228
def unwrap (self ) -> NoReturn :
208
- self ._set_unwrapped ()
209
229
# Tracebacks show the 'raise' line below out of context, so let's give
210
230
# this variable a name that makes sense out of context.
211
231
captured_error = self .error
@@ -227,11 +247,9 @@ def unwrap(self) -> NoReturn:
227
247
del captured_error , self
228
248
229
249
def send (self , gen : Generator [ResultT , NoReturn , object ]) -> ResultT :
230
- self ._set_unwrapped ()
231
250
return gen .throw (self .error )
232
251
233
252
async def asend (self , agen : AsyncGenerator [ResultT , NoReturn ]) -> ResultT :
234
- self ._set_unwrapped ()
235
253
return await agen .athrow (self .error )
236
254
237
255
0 commit comments