from os import path
from .base import WrapperBase
BASE_URL = "https://esb.isc-seo.upenn.edu/8091/open_data/"
ENDPOINTS = {
'CATALOG': BASE_URL + 'course_info',
'SEARCH': BASE_URL + 'course_section_search',
'SEARCH_PARAMS': BASE_URL + 'course_section_search_parameters'
}
[docs]class Registrar(WrapperBase):
"""The client for the Registrar. Used to make requests to the API.
:param bearer: The user code for the API
:param token: The password code for the API
Usage::
>>> from penn import Registrar
>>> r = Registrar('MY_USERNAME_TOKEN', 'MY_PASSWORD_TOKEN')
"""
def __init__(self, bearer, token):
WrapperBase.__init__(self, bearer, token)
self.val_info = None
def _iter_response(self, url, params=None):
"""Return an enumerable that iterates through a multi-page API request"""
if params is None:
params = {}
params['page_number'] = 1
# Last page lists itself as next page
while True:
response = self._request(url, params)
for item in response['result_data']:
yield item
# Last page lists itself as next page
if response['service_meta']['next_page_number'] == params['page_number']:
break
params['page_number'] += 1
[docs] def search(self, params, validate=False):
"""Return a generator of section objects for the given search params.
:param params: Dictionary of course search parameters.
:param validate: Optional. Set to true to enable request validation.
>>> cis100s = r.search({'course_id': 'cis', 'course_level_at_or_below': '200'})
"""
if self.val_info is None:
self.val_info = self.search_params()
if validate:
errors = self.validate(self.val_info, params)
if not validate or len(errors) == 0:
return self._iter_response(ENDPOINTS['SEARCH'], params)
else:
return {'Errors': errors}
[docs] def course(self, dept, course_number):
"""Return an object of semester-independent course info. All arguments
should be strings.
>>> cis120 = r.course('cis', '120')
"""
response = self._request(path.join(ENDPOINTS['CATALOG'], dept, course_number))
return response['result_data'][0]
[docs] def department(self, dept):
"""Return an iterator of all course-info objects in a department, in no
particular order.
"""
return self._iter_response(path.join(ENDPOINTS['CATALOG'], dept))
[docs] def section(self, dept, course_number, sect_number):
"""Return a single section object for the given section. All arguments
should be strings. Throws a `ValueError` if the section is not found.
>>> lgst101_bfs = r.course('lgst', '101', '301')
"""
section_id = dept + course_number + sect_number
sections = self.search({'course_id': section_id})
try:
return next(sections)
except StopIteration:
raise ValueError('Section %s not found' % section_id)
[docs] def search_params(self):
"""Return a dictionary of possible search parameters and their possible
values and descriptions.
"""
return self._request(ENDPOINTS['SEARCH_PARAMS'])['result_data'][0]