bpo-36829: Add test.support.catch_unraisable_exception() by vstinner · Pull Request #13490 · python/cpython
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this try/with/finally is somewhat tricky, how about:
try: with support.throw_unraisable_exceptions(): ... except Exception as e: ... # the exception is now here
eg:
@contextlib.contextmanager def throw_unraisable_exceptions(): unraisable = None old_hook = sys.unraisablehook def hook(exc): nonlocal unraisable unraisable = exc sys.unraisablehook = hook try: yield if unraisable is not None: raise unraisable finally: unraisable = None sys.unraisablehook = old_hook
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
or recommend usage like this:
with support.catch_unraisable_exceptions() as cm: ... cm.unraisable # only available here assert cm.unraisable is None # now it's gone
by updating __exit__:
def __exit__(self, *exc_info): self.unraisable = None # clear unraisable here sys.unraisablehook = self._old_hook
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good idea :-) I implemented your __exit__ idea to avoid the need for try/finally.
throw_unraisable_exceptions() might be useful, but modified tests needs to access the 'obj' attribute of the unraisable hook. Later, they might also want to get access to the 'err_msg' attribute: #13488
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There's still a sharp edge here:
how about making cm.unraisable throw if accessed outside the context instead of being None, eg:
def __exit__(self, *exc_info):
del self.unraisable
sys.unraisablehook = self._old_hook
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
and if you want to access additional unraisablehook info using throw_unraisable_exceptions:
class UnraisableException(Exception): def __init__(self, unraisable): self.unraisable = unraisable super().__init__(self, unraisable) @contextlib.contextmanager def throw_unraisable_exceptions(): unraisable = None old_hook = sys.unraisablehook def hook(unraisable_): nonlocal unraisable unraisable = unraisable_ sys.unraisablehook = hook try: yield if unraisable is not None: raise UnraisableException(unraisable) from unraisable.exc_value finally: sys.unraisablehook = old_hook
then you can use it with:
try: with throw_unraisable_exceptions(): ... except UnraisableException as e: print(repr(e.__cause__)) err_msg = e.unraisable.err_msg
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wrote PR #13554 to implement your "del self.unraisable" idea.