On Wed, Apr 14, 2021 at 01:29:40PM -0400, John Snow wrote: > On 4/13/21 4:00 PM, Stefan Hajnoczi wrote: > > On Tue, Apr 13, 2021 at 11:55:49AM -0400, John Snow wrote: > One of the reasons it's split out here like this is because I also wrote a > qtest protocol that uses the same infrastructure. I tried to keep both of > those looking as simple as possible. If infrastructure is needed, let's add it when it's needed but not before then. Reviewers can't take into account qtest requirements without seeing that code. > > > + try: > > > + await self._new_session(self._do_accept(address, ssl)) > > > + except Exception as err: > > > + emsg = "Failed to accept incoming connection" > > > + self.logger.error("%s:\n%s\n", emsg, pretty_traceback()) > > > + raise ConnectError(f"{emsg}: {err!s}") from err > > > > Wrapping the exception in ConnectError() obfuscates what's going on IMO. > > > > It can. I will admit to you that the reason I did it was so that at a higher > level it was possible to write things like: > > try: > await qmp.connect(('127.0.0.1', 1234)) > except ConnectError: > print("Oh no! ...") > handle_connect_problem() > > while still allowing for other problems to "bubble up", for instance, > keyboard interrupts are BaseException and won't be caught by that wrapper. That's what "except Exception as err" is for. It allows system exiting exceptions through. > I adhere to this pattern fairly regularly in this draft; using "container" > exceptions to declare a semantic problem regardless of the actual underlying > cause, assuming that a high-level user will (probably) be unable to make > sense of the internal details anyway. > > Part of the reason I do this is so that I could write in the docstrings what > exceptions we actually expect to be raised and under what circumstances. > > I suppose what I don't document, though, is how to get at the "root" > exception in these cases. You can do it: > > try: > ... > except Foo as err: > root_err = err.__cause__ > ... > > > I suppose the specific problem I wanted to solve is this: When we fail to > connect, what exception will we see? How many types of exceptions? Which > ones should I try to catch, and which ones should I not? Which error classes > merit a retry, and which do not? > > It seemed almost impossible to enumerate, so writing informed client code > seemed difficult or impossible. > > Any thoughts on this perception? I reviewed error.py again. I was assuming the other exceptions are like ConnectError, which would have been bad but they seem to be more actionable. I think ConnectError is okay as a catch-all here. requests is similar, its exceptions consist mostly of actionable exceptions but it does have catch-all exceptions for general networking errors: https://docs.python-requests.org/en/latest/api/#exceptions MultiException and the 4-level exception inheritance hierarchy in error.py still seem complex. Is it really necessary to have ProtocolError, RawProtocolError, and MsgProtocolError abstract classes? Having them forces the user to make extra decisions about how to catch exceptions. If error handling is involved, more mistakes will be made. The requests package doesn't document the exception hierarchy. They keep it simple with a flat list of exceptions. (RequestException is the base class though.) Stefan