Skip to content

Instantly share code, notes, and snippets.

@ryanlovett
Last active April 25, 2018 17:14
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ryanlovett/a6d786eede6d5f99b7d594268b4cdaea to your computer and use it in GitHub Desktop.
Save ryanlovett/a6d786eede6d5f99b7d594268b4cdaea to your computer and use it in GitHub Desktop.
Download and display an SIS course's enrollments
#!/usr/bin/env python
# Example usage:
# $0 -H headers.json -s compsci -c c8
# $0 -H headers.json -s stat -c c100
import argparse
import json
import requests
def get_descriptors(uri, params, headers):
'''Get SIS descriptor IDs.'''
if args.debug:
print(uri)
print(params)
r = requests.get(uri, params=params, headers=headers)
data = r.json()
if 'fieldValues' not in data['apiResponse']['response']:
print(data['apiResponse'])
print(uri)
print(params)
raise KeyError('No fieldValues')
if args.debug:
print(data['apiResponse']['response']['fieldValues'])
return data['apiResponse']['response']['fieldValues']
def extract_lecture_codes(sections):
return list(
map(lambda x: x['code'],
filter(lambda x: ' LEC ' in x['description'] or ' SES ' in x['description'] or ' WBL ' in x['description'], sections)
)
)
def get_enrollments(uri, params, headers):
if args.debug:
print("get_enrollments: {} {}".format(uri, params))
r = requests.get(uri, params=params, headers=headers)
if r.status_code == 404:
if args.debug: print('No more enrollments')
return []
data = r.json()
# Return if there is no response (e.g. 404)
if 'response' not in data['apiResponse']:
if args.debug: print('404 No response')
return[]
# Return if the UID has no enrollments
if 'classSectionEnrollments' not in data['apiResponse']['response']:
if args.debug: print('No enrollments')
return []
enrollments = \
data['apiResponse']['response']['classSectionEnrollments']
params['page-number'] += 1
enrollments += get_enrollments(uri, params, headers)
if args.debug: print('enrollments batch: {}'.format(len(enrollments)))
return enrollments
def enrollment_emails(enrollment):
'''Extract unique student emails from enrollments.'''
emails = {}
for email in enrollment['student'].get('emails', []):
emails[email['type']['code']] = email['emailAddress']
return emails
def email_repr(emails):
if 'CAMP' in emails:
return emails['CAMP']
elif 'OTHR' in emails:
return emails['OTHR']
return ''
def enrollment_id(enrollment):
'''Return campus-uid from and enrollment.'''
for identifier in enrollment['student']['identifiers']:
if identifier['type'] == 'campus-uid':
return identifier['id']
def enrollment_status(enrollment):
'''Return campus-uid from and enrollment.'''
return enrollment['enrollmentStatus']['reason']['code']
def enrollment_name(enrollment):
'''Return campus-uid from and enrollment.'''
for name in enrollment['student']['names']:
if name['type']['description'] == "Preferred":
return name['formattedName']
## MAIN
# there's an api for this too
TERM_FALL_2016 = 2168
TERM_SPRING_2017 = 2172
TERM_SUMMER_2017 = 2176
TERM_FALL_2017 = 2178
TERM_SPRING_2018 = 2182
TERM_SUMMER_2018 = 2185
SUBJECT_AREA = 'STAT'
# Headers file contains requests json. Example:
# { "Accept": "application/json",
# "app_id": "YOUR_SIS_APP_ID",
# "app_key": "YOUR_SIS_APP_KEY" }
HEADERS_FILE = 'fetch-enrollments.json'
parser = argparse.ArgumentParser(description="Print enrolled students' email addresses.")
parser.add_argument('-t', dest='term_id', type=int,
default=TERM_SPRING_2018, help='SIS Term ID (default=%(default)s)')
parser.add_argument('-s', dest='sa_filter', default=SUBJECT_AREA,
help='subject area filter (default=%(default)s)')
parser.add_argument('-c', dest='course', default='',
help='Course number, e.g. c8, 133, 135')
parser.add_argument('-e', dest='enrolled', action='store_false',
help='Return only enrolled students (default=true)')
parser.add_argument('-H', dest='headers_file', default=HEADERS_FILE,
help='JSON file containing requests headers')
parser.add_argument('-o', dest='output', default="email",
help='Output type. ("email", "uid")')
parser.add_argument('-v', dest='verbose', action='store_true', help='Be verbose.')
parser.add_argument('-d', dest='debug', action='store_true', help='Debug.')
args = parser.parse_args()
## main
# api secrets
headers = json.loads(open(args.headers_file).read())
enrollments_uri = "https://apis.berkeley.edu/sis/v2/enrollments"
descriptors_uri = enrollments_uri + '/terms/{}/classes/sections/descriptors'
sections_uri = enrollments_uri + "/terms/{}/classes/sections/{}"
params = {
"subject-area-code": args.sa_filter,
"catalog-number": args.course,
}
if args.enrolled:
params['enrolled-only'] = 'true'
# Retrieve the "section IDs" associated with the course.
# This gets both lecture and sections.
uri = descriptors_uri.format(args.term_id)
sections = get_descriptors(uri, params, headers)
# We only care about the lecture.
lecture_codes = extract_lecture_codes(sections)
# Get the enrollments
params['page-size'] = 100
enrollments = []
for lecture_code in lecture_codes:
params['page-number'] = 1
uri = sections_uri.format(args.term_id, lecture_code)
enrollments += get_enrollments(uri, params, headers)
# Get the emails
if args.output == 'json':
print(json.dumps(enrollments, separators=(',', ': '), indent=4))
else:
for e in enrollments:
if args.output == 'email':
print(email_repr(enrollment_emails(e)))
elif args.output == 'uid':
print(enrollment_id(e))
elif args.output == 'both':
print('{}\t{}\t{}'.format(enrollment_id(e), enrollment_status(e), email_repr(enrollment_emails(e))))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment