I am using the requests
Python module to connect to a website through a SOCKS4 proxy. While trying to connect to the website, the program fails to even connect to the SOCKS4. Therefore, the PySocks module throws a TimeoutError
exception, which gets caught and rethrown as a ProxyConnectionError
exception.
If this was the end of the story, I could have just caught the ProxyConnectionError
directly. However, the underlying urllib3
module catches the exception and re-raises a NewConnectionError
instead. You can see this in the official source code.
Here is the final traceback that I get from my program (cut many lines for brevity):
Traceback (most recent call last):
TimeoutError: [WinError 10060] A connection attempt failed because the connected party did not properly respond after a period of time, or established connection failed because connected host has failed to respond
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
socks.ProxyConnectionError: Error connecting to SOCKS4 proxy ...
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
urllib3.exceptions.NewConnectionError: <urllib3.contrib.socks.SOCKSHTTPSConnection object at 0x0000025DA70CCDC0>: Failed to establish a new connection: ...
During handling of the above exception, another exception occurred:
... (eventually raises requests.exceptions.ConnectionError, then terminates the program)
My target is to catch all the PySocks errors (such as the ProxyConnectionError
that was raised in this example), which can be done by catching the base exception class socks.ProxyError
.
As the requests
library is a downloaded module, I don't have the liberty of editing the underlying code (if I edit the source code directly, then these changes won't be updated if someone else downloads my code and installs the requests library from PyPI).
Is there any way to catch an error that was already caught inside another module?
CodePudding user response:
After doing a little digging, I found the PEP responsible for adding __context__
, an attribute on exception objects which allows implicitly chained exceptions.
This means that for every exception, there is a __context__
attribute which points to the previously caught exception. Using a little bit of node iteration, we can get to the bottom of this chain, which holds a None
(if it is the first thrown exception).
Putting this all together as code, I wrote a function which gets the current thrown exception using sys.exc_info()
, then iterates until it hits None
. If it finds a previously thrown exception which is a subclass of the one we want to catch, then it returns True
:
def contains_exception(target_exc: Type[BaseException]) -> bool:
# Extract current exception
exception_obj: Type[BaseException] = sys.exc_info()[1]
# The final exception will be None
while exception_obj:
if issubclass(exception_obj.__class__, target_exc):
return True
# Iterate to next exception in the "During handling" chain
exception_obj: Optional[BaseException] = exception_obj.__context__
# Target exception wasn't found
return False
And here is how I used it in my code:
try:
...
except BaseException:
if contains_exception(socks.ProxyError):
# We caught the ProxyConnectionError
...
else:
# Send the error back up, it's not relevant to the proxy
raise