From 08b08df9af3f6b406723f0861fbd933830a67b6d Mon Sep 17 00:00:00 2001 From: Thomas Grainger Date: Sat, 22 Mar 2025 11:35:07 +0000 Subject: [PATCH 1/3] remove instances from outcome on unwrap --- src/outcome/_impl.py | 58 ++++++++++++++++++++++++++++---------------- tests/test_sync.py | 6 +++-- 2 files changed, 41 insertions(+), 23 deletions(-) diff --git a/src/outcome/_impl.py b/src/outcome/_impl.py index 004b72d..3c287bd 100644 --- a/src/outcome/_impl.py +++ b/src/outcome/_impl.py @@ -122,13 +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: """Return or raise the contained value or exception. @@ -174,19 +167,29 @@ 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") + try: + return v + finally: + del 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 +205,28 @@ 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") + try: + return v + finally: + del 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 +245,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): From 4d40df04857f0e32214431cc3e7767c3ac39c563 Mon Sep 17 00:00:00 2001 From: Thomas Grainger Date: Sat, 22 Mar 2025 11:42:34 +0000 Subject: [PATCH 2/3] yapf --- src/outcome/_impl.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/outcome/_impl.py b/src/outcome/_impl.py index 3c287bd..cc05015 100644 --- a/src/outcome/_impl.py +++ b/src/outcome/_impl.py @@ -122,6 +122,7 @@ class Outcome(abc.ABC, Generic[ValueT]): hashable. """ + @abc.abstractmethod def unwrap(self) -> ValueT: """Return or raise the contained value or exception. From e0f317813a499f1a3629b37c3b8caed72825d9c0 Mon Sep 17 00:00:00 2001 From: Thomas Grainger Date: Sat, 22 Mar 2025 16:48:06 +0000 Subject: [PATCH 3/3] no need to delete v in unwrap/_unwrap_error --- src/outcome/_impl.py | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/src/outcome/_impl.py b/src/outcome/_impl.py index cc05015..796fcf6 100644 --- a/src/outcome/_impl.py +++ b/src/outcome/_impl.py @@ -180,10 +180,7 @@ def unwrap(self) -> ValueT: pass else: object.__delattr__(self, "value") - try: - return v - finally: - del v + return v raise AlreadyUsedError def send(self, gen: Generator[ResultT, ValueT, object]) -> ResultT: @@ -218,10 +215,7 @@ def _unwrap_error(self) -> BaseException: pass else: object.__delattr__(self, "error") - try: - return v - finally: - del v + return v raise AlreadyUsedError def unwrap(self) -> NoReturn: