Is there a best practice for cascading python exception handlers?

Viewed 224

TLDR: Is there a good way to cascade a heirarchy of exception handlers similar to what is possible with a series of if statements? e.g. one handler may attempt to handle a problem but throw an exception to be caught by the next handler, or the initial try block throws the second exception directly.

Basic premise: (There may be poory conceived code here, so bear with me) Also may be a duplicate, but I couldn't find it.

I am trying to verify the validity of a url with a head request. If I get a ConnectionError the url is not valid. The head request will helpfully throw a MissingSchema exception for missing "http://" so I added an exception handler to try the url with an "http://". However, if the url is still invalid it throws the expected ConnectionError. Is there a good way to pass that exception back to the exception handler that takes the ConnectionError directly from the try block? This would be similar to how you can cascade if statements. I could solve this particular example with some copy paste or recursion, but I could see both solutions becoming pretty annoying in more complex functions. Sample code below:

def checkURL(url):
    try:
        resp = requests.head(url) 
        return True # if request does not raise exception
    
    except requests.exceptions.MissingSchema as exception:
        try:
            #try url with schema
            resp = requests.head('http://' + url)
            return True
            #if url is still bad it will throw Connection Error
            #I would like this to be also handled by the block below
            
    except requests.ConnectionError as exception:
        #ConnectionError == bad url
        return False 

I could solve this by duplicating my ConnectionError handler in the secondary try - except block, but that seems like a bad solution. Or I could recursively call checkURL('http://' + url) in the MissingSchema handler, but I could see that being problematic / inefficient also if there was more work being done in the initial try block. There's a good chance I'm missing something obvious here, but I'd appreciate any feedback.

1 Answers

In this case it would be easier to check the protocol "http://" or "https://" with .startswith() or a regular expression. Nesting exception handling is rarely a good design choice.

Also your strategy for checking the url by trying the request multiple times can have serious performance issues if you are going to do a lot of checks.

Your best option here would be to check whatever you can without performing any request first, and only then have a single try block with multiple except clauses if you need to address the failures separately.


Edit: in the context of your question, where you want to retry the request in case of failures it is not really helpful and will force you to repeat the code. If you need some mechanics that keeps retrying some operation then you need to enclose the try-except block inside a loop. For example:

for url in url_variations:
    try:
        request.head(url)
        return url
    except BlaBlaError:
        continue
return None
Related