diff --git a/src/outcome/_impl.py b/src/outcome/_impl.py index 004b72d..796fcf6 100644 --- a/src/outcome/_impl.py +++ b/src/outcome/_impl.py @@ -122,12 +122,6 @@ class Outcome(abc.ABC, Generic[ValueT]): hashable. """ - _unwrapped: bool = attr.ib(default=False, eq=False, init=False) - - def _set_unwrapped(self) -> None: - if self._unwrapped: - raise AlreadyUsedError - object.__setattr__(self, '_unwrapped', True) @abc.abstractmethod def unwrap(self) -> ValueT: @@ -174,19 +168,26 @@ class Value(Outcome[ValueT], Generic[ValueT]): """The contained value.""" def __repr__(self) -> str: - return f'Value({self.value!r})' + try: + return f'Value({self.value!r})' + except AttributeError: + return f'Value()' def unwrap(self) -> ValueT: - self._set_unwrapped() - return self.value + try: + v = self.value + except AttributeError: + pass + else: + object.__delattr__(self, "value") + return v + raise AlreadyUsedError def send(self, gen: Generator[ResultT, ValueT, object]) -> ResultT: - self._set_unwrapped() - return gen.send(self.value) + return gen.send(self.unwrap()) async def asend(self, agen: AsyncGenerator[ResultT, ValueT]) -> ResultT: - self._set_unwrapped() - return await agen.asend(self.value) + return await agen.asend(self.unwrap()) @final @@ -202,13 +203,25 @@ class Error(Outcome[NoReturn]): """The contained exception object.""" def __repr__(self) -> str: - return f'Error({self.error!r})' + try: + return f'Error({self.error!r})' + except AttributeError: + return 'Error()' + + def _unwrap_error(self) -> BaseException: + try: + v = self.error + except AttributeError: + pass + else: + object.__delattr__(self, "error") + return v + raise AlreadyUsedError def unwrap(self) -> NoReturn: - self._set_unwrapped() # Tracebacks show the 'raise' line below out of context, so let's give # this variable a name that makes sense out of context. - captured_error = self.error + captured_error = self._unwrap_error() try: raise captured_error finally: @@ -227,12 +240,10 @@ def unwrap(self) -> NoReturn: del captured_error, self def send(self, gen: Generator[ResultT, NoReturn, object]) -> ResultT: - self._set_unwrapped() - return gen.throw(self.error) + return gen.throw(self._unwrap_error()) async def asend(self, agen: AsyncGenerator[ResultT, NoReturn]) -> ResultT: - self._set_unwrapped() - return await agen.athrow(self.error) + return await agen.athrow(self._unwrap_error()) # A convenience alias to a union of both results, allowing exhaustiveness checking. diff --git a/tests/test_sync.py b/tests/test_sync.py index 855d776..ca33401 100644 --- a/tests/test_sync.py +++ b/tests/test_sync.py @@ -10,8 +10,9 @@ def test_Outcome(): v = Value(1) assert v.value == 1 - assert v.unwrap() == 1 assert repr(v) == "Value(1)" + assert v.unwrap() == 1 + assert repr(v) == "Value()" with pytest.raises(AlreadyUsedError): v.unwrap() @@ -21,11 +22,12 @@ def test_Outcome(): exc = RuntimeError("oops") e = Error(exc) assert e.error is exc + assert repr(e) == f"Error({exc!r})" with pytest.raises(RuntimeError): e.unwrap() with pytest.raises(AlreadyUsedError): e.unwrap() - assert repr(e) == f"Error({exc!r})" + assert repr(e) == "Error()" e = Error(exc) with pytest.raises(TypeError):