Skip to content
This repository has been archived by the owner on Dec 9, 2023. It is now read-only.

Commit

Permalink
Initial implementation of the API client added
Browse files Browse the repository at this point in the history
  • Loading branch information
jessebraham committed Jun 22, 2018
1 parent 5c28be0 commit 793ebb2
Showing 1 changed file with 211 additions and 0 deletions.
211 changes: 211 additions & 0 deletions comicvine_search/client.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,211 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
'''
ComicVine API Information & Documentation:
https://comicvine.gamespot.com/api/
https://comicvine.gamespot.com/api/documentation
'''

import requests
import requests_cache

from .exceptions import (
ComicVineApiError, ComicVineUnauthorizedError, ComicVineForbiddenError
)


class ComicVineClient(object):
'''
Interacts with the ``search`` resource of the ComicVine API. Requires an
account on https://comicvine.gamespot.com/ in order to obtain an API key.
'''

# All API requests made by this client will be made to this URL.
API_URL = 'https://www.comicvine.com/api/search/'

# A valid User-Agent header must be set in order for our API requests to
# be accepted, otherwise our request will be rejected with a
# **403 - Forbidden** error.
HEADERS = {'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64; rv:7.0) '
'Gecko/20130825 Firefox/36.0'}

# A set of valid resource types to return in results.
RESOURCE_TYPES = {
'character', 'issue', 'location', 'object', 'person', 'publisher',
'story_arc', 'team', 'volume',
}

def __init__(self, api_key, expire_after=300):
'''
Store the API key in a class variable, and install the requests cache,
configuring it using the ``expire_after`` parameter.
:param api_key: Your personal ComicVine API key.
:type api_key: str
:param expire_after: The number of seconds to retain an entry in cache.
:type expire_after: int or None
'''

self.api_key = api_key
self.install_requests_cache(expire_after)

def install_requests_cache(self, expire_after):
'''
Monkey patch Requests to use requests_cache.CachedSession rather than
requests.Session. Responses will have the `from_cache` attribute set
to True if the value being returned is a cached value.
Responses will be held in cache for the number of seconds assigned to
the ``expire_after`` class variable.
:param expire_after: The number of seconds to retain an entry in cache.
:type expire_after: int
'''

requests_cache.install_cache(
__name__,
backend='memory',
expire_after=expire_after
)

def search(self, query, offset=0, limit=10, resources=None,
use_cache=True):
'''
Perform a search against the API, using the provided query term. If
required, a list of resource types to filter search results to can
be included. Return the JSON contained in the response.
:param query: The search query with which to make the request.
:type query: str
:param offset: The index of the first record returned.
:type offset: int or None
:param limit: How many records to return **(max 10)**
:type limit: int or None
:param resources: A list of resources to include in the search results.
:type resources: list or None
:param use_cache: Toggle the use of requests_cache.
:type use_cache: bool
:return: The JSON contained in the HTTP response following the search.
:rtype: dict
'''

params = self.request_params(query, offset, limit, resources)
response = self.query_api(params, use_cache=use_cache)

return response.json()

def request_params(self, query, offset, limit, resources):
'''
Construct a dict containing the required key-value pairs of parameters
required in order to make the API request.
The documentation for the ``search`` resource can be found at
https://comicvine.gamespot.com/api/documentation#toc-0-30.
Regarding 'limit', as per the documentation:
The number of results to display per page. This value defaults to
10 and can not exceed this number.
:param query: The search query with which to make the request.
:type query: str
:param offset: The index of the first record returned.
:type offset: int
:param limit: How many records to return **(max 10)**
:type limit: int
:param resources: A list of resources to include in the search results.
:type resources: list or None
:return: A dictionary of request parameters.
:rtype: dict
'''

return {'api_key': self.api_key,
'format': 'json',
'limit': min(10, limit),
'offset': max(0, offset),
'query': query,
'resources': self.validate_resources(resources)}

def validate_resources(self, resources):
'''
Provided a list of resources, first convert it to a set and perform an
intersection with the set of valid resource types, ``RESOURCE_TYPES``.
Return a comma-separted string of the remaining valid resources, or
None if the set is empty.
:param resources: A list of resources to include in the search results.
:type resources: list or None
:return: A comma-separated string of valid resources.
:rtype: str or None
'''

if not resources:
return None

valid_resources = self.RESOURCE_TYPES & set(resources)
return ','.join(valid_resources) if valid_resources else None

def query_api(self, params, use_cache):
'''
Query the ComicVine API's ``search`` resource, providing the required
headers and parameters with the request. Optionally allow the caller
of the function to specify *not* to use the request cache.
:param params: Parameters to include with the request.
:type params: dict
:param use_cache: Toggle the use of requests_cache.
:type use_cache: bool
:return: The requests.Response object returned by the HTTP request.
:rtype: requests.Response
'''

# Since we're performing the identical action regardless of whether
# or not the request cache is to be used, store the procedure in a
# local function to avoid repetition.
def __httpget():
response = requests.get(
self.API_URL, headers=self.HEADERS, params=params)

if not response.ok:
self.handle_http_error(response)

return response

# To disable the use of the request cache, we simply must make our
# HTTP requests from within the `requests_cache.disabled` context.
if not use_cache:
with requests_cache.disabled():
return __httpget()

return __httpget()

def handle_http_error(self, response):
'''
Given a response to an HTTP request, represented by a
``requests.Response`` object, if the response's status code is
anything other than **200**, we will treat it as an error.
Using the response's status code, determine which type of exception to
raise. Construct an exception message from the response's status code
and reason properties before raising the exception.
:param response: The requests.Response object returned by the HTTP
request.
:type response: requests.Response
:raises ComicVineUnauthorizedException: if no API key provided.
:raises ComicVineForbiddenException: if no User-Agent header provided.
:raises ComicVineApiException: if an unidentified error occurs.
'''

exception = {
401: ComicVineUnauthorizedError,
403: ComicVineForbiddenError
}.get(response.status_code, ComicVineApiError)
message = f'{response.status_code} {response.reason}'

raise exception(message)

0 comments on commit 793ebb2

Please sign in to comment.