HTTP Services and Searching JSON

HTTP Services Day 3 of 3

Day 45 of 100

Searching JSON and digging into nested dictionaries is fun and challenging. Python comes with lots of tools for digging into JSON and you can really build some powerful applications when you add the requests package into the mix. This 100 Days of Python challenged focused on that.

The first part of the challenge was to retrieve data from the Talk Python Search API. The first thing I did was to use the Postman app to examine the results from GET requests and how to use the API to search for terms in the Talk Python Podcast episode library. From there, I wrote a function to convert the response from the API to JSON that takes a keyword argument to use as a search term. A couple of notes on this function. First, using the .raise_for_status() method I run on the response will raise an error if the API call doesn't return a 200 success code and saves from writing a validation to check if the API call was successful. Second, running the .json() method on the response is a built-in method in requests that converts the response to json. Finally, it was very easy to append each result to the query into a list of named tuples using the **r parameter when appending the list of named tuples avoids having to specify each parameter to append.

api.py

from typing import List

import requests
import collections

Episode = collections.namedtuple('Episode',
                                 'category, id, url, title, description')


def get_episode_by_title(keyword: str) -> List[Episode]:
    url = f'https://search.talkpython.fm/api/search?q={keyword}'

    resp = requests.get(url)
    resp.raise_for_status()

    results = resp.json()
    episodes = []
    for r in results.get('results'):
        episodes.append(Episode(**r))

    return episodes

Certainly, saving the json data in a named tuple is not necessary and you could work with it as a dictionary. By storing it in a named tuple, it helps with autocomplete in PyCharm and makes the code easier to write and read in my opinion.

The app itself is more than an API call, though. It is an app that prompts the user for a keyword to search the episode database and returns the number of episodes that include the search term and a list of the episode numbers and titles. Then, the user can enter an episode number and the webpage for that episode is opened in a browser using the webbrowser.open() method. I also do some validation on the user input by checking to make sure the user inputs an integer and then prints an error message if they enter a number that is not from an episode on the list.

app.py

import search_api
import webbrowser


def main():
    # prompt the user for a search term.
    keyword = input("What is the keyword you would like to search for: ")

    # query the talkpython search API
    result = search_api.get_episode_by_title(keyword)

    print('#################')
    print('###  RESULTS  ###')
    print('#################')
    print(f"There are {len(result)} episodes found.")
    for r in result:
        if r.category == 'Episode':
            print(f'{r.category} {r.id}, titled {r.title}.')

    # prompt the user if they would like to view one of the episodes
    episode = input("What episode number do you want to view or [Q]uit: ")

    # check if the user wants to quite
    if episode.strip().lower() == 'q':
        print("Good bye")
    else:
        # validate the input is an int
        try:
            episode = int(episode)
            found = 0
            for r in result:
                if r.id == episode:
                    # open a webpage of the episode
                    webbrowser.open(f"https://talkpython.fm{r.url}", new=2)
                    found = 1
            # return an error message if the number entered was not in the results
            if found == 0:
                print(f'Sorry, episode {episode} was not in the list.')
        # print an error if the value entered wasn't an int
        except:
            print("Sorry you entered an invalid episode number.")


if __name__ == '__main__':
    main()

Example Ouput

What is the keyword you would like to search for: Flask
#################
###  RESULTS  ###
#################
There are 334 episodes found.
Episode 97, titled Flask, Django style with Flask-Diamond.
Episode 264, titled 10 tips every Flask developer should know.
Episode 226, titled Building Flask APIs for data scientists.
...
Episode 9, titled Docker for the Python Developer.
Episode 3, titled Pyramid Web Framework.
Episode 1, titled EVE - RESTful APIs for humans.
What episode number do you want to view or [Q]uit: 264

A great exercise and lots of opportunity for learning and growth. Storing JSON in a named tuple is easy and a great way to work with data. Becoming comfortable with working with JSON and API's are fundamental skills in modern web development and this challege was a great way to get comfortable with the concepts.

Resources
Talk Python Training 100 Days of Code Course