Home > Software design >  Catch a Python exception that a module already caught
Catch a Python exception that a module already caught

Time:01-04

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
  • Related