diff --git a/.coveragerc b/.coveragerc new file mode 100644 index 0000000..c744fcb --- /dev/null +++ b/.coveragerc @@ -0,0 +1,4 @@ +[run] +branch = True +source = uw_myplan/ +include = uw_myplan/* diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..539ce8c --- /dev/null +++ b/.travis.yml @@ -0,0 +1,32 @@ +sudo: false +language: python +python: +- '2.7' +- '3.4' +- '3.5' +- '3.6' +before_script: +- pip install -e . +- pip install pep8 +- pip install nose2 +- pip install coverage +- pip install commonconf +- pip install python-coveralls +script: +- pep8 uw_myplan/ --exclude=uw_myplan/tests +- coverage run --source=uw_myplan uw_myplan/test.py -v +after_script: +- coveralls +before_deploy: +- find uw_myplan/ -name \*.pyc -exec rm {} \; +- echo $TRAVIS_TAG > uw_myplan/VERSION +deploy: + provider: pypi + server: https://pypi.python.org/pypi + user: uw-it-aca + skip_cleanup: true + password: + secure: e9N4DnziSO5ZybSW38btNFw28dEMeazXVEJ+F/zsKI+S+TD3RT79YVpswL9ZKG8QUy3x1x7Ttm/zMcW9Noz7TLXi6CUij+M+s8Q1jPxecFSwuX+UqbYkzb+4s42naD6jQ18rFaUzS//+4nezIz5SmUVHQd/u3C9n+hhGvTl8nU88xqUMgOngm1nUEkssGANHgW7WFHaOgJomC590i0aPOtfFQBBNHgEaTLSacQ7PW2YQ4UB6EjcnvVQ5FO958nMS5rVgC0BsazvODavLv0RENoaJQXp5oaHnmLjmd74NsBq5fEiVI1uOsPHqWaATnOAiubiyKZGijKe8NGiRyrr861Ow6kpzrISbqzZGwsXadWN9sodmcX53nKlRe7L5sxMyLyV2ZUSyRzS9dz3s7He45PjkaRD8MDd8IuiAeWg7oBoyhy186zK/XNL/aG0Y+UPtpWaK0EGt78NbfWiHK21aS6fAX8Hc8MVodKLxkDe7Nq5HYEr46txBEy1QG4g/Ki4HgBhBeJW6k3b04YJFs+7aq6VgncgWlFCUUMVl3DpTfFYQurmLvGn+vUO5piCzSR/tkAkPUMhD60x9dPLvPf54w0YkG9MXWSyWYIMPzEbV1i/lKR0EvbGT/QQT14bt7Hiy6ewD+SHsO3OgiJ0uNUWKL6LgFAHgJir2IzW5et1pcWc= + on: + tags: true +condition: $TRAVIS_PYTHON_VERSION = "2.7" diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 0000000..f301e4e --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1,2 @@ +include uw_myplan/VERSION +recursive-include uw_myplan * diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..d8b273d --- /dev/null +++ b/setup.py @@ -0,0 +1,38 @@ +import os +from setuptools import setup + +README = """ +See the README on `GitHub +`_. +""" + +# The VERSION file is created by travis-ci, based on the tag name +version_path = 'uw_myplan/VERSION' +VERSION = open(os.path.join(os.path.dirname(__file__), version_path)).read() +VERSION = VERSION.replace("\n", "") + +# allow setup.py to be run from any path +os.chdir(os.path.normpath(os.path.join(os.path.abspath(__file__), os.pardir))) + +setup( + name='UW-RestClients-MyPlan', + version=VERSION, + packages=['uw_myplan'], + author="UW-IT AXDD", + author_email="aca-it@uw.edu", + include_package_data=True, + install_requires=['UW-RestClients-Core>=0.8.9,<1.0', + ], + license='Apache License, Version 2.0', + description=('A library for connecting to the UW MyPlan API'), + long_description=README, + url="https://github.com/uw-it-aca/uw-restclients-myplan", + classifiers=[ + 'Intended Audience :: Developers', + 'License :: OSI Approved :: Apache Software License', + 'Operating System :: OS Independent', + 'Programming Language :: Python', + 'Programming Language :: Python :: 2.7', + 'Programming Language :: Python :: 3.4', + ], +) diff --git a/travis-ci/test.conf b/travis-ci/test.conf new file mode 100644 index 0000000..4967f30 --- /dev/null +++ b/travis-ci/test.conf @@ -0,0 +1 @@ +[MyPlan] diff --git a/uw_myplan/VERSION b/uw_myplan/VERSION new file mode 100644 index 0000000..49d5957 --- /dev/null +++ b/uw_myplan/VERSION @@ -0,0 +1 @@ +0.1 diff --git a/uw_myplan/__init__.py b/uw_myplan/__init__.py new file mode 100644 index 0000000..4191552 --- /dev/null +++ b/uw_myplan/__init__.py @@ -0,0 +1,57 @@ +""" +This is the interface for interacting with MyPlan. +https://wiki.cac.washington.edu/display/MyPlan/Plan+Resource+v1 +""" + +from uw_myplan.dao import MyPlan_DAO +from restclients_core.exceptions import DataFailureException +from uw_myplan.models import MyPlan, MyPlanTerm, MyPlanCourse, \ + MyPlanCourseSection +import json + + +def get_plan(regid, year, quarter, terms=4): + dao = MyPlan_DAO() + url = get_plan_url(regid, year, quarter, terms) + + response = dao.getURL(url, {"Accept": "application/json"}) + if response.status != 200: + raise DataFailureException(url, response.status, response.data) + + data = json.loads(response.data) + + plan = MyPlan() + for term_data in data: + term = MyPlanTerm() + term.year = term_data["Term"]["Year"] + term.quarter = term_data["Term"]["Quarter"] + + term.course_search_href = term_data["CourseSearchHref"] + term.degree_audit_href = term_data["DegreeAuditHref"] + term.myplan_href = term_data["MyPlanHref"] + term.registration_href = term_data["RegistrationHref"] + term.registered_courses_count = int( + term_data["RegisteredCoursesCount"]) + term.registered_sections_count = int( + term_data["RegisteredSectionsCount"]) + + for course_data in term_data["Courses"]: + course = MyPlanCourse() + course.curriculum_abbr = course_data["CurriculumAbbreviation"] + course.course_number = course_data["CourseNumber"] + + is_available = course_data["RegistrationAvailable"] + course.registrations_available = is_available + + for section_data in course_data["Sections"]: + section = MyPlanCourseSection() + section.section_id = section_data["SectionId"] + course.sections.append(section) + + term.courses.append(course) + plan.terms.append(term) + return plan + + +def get_plan_url(regid, year, quarter, terms): + return "/student/api/plan/v1/%s,%s,%s,%s" % (year, quarter, terms, regid) diff --git a/uw_myplan/dao.py b/uw_myplan/dao.py new file mode 100644 index 0000000..5e0182b --- /dev/null +++ b/uw_myplan/dao.py @@ -0,0 +1,12 @@ +import logging +import os +from os.path import abspath, dirname +from restclients_core.dao import DAO + + +class MyPlan_DAO(DAO): + def service_name(self): + return 'myplan' + + def service_mock_paths(self): + return [abspath(os.path.join(dirname(__file__), "resources"))] diff --git a/uw_myplan/models.py b/uw_myplan/models.py new file mode 100644 index 0000000..2d221ab --- /dev/null +++ b/uw_myplan/models.py @@ -0,0 +1,96 @@ +from restclients_core import models + + +class MyPlan(models.Model): + def __init__(self): + self.terms = [] + + def json_data(self): + data = { + "terms": [] + } + for term in self.terms: + data["terms"].append(term.json_data()) + + return data + + +class MyPlanTerm(models.Model): + SPRING = 'spring' + SUMMER = 'summer' + AUTUMN = 'autumn' + WINTER = 'winter' + + QUARTERNAME_CHOICES = ( + (SPRING, 'Spring'), + (SUMMER, 'Summer'), + (AUTUMN, 'Autumn'), + (WINTER, 'Winter'), + ) + + course_search_href = models.CharField(max_length=512) + degree_audit_href = models.CharField(max_length=512) + myplan_href = models.CharField(max_length=512) + registration_href = models.CharField(max_length=512) + registered_courses_count = models.SmallIntegerField( + max_length=3, default=0) + registered_sections_count = models.SmallIntegerField( + max_length=3, default=0) + + def __init__(self): + self.courses = [] + + quarter = models.CharField(max_length=6, + choices=QUARTERNAME_CHOICES) + year = models.PositiveSmallIntegerField() + + def json_data(self): + data = { + "year": self.year, + "quarter": self.quarter, + "course_search_href": self.course_search_href, + "degree_audit_href": self.degree_audit_href, + "myplan_href": self.myplan_href, + "registration_href": self.registration_href, + "registered_courses_count": self.registered_courses_count, + "registered_sections_count": self.registered_sections_count, + "courses": [], + } + + for course in self.courses: + data["courses"].append(course.json_data()) + + return data + + +class MyPlanCourse(models.Model): + def __init__(self): + self.sections = [] + + curriculum_abbr = models.CharField(max_length=6, + db_index=True) + course_number = models.PositiveSmallIntegerField(db_index=True) + registrations_available = models.BooleanField() + + def json_data(self): + data = { + 'curriculum_abbr': self.curriculum_abbr, + 'course_number': self.course_number, + 'registrations_available': self.registrations_available, + 'sections': [], + } + + for section in self.sections: + data["sections"].append(section.json_data()) + + return data + + +class MyPlanCourseSection(models.Model): + section_id = models.CharField(max_length=2, + db_index=True) + + def json_data(self): + return { + "section_id": self.section_id + } diff --git a/uw_myplan/resources/myplan/file/student/api/plan/v1/2013_spring_4_9136CCB8F66711D5BE060004AC494FFE b/uw_myplan/resources/myplan/file/student/api/plan/v1/2013_spring_4_9136CCB8F66711D5BE060004AC494FFE new file mode 100644 index 0000000..2c4c839 --- /dev/null +++ b/uw_myplan/resources/myplan/file/student/api/plan/v1/2013_spring_4_9136CCB8F66711D5BE060004AC494FFE @@ -0,0 +1,81 @@ +[ + { + "Term":{"Quarter":"Spring","Year":2013}, + "RegistrationHref":"https://uwkseval.cac.washington.edu/student/myplan/mplogin/netid?rd=/student/myplan/registration/20132", + "MyPlanHref":"https://uwkseval.cac.washington.edu/student/myplan/mplogin/netid?rd=/student/myplan/plan/20132", + "AuditHref":"https://uwkseval.cac.washington.edu/student/myplan/mplogin/netid?rd=/student/myplan/audit", + "PlanAuditHref":"https://uwkseval.cac.washington.edu/student/myplan/mplogin/netid?rd=/student/myplan/audit/plan", + "DegreeAuditHref":"https://uwkseval.cac.washington.edu/student/myplan/mplogin/netid?rd=/student/myplan/audit/degree", + "PlanValidationHref":"https://uwkseval.cac.washington.edu/student/myplan/mplogin/netid?rd=/student/myplan/planValidation/20132", + "CourseSearchHref":"https://uwkseval.cac.washington.edu/student/myplan/mplogin/netid?rd=/student/myplan/course", + "RegisteredCoursesCount":0, + "RegisteredSectionsCount":0, + "Courses":[ + { + "CurriculumAbbreviation":"CSE", + "CourseNumber":"101", + "RegistrationAvailable":true, + "Sections":[ + {"SectionId":"A"}, + {"SectionId":"AA"}, + {"SectionId":"AB"} + ]}, + { + "CurriculumAbbreviation":"CSE", + "CourseNumber":"102", + "RegistrationAvailable":false, + "Sections":[ + {"SectionId":"A"}, + {"SectionId":"B"}, + {"SectionId":"C"}]}]}, + { + "Term":{"Quarter":"Summer","Year":2013}, + "RegistrationHref":"https://uwkseval.cac.washington.edu/student/myplan/mplogin/netid?rd=/student/myplan/registration/20133", + "MyPlanHref":"https://uwkseval.cac.washington.edu/student/myplan/mplogin/netid?rd=/student/myplan/plan/20133", + "AuditHref":"https://uwkseval.cac.washington.edu/student/myplan/mplogin/netid?rd=/student/myplan/audit", + "PlanAuditHref":"https://uwkseval.cac.washington.edu/student/myplan/mplogin/netid?rd=/student/myplan/audit/plan", + "DegreeAuditHref":"https://uwkseval.cac.washington.edu/student/myplan/mplogin/netid?rd=/student/myplan/audit/degree", + "PlanValidationHref":"https://uwkseval.cac.washington.edu/student/myplan/mplogin/netid?rd=/student/myplan/planValidation/20133", + "CourseSearchHref":"https://uwkseval.cac.washington.edu/student/myplan/mplogin/netid?rd=/student/myplan/course", + "RegisteredCoursesCount":0, + "RegisteredSectionsCount":0, + "Courses":[ + { + "CurriculumAbbreviation":"CSE", + "CourseNumber":"110", + "RegistrationAvailable":true, + "Sections":[ + {"SectionId":"A"}] + } + ] + }, + { + "Term":{"Quarter":"Autumn","Year":2013}, + "RegistrationHref":"https://uwkseval.cac.washington.edu/student/myplan/mplogin/netid?rd=/student/myplan/registration/20134", + "MyPlanHref":"https://uwkseval.cac.washington.edu/student/myplan/mplogin/netid?rd=/student/myplan/plan/20134", + "AuditHref":"https://uwkseval.cac.washington.edu/student/myplan/mplogin/netid?rd=/student/myplan/audit", + "PlanAuditHref":"https://uwkseval.cac.washington.edu/student/myplan/mplogin/netid?rd=/student/myplan/audit/plan", + "DegreeAuditHref":"https://uwkseval.cac.washington.edu/student/myplan/mplogin/netid?rd=/student/myplan/audit/degree", + "PlanValidationHref":"https://uwkseval.cac.washington.edu/student/myplan/mplogin/netid?rd=/student/myplan/planValidation/20134", + "CourseSearchHref":"https://uwkseval.cac.washington.edu/student/myplan/mplogin/netid?rd=/student/myplan/course", + "RegisteredCoursesCount":0, + "RegisteredSectionsCount":0, + "Courses":[ + ] + }, + { + "Term":{"Quarter":"Winter","Year":2014}, + "RegistrationHref":"https://uwkseval.cac.washington.edu/student/myplan/mplogin/netid?rd=/student/myplan/registration/20141", + "MyPlanHref":"https://uwkseval.cac.washington.edu/student/myplan/mplogin/netid?rd=/student/myplan/plan/20141", + "AuditHref":"https://uwkseval.cac.washington.edu/student/myplan/mplogin/netid?rd=/student/myplan/audit", + "PlanAuditHref":"https://uwkseval.cac.washington.edu/student/myplan/mplogin/netid?rd=/student/myplan/audit/plan", + "DegreeAuditHref":"https://uwkseval.cac.washington.edu/student/myplan/mplogin/netid?rd=/student/myplan/audit/degree", + "PlanValidationHref":"https://uwkseval.cac.washington.edu/student/myplan/mplogin/netid?rd=/student/myplan/planValidation/20141", + "CourseSearchHref":"https://uwkseval.cac.washington.edu/student/myplan/mplogin/netid?rd=/student/myplan/course", + "RegisteredCoursesCount":0, + "RegisteredSectionsCount":0, + "Courses":[ + ] + } + +] diff --git a/uw_myplan/test.py b/uw_myplan/test.py new file mode 100644 index 0000000..bd54c6d --- /dev/null +++ b/uw_myplan/test.py @@ -0,0 +1,12 @@ +# This is just a test runner for coverage +from commonconf.backends import use_configparser_backend +from os.path import abspath, dirname +import os + +if __name__ == '__main__': + path = abspath(os.path.join(dirname(__file__), + "..", "travis-ci", "test.conf")) + use_configparser_backend(path, 'MyPlan') + + from nose2 import discover + discover() diff --git a/uw_myplan/tests/__init__.py b/uw_myplan/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/uw_myplan/tests/test_myplan.py b/uw_myplan/tests/test_myplan.py new file mode 100644 index 0000000..c18ab7c --- /dev/null +++ b/uw_myplan/tests/test_myplan.py @@ -0,0 +1,55 @@ +from unittest import TestCase +from uw_myplan import get_plan + + +class MyPlanTestData(TestCase): + def test_javerage(self): + plan = get_plan(regid="9136CCB8F66711D5BE060004AC494FFE", year=2013, quarter="spring", terms=4) + self.assertEquals(len(plan.terms), 4) + + self.assertEquals(plan.terms[0].year, 2013) + self.assertEquals(plan.terms[1].year, 2013) + self.assertEquals(plan.terms[2].year, 2013) + self.assertEquals(plan.terms[3].year, 2014) + + self.assertEquals(plan.terms[0].quarter, 'Spring') + self.assertEquals(plan.terms[1].quarter, 'Summer') + self.assertEquals(plan.terms[2].quarter, 'Autumn') + self.assertEquals(plan.terms[3].quarter, 'Winter') + + self.assertEquals(len(plan.terms[0].courses), 2) + self.assertEquals(len(plan.terms[1].courses), 1) + self.assertEquals(len(plan.terms[2].courses), 0) + self.assertEquals(len(plan.terms[3].courses), 0) + term_data = plan.terms[0] + self.assertEquals(term_data.course_search_href, + "https://uwkseval.cac.washington.edu/student/myplan/mplogin/netid?rd=/student/myplan/course") + self.assertEquals(term_data.degree_audit_href, + "https://uwkseval.cac.washington.edu/student/myplan/mplogin/netid?rd=/student/myplan/audit/degree") + self.assertEquals(term_data.myplan_href, + "https://uwkseval.cac.washington.edu/student/myplan/mplogin/netid?rd=/student/myplan/plan/20132") + self.assertEquals(term_data.registration_href, + "https://uwkseval.cac.washington.edu/student/myplan/mplogin/netid?rd=/student/myplan/registration/20132") + self.assertEquals(term_data.registered_courses_count, 0) + self.assertEquals(term_data.registered_sections_count, 0) + self.assertEquals(term_data.courses[0].registrations_available, True) + self.assertEquals(term_data.courses[0].curriculum_abbr, 'CSE') + self.assertEquals(term_data.courses[0].course_number, '101') + self.assertEquals(len(term_data.courses[0].sections), 3) + self.assertEquals(term_data.courses[0].sections[0].section_id, 'A') + self.assertEquals(term_data.courses[0].sections[1].section_id, 'AA') + self.assertEquals(term_data.courses[0].sections[2].section_id, 'AB') + + def test_json(self): + plan = get_plan(regid="9136CCB8F66711D5BE060004AC494FFE", + year=2013, quarter="spring", + terms=4) + json_data = plan.json_data() + term_data = json_data["terms"][0] + self.assertEquals(term_data["courses"][0]["sections"][1]["section_id"], "AA") + self.assertEquals(term_data["registered_courses_count"], 0) + self.assertEquals(term_data["registration_href"], + "https://uwkseval.cac.washington.edu/student/myplan/mplogin/netid?rd=/student/myplan/registration/20132") + self.assertEquals(term_data["course_search_href"], + "https://uwkseval.cac.washington.edu/student/myplan/mplogin/netid?rd=/student/myplan/course") + self.assertEquals(term_data["quarter"], "Spring")