Skip to content

Commit

Permalink
Merge pull request #382 from ucfopen/develop
Browse files Browse the repository at this point in the history
Release v0.16.0
  • Loading branch information
Thetwam authored Jun 26, 2020
2 parents 69a236a + 44967b4 commit 42606eb
Show file tree
Hide file tree
Showing 28 changed files with 1,057 additions and 66 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,4 @@
\#*#
.vscode/settings.json
setup.sh
.python-version
41 changes: 41 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,44 @@
# Change Log

## [Unreleased]

## [0.16.0] - 2020-06-26

### New Endpoint Coverage

- Enrollment Terms
- Get a Single Enrollment Term (Thanks, [@lcamacho](https://github.com/lcamacho))
- Files
- Resolve Path for Course (Thanks,[@dsavransky](https://github.com/dsavransky))
- GraphQL (Thanks,[@jonespm](https://github.com/jonespm))
- Late Policy (Thanks, [@kennygperez](https://github.com/kennygperez))
- Quiz Assignment Overrides (Thanks, [@kennygperez](https://github.com/kennygperez))
- Quiz Statistics (Thanks, [@andrew-gardener](https://github.com/andrew-gardener))

### General

- Updated README to use updated parameters for getting a user's courses by enrollment state (Thanks,[@Vishvak365](https://github.com/Vishvak365))

### Deprecation Warnings

- :warning: **_This is the final release with support for Python 2.7_** :warning:
- [Python 2.7 is end-of-life as of January 2020](https://www.python.org/doc/sunset-python-2/)
- Future releases of CanvasAPI will *NOT* support any version of Python 2
- :warning: **_This is the final release with support for Python 3.4_** :warning:
- [Python 3.4 is end-of-life as of March 2019](https://www.python.org/downloads/release/python-3410/)
- Future releases of CanvasAPI will *NOT* support Python 3.4 or below
- This is the final deprecation warning for all methods marked as deprecated in this changelog or in our documentation. They will be removed in the next release.

### Bugfixes

- Fixed an issue where `Quiz.get_submission()` ignored data added from using the `include` kwarg. (Thanks,[@Mike-Nahmias](https://github.com/Mike-Nahmias))
- Fixed the broken `__str__` method on the `ChangeRecord` class (Thanks,[@Mike-Nahmias](https://github.com/Mike-Nahmias))
- Fixed an issue where printing an `AccountReport` would fail due to not having an ID (Thanks,[@Mike-Nahmias](https://github.com/Mike-Nahmias))
- Fixed an issue where `"report_type"` was passed improperly (Thanks,[@brucespang](https://github.com/brucespang))
- Fixed some new `flake8` issues (Thanks,[@dsavransky](https://github.com/dsavransky) and [@jonespm](https://github.com/jonespm))
- Fixed an incorrect docstring for `Course.create_page()` (Thanks,[@dsavransky](https://github.com/dsavransky))
- Fixed an issue where extra whitespace in the user-supplied canvas URL would break `PaginatedList` (Thanks,[@amorqiu](https://github.com/amorqiu))

## [0.15.0] - 2019-11-19

### New Endpoint Coverage
Expand Down Expand Up @@ -403,6 +442,8 @@ Huge thanks to [@liblit](https://github.com/liblit) for lots of issues, suggesti
- Fixed some incorrectly defined parameters
- Fixed an issue where tests would fail due to an improperly configured requires block

[Unreleased]: https://github.com/ucfopen/canvasapi/compare/v0.16.0...develop
[0.16.0]: https://github.com/ucfopen/canvasapi/compare/v0.15.0...v0.16.0
[0.15.0]: https://github.com/ucfopen/canvasapi/compare/v0.14.0...v0.15.0
[0.14.0]: https://github.com/ucfopen/canvasapi/compare/v0.13.0...v0.14.0
[0.13.0]: https://github.com/ucfopen/canvasapi/compare/v0.12.0...v0.13.0
Expand Down
2 changes: 1 addition & 1 deletion canvasapi/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,4 @@

__all__ = ["Canvas"]

__version__ = "0.15.0"
__version__ = "0.16.0"
27 changes: 26 additions & 1 deletion canvasapi/account.py
Original file line number Diff line number Diff line change
Expand Up @@ -970,6 +970,27 @@ def get_enrollment(self, enrollment, **kwargs):
)
return Enrollment(self._requester, response.json())

def get_enrollment_term(self, term, **kwargs):
"""
Retrieve the details for an enrollment term in the account. Includes overrides by default.
:calls: `GET /api/v1/accounts/:account_id/terms/:id \
<https://canvas.instructure.com/doc/api/enrollment_terms.html#method.terms_api.show>`_
:param term: The object or ID of the enrollment term to retrieve.
:type term: :class:`canvasapi.enrollment_term.EnrollmentTerm` or int
:rtype: :class:`canvasapi.enrollment_term.EnrollmentTerm`
"""
from canvasapi.enrollment_term import EnrollmentTerm

term_id = obj_or_id(term, "term", (EnrollmentTerm,))

response = self._requester.request(
"GET", "accounts/{}/terms/{}".format(self.id, term_id)
)
return EnrollmentTerm(self._requester, response.json())

def get_enrollment_terms(self, **kwargs):
"""
List enrollment terms for a context.
Expand Down Expand Up @@ -1925,7 +1946,11 @@ def update_global_notification(self, account_notification, **kwargs):
@python_2_unicode_compatible
class AccountReport(CanvasObject):
def __str__(self):
return "{} ({})".format(self.report, self.id)
try:
return "{} ({})".format(self.report, self.id)
except AttributeError:
# Print params if not a report instance
return "{} ({})".format(self.report, self.parameters)

def delete_report(self, **kwargs):
"""
Expand Down
4 changes: 2 additions & 2 deletions canvasapi/blueprint.py
Original file line number Diff line number Diff line change
Expand Up @@ -236,8 +236,8 @@ def get_import_details(self, **kwargs):

@python_2_unicode_compatible
class ChangeRecord(CanvasObject):
def __str__(self): # pragma: no cover
return "{} {}".format(self.id, self.template_id)
def __str__(self):
return "{} {}".format(self.asset_id, self.asset_name)


@python_2_unicode_compatible
Expand Down
37 changes: 31 additions & 6 deletions canvasapi/canvas.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import warnings

from canvasapi.account import Account
from canvasapi.comm_message import CommMessage
from canvasapi.course import Course
from canvasapi.course_epub_export import CourseEpubExport
from canvasapi.current_user import CurrentUser
Expand All @@ -14,7 +15,6 @@
from canvasapi.requester import Requester
from canvasapi.section import Section
from canvasapi.user import User
from canvasapi.comm_message import CommMessage
from canvasapi.util import combine_kwargs, get_institution_url, obj_or_id


Expand Down Expand Up @@ -59,12 +59,10 @@ def __init__(self, base_url, access_token):
UserWarning,
)

# Ensure that the user-supplied access token contains no leading or
# trailing spaces that may cause issues when communicating with
# the API.
# Ensure that the user-supplied access token and base_url contain no leading or
# trailing spaces that might cause issues when communicating with the API.
access_token = access_token.strip()

base_url = new_url + "/api/v1/"
base_url = base_url.strip()

self.__requester = Requester(base_url, access_token)

Expand Down Expand Up @@ -1184,6 +1182,33 @@ def get_user_participants(self, appointment_group, **kwargs):
_kwargs=combine_kwargs(**kwargs),
)

def graphql(self, query, variables=None, **kwargs):
"""
Makes a GraphQL formatted request to Canvas
:calls: `POST /api/graphql \
<https://canvas.instructure.com/doc/api/file.graphql.html>`_
:param query: The GraphQL query to execute as a String
:type query: str
:param variables: The variable values as required by the supplied query
:type variables: dict
:rtype: dict
"""
response = self.__requester.request(
"POST",
"graphql",
headers={"Content-Type": "application/json"},
_kwargs=combine_kwargs(**kwargs)
+ [("query", query), ("variables", variables)],
# Needs to call special endpoint without api/v1
_url=self.__requester.original_url + "/api/graphql",
json=True,
)

return response.json()

def list_appointment_groups(self, **kwargs):
"""
List appointment groups.
Expand Down
119 changes: 114 additions & 5 deletions canvasapi/course.py
Original file line number Diff line number Diff line change
Expand Up @@ -358,6 +358,26 @@ def create_group_category(self, name, **kwargs):
)
return GroupCategory(self._requester, response.json())

def create_late_policy(self, **kwargs):
"""
Create a late policy. If the course already has a late policy, a bad_request
is returned since there can only be one late policy per course.
:calls: `POST /api/v1/courses/:id/late_policy \
<https://canvas.instructure.com/doc/api/late_policy.html#method.late_policy.create>`_
:rtype: :class:`canvasapi.course.LatePolicy`
"""

response = self._requester.request(
"POST",
"courses/{}/late_policy".format(self.id),
_kwargs=combine_kwargs(**kwargs),
)
late_policy_json = response.json()

return LatePolicy(self._requester, late_policy_json["late_policy"])

def create_module(self, module, **kwargs):
"""
Create a new module.
Expand Down Expand Up @@ -394,10 +414,10 @@ def create_page(self, wiki_page, **kwargs):
:calls: `POST /api/v1/courses/:course_id/pages \
<https://canvas.instructure.com/doc/api/pages.html#method.wiki_pages_api.create>`_
:param title: The title for the page.
:type title: dict
:param wiki_page: The title for the page.
:type wiki_page: dict
:returns: The created page.
:rtype: :class:`canvasapi.course.Course`
:rtype: :class:`canvasapi.page.Page`
"""

if isinstance(wiki_page, dict) and "title" in wiki_page:
Expand Down Expand Up @@ -554,6 +574,25 @@ def edit_front_page(self, **kwargs):

return Page(self._requester, page_json)

def edit_late_policy(self, **kwargs):
"""
Patch a late policy. No body is returned upon success.
:calls: `PATCH /api/v1/courses/:id/late_policy \
<https://canvas.instructure.com/doc/api/late_policy.html#method.late_policy.update>`_
:returns: True if Late Policy was updated successfully. False otherwise.
:rtype: bool
"""

response = self._requester.request(
"PATCH",
"courses/{}/late_policy".format(self.id),
_kwargs=combine_kwargs(**kwargs),
)

return response.status_code == 204

def enroll_user(self, user, enrollment_type, **kwargs):
"""
Create a new user enrollment for a course or a section.
Expand Down Expand Up @@ -1410,6 +1449,25 @@ def get_groups(self, **kwargs):
_kwargs=combine_kwargs(**kwargs),
)

def get_late_policy(self, **kwargs):
"""
Returns the late policy for a course.
:calls: `GET /api/v1/courses/:id/late_policy \
<https://canvas.instructure.com/doc/api/late_policy.html#method.late_policy.show>`_
:rtype: :class:`canvasapi.course.LatePolicy`
"""

response = self._requester.request(
"GET",
"courses/{}/late_policy".format(self.id),
_kwargs=combine_kwargs(**kwargs),
)
late_policy_json = response.json()

return LatePolicy(self._requester, late_policy_json["late_policy"])

def get_licenses(self, **kwargs):
"""
Returns a paginated list of the licenses that can be applied to the
Expand Down Expand Up @@ -1698,6 +1756,29 @@ def get_quiz(self, quiz):

return Quiz(self._requester, quiz_json)

def get_quiz_overrides(self, **kwargs):
"""
Retrieve the actual due-at, unlock-at,
and available-at dates for quizzes based on
the assignment overrides active for the current API user.
:calls: `GET /api/v1/courses/:course_id/quizzes/assignment_overrides \
<https://canvas.instructure.com/doc/api/quiz_assignment_overrides.html#method.quizzes/quiz_assignment_overrides.index>`_
:rtype: :class:`canvasapi.paginated_list.PaginatedList` of
:class:`canvasapi.quiz.QuizAssignmentOverrideSet`
"""
from canvasapi.quiz import QuizAssignmentOverrideSet

return PaginatedList(
QuizAssignmentOverrideSet,
self._requester,
"GET",
"courses/{}/quizzes/assignment_overrides".format(self.id),
_root="quiz_assignment_overrides",
_kwargs=combine_kwargs(**kwargs),
)

def get_quizzes(self, **kwargs):
"""
Return a list of quizzes belonging to this course.
Expand Down Expand Up @@ -2577,6 +2658,28 @@ def reset(self):
)
return Course(self._requester, response.json())

def resolve_path(self, full_path, **kwargs):
"""
Returns the paginated list of all of the folders in the given
path starting at the course root folder.
:calls: `GET /api/v1/courses/:course_id/folders/by_path/*full_path \
<https://canvas.instructure.com/doc/api/files.html#method.folders.resolve_path>`_
:param full_path: Full path to resolve, relative to course root
:type full_path: string
:rtype: :class:`canvasapi.paginated_list.PaginatedList` of
:class:`canvasapi.folder.Folder`
"""
return PaginatedList(
Folder,
self._requester,
"GET",
"courses/{0}/folders/by_path/{1}".format(self.id, full_path),
_kwargs=combine_kwargs(**kwargs),
)

def set_quiz_extensions(self, quiz_extensions, **kwargs):
"""
Set extensions for student all quiz submissions in a course.
Expand Down Expand Up @@ -2725,8 +2828,8 @@ def update(self, **kwargs):
:calls: `PUT /api/v1/courses/:id \
<https://canvas.instructure.com/doc/api/courses.html#method.courses.update>`_
:returns: True if the course was updated, False otherwise.
:rtype: bool
:returns: `True` if the course was updated, `False` otherwise.
:rtype: `bool`
"""
response = self._requester.request(
"PUT", "courses/{}".format(self.id), _kwargs=combine_kwargs(**kwargs)
Expand Down Expand Up @@ -2878,3 +2981,9 @@ def remove(self):
"DELETE", "users/self/course_nicknames/{}".format(self.course_id)
)
return CourseNickname(self._requester, response.json())


@python_2_unicode_compatible
class LatePolicy(CanvasObject):
def __str__(self):
return "Late Policy {}".format(self.id)
6 changes: 6 additions & 0 deletions canvasapi/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,3 +65,9 @@ class Conflict(CanvasException):
"""Canvas had a conflict with an existing resource."""

pass


class UnprocessableEntity(CanvasException):
"""Canvas was unable to process the entity."""

pass
Loading

0 comments on commit 42606eb

Please sign in to comment.