From 33f0e1000a628f7fdbe7138acf311a9a0e1384d1 Mon Sep 17 00:00:00 2001 From: Mark Breedlove Date: Thu, 7 Nov 2019 17:13:07 -0500 Subject: [PATCH 1/2] Add Virtualenv dir to .gitignore --- .gitignore | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 54156b7..cd4d7f2 100644 --- a/.gitignore +++ b/.gitignore @@ -60,4 +60,6 @@ docs/_build/ # PyBuilder target/ #PyCharm -.idea \ No newline at end of file +.idea +# Virtualenv +venv/ From da10ede5ab34a2d4f1bf713c47725be2f4507c87 Mon Sep 17 00:00:00 2001 From: Mark Breedlove Date: Fri, 8 Nov 2019 11:53:18 -0500 Subject: [PATCH 2/2] Upgrade to Python 3.7 * Change Python version from 2.7 to 3.7 * Fix Python syntax where the specification changed from 2 to 3. * Change string-related code where Unicode behavior changed from Python 2 to 3. * Remove deprecated test-related configuration in setup.py. See setuptools docs at: https://setuptools.readthedocs.io/en/latest/setuptools.html#test-build-package-and-run-a-unittest-suite * Update dependency versions and pin them in requirements files. Explain how developers should generate requirements files that pin requirements based on more general definitions in setup.py. * Update year in copyright notices. * Add development notes regarding upgrades and running tests. * Add back installation of coveralls for Travis (Fix removal of pip command to install coveralls in: https://github.com/mitodl/PyLmod/commit/db6a10addf82e1e4cf936cadd713bb14ad0ede02) --- .python-version | 1 + .travis.yml | 3 +- Development.rst | 24 ++ LICENSE | 2 +- README.rst | 4 + doc_requirements.txt | 55 +++- docs/conf.py | 4 +- docs/development.rst | 5 + docs/index.rst | 1 + pylmod/base.py | 6 +- pylmod/gradebook.py | 464 ++++++++++++++++---------------- pylmod/membership.py | 24 +- pylmod/tests/test_base.py | 3 +- pylmod/tests/test_gradebook.py | 13 +- pylmod/tests/test_membership.py | 10 +- requirements.txt | 6 + setup.py | 24 +- test_requirements.txt | 49 +++- tox.ini | 6 +- 19 files changed, 418 insertions(+), 286 deletions(-) create mode 100644 .python-version create mode 100644 Development.rst create mode 100644 docs/development.rst diff --git a/.python-version b/.python-version new file mode 100644 index 0000000..aaf18d2 --- /dev/null +++ b/.python-version @@ -0,0 +1 @@ +3.7.5 diff --git a/.travis.yml b/.travis.yml index 2fbf085..d25cf6e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,8 +1,9 @@ language: python python: - - 2.7 + - 3.7 install: - pip install tox + - pip install coveralls script: - tox after_success: diff --git a/Development.rst b/Development.rst new file mode 100644 index 0000000..a1c21e5 --- /dev/null +++ b/Development.rst @@ -0,0 +1,24 @@ + +Upgrading or changing package dependencies +========================================== + +``setup.py`` specifies acceptable constraints for package versions, which +usually follow Semantic Versioning conventions. The ``requirements`` files +specify exact versions for consistency between development, CI, and deployments. + +1. Delete out and recreate your virtualenv environment, or uninstall packages. (E.g. ``pip uninstall -r requirements.txt``, etc.) +2. Edit ``setup.py``, specifying acceptable version constraints. The ``dev`` group of ``extras_require`` is for development and unit testing. +3. ``pip install -e .`` +4. ``pip freeze >> requirements.txt`` and edit ``requirements.txt`` to preserve the ``--index-url`` and ``-e .`` lines. +5. ``pip install -e .[dev] >> test_requirements.txt`` and edit the file as above. +6. ``pip install -e .[doc] >> doc_requirements.txt`` and edit the file as above. + +Running Tests +============= + +Use ``tox``: + +.. code-block:: sh + + pip install -r test_requirements.txt + tox diff --git a/LICENSE b/LICENSE index 3028fff..e2d8efa 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2014, Massachusetts Institute of Technology +Copyright (c) 2019, Massachusetts Institute of Technology All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. diff --git a/README.rst b/README.rst index 442dce1..11b6a8a 100644 --- a/README.rst +++ b/README.rst @@ -65,6 +65,10 @@ grades. PyLmod was created to support MIT's use of OpenedX for residential courses, but the library is open source to enable easier access for Python application developers at MIT. +Development +=========== +See the `Development Notes `_ + Licensing ========= PyLmod is licensed under the BSD license, version January 9, 2008. See diff --git a/doc_requirements.txt b/doc_requirements.txt index e717ac5..a0e5e23 100644 --- a/doc_requirements.txt +++ b/doc_requirements.txt @@ -1,6 +1,53 @@ --index-url https://pypi.python.org/simple/ -# Handy for development to install both + +alabaster==0.7.12 +apipkg==1.5 +atomicwrites==1.3.0 +attrs==19.3.0 +Babel==2.7.0 +certifi==2019.9.11 +chardet==3.0.4 +coverage==4.5.4 +ddt==1.2.1 +docutils==0.15.2 +execnet==1.7.1 +httpretty==0.9.7 +idna==2.8 +imagesize==1.1.0 +importlib-metadata==0.23 +Jinja2==2.10.3 +MarkupSafe==1.1.1 +mock==3.0.5 +more-itertools==7.2.0 +packaging==19.2 +pep8==1.7.1 +pluggy==0.13.0 +pockets==0.9.1 +py==1.8.0 +pyflakes==2.1.1 +Pygments==2.4.2 +pyparsing==2.4.4 +pytest==5.2.2 +pytest-cache==1.0 +pytest-cov==2.8.1 +pytest-flakes==4.0.0 +pytest-pep8==1.0.6 +pytz==2019.3 +requests==2.22.0 +semantic-version==2.8.2 +six==1.13.0 +snowballstemmer==2.0.0 +Sphinx==2.2.1 +sphinx-bootstrap-theme==0.7.1 +sphinxcontrib-applehelp==1.0.1 +sphinxcontrib-devhelp==1.0.1 +sphinxcontrib-htmlhelp==1.0.2 +sphinxcontrib-jsmath==1.0.1 +sphinxcontrib-napoleon==0.7 +sphinxcontrib-qthelp==1.0.2 +sphinxcontrib-serializinghtml==1.1.3 +urllib3==1.25.6 +wcwidth==0.1.7 +zipp==0.6.0 + -e . -sphinx>=1.2.3 -sphinx_bootstrap_theme>=0.4.5 -sphinxcontrib-napoleon diff --git a/docs/conf.py b/docs/conf.py index bd263e7..0f2d94a 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -37,7 +37,7 @@ # General information about the project. project = u'PyLmod' -copyright = u'2015, MIT Office of Digital Learning' +copyright = u'2019, MIT Office of Digital Learning' # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the @@ -121,7 +121,7 @@ epub_title = u'PyLmod' epub_author = u'MIT Office of Digital Learning' epub_publisher = u'MIT Office of Digital Learning' -epub_copyright = u'2015, MIT Office of Digital Learning' +epub_copyright = u'2019, MIT Office of Digital Learning' # A list of files that should not be packed into the epub file. epub_exclude_files = ['search.html'] diff --git a/docs/development.rst b/docs/development.rst new file mode 100644 index 0000000..c0df305 --- /dev/null +++ b/docs/development.rst @@ -0,0 +1,5 @@ +Development Notes +----------------- + +.. include:: ../Development.rst + diff --git a/docs/index.rst b/docs/index.rst index 2f6844c..d7f2d4f 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -12,6 +12,7 @@ Table of Contents :maxdepth: 3 api + development Indices and Search ================== diff --git a/pylmod/base.py b/pylmod/base.py index 044bc2c..8357e28 100644 --- a/pylmod/base.py +++ b/pylmod/base.py @@ -72,7 +72,7 @@ def _data_to_json(data): Args: data (str): data to convert to json """ - if type(data) not in [str, unicode]: + if type(data) != str: data = json.dumps(data) return data @@ -107,7 +107,7 @@ def rest_action(self, func, url, **kwargs): """ try: response = func(url, timeout=self.TIMEOUT, **kwargs) - except requests.RequestException, err: + except requests.RequestException as err: log.exception( "[PyLmod] Error - connection error in " "rest_action, err=%s", err @@ -115,7 +115,7 @@ def rest_action(self, func, url, **kwargs): raise err try: return response.json() - except ValueError, err: + except ValueError as err: log.exception('Unable to decode %s', response.content) raise err diff --git a/pylmod/gradebook.py b/pylmod/gradebook.py index eaaa6c1..396c38d 100644 --- a/pylmod/gradebook.py +++ b/pylmod/gradebook.py @@ -39,9 +39,9 @@ class GradeBook(Base): .. code-block:: python { - u'status':1, - u'message':u'', - u'data':{...} + 'status': 1, + 'message': '', + 'data': {...} } API reference at @@ -138,39 +138,39 @@ def get_options(self, gradebook_id): .. code-block:: python { - u'data': + 'data': { - u'accessLevel': u'class', - u'archived': False, - u'calc_on_approved_only': False, - u'configured': None, - u'courseName': u'', - u'courseNumber': u'mitxdemosite', - u'deriveOverallGrades': False, - u'gradebookEwsEnabled': False, - u'gradebookId': 1293808, - u'gradebookName': u'Gradebook for mitxdemosite', - u'gradebookReadOnly': False, - u'gradebookVisibleToAdvisors': False, - u'graders_change_approved': False, - u'hideExcuseButtonInUI': False, - u'homeworkBetaEnabled': False, - u'membershipQualifier': u'/project/mitxdemosite', - u'membershipSource': u'stellar', - u'student_sees_actual_grades': True, - u'student_sees_category_info': True, - u'student_sees_comments': True, - u'student_sees_cumulative_score': True, - u'student_sees_histograms': True, - u'student_sees_submissions': False, - u'ta_approves': False, - u'ta_change_approved': False, - u'ta_configures': False, - u'ta_edits': False, - u'use_grade_weighting': False, - u'usingAttendance': False, - u'versionCompatible': 4, - u'versionCompatibleString': u'General Availability' + 'accessLevel': 'class', + 'archived': False, + 'calc_on_approved_only': False, + 'configured': None, + 'courseName': '', + 'courseNumber': 'mitxdemosite', + 'deriveOverallGrades': False, + 'gradebookEwsEnabled': False, + 'gradebookId': 1293808, + 'gradebookName': 'Gradebook for mitxdemosite', + 'gradebookReadOnly': False, + 'gradebookVisibleToAdvisors': False, + 'graders_change_approved': False, + 'hideExcuseButtonInUI': False, + 'homeworkBetaEnabled': False, + 'membershipQualifier': '/project/mitxdemosite', + 'membershipSource': 'stellar', + 'student_sees_actual_grades': True, + 'student_sees_category_info': True, + 'student_sees_comments': True, + 'student_sees_cumulative_score': True, + 'student_sees_histograms': True, + 'student_sees_submissions': False, + 'ta_approves': False, + 'ta_change_approved': False, + 'ta_configures': False, + 'ta_edits': False, + 'use_grade_weighting': False, + 'usingAttendance': False, + 'versionCompatible': 4, + 'versionCompatibleString': 'General Availability' }, } @@ -221,40 +221,40 @@ def get_assignments( [ { - u'assignmentId': 2431240, - u'categoryId': 1293820, - u'description': u'', - u'dueDate': 1372392000000, - u'dueDateString': u'06-28-2013', - u'gradebookId': 1293808, - u'graderVisible': True, - u'gradingSchemeId': 2431243, - u'gradingSchemeType': u'NUMERIC', - u'isComposite': False, - u'isHomework': False, - u'maxPointsTotal': 10.0, - u'name': u'Homework 1', - u'shortName': u'HW1', - u'userDeleted': False, - u'weight': 1.0 + 'assignmentId': 2431240, + 'categoryId': 1293820, + 'description': '', + 'dueDate': 1372392000000, + 'dueDateString': '06-28-2013', + 'gradebookId': 1293808, + 'graderVisible': True, + 'gradingSchemeId': 2431243, + 'gradingSchemeType': 'NUMERIC', + 'isComposite': False, + 'isHomework': False, + 'maxPointsTotal': 10.0, + 'name': 'Homework 1', + 'shortName': 'HW1', + 'userDeleted': False, + 'weight': 1.0 }, { - u'assignmentId': 16708850, - u'categoryId': 1293820, - u'description': u'', - u'dueDate': 1383541200000, - u'dueDateString': u'11-04-2013', - u'gradebookId': 1293808, - u'graderVisible': False, - u'gradingSchemeId': 16708851, - u'gradingSchemeType': u'NUMERIC', - u'isComposite': False, - u'isHomework': False, - u'maxPointsTotal': 100.0, - u'name': u'midterm1', - u'shortName': u'mid1', - u'userDeleted': False, - u'weight': 1.0 + 'assignmentId': 16708850, + 'categoryId': 1293820, + 'description': '', + 'dueDate': 1383541200000, + 'dueDateString': '11-04-2013', + 'gradebookId': 1293808, + 'graderVisible': False, + 'gradingSchemeId': 16708851, + 'gradingSchemeType': 'NUMERIC', + 'isComposite': False, + 'isHomework': False, + 'maxPointsTotal': 100.0, + 'name': 'midterm1', + 'shortName': 'mid1', + 'userDeleted': False, + 'weight': 1.0 }, ] @@ -305,22 +305,22 @@ def get_assignment_by_name(self, assignment_name, assignments=None): ( 16708850, { - u'assignmentId': 16708850, - u'categoryId': 1293820, - u'description': u'', - u'dueDate': 1383541200000, - u'dueDateString': u'11-04-2013', - u'gradebookId': 1293808, - u'graderVisible': False, - u'gradingSchemeId': 16708851, - u'gradingSchemeType': u'NUMERIC', - u'isComposite': False, - u'isHomework': False, - u'maxPointsTotal': 100.0, - u'name': u'midterm1', - u'shortName': u'mid1', - u'userDeleted': False, - u'weight': 1.0 + 'assignmentId': 16708850, + 'categoryId': 1293820, + 'description': '', + 'dueDate': 1383541200000, + 'dueDateString': '11-04-2013', + 'gradebookId': 1293808, + 'graderVisible': False, + 'gradingSchemeId': 16708851, + 'gradingSchemeType': 'NUMERIC', + 'isComposite': False, + 'isHomework': False, + 'maxPointsTotal': 100.0, + 'name': 'midterm1', + 'shortName': 'mid1', + 'userDeleted': False, + 'weight': 1.0 } ) @@ -366,9 +366,9 @@ def create_assignment( # pylint: disable=too-many-arguments .. code-block:: python { - u'graderVisible': True, - u'totalAverage': None - u'categoryId': 1007964, + 'graderVisible': True, + 'totalAverage': None + 'categoryId': 1007964, } Raises: @@ -382,29 +382,29 @@ def create_assignment( # pylint: disable=too-many-arguments .. code-block:: python { - u'data': + 'data': { - u'assignmentId': 18490492, - u'categoryId': 1293820, - u'description': u'', - u'dueDate': 1312171200000, - u'dueDateString': u'08-01-2011', - u'gradebookId': 1293808, - u'graderVisible': False, - u'gradingSchemeId': 18490493, - u'gradingSchemeType': u'NUMERIC', - u'isComposite': False, - u'isHomework': False, - u'maxPointsTotal': 100.0, - u'name': u'new NUMERIC SIMPLE ASSIGNMENT', - u'numStudentGradesToBeApproved': 0, - u'numStudentsToBeGraded': 614, - u'shortName': u'SAnew', - u'userDeleted': False, - u'weight': 1.0 + 'assignmentId': 18490492, + 'categoryId': 1293820, + 'description': '', + 'dueDate': 1312171200000, + 'dueDateString': '08-01-2011', + 'gradebookId': 1293808, + 'graderVisible': False, + 'gradingSchemeId': 18490493, + 'gradingSchemeType': 'NUMERIC', + 'isComposite': False, + 'isHomework': False, + 'maxPointsTotal': 100.0, + 'name': 'new NUMERIC SIMPLE ASSIGNMENT', + 'numStudentGradesToBeApproved': 0, + 'numStudentsToBeGraded': 614, + 'shortName': 'SAnew', + 'userDeleted': False, + 'weight': 1.0 }, - u'message': u'assignment is created successfully', - u'status': 1 + 'message': 'assignment is created successfully', + 'status': 1 } """ @@ -442,8 +442,8 @@ def delete_assignment(self, assignment_id): .. code-block:: python { - u'message': u'assignment is deleted successfully', - u'status': 1 + 'message': 'assignment is deleted successfully', + 'status': 1 } """ @@ -481,13 +481,13 @@ def set_grade( .. code-block:: python { - u'letterGradeValue':None, - u'booleanGradeValue':None, - u'specialGradeValue':None, - u'mode':2, - u'isGradeApproved':False, - u'comment':None, - u'returnAffectedValues': True, + 'letterGradeValue':None, + 'booleanGradeValue':None, + 'specialGradeValue':None, + 'mode':2, + 'isGradeApproved':False, + 'comment':None, + 'returnAffectedValues': True, } Raises: @@ -500,8 +500,8 @@ def set_grade( .. code-block:: python { - u'message': u'grade saved successfully', - u'status': 1 + 'message': 'grade saved successfully', + 'status': 1 } """ @@ -551,40 +551,40 @@ def multi_grade(self, grade_array, gradebook_id=''): [ { - u'comment': None, - u'booleanGradeValue': None, - u'studentId': 1135, - u'assignmentId': 4522, - u'specialGradeValue': None, - u'returnAffectedValues': True, - u'letterGradeValue': None, - u'mode': 2, - u'numericGradeValue': 50, - u'isGradeApproved': False + 'comment': None, + 'booleanGradeValue': None, + 'studentId': 1135, + 'assignmentId': 4522, + 'specialGradeValue': None, + 'returnAffectedValues': True, + 'letterGradeValue': None, + 'mode': 2, + 'numericGradeValue': 50, + 'isGradeApproved': False }, { - u'comment': None, - u'booleanGradeValue': None, - u'studentId': 1135, - u'assignmentId': 4522, - u'specialGradeValue': u'x', - u'returnAffectedValues': True, - u'letterGradeValue': None, - u'mode': 2, - u'numericGradeValue': None, - u'isGradeApproved': False + 'comment': None, + 'booleanGradeValue': None, + 'studentId': 1135, + 'assignmentId': 4522, + 'specialGradeValue': 'x', + 'returnAffectedValues': True, + 'letterGradeValue': None, + 'mode': 2, + 'numericGradeValue': None, + 'isGradeApproved': False }, { - u'comment': None, - u'booleanGradeValue': None, - u'studentId': 1135, - u'assignmentId': None, - u'specialGradeValue': None, - u'returnAffectedValues': True, - u'letterGradeValue': u'A', - u'mode': 1, - u'numericGradeValue': None, - u'isGradeApproved': False + 'comment': None, + 'booleanGradeValue': None, + 'studentId': 1135, + 'assignmentId': None, + 'specialGradeValue': None, + 'returnAffectedValues': True, + 'letterGradeValue': 'A', + 'mode': 1, + 'numericGradeValue': None, + 'isGradeApproved': False } ] @@ -635,32 +635,32 @@ def get_sections(self, gradebook_id='', simple=False): .. code-block:: python { - u'recitation': + 'recitation': [ { - u'editable': False, - u'groupId': 1293925, - u'groupingScheme': u'Recitation', - u'members': None, - u'name': u'Unassigned', - u'shortName': u'DefaultGroupNoCollisionPlease1234', - u'staffs': None + 'editable': False, + 'groupId': 1293925, + 'groupingScheme': 'Recitation', + 'members': None, + 'name': 'Unassigned', + 'shortName': 'DefaultGroupNoCollisionPlease1234', + 'staffs': None }, { - u'editable': True, - u'groupId': 1327565, - u'groupingScheme': u'Recitation', - u'members': None, - u'name': u'r01', - u'shortName': u'r01', - u'staffs': None}, - {u'editable': True, - u'groupId': 1327555, - u'groupingScheme': u'Recitation', - u'members': None, - u'name': u'r02', - u'shortName': u'r02', - u'staffs': None + 'editable': True, + 'groupId': 1327565, + 'groupingScheme': 'Recitation', + 'members': None, + 'name': 'r01', + 'shortName': 'r01', + 'staffs': None}, + {'editable': True, + 'groupId': 1327555, + 'groupingScheme': 'Recitation', + 'members': None, + 'name': 'r02', + 'shortName': 'r02', + 'staffs': None } ] } @@ -703,13 +703,13 @@ def get_section_by_name(self, section_name): ( 1327565, { - u'editable': True, - u'groupId': 1327565, - u'groupingScheme': u'Recitation', - u'members': None, - u'name': u'r01', - u'shortName': u'r01', - u'staffs': None + 'editable': True, + 'groupId': 1327565, + 'groupingScheme': 'Recitation', + 'members': None, + 'name': 'r01', + 'shortName': 'r01', + 'staffs': None } ) @@ -759,21 +759,21 @@ def get_students( .. code-block:: python [{ - u'accountEmail': u'stellar.test2@gmail.com', - u'displayName': u'Molly Parker', - u'photoUrl': None, - u'middleName': None, - u'section': u'Unassigned', - u'sectionId': 1293925, - u'editable': False, - u'overallGradeInformation': None, - u'studentId': 1145, - u'studentAssignmentInfo': None, - u'sortableName': u'Parker, Molly', - u'surname': u'Parker', - u'givenName': u'Molly', - u'nickName': u'Molly', - u'email': u'stellar.test2@gmail.com' + 'accountEmail': 'stellar.test2@gmail.com', + 'displayName': 'Molly Parker', + 'photoUrl': None, + 'middleName': None, + 'section': 'Unassigned', + 'sectionId': 1293925, + 'editable': False, + 'overallGradeInformation': None, + 'studentId': 1145, + 'studentAssignmentInfo': None, + 'sortableName': 'Parker, Molly', + 'surname': 'Parker', + 'givenName': 'Molly', + 'nickName': 'Molly', + 'email': 'stellar.test2@gmail.com' },] """ @@ -1159,53 +1159,53 @@ def get_staff(self, gradebook_id, simple=False): .. code-block:: python { - u'data': { - u'COURSE_ADMIN': [ + 'data': { + 'COURSE_ADMIN': [ { - u'accountEmail': u'benfranklin@mit.edu', - u'displayName': u'Benjamin Franklin', - u'editable': False, - u'email': u'benfranklin@mit.edu', - u'givenName': u'Benjamin', - u'middleName': None, - u'mitId': u'921344431', - u'nickName': u'Benjamin', - u'personId': 10710616, - u'sortableName': u'Franklin, Benjamin', - u'surname': u'Franklin', - u'year': None + 'accountEmail': 'benfranklin@mit.ed', + 'displayName': 'Benjamin Franklin', + 'editable': False, + 'email': 'benfranklin@mit.ed', + 'givenName': 'Benjamin', + 'middleName': None, + 'mitId': '921344431', + 'nickName': 'Benjamin', + 'personId': 10710616, + 'sortableName': 'Franklin, Benjamin', + 'surname': 'Franklin', + 'year': None }, ], - u'COURSE_PROF': [ + 'COURSE_PROF': [ { - u'accountEmail': u'dduck@mit.edu', - u'displayName': u'Donald Duck', - u'editable': False, - u'email': u'dduck@mit.edu', - u'givenName': u'Donald', - u'middleName': None, - u'mitId': u'916144889', - u'nickName': u'Donald', - u'personId': 8117160, - u'sortableName': u'Duck, Donald', - u'surname': u'Duck', - u'year': None + 'accountEmail': 'dduck@mit.ed', + 'displayName': 'Donald Duck', + 'editable': False, + 'email': 'dduck@mit.ed', + 'givenName': 'Donald', + 'middleName': None, + 'mitId': '916144889', + 'nickName': 'Donald', + 'personId': 8117160, + 'sortableName': 'Duck, Donald', + 'surname': 'Duck', + 'year': None }, ], - u'COURSE_TA': [ + 'COURSE_TA': [ { - u'accountEmail': u'hduck@mit.edu', - u'displayName': u'Huey Duck', - u'editable': False, - u'email': u'hduck@mit.edu', - u'givenName': u'Huey', - u'middleName': None, - u'mitId': u'920445024', - u'nickName': u'Huey', - u'personId': 1299059, - u'sortableName': u'Duck, Huey', - u'surname': u'Duck', - u'year': None + 'accountEmail': 'hduck@mit.ed', + 'displayName': 'Huey Duck', + 'editable': False, + 'email': 'hduck@mit.ed', + 'givenName': 'Huey', + 'middleName': None, + 'mitId': '920445024', + 'nickName': 'Huey', + 'personId': 1299059, + 'sortableName': 'Duck, Huey', + 'surname': 'Duck', + 'year': None }, ] }, diff --git a/pylmod/membership.py b/pylmod/membership.py index f5a9ff8..15d6a1d 100644 --- a/pylmod/membership.py +++ b/pylmod/membership.py @@ -180,24 +180,24 @@ def get_course_guide_staff(self, course_id=''): [ { - u'displayName': u'Huey Duck', - u'role': u'TA', - u'sortableDisplayName': u'Duck, Huey' + 'displayName': 'Huey Duck', + 'role': 'TA', + 'sortableDisplayName': 'Duck, Huey' }, { - u'displayName': u'Louie Duck', - u'role': u'CourseAdmin', - u'sortableDisplayName': u'Duck, Louie' + 'displayName': 'Louie Duck', + 'role': 'CourseAdmin', + 'sortableDisplayName': 'Duck, Louie' }, { - u'displayName': u'Benjamin Franklin', - u'role': u'CourseAdmin', - u'sortableDisplayName': u'Franklin, Benjamin' + 'displayName': 'Benjamin Franklin', + 'role': 'CourseAdmin', + 'sortableDisplayName': 'Franklin, Benjamin' }, { - u'displayName': u'George Washington', - u'role': u'Instructor', - u'sortableDisplayName': u'Washington, George' + 'displayName': 'George Washington', + 'role': 'Instructor', + 'sortableDisplayName': 'Washington, George' }, ] """ diff --git a/pylmod/tests/test_base.py b/pylmod/tests/test_base.py index 20f8b2b..b4218fb 100644 --- a/pylmod/tests/test_base.py +++ b/pylmod/tests/test_base.py @@ -57,7 +57,6 @@ def test_data_to_json(self): json.dumps(data) ) self.assertEqual('a', Base._data_to_json('a')) - self.assertEqual('a', Base._data_to_json(unicode('a'))) def test_url_format(self): """Verify url format does the right thing""" @@ -101,7 +100,7 @@ def test_rest_action_timeout(self): self._register_uri(timeout=True) test_base = Base(self.CERT, self.URLBASE) rest_function = test_base._session.get - with self.assertRaisesRegexp( + with self.assertRaisesRegex( requests.ConnectionError, 'Max retries exceeded with url' ): diff --git a/pylmod/tests/test_gradebook.py b/pylmod/tests/test_gradebook.py index 631d5fa..c0a0a48 100644 --- a/pylmod/tests/test_gradebook.py +++ b/pylmod/tests/test_gradebook.py @@ -445,7 +445,7 @@ def test_create_assignment(self): last_request = httpretty.last_request() self.assertEqual( - last_request.body, + str(last_request.body, encoding='utf-8'), json.dumps({ 'name': 'Test Assign', 'shortName': 'test-assign', @@ -499,7 +499,7 @@ def test_set_grade(self): self.assertEqual(response_data, response) last_request = httpretty.last_request() self.assertEqual( - last_request.body, + str(last_request.body, encoding='utf-8'), json.dumps(grade) ) @@ -517,7 +517,7 @@ def test_multi_grade(self): self.assertEqual(response_data, response) last_request = httpretty.last_request() self.assertEqual( - last_request.body, + str(last_request.body, encoding='utf-8'), json.dumps(grades) ) @@ -985,10 +985,9 @@ def test_max_points_argument_mismatch(self, key): del kwargs[key] with self.assertRaises(ValueError) as ex: gradebook._spreadsheet2gradebook_multi(**kwargs) - - assert ex.exception[0] == ( - '{} must be set if use_max_points_column is set'.format(key) - ) + assert str(ex) == ( + '{} must be set if use_max_points_column is set'.format(key) + ) @mock.patch.object( GradeBook, diff --git a/pylmod/tests/test_membership.py b/pylmod/tests/test_membership.py index 4e0f901..093ac6f 100644 --- a/pylmod/tests/test_membership.py +++ b/pylmod/tests/test_membership.py @@ -180,7 +180,7 @@ def test_get_course_id(self, mock_log): test_membership.get_course_id(self.CUUID) mock_log.exception.assert_called_with( "KeyError in get_course_id - " - "got {u'response': {u'docs': {u'enabled': True}}}" + "got {'response': {'docs': {'enabled': True}}}" ) # Produce TypeError and assert exception raised @@ -188,7 +188,7 @@ def test_get_course_id(self, mock_log): with self.assertRaises(PyLmodUnexpectedData): test_membership.get_course_id(self.CUUID) mock_log.exception.assert_called_with( - "TypeError in get_course_id - got [u'arnold']" + "TypeError in get_course_id - got ['arnold']" ) # Remove data and assert exception raised @@ -252,7 +252,7 @@ def test_get_group_id_errors(self, mock_log): test_membership.get_group_id(self.CUUID) mock_log.exception.assert_called_with( "Error in get_group response data - " - "got {u'response': {u'docs': []}}" + "got {'response': {'docs': []}}" ) # KeyError @@ -262,7 +262,7 @@ def test_get_group_id_errors(self, mock_log): test_membership.get_group_id(self.CUUID) mock_log.exception.assert_called_with( "Error in get_group response data - " - "got {u'response': {u'foo': []}}" + "got {'response': {'foo': []}}" ) @httpretty.activate @@ -280,7 +280,7 @@ def test_email_has_role_errors(self, mock_log): ) mock_log.exception.assert_called_with( "KeyError in membership data - " - "got {u'response': {u'foo': []}}" + "got {'response': {'foo': []}}" ) @httpretty.activate diff --git a/requirements.txt b/requirements.txt index e06c97b..d1fa62e 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,10 @@ --index-url https://pypi.python.org/simple/ +certifi==2019.9.11 +chardet==3.0.4 +idna==2.8 +requests==2.22.0 +urllib3==1.25.6 + -e . diff --git a/setup.py b/setup.py index 66b52d9..2628f3e 100644 --- a/setup.py +++ b/setup.py @@ -58,15 +58,33 @@ def run_tests(self): description="PyLmod is a Python Implementation of MIT Learning Modules", long_description=README, packages=find_packages(), - install_requires=["requests>=2.5.1", ], + install_requires=[ + 'requests~=2.0' + ], + extras_require={ + 'dev': [ + 'pytest~=5.0', + 'pytest-cov~=2.0', + 'pytest-flakes~=4.0', + 'pytest-pep8~=1.0', + 'pytest-cache~=1.0', + 'httpretty~=0.9.0', + 'semantic_version~=2.0', + 'mock~=3.0', + 'ddt~=1.0' + ], + 'doc': [ + 'sphinx~=2.0', + 'sphinx_bootstrap_theme==0.7.0', + 'sphinxcontrib-napoleon==0.7' + ] + }, classifiers=[ 'Development Status :: 2 - Pre-Alpha', 'Intended Audience :: Developers', 'Intended Audience :: Education', 'Programming Language :: Python', ], - test_suite="pylmod.tests", - tests_require=tests_require, cmdclass={"test": PyTest}, include_package_data=True, zip_safe=True, diff --git a/test_requirements.txt b/test_requirements.txt index 337021e..ec43e16 100644 --- a/test_requirements.txt +++ b/test_requirements.txt @@ -1,10 +1,39 @@ -pytest-cov>=1.8.0 -pytest-pep8>=1.0.6 -pytest-flakes>=0.2 -pytest>=2.6.3 -pyflakes>=0.8.1 -pytest-cache>=1.0 -httpretty>=0.8.3 -semantic_version>=2.3.1 -mock>=1.0.1 -ddt==1.0.1 + +--index-url https://pypi.python.org/simple/ + +apipkg==1.5 +atomicwrites==1.3.0 +attrs==19.3.0 +certifi==2019.9.11 +chardet==3.0.4 +coverage==4.5.4 +ddt==1.2.1 +execnet==1.7.1 +filelock==3.0.12 +httpretty==0.9.7 +idna==2.8 +importlib-metadata==0.23 +mock==3.0.5 +more-itertools==7.2.0 +packaging==19.2 +pep8==1.7.1 +pluggy==0.13.0 +py==1.8.0 +pyflakes==2.1.1 +pyparsing==2.4.4 +pytest==5.2.2 +pytest-cache==1.0 +pytest-cov==2.8.1 +pytest-flakes==4.0.0 +pytest-pep8==1.0.6 +requests==2.22.0 +semantic-version==2.8.2 +six==1.13.0 +toml==0.10.0 +tox==3.14.0 +urllib3==1.25.6 +virtualenv==16.7.7 +wcwidth==0.1.7 +zipp==0.6.0 + +-e . diff --git a/tox.ini b/tox.ini index 1417266..f652ea5 100644 --- a/tox.ini +++ b/tox.ini @@ -1,18 +1,16 @@ [tox] -# No docs or py34 at the moment due to test failures -envlist = py27 +envlist = py37 skip_missing_interpreters = True skipsdist = True [testenv] deps = - -r{toxinidir}/requirements.txt -r{toxinidir}/test_requirements.txt commands = py.test {posargs} [testenv:docs] -basepython = python2.7 +basepython = python3.7 changedir = docs deps=