-
Notifications
You must be signed in to change notification settings - Fork 61
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
How should exceptions from underlying client libraries be handled? #57
Comments
This is a great question. Personally, I prefer an approach that sits somewhere in the middle; I think we can "reexport" and "wrap" client exceptions by introducing a virtual abstract base class (see For instance, our base exception could be try:
# some code that uses a uplink consumer
except uplink.client.errors.HttpError as error:
# do something with this error. Of course, we could do this same thing with other common exceptions, such as This way, we support a general solution for client agnostic code with no information loss, and users can still explicitly catch client specific exceptions (e.g., @daa - What are your thoughts about this approach? |
I like your idea with virtual subclasses for its clearance and simplicity. However it has global effect and unrelated say About |
Thank you, nice work. It's a pity that approach with virtual subclasses didn't work, it was a nice idea. Exceptions proxying should work for most cases. But I can see one drawback following from that proxies are tuples named like exceptions but not exceptions themselves: one cannot catch several client exceptions in usual manner. For example following code will not work at python3:
It will result in |
@daa - Thanks! Hmm... that's an interesting and noteworthy drawback that you have found. In fact, I'm puzzled... why would Python raise a Firstly, the CPython documentation for
Further, the C function's definition seems to indicate that this is the intended behavior, too. In brief, I wonder if this is a latent defect in CPython? 🤔 It might be worth you filing a bug report through the CPython Issue Tracker, so that they can either update the documentation or fix the bug. Anyways, I think a possible workaround for us would be to rename the proxy errors to make it clearer that these are sequences/collections of errors. For instance, we would rename |
Here's some interesting trivia: this ten-year old commit from the CPython repo seems to be the root cause of the behavior difference between how Python 2.7 and 3 does exception handling with tuples. I believe this change may have been unintentional and has gone unnoticed, as the commit is part of the 3.0 release but even the What's New in Python 3.0 documentation doesn't cover it. |
Actually recursive exception matching was a rather strange feature of Python, and I was surprised when discovered it. Renamed exception collections will really look less neat but at least they'll be honest about their type and supposed usage. Another way for proxying would be to force client adapters to define some set of exceptions as their attributes and then expose them at
All such consumer attributes will be exceptions taken from underlying client. But this will require changes in error handler signature - it will have to accept consumer instance to be able to check exceptions. Also these exceptions will not be visible from |
@daa I like your alternative. It appears neater than my workaround, and I think we can make it work. But before we go down that path, I have one final alternative and would appreciate your opinion: on registration of a client exception, we could make the proxy a direct superclass by dynamically appending the proxy exception to the client exceptions class hierarchy. It's a little hacky but it seems to work: I just pushed the change to #70. Let me know what you think! |
Dynamic superclassing is clever but I'm afraid it's too hacky and may rely on implementation details, and personally I prefer more simple and straightforward code. Also Python data model documentation (https://docs.python.org/3/library/stdtypes.html#special-attributes) tells us that At all I think problems and hacks arise from two contradictory wishes:
At the end I have another idea, may be not so bright as dynamically setting superclass, and involving more code - you may define hierarchy of client exceptions and then dynamically construct proxy classes for real exceptions as inherited from real exception class and corresponding Also you may look at django if you haven't already - they had similar issue and similar thoughts - https://code.djangoproject.com/ticket/15901 - and decided to wrap exceptions to their own, but they had simpler case - all needed exceptions were listed in pep and libraries conformed to it. Again, thank you for your work, this issue happened to be rather difficult and non-obvious and involve different tradeoffs. |
@daa - Sorry for taking this long to reply. Last couple weeks have been pretty busy. You make several fair points. I do appreciate the simplicity of appending our proxy exceptions onto the MRO, but I can't ignore that it muddies the well-defined boundaries between our codebases and the client's. It's also a maintainability headache waiting to happen. Your idea of dynamically constructing proxy classes sounds like a solid alternative. I've pushed an implementation to #70. Here's a quick overview, taken from the
This implementation shares a similar API to the previous iteration. Namely, proxy exceptions are defined in the |
Sorry for delay in response. Code looks good for me, however I noticed that subclasses of registered exceptions will not be wrapped. Consider |
Thank you, I really appreciate your effort to solve this issue. I like that it is responsibility of an adapter to correctly expose required exceptions, also I like that http client now carries all essential bits of interface to use. |
@daa - No problem! Thank you for providing helpful input and sticking with me throughout the numerous iterations. I'll merge this into |
Suppose I have a consumer for some API and suppose there is some network or server problems resulting in some sort of connection error. Currently
ConnectionError
fromrequests
orClientConnectionError
fromaiohttp
are raised and it results in that calling code must know which library is used and it must handle different exceptions in synchronous and asynchronous case, this is rather inconvenient. Situation becomes worse if I make some another adapter, for example withpycurl
: interface and calling convention remain the same but very different exceptions are raised.I can see 2 ways to deal with it:
uplink
's exceptions and wrap libraries' errors in themHowever both of these ways may lead to information loss which may be not desirable or may be not.
What are your thoughts about it?
And one more question: suppose I want to handle some exceptions or errors in Consumer to do something with them, for example to wrap them in my custom exception, how can I do it?
The text was updated successfully, but these errors were encountered: