Re-raise errors stemming from iteration#121
Conversation
Signed-off-by: Rodrigo Tobar <rtobar@icrar.org>
calgray
left a comment
There was a problem hiding this comment.
Thanks for the MR ✨ It seems I've mostly up until this point been looking at exceptions raised from async loops being able to cancel the background thread context, but not so much exceptions raised from the the worker thread propagating up to async/main thread. Monad results through the queue is nice type-safe approach to handling it.
I might need to change some repo setting to get this MR to run pre-commit before approval
| @dataclasses.dataclass | ||
| class _Result(Generic[_T]): | ||
| value: _T | None | ||
| error: Exception | None |
There was a problem hiding this comment.
This requires a cast below as this monad doesn't enforce it must contain either value or error.
Rust std::Result<T, E> and C++ std::expected<T, E> use a compact type union to achieve this (but not perfectly compact, they must allocate descriminator bit). Python objects fortunately carry the runtime type, a tiny equivalent monad that handles the T == E edge case is the following (until Python adds standard monads):
_T = TypeVar("_T")
_E = TypeVar("_E")
class _Ok(Generic[T]):
def __init__(self, value: T):
self.value = value
class _Err(Generic[E]):
def __init__(self, error: E):
self.error = error
# from python 3.12 onwards
# type _Result[T, E] = _Ok[T] | _Err[E]These thin wrappers then allow:
if isinstance(result, _Err):
raise result.error
return result.value
No tests/docs yet, but it hopefully shows the shortcoming that needed fixing. I thought I could be more careful and try/catch
next(self._iterator)more specifically, but it doesn't seem to be worth the effort -- I think any exception needs to be re-raised anyway.