Categories
Code

Step 11: Create high-level endpoint abstractions

Over the past 10 chapters, we’ve created a low-level REST API adapter, refactored it, made it DRY, and implemented multiple strong data models. We even implemented inheritance!

Now we’re getting to the real heart of this REST API adapter:

The high-level endpoint abstractions!

But we need to have a side-bar about “code smells” first…

Code Smells

Some of you reading this may be familiar with the concept of “code smells”, but if you’re still learning to program, there’s a better than average chance that you’ve never come across this concept before.

What IS a “code smell”, you ask?

As defined in Wikipedia, a Code Smell

“is any characteristic in the source code of a program that possibly indicates a deeper problem. Determining what is and is not a code smell is subjective, and varies by language, developer, and development technology.”

The Wikipedia entry then goes on to talk about types of common code smells, such as:

  • Application level
  • Class level
  • Method level

The one that you’re probably already familiar with is an Application-level smell known as “Duplicated Code”. We already covered this in a previous chapter where we talked about WET and DRY code.

Another one that speaks most clearly to what we’re about to do is a “Class-level” code smell known as:

Excessive use of Literals. Specifically a type known as “String Literals”.

What does “excessive use of string literals” look like?

Take this short 4 line script. It uses the requests module to GET a cat from TheCatApi:

from requests import request
response = request(method="GET", url='https://api.thecatapi.com/v1/images/search')
cat_metadata = response.json()
cat_image_response = request(method="GET", url=cat_metadata[0]['url'])

As you can see this example has 2 “GET” strings, a full URL, and the string “url” used as a key in the dictionary to get a value. These are all very specific to TheCatAPI.

Do you really want to memorize those endpoints and keys or ever have to refer back to them? One small typo can completely break your code.

To remedy this, we’re going to create high-level endpoint abstractions. Let’s start by creating a new class in a new file called cat_api.py

import logging
from the_cat_api.rest_adapter import RestAdapter
from the_cat_api.exceptions import TheCatApiException
from the_cat_api.models import *

class TheCatApi:
    def __init__(self, hostname: str = 'api.thecatapi.com', api_key: str = '', ver: str = 'v1', ssl_verify: bool = True, logger: logging.Logger = None):
        self._rest_adapter = RestAdapter(hostname, api_key, ver, ssl_verify, logger)

What we’ve done here is create a new high-level abstraction, but so far we only have a new Class with all the basics that we would need to pass to the lower-level REST Adapter. (hostname, and optionally, the api_key, ver, ssl_verify, and logger.)

Let’s start by implementing our first endpoint in the class TheCatApi:

def get_kitty(self) -> Result:
    result = self._rest_adapter.get(endpoint='/images/search')
    return result

And if we run this…

from the_cat_api.thecat_api import TheCatApi
from the_cat_api.models import *
catapi = TheCatApi()
result = catapi.get_kitty()

We get a result… that is… not very helpful. We’re really only interested in the Image data, not the HTTP info. So let’s grab the data and push it into a strongly typed object.

def get_kitty(self) -> ImageShort:
    result = self._rest_adapter.get(endpoint='/images/search')
    kitty_img = ImageShort(**result.data[0])
    return kitty_img

This is a simple method, but in 4 lines it does a lot of clever work for us.
Line 1 declares the method and hints to the developer environment that we will be getting an “ImageShort” object back.
Line 2 does the work of fetching the random kitty data
Line 3 selects only the first (0th) kitty from the data set and then uses the double-star (**) operator to unpack the dictionary into the ImageShort
Line 4 returns the new ImageShort object

If we try this again…

from the_cat_api.thecat_api import TheCatApi
from the_cat_api.models import *
catapi = TheCatApi()
result = catapi.get_kitty()

This is much more useful. As we can see in the result object, the id, width, height, and url are all readily available. The piddly HTTP details are still available to us in the method, but the method itself only returns the high-level data that we’re interested in.

This is the power of layers of abstraction in your code.

Chapter 12: More endpoints and helper methods


Source code: https://github.com/PretzelLogix/py-cat-api/tree/11_highlevel_endpoints

2 replies on “Step 11: Create high-level endpoint abstractions”

Leave a Reply

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