Commit e64dc8ca by Tyler Hallada Committed by GitHub

Merge pull request #32 from edx/thallada/programs-endpoint

AN-8556 Add programs endpoint to client
parents f06a9606 6f5ec227
...@@ -129,7 +129,7 @@ generated-members= ...@@ -129,7 +129,7 @@ generated-members=
[BASIC] [BASIC]
# Required attributes for module, separated by a comma # Required attributes for module, separated by a comma
required-attributes= # required-attributes=
# List of builtins function names that should not be used, separated by a comma # List of builtins function names that should not be used, separated by a comma
bad-functions=map,filter,apply,input bad-functions=map,filter,apply,input
...@@ -274,7 +274,7 @@ max-public-methods=20 ...@@ -274,7 +274,7 @@ max-public-methods=20
# List of interface methods to ignore, separated by a comma. This is used for # List of interface methods to ignore, separated by a comma. This is used for
# instance to not check methods defines in Zope's Interface base class. # instance to not check methods defines in Zope's Interface base class.
ignore-iface-methods=isImplementedBy,deferred,extends,names,namesAndDescriptions,queryDescriptionFor,getBases,getDescriptionFor,getDoc,getName,getTaggedValue,getTaggedValueTags,isEqualOrExtendedBy,setTaggedValue,isImplementedByInstancesOf,adaptWith,is_implemented_by # ignore-iface-methods=isImplementedBy,deferred,extends,names,namesAndDescriptions,queryDescriptionFor,getBases,getDescriptionFor,getDoc,getName,getTaggedValue,getTaggedValueTags,isEqualOrExtendedBy,setTaggedValue,isImplementedByInstancesOf,adaptWith,is_implemented_by
# List of method names used to declare (i.e. assign) instance attributes. # List of method names used to declare (i.e. assign) instance attributes.
defining-attr-methods=__init__,__new__,setUp defining-attr-methods=__init__,__new__,setUp
......
...@@ -8,6 +8,7 @@ from analyticsclient.course import Course ...@@ -8,6 +8,7 @@ from analyticsclient.course import Course
from analyticsclient.course_summaries import CourseSummaries from analyticsclient.course_summaries import CourseSummaries
from analyticsclient.exceptions import ClientError, InvalidRequestError, NotFoundError, TimeoutError from analyticsclient.exceptions import ClientError, InvalidRequestError, NotFoundError, TimeoutError
from analyticsclient.module import Module from analyticsclient.module import Module
from analyticsclient.programs import Programs
from analyticsclient.status import Status from analyticsclient.status import Status
...@@ -42,6 +43,7 @@ class Client(object): ...@@ -42,6 +43,7 @@ class Client(object):
self.status = Status(self) self.status = Status(self)
self.course_summaries = lambda: CourseSummaries(self) self.course_summaries = lambda: CourseSummaries(self)
self.programs = lambda: Programs(self)
self.courses = lambda course_id: Course(self, course_id) self.courses = lambda course_id: Course(self, course_id)
self.modules = lambda course_id, module_id: Module(self, course_id, module_id) self.modules = lambda course_id, module_id: Module(self, course_id, module_id)
......
...@@ -17,16 +17,19 @@ class CourseSummaries(object): ...@@ -17,16 +17,19 @@ class CourseSummaries(object):
""" """
self.client = client self.client = client
def course_summaries(self, course_ids=None, fields=None, data_format=DF.JSON): def course_summaries(self, course_ids=None, fields=None, exclude=None, programs=None, data_format=DF.JSON):
""" """
Get list of summaries. Get list of summaries.
Arguments: Arguments:
course_ids: Array of course IDs as strings to return. Default is to return all. course_ids: Array of course IDs as strings to return. Default is to return all.
fields: Array of fields to return. Default is to return all. fields: Array of fields to return. Default is to return all.
exclude: Array of fields to exclude from response. Default is to not exclude any fields.
programs: If included in the query parameters, will include the programs array in the response.
""" """
query_params = {} query_params = {}
for query_arg, data in zip(['course_ids', 'fields'], [course_ids, fields]): for query_arg, data in zip(['course_ids', 'fields', 'exclude', 'programs'],
[course_ids, fields, exclude, programs]):
if data: if data:
query_params[query_arg] = ','.join(data) query_params[query_arg] = ','.join(data)
......
import urllib
import analyticsclient.constants.data_format as DF
class Programs(object):
"""Programs client."""
def __init__(self, client):
"""
Initialize the Programs client.
Arguments:
client (analyticsclient.client.Client): The client to use to access remote resources.
"""
self.client = client
def programs(self, program_ids=None, fields=None, exclude=None, data_format=DF.JSON):
"""
Get list of programs metadata.
Arguments:
program_ids: Array of program IDs as strings to return. Default is to return all.
fields: Array of fields to return. Default is to return all.
exclude: Array of fields to exclude from response. Default is to not exclude any fields.
"""
query_params = {}
for query_arg, data in zip(['program_ids', 'fields', 'exclude'],
[program_ids, fields, exclude]):
if data:
query_params[query_arg] = ','.join(data)
path = 'programs/'
querystring = urllib.urlencode(query_params)
if querystring:
path += '?{0}'.format(querystring)
return self.client.get(path, data_format=data_format)
from unittest import TestCase from unittest import TestCase
import ddt
import httpretty
from analyticsclient.client import Client from analyticsclient.client import Client
...@@ -22,3 +25,82 @@ class ClientTestCase(TestCase): ...@@ -22,3 +25,82 @@ class ClientTestCase(TestCase):
Complete API URL and path Complete API URL and path
""" """
return "{0}/{1}".format(self.client.base_url, path) return "{0}/{1}".format(self.client.base_url, path)
@ddt.ddt
class APIListTestCase(object):
"""Base class for API list view tests."""
# Override in the subclass:
endpoint = 'list'
id_field = 'id'
def setUp(self):
"""Set up the test case."""
super(APIListTestCase, self).setUp()
self.base_uri = self.get_api_url('{}/'.format(self.endpoint))
self.client_class = getattr(self.client, self.endpoint)()
httpretty.enable()
def verify_last_querystring_equal(self, expected_query):
"""Convenience method for asserting the last request was made with the expected query parameters."""
self.assertDictEqual(httpretty.last_request().querystring, expected_query)
def expected_query(self, **kwargs):
"""Pack the query arguments into expected format for http pretty."""
query = {}
for field, data in kwargs.items():
if data is not None:
query[field] = [','.join(data)]
return query
@httpretty.activate
def kwarg_test(self, **kwargs):
"""Construct URL with given query parameters and check if it is what we expect."""
httpretty.reset()
uri_template = '{uri}?'
for key in kwargs:
uri_template += '%s={%s}' % (key, key)
uri = uri_template.format(uri=self.base_uri, **kwargs)
httpretty.register_uri(httpretty.GET, uri, body='{}')
getattr(self.client_class, self.endpoint)(**kwargs)
self.verify_last_querystring_equal(self.expected_query(**kwargs))
def test_all_items_url(self):
"""Endpoint can be called without parameters."""
httpretty.register_uri(httpretty.GET, self.base_uri, body='{}')
getattr(self.client_class, self.endpoint)()
@ddt.data(
['edx/demo/course'],
['edx/demo/course', 'another/demo/course']
)
def test_courses_ids(self, ids):
"""Endpoint can be called with IDs."""
self.kwarg_test(**{self.id_field: ids})
@ddt.data(
['course_id'],
['course_id', 'enrollment_modes']
)
def test_fields(self, fields):
"""Endpoint can be called with fields."""
self.kwarg_test(fields=fields)
@ddt.data(
['course_id'],
['course_id', 'enrollment_modes']
)
def test_exclude(self, exclude):
"""Endpoint can be called with exclude."""
self.kwarg_test(exclude=exclude)
@ddt.data(
(['edx/demo/course'], ['course_id'], ['enrollment_modes']),
(['edx/demo/course', 'another/demo/course'], ['course_id', 'enrollment_modes'],
['created', 'pacing_type'])
)
@ddt.unpack
def test_all_parameters(self, ids, fields, exclude):
"""Endpoint can be called with all parameters."""
self.kwarg_test(**{self.id_field: ids, 'fields': fields, 'exclude': exclude})
# pylint: disable=arguments-differ
import ddt import ddt
import httpretty
from analyticsclient.tests import ClientTestCase, APIListTestCase
from analyticsclient.tests import ClientTestCase
@ddt.ddt @ddt.ddt
class CourseSummariesTests(ClientTestCase): class CourseSummariesTests(APIListTestCase, ClientTestCase):
def setUp(self):
super(CourseSummariesTests, self).setUp()
self.base_uri = self.get_api_url('course_summaries/')
self.course_summaries_client = self.client.course_summaries()
httpretty.enable()
def verify_last_querystring_equal(self, expected_query):
"""
Convenience method for asserting the last request was made with the
expected query parameters.
"""
self.assertDictEqual(httpretty.last_request().querystring, expected_query)
def expected_query(self, course_ids=None, fields=None):
"""Packs the query arguments into expected format for http pretty."""
query = {}
for field, data in zip(['course_ids', 'fields'], [course_ids, fields]):
if data is not None:
query[field] = [','.join(data)]
return query
@httpretty.activate endpoint = 'course_summaries'
def test_all_summaries_url(self): id_field = 'course_ids'
"""Course summaries can be called without parameters."""
httpretty.register_uri(httpretty.GET, self.base_uri, body='{}')
self.course_summaries_client.course_summaries()
@httpretty.activate
@ddt.data(
['edx/demo/course'],
['edx/demo/course', 'another/demo/course']
)
def test_courses_ids(self, course_ids):
"""Course summaries can be called with course IDs"""
uri_template = '{uri}?course_ids={ids}'
uri = uri_template.format(uri=self.base_uri, ids=course_ids)
httpretty.register_uri(httpretty.GET, uri, body='{}')
self.course_summaries_client.course_summaries(course_ids=course_ids)
self.verify_last_querystring_equal(self.expected_query(course_ids=course_ids))
@httpretty.activate
@ddt.data( @ddt.data(
['course_id'], ['123'],
['course_id', 'enrollment_modes'] ['123', '456']
) )
def test_fields(self, fields): def test_programs(self, programs):
"""Course summaries can be called with fields.""" """Course summaries can be called with programs."""
uri_template = '{uri}?fields={fields}' self.kwarg_test(programs=programs)
uri = uri_template.format(uri=self.base_uri, fields=fields[0])
httpretty.register_uri(httpretty.GET, uri, body='{}')
self.course_summaries_client.course_summaries(fields=fields)
self.verify_last_querystring_equal(self.expected_query(fields=fields))
@httpretty.activate
@ddt.data( @ddt.data(
(['edx/demo/course'], ['course_id']), (['edx/demo/course'], ['course_id'], ['enrollment_modes'], ['123']),
(['edx/demo/course', 'another/demo/course'], ['course_id', 'enrollment_modes']) (['edx/demo/course', 'another/demo/course'], ['course_id', 'enrollment_modes'],
['created', 'pacing_type'], ['123', '456'])
) )
@ddt.unpack @ddt.unpack
def test_all_parameters(self, course_ids, fields): def test_all_parameters(self, course_ids, fields, exclude, programs):
"""Course summaries can be called with both fields and course IDs.""" """Course summaries can be called with all parameters including programs."""
httpretty.reset() self.kwarg_test(course_ids=course_ids, fields=fields, exclude=exclude, programs=programs)
uri_template = '{uri}?course_ids={ids}fields={fields}'
uri = uri_template.format(uri=self.base_uri, ids=course_ids, fields=fields)
httpretty.register_uri(httpretty.GET, uri, body='{}')
self.course_summaries_client.course_summaries(course_ids=course_ids, fields=fields)
self.verify_last_querystring_equal(self.expected_query(course_ids=course_ids, fields=fields))
import ddt
from analyticsclient.tests import APIListTestCase, ClientTestCase
@ddt.ddt
class ProgramsTests(APIListTestCase, ClientTestCase):
endpoint = 'programs'
id_field = 'program_ids'
...@@ -2,7 +2,7 @@ from distutils.core import setup ...@@ -2,7 +2,7 @@ from distutils.core import setup
setup( setup(
name='edx-analytics-data-api-client', name='edx-analytics-data-api-client',
version='0.10.0', version='0.11.0',
packages=['analyticsclient', 'analyticsclient.constants'], packages=['analyticsclient', 'analyticsclient.constants'],
url='https://github.com/edx/edx-analytics-data-api-client', url='https://github.com/edx/edx-analytics-data-api-client',
description='Client used to access edX analytics data warehouse', description='Client used to access edX analytics data warehouse',
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment