I built a minimal working model of a consumer of a RESTful API in Python 3.6. It only consumes one end point in this example. It is using the API from a video game (Destiny 2) to collect data about all the members of a clan (a clan is basically a team that likes to play together).
Some features
- The core interface consists of a wrapper for
requests
(calledrequest
), and a class that summarizes the response (ResponseSummary
). - URL-generating functions produce the URL for each end point.
- Helper functions process the
ResponseSummary
. - Secret stuff (the API key) is stored as environment variables.
Questions
This is my first project interacting with an API, so any comments, or suggestions are welcome, as I may be making a stupid blunder here. Places of concern include, but are not limited to:
- The
ResponseSummary
class seems a bit smelly to me. It includes all the data I want, but I'm not sure about defaulting all fields toNone
, the error handling, and the__repr__
may be too verbose. - Is my division of labor into core API code, URL-generators, and helper functions reasonable and Pythonic (is there a better way to do it that makes it more readable)?
- Naming: are my variable names OK: in particular is the function
request
a bad name because it is too close to 'requests'? - Is the way I handled the
headers
argument forrequests
OK? I turned it into a constant (it is used in every API call).
The code
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import os
import requests
BASE_URL = 'https://bungie.net/Platform/GroupV2/'
"""
CORE CODE
For making requests, and summarizing the response
"""
def request(url, request_headers):
response = requests.get(url, headers = request_headers)
return ResponseSummary(response)
class ResponseSummary:
"""Important information about the response."""
def __init__(self, response):
self.status = response.status_code
self.url = response.url
self.data = None
self.message = None
self.error_code = None
self.error_status = None
self.exception = None
if self.status == 200:
result = response.json()
self.message = result['Message']
self.error_code = result['ErrorCode']
self.error_status = result['ErrorStatus']
if self.error_code == 1:
try:
self.data = result['Response']
except Exception as ex:
print("ResponseSummary: 200 status and error_code 1, but there was no result['Response']")
print("Exception: {0}.\nType: {1}".format(ex, ex.__class__.__name__))
self.exception = ex.__class__.__name__
else:
print('No data returned for url: {0}.\n {1} was the error code with status 200.'. \
format(self.url, self.error_code))
else:
print('Request failed for url: {0}.\n.Status: {0}'.format(self.url, self.status))
def __repr__(self):
"""What will be displayed/printed for the class instance."""
disp_header = "<" + self.__class__.__name__ + " instance>\n"
disp_data = ".data: " + str(self.data) + "\n\n"
disp_url = ".url: " + str(self.url) + "\n"
disp_message = ".message: " + str(self.message) + "\n"
disp_status = ".status: " + str(self.status) + "\n"
disp_error_code = ".error_code: " + str(self.error_code) + "\n"
disp_error_status = ".error_status: " + str(self.error_status) + "\n"
disp_exception = ".exception: " + str(self.exception)
return disp_header + disp_data + disp_url + disp_message + \
disp_status + disp_error_code + disp_error_status + disp_exception
"""
URL GENERATORS
"""
def get_members_of_group_url(group_id):
"""
Pull all members of a clan.
Documentation: https://bungie-net.github.io/multi/operation_get_GroupV2-GetMembersOfGroup.html
"""
return BASE_URL + group_id + '/Members/?currentPage=1'
"""
HELPER FUNCTIONS
"""
def generate_clan_list(member_data):
"""
Using GetMembersOfGroup end point, create list of member info for clan members.
Each elt is a dict with username. id, join date. Filters out people not on psn.
"""
member_data = member_data['results']
clan_members_data = []
for member in member_data:
clan_member = {}
clan_member['membership_type'] = member['destinyUserInfo']['membershipType']
if clan_member['membership_type'] == 2:
clan_member['name'] = member['destinyUserInfo']['displayName']
clan_member['id'] = member['destinyUserInfo']['membershipId']
clan_member['date_joined'] = member['joinDate']
clan_members_data.append(clan_member)
return clan_members_data
def print_clan_roster(clan_members_data):
"""Print name, membership type, id, and date joined."""
if clan_members_data:
name_list = [clanfolk['name'] for clanfolk in clan_members_data]
col_width = max(len(word) for word in name_list)
for clan_member in clan_members_data:
memb_name = clan_member['name']
length_name = len(memb_name)
num_spaces = col_width - length_name
memb_name_extended = memb_name + " "*num_spaces
print("{0}\tMembership type: {1}\t Id: {2}\tJoined: {3}".format(memb_name_extended, \
clan_member['membership_type'], clan_member['id'], clan_member['date_joined']))
else:
print("print_clan_roster: roster is empty")
def get_environment_variable(var_name):
"""get environmental variable, or return exception"""
try:
return os.environ.get(var_name)
except KeyError:
error_msg = 'KeyError in get_environment_variable: {}.'.format(var_name)
print(error_msg)
if __name__ == "__main__":
"""
Extract and print list of all clan members
"""
#Set constants
D2_KEY = get_environment_variable('D2_KEY')
D2_HEADERS = {"X-API-Key": D2_KEY}
CLAN_ID = '623172'
#Make request to api for clan members, and print list to stdout
get_members_url = get_members_of_group_url(CLAN_ID)
get_members_summary = request(get_members_url, D2_HEADERS)
member_data = get_members_summary.data
clan_members_data = generate_clan_list(member_data)
print_clan_roster(clan_members_data)