edx_api_utils.py 3.6 KB
Newer Older
1 2
"""Helper functions to get data from APIs"""
from __future__ import unicode_literals
3

4 5 6 7
import logging

from django.core.cache import cache

8
from openedx.core.lib.cache_utils import zpickle, zunpickle
9 10 11 12

log = logging.getLogger(__name__)


13 14 15 16
def get_fields(fields, response):
    """Extracts desired fields from the API response"""
    results = {}
    for field in fields:
17
        results[field] = response.get(field)
18
    return results
19 20


21
def get_edx_api_data(api_config, resource, api, resource_id=None, querystring=None, cache_key=None, many=True,
22
                     traverse_pagination=True, fields=None, long_term_cache=False):
23 24 25
    """GET data from an edX REST API.

    DRY utility for handling caching and pagination.
26 27

    Arguments:
28 29 30 31
        api_config (ConfigurationModel): The configuration model governing interaction with the API.
        resource (str): Name of the API resource being requested.

    Keyword Arguments:
32
        api (APIClient): API client to use for requesting data.
33 34 35 36
        resource_id (int or str): Identifies a specific resource to be retrieved.
        querystring (dict): Optional query string parameters.
        cache_key (str): Where to cache retrieved data. The cache will be ignored if this is omitted
            (neither inspected nor updated).
37 38
        many (bool): Whether the resource requested is a collection of objects, or a single object.
            If false, an empty dict will be returned in cases of failure rather than the default empty list.
39
        traverse_pagination (bool): Whether to traverse pagination or return paginated response..
40
        long_term_cache (bool): Whether to use the long term cache ttl or the standard cache ttl
41 42

    Returns:
43 44
        Data returned by the API. When hitting a list endpoint, extracts "results" (list of dict)
        returned by DRF-powered APIs.
45
    """
46
    no_data = [] if many else {}
47 48

    if not api_config.enabled:
49
        log.warning('%s configuration is disabled.', api_config.API_NAME)
50 51
        return no_data

52
    if cache_key:
53
        cache_key = '{}.{}'.format(cache_key, resource_id) if resource_id is not None else cache_key
54
        cache_key += '.zpickled'
55

56
        cached = cache.get(cache_key)
57
        if cached:
58
            return zunpickle(cached)
59 60

    try:
61 62 63 64
        endpoint = getattr(api, resource)
        querystring = querystring if querystring else {}
        response = endpoint(resource_id).get(**querystring)

65
        if resource_id is not None:
66 67 68 69
            if fields:
                results = get_fields(fields, response)
            else:
                results = response
70
        elif traverse_pagination:
71
            results = _traverse_pagination(response, endpoint, querystring, no_data)
72 73
        else:
            results = response
74
    except:  # pylint: disable=bare-except
75
        log.exception('Failed to retrieve data from the %s API.', api_config.API_NAME)
76 77
        return no_data

78
    if cache_key:
79
        zdata = zpickle(results)
80 81 82 83
        cache_ttl = api_config.cache_ttl
        if long_term_cache:
            cache_ttl = api_config.long_term_cache_ttl
        cache.set(cache_key, zdata, cache_ttl)
84 85

    return results
86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104


def _traverse_pagination(response, endpoint, querystring, no_data):
    """Traverse a paginated API response.

    Extracts and concatenates "results" (list of dict) returned by DRF-powered APIs.
    """
    results = response.get('results', no_data)

    page = 1
    next_page = response.get('next')
    while next_page:
        page += 1
        querystring['page'] = page
        response = endpoint.get(**querystring)
        results += response.get('results', no_data)
        next_page = response.get('next')

    return results