Skip to content

Commit 08b08df

Browse files
committed
remove instances from outcome on unwrap
1 parent 03ed621 commit 08b08df

File tree

2 files changed

+41
-23
lines changed

2 files changed

+41
-23
lines changed

src/outcome/_impl.py

+37-21
Original file line numberDiff line numberDiff line change
@@ -122,13 +122,6 @@ class Outcome(abc.ABC, Generic[ValueT]):
122122
hashable.
123123
124124
"""
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-
132125
@abc.abstractmethod
133126
def unwrap(self) -> ValueT:
134127
"""Return or raise the contained value or exception.
@@ -174,19 +167,29 @@ class Value(Outcome[ValueT], Generic[ValueT]):
174167
"""The contained value."""
175168

176169
def __repr__(self) -> str:
177-
return f'Value({self.value!r})'
170+
try:
171+
return f'Value({self.value!r})'
172+
except AttributeError:
173+
return f'Value(<DEAD>)'
178174

179175
def unwrap(self) -> ValueT:
180-
self._set_unwrapped()
181-
return self.value
176+
try:
177+
v = self.value
178+
except AttributeError:
179+
pass
180+
else:
181+
object.__delattr__(self, "value")
182+
try:
183+
return v
184+
finally:
185+
del v
186+
raise AlreadyUsedError
182187

183188
def send(self, gen: Generator[ResultT, ValueT, object]) -> ResultT:
184-
self._set_unwrapped()
185-
return gen.send(self.value)
189+
return gen.send(self.unwrap())
186190

187191
async def asend(self, agen: AsyncGenerator[ResultT, ValueT]) -> ResultT:
188-
self._set_unwrapped()
189-
return await agen.asend(self.value)
192+
return await agen.asend(self.unwrap())
190193

191194

192195
@final
@@ -202,13 +205,28 @@ class Error(Outcome[NoReturn]):
202205
"""The contained exception object."""
203206

204207
def __repr__(self) -> str:
205-
return f'Error({self.error!r})'
208+
try:
209+
return f'Error({self.error!r})'
210+
except AttributeError:
211+
return 'Error(<DEAD>)'
212+
213+
def _unwrap_error(self) -> BaseException:
214+
try:
215+
v = self.error
216+
except AttributeError:
217+
pass
218+
else:
219+
object.__delattr__(self, "error")
220+
try:
221+
return v
222+
finally:
223+
del v
224+
raise AlreadyUsedError
206225

207226
def unwrap(self) -> NoReturn:
208-
self._set_unwrapped()
209227
# Tracebacks show the 'raise' line below out of context, so let's give
210228
# this variable a name that makes sense out of context.
211-
captured_error = self.error
229+
captured_error = self._unwrap_error()
212230
try:
213231
raise captured_error
214232
finally:
@@ -227,12 +245,10 @@ def unwrap(self) -> NoReturn:
227245
del captured_error, self
228246

229247
def send(self, gen: Generator[ResultT, NoReturn, object]) -> ResultT:
230-
self._set_unwrapped()
231-
return gen.throw(self.error)
248+
return gen.throw(self._unwrap_error())
232249

233250
async def asend(self, agen: AsyncGenerator[ResultT, NoReturn]) -> ResultT:
234-
self._set_unwrapped()
235-
return await agen.athrow(self.error)
251+
return await agen.athrow(self._unwrap_error())
236252

237253

238254
# A convenience alias to a union of both results, allowing exhaustiveness checking.

tests/test_sync.py

+4-2
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,9 @@
1010
def test_Outcome():
1111
v = Value(1)
1212
assert v.value == 1
13-
assert v.unwrap() == 1
1413
assert repr(v) == "Value(1)"
14+
assert v.unwrap() == 1
15+
assert repr(v) == "Value(<DEAD>)"
1516

1617
with pytest.raises(AlreadyUsedError):
1718
v.unwrap()
@@ -21,11 +22,12 @@ def test_Outcome():
2122
exc = RuntimeError("oops")
2223
e = Error(exc)
2324
assert e.error is exc
25+
assert repr(e) == f"Error({exc!r})"
2426
with pytest.raises(RuntimeError):
2527
e.unwrap()
2628
with pytest.raises(AlreadyUsedError):
2729
e.unwrap()
28-
assert repr(e) == f"Error({exc!r})"
30+
assert repr(e) == "Error(<DEAD>)"
2931

3032
e = Error(exc)
3133
with pytest.raises(TypeError):

0 commit comments

Comments
 (0)