Categories
Code

Step 4: Exception handling and Raising new custom Exceptions

In our last chapter on “WET code, DRY code, and Refactoring the low-level REST Adapter”, we refactored our code into a basic, yet reasonable low-level REST API adapter.

In this chapter, we’re going to cover proper Exception handling as well as creating our own custom Exceptions. Let’s start by looking back at our code, specifically our _do method.

Here are the ~30 lines we wrote last time:

import requests
import requests.packages
from typing import List, Dict

class RestAdapter:
    def __init__(self, hostname: str, api_key: str = '', ver: str = 'v1', ssl_verify: bool = True):
        self.url = f"https://{hostname}/{ver}"
        self._api_key = api_key
        self._ssl_verify = ssl_verify
        if not ssl_verify:
            # noinspection PyUnresolvedReferences
            requests.packages.urllib3.disable_warnings()

    def _do(self, http_method: str, endpoint: str, ep_params: Dict = None, data: Dict = None):
        full_url = self.url + endpoint
        headers = {'x-api-key': self._api_key}
        response = requests.request(method=http_method, url=full_url, verify=self._ssl_verify, 
                                    headers=headers, params=ep_params, json=data)
        data_out = response.json()
        if response.status_code >= 200 and response.status_code <= 299:     # OK
            return data_out
        raise Exception(data_out["message"])

    def get(self, endpoint: str, ep_params: Dict = None) -> List[Dict]:
        return self.do(http_method='GET', endpoint=endpoint, ep_params=ep_params)

    def post(self, endpoint: str, ep_params: Dict = None, data: Dict = None):
        return self.do(http_method='POST', endpoint=endpoint, ep_params=ep_params, data=data)

    def delete(self, endpoint: str, ep_params: Dict = None, data: Dict = None):
        return self.do(http_method='DELETE', endpoint=endpoint, ep_params=ep_params, data=data)

Exceptions everywhere!

In case you weren’t aware, the requests.request method can raise an Exception. Specifically, requests.exceptions.RequestException — Let’s wrap that line with a try-except block:

try:
    response = requests.request(method=http_method, url=full_url, verify=self._ssl_verify, 
                                headers=headers, params=ep_params, json=data)
except requests.exceptions.RequestException as e:
        # do something here -- don't pass silently!

Ok, we’re halfway there. The requests.request method is now contained in a try-except block and we’ve explicitly declared that we want to handle any RequestException type exceptions… But what do we do with this?

I’ll tell you what! We make a new exception for the TheCatApi library that we’re writing.

Right-click on the the_cat_api folder (the module) in the Project browser tree and Create a NEW Python file and call it: exceptions.py

Now add the following 2 lines to it:

class TheCatApiException(Exception):
    pass

Congrats! You’ve just created your own custom exception unique to the_cat_api library.

Using TheCatApiException

Go back to the rest_adapter.py file and import this exception at the top of the file.

from the_cat_api.exceptions import TheCatApiException

And let’s add the final line to our try-except block:

    try:
        response = requests.request(method=http_method, url=full_url, verify=self._ssl_verify, 
                                    headers=headers, params=ep_params, json=data)
    except requests.exceptions.RequestException as e:
        raise TheCatApiException("Request failed") from e

What have we done here? By raising our own custom exception, TheCatApiException, we’re indicating to any program that consumes our adapter where the failure happened. Also, note the as e and the from e.

Catching the Request exception as e is a common technique to capture the Exception in a variable when it happens. We’re taking the message from the exception, e, stuffing the message into TheCatApiException and then using from e when raise-ing, we’re continuing the stack trace. If we hadn’t used from e after raise-ing, then the stack trace would have been disrupted. Keeping the stack trace intact is important later if you’re ever debugging a program and trying to figure out where in the stack a failure happened.

This leads us to further enhancing our rest adapter with a custom Response model and Logging in the next chapter… Step 5: Creating a new Response Model


Source code: https://github.com/PretzelLogix/py-cat-api/tree/04_exception_handling

3 replies on “Step 4: Exception handling and Raising new custom Exceptions”

Leave a Reply

Your email address will not be published. Required fields are marked *