├── .github ├── CODEOWNERS ├── CONTRIBUTING.md ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── endpoint-coverage-request.md ├── PULL_REQUEST_TEMPLATE.md └── workflows │ ├── deploy.yml │ └── run-tests.yml ├── .gitignore ├── .mdlrc ├── .pre-commit-config.yaml ├── .readthedocs.yml ├── AUTHORS.md ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── DEPLOY.md ├── LICENSE ├── README.md ├── _config.yml ├── canvasapi ├── __init__.py ├── account.py ├── account_calendar.py ├── appointment_group.py ├── assignment.py ├── authentication_event.py ├── authentication_provider.py ├── avatar.py ├── blueprint.py ├── bookmark.py ├── calendar_event.py ├── canvas.py ├── canvas_object.py ├── collaboration.py ├── comm_message.py ├── communication_channel.py ├── content_export.py ├── content_migration.py ├── conversation.py ├── course.py ├── course_epub_export.py ├── course_event.py ├── current_user.py ├── custom_gradebook_columns.py ├── discussion_topic.py ├── enrollment.py ├── enrollment_term.py ├── eportfolio.py ├── exceptions.py ├── external_feed.py ├── external_tool.py ├── favorite.py ├── feature.py ├── file.py ├── folder.py ├── grade_change_log.py ├── gradebook_history.py ├── grading_period.py ├── grading_standard.py ├── group.py ├── jwt.py ├── license.py ├── login.py ├── lti_resource_link.py ├── module.py ├── new_quiz.py ├── notification_preference.py ├── outcome.py ├── outcome_import.py ├── page.py ├── page_view.py ├── paginated_list.py ├── pairing_code.py ├── peer_review.py ├── planner.py ├── poll.py ├── poll_choice.py ├── poll_session.py ├── poll_submission.py ├── progress.py ├── quiz.py ├── quiz_group.py ├── requester.py ├── rubric.py ├── scope.py ├── section.py ├── sis_import.py ├── submission.py ├── tab.py ├── todo.py ├── upload.py ├── usage_rights.py ├── user.py └── util.py ├── dev_requirements.txt ├── docs ├── Makefile ├── account-calendar-ref.rst ├── account-ref.rst ├── appointment-group-ref.rst ├── assignment-ref.rst ├── authentication-event-ref.rst ├── authentication-provider-ref.rst ├── avatar-ref.rst ├── blueprint-ref.rst ├── bookmark-ref.rst ├── calendar-event-ref.rst ├── canvas-object-ref.rst ├── canvas-ref.rst ├── class-reference.rst ├── collaboration-ref.rst ├── comm-message-ref.rst ├── communication-channel-ref.rst ├── conf.py ├── content-export-ref.rst ├── content-migration-ref.rst ├── conversation-ref.rst ├── course-epub-export-ref.rst ├── course-event-ref.rst ├── course-ref.rst ├── current-user-ref.rst ├── custom-gradebook-columns-ref.rst ├── debugging.rst ├── discussion-entry-ref.rst ├── discussion-topic-ref.rst ├── enrollment-ref.rst ├── enrollment-term-ref.rst ├── eportfolio-ref.rst ├── examples.rst ├── exceptions.rst ├── external-tool-ref.rst ├── favorite-ref.rst ├── feature-ref.rst ├── file-ref.rst ├── folder-ref.rst ├── getting-started.rst ├── grade-change-log-ref.rst ├── grading-period-ref.rst ├── group-ref.rst ├── index.rst ├── internal-classes.rst ├── jwt-ref.rst ├── keyword-args.rst ├── license-ref.rst ├── login-ref.rst ├── lti-resource-link-ref.rst ├── make.bat ├── module-ref.rst ├── outcome-import-ref.rst ├── outcome-ref.rst ├── page-ref.rst ├── paginated-list-ref.rst ├── pairing-code-ref.rst ├── planner-ref.rst ├── poll-choice-ref.rst ├── poll-ref.rst ├── poll-session-ref.rst ├── poll-submission-ref.rst ├── progress-ref.rst ├── quiz-group-ref.rst ├── quiz-ref.rst ├── requester-ref.rst ├── rubric-ref.rst ├── scope-ref.rst ├── section-ref.rst ├── sis-import-ref.rst ├── submission-ref.rst ├── tab-ref.rst ├── todo-ref.rst ├── troubleshooting.rst ├── upload-ref.rst ├── usage-rights-ref.rst ├── user-ref.rst └── util-ref.rst ├── markdown-style.rb ├── requirements.txt ├── scripts ├── __init__.py ├── alphabetic.py ├── find_missing_kwargs.py ├── find_missing_modules.py ├── run_tests.sh └── validate_docstrings.py ├── setup.cfg ├── setup.py ├── tests ├── __init__.py ├── fixtures │ ├── 404.html │ ├── account.json │ ├── announcements.json │ ├── appointment_group.json │ ├── assignment.json │ ├── authentication_providers.json │ ├── blueprint.json │ ├── bookmark.json │ ├── calendar_event.json │ ├── collaboration.json │ ├── comm_message.json │ ├── communication_channel.json │ ├── content_migration.json │ ├── conversation.json │ ├── course.json │ ├── current_user.json │ ├── custom_gradebook_columns.json │ ├── discussion_topic.json │ ├── enrollment.json │ ├── enrollment_term.json │ ├── eportfolio.json │ ├── external_tool.json │ ├── favorite.json │ ├── file.json │ ├── files.html │ ├── folder.json │ ├── generic.json │ ├── generic_file.txt │ ├── grading_period.json │ ├── graphql.json │ ├── group.json │ ├── jwt.json │ ├── login.json │ ├── lti_resource_link.json │ ├── module.json │ ├── new_quiz.json │ ├── notification_preferences.html │ ├── outcome.json │ ├── page.json │ ├── paginated_list.json │ ├── planner.json │ ├── poll.json │ ├── poll_choice.json │ ├── poll_session.json │ ├── poll_submission.json │ ├── progress.json │ ├── quiz.json │ ├── quiz_group.json │ ├── requests.json │ ├── rubric.json │ ├── section.json │ ├── sis_import.json │ ├── submission.json │ ├── test_create_sis_import.csv │ ├── test_import_outcome.csv │ ├── uploader.json │ └── user.json ├── settings.py ├── test_account.py ├── test_account_calendar.py ├── test_appointment_group.py ├── test_assignment.py ├── test_authentication_event.py ├── test_authentication_providers.py ├── test_blueprint.py ├── test_bookmark.py ├── test_calendar_event.py ├── test_canvas.py ├── test_canvas_object.py ├── test_collaboration.py ├── test_comm_messages.py ├── test_communication_channel.py ├── test_content_export.py ├── test_content_migration.py ├── test_conversation.py ├── test_course.py ├── test_course_epub_export.py ├── test_course_event.py ├── test_current_user.py ├── test_custom_gradebook_columns.py ├── test_discussion_topic.py ├── test_enrollment.py ├── test_enrollment_term.py ├── test_eportfolio.py ├── test_external_feed.py ├── test_external_tool.py ├── test_favorite.py ├── test_feature.py ├── test_file.py ├── test_folder.py ├── test_grade_change_log.py ├── test_gradebook_history.py ├── test_grading_period.py ├── test_grading_standard.py ├── test_group.py ├── test_jwt.py ├── test_licenses.py ├── test_login.py ├── test_lti_resource_link.py ├── test_module.py ├── test_new_quiz.py ├── test_notification_preference.py ├── test_outcome.py ├── test_outcome_import.py ├── test_page.py ├── test_page_view.py ├── test_paginated_list.py ├── test_pairing_code.py ├── test_peer_review.py ├── test_planner.py ├── test_poll.py ├── test_poll_choice.py ├── test_poll_session.py ├── test_poll_submission.py ├── test_progress.py ├── test_quiz.py ├── test_quiz_group.py ├── test_requester.py ├── test_rubric.py ├── test_scope.py ├── test_section.py ├── test_sis_import.py ├── test_submission.py ├── test_tab.py ├── test_todo.py ├── test_uploader.py ├── test_usage_rights.py ├── test_user.py ├── test_util.py ├── test_validate_docstrings.py └── util.py └── tests_requirements.txt /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @Thetwam @bennettscience @jonespm 2 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: bug 6 | assignees: '' 7 | 8 | --- 9 | 10 | # Describe the bug 11 | 12 | A clear and concise description of what the bug is. 13 | 14 | # To Reproduce 15 | 16 | Steps to reproduce the behavior: 17 | 18 | 1. Go to '...' 19 | 2. Click on '....' 20 | 3. Scroll down to '....' 21 | 4. See error 22 | 23 | # Expected behavior 24 | 25 | A clear and concise description of what you expected to happen. 26 | 27 | # Environment information 28 | 29 | 30 | 31 | - Python version (`python --version`) 32 | - CanvasAPI version (`pip show canvasapi`) 33 | 34 | # Additional context 35 | 36 | Add any other context about the problem here. 37 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/endpoint-coverage-request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Request Endpoint Coverage 3 | about: Request new endpoint coverage 4 | title: '' 5 | labels: api-coverage 6 | assignees: '' 7 | 8 | --- 9 | 10 | **What resource needs additional coverage?** 11 | e.g. Courses, Users, Accounts, Quizzes 12 | 13 | **What endpoints need to be covered?** 14 | Specify individual endpoints and provide a link to the endpoint in Canvas's API documentation 15 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | # Proposed Changes 2 | 3 | * 4 | * 5 | 6 | Fixes # . 7 | -------------------------------------------------------------------------------- /.github/workflows/deploy.yml: -------------------------------------------------------------------------------- 1 | name: Deploy to PyPI 2 | 3 | on: 4 | push: 5 | tags: 6 | - v[0-9]+.[0-9]+.[0-9]+ 7 | 8 | jobs: 9 | deploy: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@v4 13 | 14 | - name: Set up Python 15 | uses: actions/setup-python@v5 16 | 17 | - name: Install build dependency 18 | run: pip install build 19 | 20 | - name: Create source distribution 21 | run: python -m build 22 | 23 | - name: Publish distribution to PyPI 24 | uses: pypa/gh-action-pypi-publish@release/v1 25 | with: 26 | user: __token__ 27 | password: ${{ secrets.PYPI_API_TOKEN }} 28 | -------------------------------------------------------------------------------- /.github/workflows/run-tests.yml: -------------------------------------------------------------------------------- 1 | name: Run tests 2 | 3 | on: 4 | push: 5 | branches: [master, develop] 6 | pull_request: 7 | branches: [master, develop] 8 | 9 | jobs: 10 | build: 11 | runs-on: ubuntu-latest 12 | strategy: 13 | matrix: 14 | python-version: ["3.7", "3.8", "3.9", "3.10", "3.11"] 15 | 16 | steps: 17 | - uses: actions/checkout@v2 18 | - name: Set up Python ${{ matrix.python-version }} 19 | uses: actions/setup-python@v2 20 | with: 21 | python-version: ${{ matrix.python-version }} 22 | - name: Install dependencies 23 | run: | 24 | python -m pip install --upgrade pip 25 | pip install -r tests_requirements.txt 26 | - name: Lint with flake8 27 | run: flake8 28 | - name: Check formatting 29 | run: black --check canvasapi tests 30 | - name: Check import sorting 31 | run: isort --check canvasapi tests 32 | - name: Run tests 33 | run: coverage run -m unittest discover 34 | - name: Lint markdown files 35 | uses: bewuethr/mdl-action@v1 36 | - name: Check if all modules are visible to inspect 37 | run: python scripts/find_missing_modules.py 38 | - name: Check if methods are alphabetical 39 | run: python scripts/alphabetic.py 40 | - name: Check for missing kwargs 41 | run: python scripts/find_missing_kwargs.py 42 | - name: Upload coverage to Codecov 43 | uses: codecov/codecov-action@v4 44 | with: 45 | fail_ci_if_error: true 46 | token: ${{ secrets.CODECOV_TOKEN }} 47 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | *~ 3 | .coverage 4 | .DS_Store 5 | /*.egg-info 6 | /.project 7 | /.pyproject 8 | /_build/ 9 | /_static/ 10 | /_templates/ 11 | /_test.py 12 | /build/ 13 | /coverage.xml 14 | /dist/ 15 | /docs/_build/ 16 | /env/ 17 | /env3/ 18 | venv/ 19 | /htmlcov/ 20 | \#*# 21 | .vscode/settings.json 22 | setup.sh 23 | .python-version 24 | .*.swp 25 | -------------------------------------------------------------------------------- /.mdlrc: -------------------------------------------------------------------------------- 1 | style "markdown-style.rb" 2 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | repos: 2 | - repo: https://github.com/psf/black 3 | rev: 23.3.0 4 | hooks: 5 | - id: black 6 | language_version: python3 7 | -------------------------------------------------------------------------------- /.readthedocs.yml: -------------------------------------------------------------------------------- 1 | # .readthedocs.yml 2 | # Read the Docs configuration file 3 | # See https://docs.readthedocs.io/en/stable/config-file/v2.html for details 4 | 5 | # Required 6 | version: 2 7 | 8 | # Build documentation in the docs/ directory with Sphinx 9 | sphinx: 10 | configuration: docs/conf.py 11 | 12 | python: 13 | version: 3.7 14 | install: 15 | - requirements: dev_requirements.txt 16 | -------------------------------------------------------------------------------- /DEPLOY.md: -------------------------------------------------------------------------------- 1 | CanvasAPI Deploy Procedures 2 | =========================== 3 | 4 | Pre-Flight Checklist 5 | -------------------- 6 | 7 | - On branch `develop` and up-to-date 8 | - All tests pass 9 | - 100% coverage 10 | - No linter errors 11 | - `CHANGELOG` is accurate 12 | 13 | Generate Documentation 14 | ---------------------- 15 | 16 | Documentation should now be automatically pushed to readthedocs. 17 | 18 | Deploy 19 | ------ 20 | 21 | Update version number in `__init__.py`. 22 | 23 | Commit the the changes to `__init__.py` and push. 24 | 25 | Create a merge request from `develop` to `master`, and merge. 26 | 27 | Tag the merge commit with the version number: `git tag -s v0.0.0 -m "Release version 0.0.0" abc1234` 28 | 29 | Push the tag: `git push upstream v0.0.0` 30 | 31 | GitHub Actions should automatically deploy the tagged code to PyPI. 32 | 33 | Create release on GitHub for the new tag. Use the text from the changelog for content. 34 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2017 University of Central Florida - Center for Distributed Learning 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /_config.yml: -------------------------------------------------------------------------------- 1 | theme: jekyll-theme-cayman -------------------------------------------------------------------------------- /canvasapi/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from canvasapi.canvas import Canvas 4 | 5 | __all__ = ["Canvas"] 6 | 7 | __version__ = "3.3.0" 8 | -------------------------------------------------------------------------------- /canvasapi/account_calendar.py: -------------------------------------------------------------------------------- 1 | from canvasapi.canvas_object import CanvasObject 2 | 3 | 4 | class AccountCalendar(CanvasObject): 5 | def __str__(self): 6 | return "{} {} ({})".format(self.name, self.visible, self.calendar_event_url) 7 | -------------------------------------------------------------------------------- /canvasapi/appointment_group.py: -------------------------------------------------------------------------------- 1 | from canvasapi.canvas_object import CanvasObject 2 | from canvasapi.exceptions import RequiredFieldMissing 3 | from canvasapi.util import combine_kwargs 4 | 5 | 6 | class AppointmentGroup(CanvasObject): 7 | def __str__(self): 8 | return "{} ({})".format(self.title, self.id) 9 | 10 | def delete(self, **kwargs): 11 | """ 12 | Delete this appointment group. 13 | 14 | :calls: `DELETE /api/v1/appointment_groups/:id \ 15 | `_ 16 | 17 | :rtype: :class:`canvasapi.appointment_group.AppointmentGroup` 18 | """ 19 | response = self._requester.request( 20 | "DELETE", 21 | "appointment_groups/{}".format(self.id), 22 | _kwargs=combine_kwargs(**kwargs), 23 | ) 24 | return AppointmentGroup(self._requester, response.json()) 25 | 26 | def edit(self, appointment_group, **kwargs): 27 | """ 28 | Modify this appointment group. 29 | 30 | :calls: `PUT /api/v1/appointment_groups/:id \ 31 | `_ 32 | 33 | :param appointment_group: Dict containing an array of context codes 34 | :type appointment_group: dict 35 | 36 | :rtype: :class:`canvasapi.appointment_group.AppointmentGroup` 37 | """ 38 | if isinstance(appointment_group, dict) and "context_codes" in appointment_group: 39 | kwargs["appointment_group"] = appointment_group 40 | else: 41 | raise RequiredFieldMissing( 42 | "Dictionary with key 'context_code' is required." 43 | ) 44 | 45 | response = self._requester.request( 46 | "PUT", 47 | "appointment_groups/{}".format(self.id), 48 | _kwargs=combine_kwargs(**kwargs), 49 | ) 50 | 51 | if "title" in response.json(): 52 | super(AppointmentGroup, self).set_attributes(response.json()) 53 | 54 | return AppointmentGroup(self._requester, response.json()) 55 | -------------------------------------------------------------------------------- /canvasapi/authentication_event.py: -------------------------------------------------------------------------------- 1 | from canvasapi.canvas_object import CanvasObject 2 | 3 | 4 | class AuthenticationEvent(CanvasObject): 5 | def __str__(self): 6 | return "{} {} ({})".format(self.created_at, self.event_type, self.pseudonym_id) 7 | -------------------------------------------------------------------------------- /canvasapi/authentication_provider.py: -------------------------------------------------------------------------------- 1 | from canvasapi.canvas_object import CanvasObject 2 | from canvasapi.util import combine_kwargs 3 | 4 | 5 | class AuthenticationProvider(CanvasObject): 6 | def __str__(self): # pragma: no cover 7 | return "{} ({})".format(self.auth_type, self.position) 8 | 9 | def delete(self, **kwargs): 10 | """ 11 | Delete the config 12 | 13 | :calls: `DELETE /api/v1/accounts/:account_id/authentication_providers/:id \ 14 | `_ 15 | 16 | :rtype: :class:`canvasapi.authentication_provider.AuthenticationProvider` 17 | """ 18 | response = self._requester.request( 19 | "DELETE", 20 | "accounts/{}/authentication_providers/{}".format(self.account_id, self.id), 21 | _kwargs=combine_kwargs(**kwargs), 22 | ) 23 | return AuthenticationProvider(self._requester, response.json()) 24 | 25 | def update(self, **kwargs): 26 | """ 27 | Update an authentication provider using the same options as the create endpoint 28 | 29 | :calls: `PUT /api/v1/accounts/:account_id/authentication_providers/:id \ 30 | `_ 31 | 32 | :rtype: :class:`canvasapi.authentication_provider.AuthenticationProvider` 33 | """ 34 | response = self._requester.request( 35 | "PUT", 36 | "accounts/{}/authentication_providers/{}".format(self.account_id, self.id), 37 | _kwargs=combine_kwargs(**kwargs), 38 | ) 39 | 40 | if response.json().get("auth_type"): 41 | super(AuthenticationProvider, self).set_attributes(response.json()) 42 | 43 | return response.json().get("auth_type") 44 | -------------------------------------------------------------------------------- /canvasapi/avatar.py: -------------------------------------------------------------------------------- 1 | from canvasapi.canvas_object import CanvasObject 2 | 3 | 4 | class Avatar(CanvasObject): 5 | def __str__(self): # pragma: no cover 6 | return "{}".format(self.display_name) 7 | -------------------------------------------------------------------------------- /canvasapi/bookmark.py: -------------------------------------------------------------------------------- 1 | from canvasapi.canvas_object import CanvasObject 2 | from canvasapi.util import combine_kwargs 3 | 4 | 5 | class Bookmark(CanvasObject): 6 | def __str__(self): 7 | return "{} ({})".format(self.name, self.id) 8 | 9 | def delete(self, **kwargs): 10 | """ 11 | Delete this bookmark. 12 | 13 | :calls: `DELETE /api/v1/users/self/bookmarks/:id \ 14 | `_ 15 | 16 | :rtype: :class:`canvasapi.bookmark.Bookmark` 17 | """ 18 | response = self._requester.request( 19 | "DELETE", 20 | "users/self/bookmarks/{}".format(self.id), 21 | _kwargs=combine_kwargs(**kwargs), 22 | ) 23 | return Bookmark(self._requester, response.json()) 24 | 25 | def edit(self, **kwargs): 26 | """ 27 | Modify this bookmark. 28 | 29 | :calls: `PUT /api/v1/users/self/bookmarks/:id \ 30 | `_ 31 | 32 | :rtype: :class:`canvasapi.bookmark.Bookmark` 33 | """ 34 | response = self._requester.request( 35 | "PUT", 36 | "users/self/bookmarks/{}".format(self.id), 37 | _kwargs=combine_kwargs(**kwargs), 38 | ) 39 | 40 | if "name" in response.json() and "url" in response.json(): 41 | super(Bookmark, self).set_attributes(response.json()) 42 | 43 | return Bookmark(self._requester, response.json()) 44 | -------------------------------------------------------------------------------- /canvasapi/calendar_event.py: -------------------------------------------------------------------------------- 1 | from canvasapi.canvas_object import CanvasObject 2 | from canvasapi.util import combine_kwargs 3 | 4 | 5 | class CalendarEvent(CanvasObject): 6 | def __str__(self): 7 | return "{} ({})".format(self.title, self.id) 8 | 9 | def delete(self, **kwargs): 10 | """ 11 | Delete this calendar event. 12 | 13 | :calls: `DELETE /api/v1/calendar_events/:id \ 14 | `_ 15 | 16 | :rtype: :class:`canvasapi.calendar_event.CalendarEvent` 17 | """ 18 | response = self._requester.request( 19 | "DELETE", 20 | "calendar_events/{}".format(self.id), 21 | _kwargs=combine_kwargs(**kwargs), 22 | ) 23 | return CalendarEvent(self._requester, response.json()) 24 | 25 | def edit(self, **kwargs): 26 | """ 27 | Modify this calendar event. 28 | 29 | :calls: `PUT /api/v1/calendar_events/:id \ 30 | `_ 31 | 32 | :rtype: :class:`canvasapi.calendar_event.CalendarEvent` 33 | """ 34 | response = self._requester.request( 35 | "PUT", 36 | "calendar_events/{}".format(self.id), 37 | _kwargs=combine_kwargs(**kwargs), 38 | ) 39 | 40 | if "title" in response.json(): 41 | super(CalendarEvent, self).set_attributes(response.json()) 42 | 43 | return CalendarEvent(self._requester, response.json()) 44 | -------------------------------------------------------------------------------- /canvasapi/canvas_object.py: -------------------------------------------------------------------------------- 1 | import arrow 2 | import pytz 3 | 4 | 5 | class CanvasObject(object): 6 | """ 7 | Base class for all classes representing objects returned by the API. 8 | 9 | This makes a call to :func:`canvasapi.canvas_object.CanvasObject.set_attributes` 10 | to dynamically construct this object's attributes with a JSON object. 11 | """ 12 | 13 | def __getattribute__(self, name): 14 | return super(CanvasObject, self).__getattribute__(name) 15 | 16 | def __init__(self, requester, attributes): 17 | """ 18 | :param requester: The requester to pass HTTP requests through. 19 | :type requester: :class:`canvasapi.requester.Requester` 20 | :param attributes: The JSON object to build this object with. 21 | :type attributes: dict 22 | """ 23 | self._requester = requester 24 | self.set_attributes(attributes) 25 | 26 | def __repr__(self): # pragma: no cover 27 | classname = self.__class__.__name__ 28 | attrs = ", ".join( 29 | [ 30 | "{}={}".format(attr, val) 31 | for attr, val in self.__dict__.items() 32 | if attr != "attributes" 33 | ] 34 | ) # noqa 35 | return "{}({})".format(classname, attrs) 36 | 37 | def set_attributes(self, attributes): 38 | """ 39 | Load this object with attributes. 40 | 41 | This method attempts to detect special types based on the field's content 42 | and will create an additional attribute of that type. 43 | 44 | Consider a JSON response with the following fields:: 45 | 46 | { 47 | "name": "New course name", 48 | "course_code": "COURSE-001", 49 | "start_at": "2012-05-05T00:00:00Z", 50 | "end_at": "2012-08-05T23:59:59Z", 51 | "sis_course_id": "12345" 52 | } 53 | 54 | The `start_at` and `end_at` fields match a date in ISO8601 format, 55 | so two additional datetime attributes are created, `start_at_date` 56 | and `end_at_date`. 57 | 58 | :param attributes: The JSON object to build this object with. 59 | :type attributes: dict 60 | """ 61 | for attribute, value in attributes.items(): 62 | self.__setattr__(attribute, value) 63 | 64 | try: 65 | naive = arrow.get(str(value)).datetime 66 | aware = naive.replace(tzinfo=pytz.utc) - naive.utcoffset() 67 | self.__setattr__(attribute + "_date", aware) 68 | except arrow.ParserError: 69 | pass 70 | except ValueError: 71 | pass 72 | -------------------------------------------------------------------------------- /canvasapi/collaboration.py: -------------------------------------------------------------------------------- 1 | from canvasapi.canvas_object import CanvasObject 2 | from canvasapi.paginated_list import PaginatedList 3 | from canvasapi.util import combine_kwargs 4 | 5 | 6 | class Collaboration(CanvasObject): 7 | def __str__(self): 8 | return "{} ({})".format(self.document_id, self.id) 9 | 10 | def get_collaborators(self, **kwargs): 11 | """ 12 | Return a list of collaborators for this collaboration. 13 | 14 | :calls: `GET /api/v1/collaborations/:id/members \ 15 | `_ 16 | 17 | :rtype: :class:`canvasapi.collaboration.Collaborator` 18 | """ 19 | return PaginatedList( 20 | Collaborator, 21 | self._requester, 22 | "GET", 23 | "collaborations/{}/members".format(self.id), 24 | _root="collaborators", 25 | kwargs=combine_kwargs(**kwargs), 26 | ) 27 | 28 | 29 | class Collaborator(CanvasObject): 30 | def __str__(self): 31 | return "{} ({})".format(self.name, self.id) 32 | -------------------------------------------------------------------------------- /canvasapi/comm_message.py: -------------------------------------------------------------------------------- 1 | from canvasapi.canvas_object import CanvasObject 2 | 3 | 4 | class CommMessage(CanvasObject): 5 | def __str__(self): 6 | return "{} ({})".format(self.subject, self.id) 7 | -------------------------------------------------------------------------------- /canvasapi/content_export.py: -------------------------------------------------------------------------------- 1 | from canvasapi.canvas_object import CanvasObject 2 | 3 | 4 | class ContentExport(CanvasObject): 5 | def __str__(self): 6 | return "{} {} ({})".format(self.export_type, self.user_id, self.id) 7 | -------------------------------------------------------------------------------- /canvasapi/course_epub_export.py: -------------------------------------------------------------------------------- 1 | from canvasapi.canvas_object import CanvasObject 2 | 3 | 4 | class CourseEpubExport(CanvasObject): 5 | def __str__(self): 6 | return "{} course_id:({}) epub_id:({}) {} ".format( 7 | self.name, 8 | self.id, 9 | self.epub_export["id"], 10 | self.epub_export["workflow_state"], 11 | ) 12 | -------------------------------------------------------------------------------- /canvasapi/course_event.py: -------------------------------------------------------------------------------- 1 | from canvasapi.canvas_object import CanvasObject 2 | 3 | 4 | class CourseEvent(CanvasObject): 5 | def __str__(self): 6 | return "{} {} ({})".format(self.name, self.start_at, self.conclude_at) 7 | -------------------------------------------------------------------------------- /canvasapi/enrollment_term.py: -------------------------------------------------------------------------------- 1 | from canvasapi.canvas_object import CanvasObject 2 | from canvasapi.util import combine_kwargs 3 | 4 | 5 | class EnrollmentTerm(CanvasObject): 6 | def __str__(self): 7 | return "{} ({})".format(self.name, self.id) 8 | 9 | def delete(self, **kwargs): 10 | """ 11 | Delete this Enrollment Term. 12 | 13 | :calls: `DELETE /api/v1/accounts/:account_id/terms/:id \ 14 | `_ 15 | 16 | :rtype: :class:`canvasapi.enrollment_term.EnrollmentTerm` 17 | """ 18 | response = self._requester.request( 19 | "DELETE", 20 | "accounts/{}/terms/{}".format(self.account_id, self.id), 21 | _kwargs=combine_kwargs(**kwargs), 22 | ) 23 | return EnrollmentTerm(self._requester, response.json()) 24 | 25 | def edit(self, **kwargs): 26 | """ 27 | Modify this Enrollment Term. 28 | 29 | :calls: `PUT /api/v1/accounts/:account_id/terms/:id \ 30 | `_ 31 | 32 | :rtype: :class:`canvasapi.enrollment_term.EnrollmentTerm` 33 | """ 34 | response = self._requester.request( 35 | "PUT", 36 | "accounts/{}/terms/{}".format(self.account_id, self.id), 37 | _kwargs=combine_kwargs(**kwargs), 38 | ) 39 | 40 | return EnrollmentTerm(self._requester, response.json()) 41 | -------------------------------------------------------------------------------- /canvasapi/eportfolio.py: -------------------------------------------------------------------------------- 1 | from canvasapi.canvas_object import CanvasObject 2 | from canvasapi.paginated_list import PaginatedList 3 | from canvasapi.util import combine_kwargs 4 | 5 | 6 | class EPortfolio(CanvasObject): 7 | def __str__(self): 8 | return "{}".format(self.name) 9 | 10 | def delete(self, **kwargs): 11 | """ 12 | Delete an ePortfolio. 13 | 14 | :calls: `DELETE /api/v1/eportfolios/:id \ 15 | `_ 16 | 17 | :returns: ePortfolio with deleted date set. 18 | :rtype: :class:`canvasapi.eportfolio.EPortfolio` 19 | """ 20 | response = self._requester.request( 21 | "DELETE", "eportfolios/{}".format(self.id), _kwargs=combine_kwargs(**kwargs) 22 | ) 23 | return EPortfolio(self._requester, response.json()) 24 | 25 | def get_eportfolio_pages(self, **kwargs): 26 | """ 27 | Return a list of pages for an ePortfolio. 28 | 29 | :calls: `GET /api/v1/eportfolios/:eportfolio_id/pages \ 30 | `_ 31 | 32 | :returns: List of ePortfolio pages. 33 | :rtype: :class:`canvasapi.paginated_list.PaginatedList` of 34 | :class:`canvasapi.eportfolio.EPortfolioPage` 35 | """ 36 | 37 | return PaginatedList( 38 | EPortfolioPage, 39 | self._requester, 40 | "GET", 41 | "eportfolios/{}/pages".format(self.id), 42 | _kwargs=combine_kwargs(**kwargs), 43 | ) 44 | 45 | def moderate_eportfolio(self, **kwargs): 46 | """ 47 | Update the spam_status of an eportfolio. 48 | Only available to admins who can `moderate_user_content`. 49 | 50 | :calls: `PUT /api/v1/eportfolios/:eportfolio_id/moderate \ 51 | `_ 52 | 53 | :returns: Updated ePortfolio. 54 | :rtype: :class:`canvasapi.eportfolio.EPortfolio` 55 | """ 56 | response = self._requester.request( 57 | "PUT", 58 | "eportfolios/{}/moderate".format(self.id), 59 | _kwargs=combine_kwargs(**kwargs), 60 | ) 61 | 62 | return EPortfolio(self._requester, response.json()) 63 | 64 | def restore(self, **kwargs): 65 | """ 66 | Restore an ePortfolio back to active that was previously deleted. 67 | Only available to admins who can moderate_user_content. 68 | 69 | :calls: `PUT /api/v1/eportfolios/:eportfolio_id/restore \ 70 | `_ 71 | 72 | :returns: Updated ePortfolio. 73 | :rtype: :class:`canvasapi.eportfolio.EPortfolio` 74 | """ 75 | response = self._requester.request( 76 | "PUT", 77 | "eportfolios/{}/restore".format(self.id), 78 | _kwargs=combine_kwargs(**kwargs), 79 | ) 80 | 81 | return EPortfolio(self._requester, response.json()) 82 | 83 | 84 | class EPortfolioPage(CanvasObject): 85 | def __str__(self): 86 | return "{}. {}".format(self.position, self.name) 87 | -------------------------------------------------------------------------------- /canvasapi/exceptions.py: -------------------------------------------------------------------------------- 1 | class CanvasException(Exception): # pragma: no cover 2 | """ 3 | Base class for all errors returned by the Canvas API. 4 | """ 5 | 6 | def __init__(self, message): 7 | if isinstance(message, dict): 8 | self.error_report_id = message.get("error_report_id", None) 9 | 10 | errors = message.get("errors", False) 11 | if errors: 12 | self.message = errors 13 | else: 14 | self.message = ("Something went wrong. ", message) 15 | else: 16 | self.message = message 17 | 18 | def __str__(self): 19 | return str(self.message) 20 | 21 | 22 | class BadRequest(CanvasException): 23 | """Canvas was unable to understand the request. More information may be needed.""" 24 | 25 | pass 26 | 27 | 28 | class InvalidAccessToken(CanvasException): 29 | """CanvasAPI was unable to make an API connection.""" 30 | 31 | pass 32 | 33 | 34 | class Unauthorized(CanvasException): 35 | """CanvasAPI's key is valid, but is unauthorized to access the requested resource.""" 36 | 37 | pass 38 | 39 | 40 | class ResourceDoesNotExist(CanvasException): 41 | """Canvas could not locate the requested resource.""" 42 | 43 | pass 44 | 45 | 46 | class RequiredFieldMissing(CanvasException): 47 | """A required field is missing.""" 48 | 49 | pass 50 | 51 | 52 | class Forbidden(CanvasException): 53 | """Canvas has denied access to the resource for this user.""" 54 | 55 | pass 56 | 57 | 58 | class RateLimitExceeded(Forbidden): 59 | """ 60 | Canvas has recieved to many requests from this access token and is 61 | throttling this request. Try again later. 62 | """ 63 | 64 | pass 65 | 66 | 67 | class Conflict(CanvasException): 68 | """Canvas had a conflict with an existing resource.""" 69 | 70 | pass 71 | 72 | 73 | class UnprocessableEntity(CanvasException): 74 | """Canvas was unable to process the entity.""" 75 | 76 | pass 77 | -------------------------------------------------------------------------------- /canvasapi/external_feed.py: -------------------------------------------------------------------------------- 1 | from canvasapi.canvas_object import CanvasObject 2 | 3 | 4 | class ExternalFeed(CanvasObject): 5 | def __str__(self): 6 | return "{}".format(self.display_name) 7 | -------------------------------------------------------------------------------- /canvasapi/favorite.py: -------------------------------------------------------------------------------- 1 | from canvasapi.canvas_object import CanvasObject 2 | from canvasapi.util import combine_kwargs 3 | 4 | 5 | class Favorite(CanvasObject): 6 | def __str__(self): 7 | return "{} ({})".format(self.context_type, self.context_id) 8 | 9 | def remove(self, **kwargs): 10 | """ 11 | Remove a course or group from the current user's favorites. 12 | 13 | :calls: :Course: `DELETE /api/v1/users/self/favorites/courses/:id \ 14 | `_ 15 | :Group: `DELETE /api/v1/users/self/favorites/groups/:id \ 16 | `_ 17 | 18 | :rtype: :class:`canvasapi.favorite.Favorite` 19 | """ 20 | if self.context_type.lower() == "course": 21 | id = self.context_id 22 | uri_str = "users/self/favorites/courses/{}" 23 | 24 | elif self.context_type.lower() == "group": 25 | id = self.context_id 26 | uri_str = "users/self/favorites/groups/{}" 27 | 28 | response = self._requester.request( 29 | "DELETE", uri_str.format(id), _kwargs=combine_kwargs(**kwargs) 30 | ) 31 | return Favorite(self._requester, response.json()) 32 | -------------------------------------------------------------------------------- /canvasapi/file.py: -------------------------------------------------------------------------------- 1 | from canvasapi.canvas_object import CanvasObject 2 | from canvasapi.util import combine_kwargs 3 | 4 | 5 | class File(CanvasObject): 6 | def __str__(self): 7 | return "{}".format(self.display_name) 8 | 9 | def delete(self, **kwargs): 10 | """ 11 | Delete this file. 12 | 13 | :calls: `DELETE /api/v1/files/:id \ 14 | `_ 15 | 16 | :rtype: :class:`canvasapi.file.File` 17 | """ 18 | response = self._requester.request( 19 | "DELETE", "files/{}".format(self.id), _kwargs=combine_kwargs(**kwargs) 20 | ) 21 | return File(self._requester, response.json()) 22 | 23 | def download(self, location): 24 | """ 25 | Download the file to specified location. 26 | 27 | :param location: The path to download to. 28 | :type location: str 29 | """ 30 | response = self._requester.request("GET", _url=self.url) 31 | 32 | with open(location, "wb") as file_out: 33 | file_out.write(response.content) 34 | 35 | def get_contents(self, binary=False): 36 | """ 37 | Download the contents of this file. 38 | Pass binary=True to return a bytes object instead of a str. 39 | 40 | :rtype: str or bytes 41 | """ 42 | response = self._requester.request("GET", _url=self.url) 43 | if binary: 44 | return response.content 45 | else: 46 | return response.text 47 | 48 | def update(self, **kwargs): 49 | """ 50 | Update some settings on the specified file. 51 | 52 | :calls: `PUT /api/v1/files/:id \ 53 | `_ 54 | 55 | :rtype: :class:`canvasapi.file.File` 56 | """ 57 | response = self._requester.request( 58 | "PUT", "files/{}".format(self.id), _kwargs=combine_kwargs(**kwargs) 59 | ) 60 | return File(self._requester, response.json()) 61 | -------------------------------------------------------------------------------- /canvasapi/grade_change_log.py: -------------------------------------------------------------------------------- 1 | from canvasapi.canvas_object import CanvasObject 2 | 3 | 4 | class GradeChangeEvent(CanvasObject): 5 | def __str__(self): 6 | return "{} {} - {} ({})".format( 7 | self.event_type, self.grade_before, self.grade_after, self.id 8 | ) 9 | -------------------------------------------------------------------------------- /canvasapi/gradebook_history.py: -------------------------------------------------------------------------------- 1 | from canvasapi.canvas_object import CanvasObject 2 | 3 | 4 | class Day(CanvasObject): 5 | def __str__(self): 6 | return "{}".format(self.date) 7 | 8 | 9 | class Grader(CanvasObject): 10 | def __str__(self): 11 | return "{}".format(self.id) 12 | 13 | 14 | class SubmissionHistory(CanvasObject): 15 | def __str__(self): 16 | return "{}".format(self.submission_id) 17 | 18 | 19 | class SubmissionVersion(CanvasObject): 20 | def __str__(self): 21 | return "{} {}".format(self.assignment_id, self.id) 22 | -------------------------------------------------------------------------------- /canvasapi/grading_period.py: -------------------------------------------------------------------------------- 1 | from canvasapi.canvas_object import CanvasObject 2 | from canvasapi.exceptions import RequiredFieldMissing 3 | from canvasapi.util import combine_kwargs 4 | 5 | 6 | class GradingPeriod(CanvasObject): 7 | def __str__(self): 8 | return "{} ({})".format(self.title, self.id) 9 | 10 | def delete(self, **kwargs): 11 | """ 12 | Delete a grading period for a course. 13 | 14 | :calls: `DELETE /api/v1/courses/:course_id/grading_periods/:id \ 15 | `_ 16 | 17 | :returns: Status code 204 if delete was successful 18 | :rtype: int 19 | """ 20 | response = self._requester.request( 21 | "DELETE", 22 | "courses/{}/grading_periods/{}".format(self.course_id, self.id), 23 | _kwargs=combine_kwargs(**kwargs), 24 | ) 25 | 26 | return response.status_code 27 | 28 | def update(self, grading_period, **kwargs): 29 | """ 30 | Update a grading period for a course. 31 | 32 | :calls: `PUT /api/v1/courses/:course_id/grading_periods/:id \ 33 | `_ 34 | 35 | :param grading_period: List of nested paramameters. 36 | :type grading_period: list[dict] 37 | 38 | :rtype: :class:`canvasapi.grading_period.GradingPeriod` 39 | """ 40 | if isinstance(grading_period, list): 41 | kwargs["grading_periods"] = grading_period 42 | else: 43 | raise RequiredFieldMissing("List is required") 44 | 45 | if "start_date" not in kwargs["grading_periods"][0]: 46 | raise RequiredFieldMissing("start_date is missing") 47 | 48 | if "end_date" not in kwargs["grading_periods"][0]: 49 | raise RequiredFieldMissing("end_date is missing") 50 | 51 | response = self._requester.request( 52 | "PUT", 53 | "courses/{}/grading_periods/{}".format(self.course_id, self.id), 54 | _kwargs=combine_kwargs(**kwargs), 55 | ) 56 | 57 | response_json = response.json() 58 | grading_period = response_json["grading_periods"][0] 59 | grading_period.update({"course_id": self.course_id}) 60 | 61 | return GradingPeriod(self._requester, grading_period) 62 | -------------------------------------------------------------------------------- /canvasapi/grading_standard.py: -------------------------------------------------------------------------------- 1 | from canvasapi.canvas_object import CanvasObject 2 | 3 | 4 | class GradingStandard(CanvasObject): 5 | def __str__(self): 6 | return "{} ({})".format(self.title, self.id) 7 | -------------------------------------------------------------------------------- /canvasapi/jwt.py: -------------------------------------------------------------------------------- 1 | from canvasapi.canvas_object import CanvasObject 2 | 3 | 4 | class JWT(CanvasObject): 5 | def __str__(self): 6 | return "{}".format(self.token) 7 | -------------------------------------------------------------------------------- /canvasapi/license.py: -------------------------------------------------------------------------------- 1 | from canvasapi.canvas_object import CanvasObject 2 | 3 | 4 | class License(CanvasObject): 5 | def __str__(self): 6 | return "{} {}".format(self.name, self.id) 7 | -------------------------------------------------------------------------------- /canvasapi/login.py: -------------------------------------------------------------------------------- 1 | from canvasapi.canvas_object import CanvasObject 2 | from canvasapi.paginated_list import PaginatedList 3 | from canvasapi.util import combine_kwargs 4 | 5 | 6 | class Login(CanvasObject): 7 | def __str__(self): 8 | return "{} ({})".format(self.id, self.unique_id) 9 | 10 | def delete(self, **kwargs): 11 | """ 12 | Delete an existing login. 13 | 14 | :calls: `DELETE /api/v1/users/:user_id/logins/:id \ 15 | `_ 16 | 17 | :rtype: :class:`canvasapi.login.Login` 18 | """ 19 | response = self._requester.request( 20 | "DELETE", 21 | "users/{}/logins/{}".format(self.user_id, self.id), 22 | _kwargs=combine_kwargs(**kwargs), 23 | ) 24 | return Login(self._requester, response.json()) 25 | 26 | def edit(self, **kwargs): 27 | """ 28 | Update an existing login for a user in the given account. 29 | 30 | :calls: `PUT /api/v1/accounts/:account_id/logins/:id \ 31 | `_ 32 | 33 | :rtype: :class:`canvasapi.login.Login` 34 | """ 35 | response = self._requester.request( 36 | "PUT", 37 | "accounts/{}/logins/{}".format(self.account_id, self.id), 38 | _kwargs=combine_kwargs(**kwargs), 39 | ) 40 | return Login(self._requester, response.json()) 41 | 42 | def get_authentication_events(self, **kwargs): 43 | """ 44 | List authentication events for a given login. 45 | 46 | :calls: `GET /api/v1/audit/authentication/logins/:login_id \ 47 | `_ 48 | 49 | :rtype: :class:`canvasapi.paginated_list.PaginatedList` of 50 | :class:`canvasapi.authentication_event.AuthenticationEvent` 51 | """ 52 | from canvasapi.authentication_event import AuthenticationEvent 53 | 54 | return PaginatedList( 55 | AuthenticationEvent, 56 | self._requester, 57 | "GET", 58 | "audit/authentication/logins/{}".format(self.id), 59 | _kwargs=combine_kwargs(**kwargs), 60 | ) 61 | -------------------------------------------------------------------------------- /canvasapi/lti_resource_link.py: -------------------------------------------------------------------------------- 1 | from canvasapi.canvas_object import CanvasObject 2 | 3 | 4 | class LTIResourceLink(CanvasObject): 5 | def __str__(self): 6 | return "{} ({})".format(self.url, self.title) 7 | -------------------------------------------------------------------------------- /canvasapi/new_quiz.py: -------------------------------------------------------------------------------- 1 | from canvasapi.canvas_object import CanvasObject 2 | from canvasapi.util import combine_kwargs 3 | 4 | 5 | class NewQuiz(CanvasObject): 6 | def __str__(self): 7 | return "{} ({})".format(self.title, self.id) 8 | 9 | def delete(self, **kwargs): 10 | """ 11 | Delete a single new quiz. 12 | 13 | :calls: `DELETE /api/quiz/v1/courses/:course_id/quizzes/:assignment_id \ 14 | `_ 15 | 16 | :returns: The deleted New Quiz object 17 | :rtype: :class:`canvasapi.new_quiz.NewQuiz` 18 | """ 19 | endpoint = "courses/{}/quizzes/{}".format(self.course_id, self.id) 20 | 21 | response = self._requester.request( 22 | "DELETE", 23 | endpoint, 24 | _url="new_quizzes", 25 | _kwargs=combine_kwargs(**kwargs), 26 | ) 27 | response_json = response.json() 28 | response_json.update({"course_id": self.course_id}) 29 | 30 | return NewQuiz(self._requester, response_json) 31 | 32 | def update(self, **kwargs): 33 | """ 34 | Update a single New Quiz for the course. 35 | 36 | :calls: `PATCH /api/quiz/v1/courses/:course_id/quizzes/:assignment_id \ 37 | `_ 38 | 39 | :returns: The updated New Quiz object 40 | :rtype: :class:`canvasapi.new_quiz.NewQuiz` 41 | """ 42 | endpoint = "courses/{}/quizzes/{}".format(self.course_id, self.id) 43 | 44 | response = self._requester.request( 45 | "PATCH", 46 | endpoint, 47 | _url="new_quizzes", 48 | _kwargs=combine_kwargs(**kwargs), 49 | ) 50 | response_json = response.json() 51 | response_json.update({"course_id": self.course_id}) 52 | 53 | return NewQuiz(self._requester, response_json) 54 | -------------------------------------------------------------------------------- /canvasapi/notification_preference.py: -------------------------------------------------------------------------------- 1 | from canvasapi.canvas_object import CanvasObject 2 | 3 | 4 | class NotificationPreference(CanvasObject): 5 | def __str__(self): 6 | return "{} ({})".format(self.notification, self.frequency) 7 | -------------------------------------------------------------------------------- /canvasapi/outcome_import.py: -------------------------------------------------------------------------------- 1 | from canvasapi.canvas_object import CanvasObject 2 | 3 | 4 | class OutcomeImport(CanvasObject): 5 | def __str__(self): 6 | return "{} ({})".format(self.workflow_state, self.id) 7 | -------------------------------------------------------------------------------- /canvasapi/page_view.py: -------------------------------------------------------------------------------- 1 | from canvasapi.canvas_object import CanvasObject 2 | 3 | 4 | class PageView(CanvasObject): 5 | def __str__(self): 6 | return "{} ({})".format(self.context_type, self.id) 7 | -------------------------------------------------------------------------------- /canvasapi/pairing_code.py: -------------------------------------------------------------------------------- 1 | from canvasapi.canvas_object import CanvasObject 2 | 3 | 4 | class PairingCode(CanvasObject): 5 | def __str__(self): 6 | return "{} - {}".format(self.user_id, self.code) 7 | -------------------------------------------------------------------------------- /canvasapi/peer_review.py: -------------------------------------------------------------------------------- 1 | from canvasapi.canvas_object import CanvasObject 2 | 3 | 4 | class PeerReview(CanvasObject): 5 | def __str__(self): 6 | return "{} {} ({})".format(self.asset_id, self.user_id, self.id) 7 | -------------------------------------------------------------------------------- /canvasapi/planner.py: -------------------------------------------------------------------------------- 1 | from canvasapi.canvas_object import CanvasObject 2 | from canvasapi.util import combine_kwargs 3 | 4 | 5 | class PlannerNote(CanvasObject): 6 | def __str__(self): 7 | return "{} {} ({})".format(self.title, self.todo_date, self.id) 8 | 9 | def delete(self, **kwargs): 10 | """ 11 | Delete a planner note for the current user 12 | 13 | :calls: `DELETE /api/v1/planner_notes/:id \ 14 | `_ 15 | 16 | :rtype: :class:`canvasapi.planner.PlannerNote` 17 | """ 18 | response = self._requester.request( 19 | "DELETE", 20 | "planner_notes/{}".format(self.id), 21 | _kwargs=combine_kwargs(**kwargs), 22 | ) 23 | 24 | return PlannerNote(self._requester, response.json()) 25 | 26 | def update(self, **kwargs): 27 | """ 28 | Update a planner note for the current user 29 | 30 | :calls: `PUT /api/v1/planner_notes/:id \ 31 | `_ 32 | 33 | :rtype: :class:`canvasapi.planner.PlannerNote` 34 | """ 35 | 36 | response = self._requester.request( 37 | "PUT", "planner_notes/{}".format(self.id), _kwargs=combine_kwargs(**kwargs) 38 | ) 39 | return PlannerNote(self._requester, response.json()) 40 | 41 | 42 | class PlannerOverride(CanvasObject): 43 | def __str__(self): 44 | return "{} {} ({})".format(self.plannable_id, self.marked_complete, self.id) 45 | 46 | def delete(self, **kwargs): 47 | """ 48 | Delete a planner override for the current user 49 | 50 | :calls: `DELETE /api/v1/planner/overrides/:id \ 51 | `_ 52 | 53 | :rtype: :class:`canvasapi.planner.PlannerOverride` 54 | """ 55 | response = self._requester.request( 56 | "DELETE", 57 | "planner/overrides/{}".format(self.id), 58 | _kwargs=combine_kwargs(**kwargs), 59 | ) 60 | 61 | return PlannerOverride(self._requester, response.json()) 62 | 63 | def update(self, **kwargs): 64 | """ 65 | Update a planner override's visibilty for the current user 66 | 67 | :calls: `PUT /api/v1/planner/overrides/:id \ 68 | `_ 69 | 70 | :rtype: :class:`canvasapi.planner.PlannerOverride` 71 | """ 72 | 73 | response = self._requester.request( 74 | "PUT", 75 | "planner/overrides/{}".format(self.id), 76 | _kwargs=combine_kwargs(**kwargs), 77 | ) 78 | return PlannerOverride(self._requester, response.json()) 79 | -------------------------------------------------------------------------------- /canvasapi/poll_choice.py: -------------------------------------------------------------------------------- 1 | from canvasapi.canvas_object import CanvasObject 2 | from canvasapi.exceptions import RequiredFieldMissing 3 | from canvasapi.util import combine_kwargs 4 | 5 | 6 | class PollChoice(CanvasObject): 7 | def __str__(self): 8 | return "{} ({})".format(self.text, self.id) 9 | 10 | def delete(self, **kwargs): 11 | """ 12 | Delete a single poll, based on the poll id. 13 | 14 | :calls: `DELETE /api/v1/polls/:poll_id/poll_choices/:id \ 15 | `_ 16 | 17 | :returns: True if the deletion was successful, false otherwise. 18 | 19 | :rtype: bool 20 | """ 21 | response = self._requester.request( 22 | "DELETE", 23 | "polls/{}/poll_choices/{}".format(self.poll_id, self.id), 24 | _kwargs=combine_kwargs(**kwargs), 25 | ) 26 | return response.status_code == 204 27 | 28 | def update(self, poll_choice, **kwargs): 29 | """ 30 | Update an existing choice for this poll. 31 | 32 | :calls: `PUT /api/v1/polls/:poll_id/poll_choices/:id \ 33 | `_ 34 | 35 | :param poll_choice: List of arguments. 'text' is required and 'is_correct' and 'position' \ 36 | are optional. 37 | :type poll_choice: list 38 | :rtype: :class:`canvasapi.poll_choice.PollChoice` 39 | """ 40 | if ( 41 | isinstance(poll_choice, list) 42 | and isinstance(poll_choice[0], dict) 43 | and "text" in poll_choice[0] 44 | ): 45 | kwargs["poll_choice"] = poll_choice 46 | else: 47 | raise RequiredFieldMissing("Dictionary with key 'text' is required.") 48 | 49 | response = self._requester.request( 50 | "PUT", 51 | "polls/{}/poll_choices/{}".format(self.poll_id, self.id), 52 | _kwargs=combine_kwargs(**kwargs), 53 | ) 54 | return PollChoice(self._requester, response.json()["poll_choices"][0]) 55 | -------------------------------------------------------------------------------- /canvasapi/poll_submission.py: -------------------------------------------------------------------------------- 1 | from canvasapi.canvas_object import CanvasObject 2 | 3 | 4 | class PollSubmission(CanvasObject): 5 | def __str__(self): 6 | return "{} ({})".format(self.poll_choice_id, self.id) 7 | -------------------------------------------------------------------------------- /canvasapi/progress.py: -------------------------------------------------------------------------------- 1 | from canvasapi.canvas_object import CanvasObject 2 | from canvasapi.util import combine_kwargs 3 | 4 | 5 | class Progress(CanvasObject): 6 | def __str__(self): 7 | return "{} - {} ({})".format(self.tag, self.workflow_state, self.id) 8 | 9 | def query(self, **kwargs): 10 | """ 11 | Return completion and status information about an asynchronous job. 12 | 13 | :calls: `GET /api/v1/progress/:id \ 14 | `_ 15 | 16 | :rtype: :class:`canvasapi.progress.Progress` 17 | """ 18 | response = self._requester.request( 19 | "GET", 20 | "progress/{}".format(self.id), 21 | _kwargs=combine_kwargs(**kwargs), 22 | ) 23 | response_json = response.json() 24 | 25 | super(Progress, self).set_attributes(response_json) 26 | 27 | return Progress(self._requester, response_json) 28 | -------------------------------------------------------------------------------- /canvasapi/scope.py: -------------------------------------------------------------------------------- 1 | from canvasapi.canvas_object import CanvasObject 2 | 3 | 4 | class Scope(CanvasObject): 5 | def __str__(self): 6 | return "{}".format(self.resource) 7 | -------------------------------------------------------------------------------- /canvasapi/sis_import.py: -------------------------------------------------------------------------------- 1 | from canvasapi.canvas_object import CanvasObject 2 | from canvasapi.progress import Progress 3 | from canvasapi.util import combine_kwargs 4 | 5 | 6 | class SisImport(CanvasObject): 7 | def __str__(self): # pragma: no cover 8 | return "{} ({})".format(self.workflow_state, self.id) 9 | 10 | def abort(self, **kwargs): 11 | """ 12 | Abort this SIS import. 13 | 14 | :calls: `PUT /api/v1/accounts/:account_id/sis_imports/:id/abort \ 15 | `_ 16 | 17 | :rtype: :class:`canvasapi.sis_import.SisImport` 18 | """ 19 | response = self._requester.request( 20 | "PUT", 21 | "accounts/{}/sis_imports/{}/abort".format(self.account_id, self.id), 22 | _kwargs=combine_kwargs(**kwargs), 23 | ) 24 | return SisImport(self._requester, response.json()) 25 | 26 | def restore_states(self, **kwargs): 27 | """ 28 | Restore workflow_states of SIS imported items. 29 | 30 | :calls: `PUT /api/v1/accounts/:account_id/sis_imports/:id/restore_states \ 31 | `_ 32 | 33 | :rtype: :class:`canvasapi.progress.Progress` 34 | """ 35 | response = self._requester.request( 36 | "PUT", 37 | "accounts/{}/sis_imports/{}/restore_states".format( 38 | self.account_id, self.id 39 | ), 40 | _kwargs=combine_kwargs(**kwargs), 41 | ) 42 | return Progress(self._requester, response.json()) 43 | -------------------------------------------------------------------------------- /canvasapi/tab.py: -------------------------------------------------------------------------------- 1 | from canvasapi.canvas_object import CanvasObject 2 | from canvasapi.util import combine_kwargs 3 | 4 | 5 | class Tab(CanvasObject): 6 | def __str__(self): 7 | return "{} ({})".format(self.label, self.id) 8 | 9 | def update(self, **kwargs): 10 | """ 11 | Update a tab for a course. 12 | 13 | Note: Home and Settings tabs are not manageable, and can't be 14 | hidden or moved. 15 | 16 | :calls: `PUT /api/v1/courses/:course_id/tabs/:tab_id \ 17 | `_ 18 | 19 | :rtype: :class:`canvasapi.tab.Tab` 20 | """ 21 | if not hasattr(self, "course_id"): 22 | raise ValueError("Can only update tabs from a Course.") 23 | 24 | response = self._requester.request( 25 | "PUT", 26 | "courses/{}/tabs/{}".format(self.course_id, self.id), 27 | _kwargs=combine_kwargs(**kwargs), 28 | ) 29 | response_json = response.json() 30 | response_json.update({"course_id": self.course_id}) 31 | 32 | super(Tab, self).set_attributes(response_json) 33 | 34 | return self 35 | -------------------------------------------------------------------------------- /canvasapi/todo.py: -------------------------------------------------------------------------------- 1 | from canvasapi.canvas_object import CanvasObject 2 | 3 | 4 | class Todo(CanvasObject): 5 | def __str__(self): 6 | return "Todo Item ({})".format(self.type) 7 | -------------------------------------------------------------------------------- /canvasapi/usage_rights.py: -------------------------------------------------------------------------------- 1 | from canvasapi.canvas_object import CanvasObject 2 | 3 | 4 | class UsageRights(CanvasObject): 5 | def __str__(self): 6 | return "{} {}".format(self.use_justification, self.license) 7 | -------------------------------------------------------------------------------- /dev_requirements.txt: -------------------------------------------------------------------------------- 1 | -r tests_requirements.txt 2 | 3 | docutils==0.15.2 4 | pre-commit 5 | Sphinx 6 | sphinx-rtd-theme 7 | sphinx-version-warning 8 | -------------------------------------------------------------------------------- /docs/account-calendar-ref.rst: -------------------------------------------------------------------------------- 1 | =============== 2 | AccountCalendar 3 | =============== 4 | 5 | .. autoclass:: canvasapi.account_calendar.AccountCalendar 6 | :members: 7 | -------------------------------------------------------------------------------- /docs/account-ref.rst: -------------------------------------------------------------------------------- 1 | ======= 2 | Account 3 | ======= 4 | 5 | .. autoclass:: canvasapi.account.Account 6 | :members: 7 | 8 | =================== 9 | AccountNotification 10 | =================== 11 | 12 | .. autoclass:: canvasapi.account.AccountNotification 13 | :members: 14 | 15 | ============= 16 | AccountReport 17 | ============= 18 | 19 | .. autoclass:: canvasapi.account.AccountReport 20 | :members: 21 | 22 | ===== 23 | Admin 24 | ===== 25 | 26 | .. autoclass:: canvasapi.account.Admin 27 | :members: 28 | 29 | ==== 30 | Role 31 | ==== 32 | 33 | .. autoclass:: canvasapi.account.Role 34 | :members: 35 | 36 | =========== 37 | SSOSettings 38 | =========== 39 | 40 | .. autoclass:: canvasapi.account.SSOSettings 41 | :members: 42 | -------------------------------------------------------------------------------- /docs/appointment-group-ref.rst: -------------------------------------------------------------------------------- 1 | ================ 2 | AppointmentGroup 3 | ================ 4 | 5 | .. autoclass:: canvasapi.appointment_group.AppointmentGroup 6 | :members: -------------------------------------------------------------------------------- /docs/assignment-ref.rst: -------------------------------------------------------------------------------- 1 | ========== 2 | Assignment 3 | ========== 4 | 5 | .. autoclass:: canvasapi.assignment.Assignment 6 | :members: 7 | 8 | =================== 9 | AssignmentExtension 10 | =================== 11 | .. autoclass:: canvasapi.assignment.AssignmentExtension 12 | :members: 13 | 14 | =============== 15 | AssignmentGroup 16 | =============== 17 | .. autoclass:: canvasapi.assignment.AssignmentGroup 18 | :members: 19 | 20 | ================== 21 | AssignmentOverride 22 | ================== 23 | .. autoclass:: canvasapi.assignment.AssignmentOverride 24 | :members: 25 | -------------------------------------------------------------------------------- /docs/authentication-event-ref.rst: -------------------------------------------------------------------------------- 1 | =================== 2 | AuthenticationEvent 3 | =================== 4 | 5 | .. autoclass:: canvasapi.authentication_event.AuthenticationEvent 6 | :members: 7 | -------------------------------------------------------------------------------- /docs/authentication-provider-ref.rst: -------------------------------------------------------------------------------- 1 | ====================== 2 | AuthenticationProvider 3 | ====================== 4 | 5 | .. autoclass:: canvasapi.authentication_provider.AuthenticationProvider 6 | :members: 7 | -------------------------------------------------------------------------------- /docs/avatar-ref.rst: -------------------------------------------------------------------------------- 1 | ====== 2 | Avatar 3 | ====== 4 | 5 | .. autoclass:: canvasapi.avatar.Avatar 6 | :members: -------------------------------------------------------------------------------- /docs/blueprint-ref.rst: -------------------------------------------------------------------------------- 1 | ================== 2 | BlueprintMigration 3 | ================== 4 | 5 | .. autoclass:: canvasapi.blueprint.BlueprintMigration 6 | :members: 7 | 8 | ===================== 9 | BlueprintSubscription 10 | ===================== 11 | 12 | .. autoclass:: canvasapi.blueprint.BlueprintSubscription 13 | :members: 14 | 15 | ================= 16 | BlueprintTemplate 17 | ================= 18 | 19 | .. autoclass:: canvasapi.blueprint.BlueprintTemplate 20 | :members: 21 | 22 | ============ 23 | ChangeRecord 24 | ============ 25 | 26 | .. autoclass:: canvasapi.blueprint.ChangeRecord 27 | :members: 28 | -------------------------------------------------------------------------------- /docs/bookmark-ref.rst: -------------------------------------------------------------------------------- 1 | ======== 2 | Bookmark 3 | ======== 4 | 5 | .. autoclass:: canvasapi.bookmark.Bookmark 6 | :members: -------------------------------------------------------------------------------- /docs/calendar-event-ref.rst: -------------------------------------------------------------------------------- 1 | ============= 2 | CalendarEvent 3 | ============= 4 | 5 | .. autoclass:: canvasapi.calendar_event.CalendarEvent 6 | :members: -------------------------------------------------------------------------------- /docs/canvas-object-ref.rst: -------------------------------------------------------------------------------- 1 | ============= 2 | Canvas Object 3 | ============= 4 | 5 | .. autoclass:: canvasapi.canvas_object.CanvasObject 6 | :members: 7 | -------------------------------------------------------------------------------- /docs/canvas-ref.rst: -------------------------------------------------------------------------------- 1 | ====== 2 | Canvas 3 | ====== 4 | 5 | .. autoclass:: canvasapi.Canvas 6 | :members: -------------------------------------------------------------------------------- /docs/class-reference.rst: -------------------------------------------------------------------------------- 1 | Class Reference 2 | =============== 3 | 4 | .. toctree:: 5 | 6 | canvas-ref 7 | account-ref 8 | account-calendar-ref 9 | appointment-group-ref 10 | assignment-ref 11 | authentication-event-ref 12 | authentication-provider-ref 13 | avatar-ref 14 | blueprint-ref 15 | bookmark-ref 16 | calendar-event-ref 17 | collaboration-ref 18 | comm-message-ref 19 | communication-channel-ref 20 | content-export-ref 21 | content-migration-ref 22 | conversation-ref 23 | course-ref 24 | course-epub-export-ref 25 | course-event-ref 26 | current-user-ref 27 | custom-gradebook-columns-ref 28 | discussion-entry-ref 29 | discussion-topic-ref 30 | enrollment-ref 31 | enrollment-term-ref 32 | eportfolio-ref 33 | external-tool-ref 34 | favorite-ref 35 | feature-ref 36 | file-ref 37 | folder-ref 38 | grading-period-ref 39 | grade-change-log-ref 40 | group-ref 41 | jwt-ref 42 | login-ref 43 | license-ref 44 | lti-resource-link-ref 45 | module-ref 46 | outcome-ref 47 | outcome-import-ref 48 | page-ref 49 | pairing-code-ref 50 | planner-ref 51 | poll-ref 52 | poll-choice-ref 53 | poll-session-ref 54 | poll-submission-ref 55 | progress-ref 56 | quiz-ref 57 | quiz-group-ref 58 | rubric-ref 59 | scope-ref 60 | section-ref 61 | sis-import-ref 62 | submission-ref 63 | tab-ref 64 | todo-ref 65 | upload-ref 66 | user-ref 67 | usage-rights-ref 68 | -------------------------------------------------------------------------------- /docs/collaboration-ref.rst: -------------------------------------------------------------------------------- 1 | ============= 2 | Collaboration 3 | ============= 4 | 5 | .. autoclass:: canvasapi.collaboration.Collaboration 6 | :members: 7 | 8 | 9 | ============ 10 | Collaborator 11 | ============ 12 | 13 | .. autoclass:: canvasapi.collaboration.Collaborator 14 | :members: -------------------------------------------------------------------------------- /docs/comm-message-ref.rst: -------------------------------------------------------------------------------- 1 | =========== 2 | CommMessage 3 | =========== 4 | 5 | .. autoclass:: canvasapi.comm_message.CommMessage 6 | :members: 7 | -------------------------------------------------------------------------------- /docs/communication-channel-ref.rst: -------------------------------------------------------------------------------- 1 | ==================== 2 | CommunicationChannel 3 | ==================== 4 | 5 | .. autoclass:: canvasapi.communication_channel.CommunicationChannel 6 | :members: 7 | -------------------------------------------------------------------------------- /docs/content-export-ref.rst: -------------------------------------------------------------------------------- 1 | ================ 2 | ContentExport 3 | ================ 4 | 5 | .. autoclass:: canvasapi.content_export.ContentExport 6 | :members: 7 | -------------------------------------------------------------------------------- /docs/content-migration-ref.rst: -------------------------------------------------------------------------------- 1 | ================ 2 | ContentMigration 3 | ================ 4 | 5 | .. autoclass:: canvasapi.content_migration.ContentMigration 6 | :members: 7 | 8 | ============================= 9 | ContentMigrationSelectionNode 10 | ============================= 11 | 12 | .. autoclass:: canvasapi.content_migration.ContentMigrationSelectionNode 13 | :members: 14 | 15 | ============== 16 | MigrationIssue 17 | ============== 18 | 19 | .. autoclass:: canvasapi.content_migration.MigrationIssue 20 | :members: 21 | 22 | ======== 23 | Migrator 24 | ======== 25 | 26 | .. autoclass:: canvasapi.content_migration.Migrator 27 | :members: 28 | -------------------------------------------------------------------------------- /docs/conversation-ref.rst: -------------------------------------------------------------------------------- 1 | ============ 2 | Conversation 3 | ============ 4 | 5 | .. autoclass:: canvasapi.conversation.Conversation 6 | :members: 7 | -------------------------------------------------------------------------------- /docs/course-epub-export-ref.rst: -------------------------------------------------------------------------------- 1 | ================ 2 | CourseEpubExport 3 | ================ 4 | 5 | .. autoclass:: canvasapi.course_epub_export.CourseEpubExport 6 | :members: -------------------------------------------------------------------------------- /docs/course-event-ref.rst: -------------------------------------------------------------------------------- 1 | =========== 2 | CourseEvent 3 | =========== 4 | 5 | .. autoclass:: canvasapi.course_event.CourseEvent 6 | :members: 7 | -------------------------------------------------------------------------------- /docs/course-ref.rst: -------------------------------------------------------------------------------- 1 | ====== 2 | Course 3 | ====== 4 | 5 | .. autoclass:: canvasapi.course.Course 6 | :members: 7 | 8 | ============== 9 | CourseNickname 10 | ============== 11 | 12 | .. autoclass:: canvasapi.course.CourseNickname 13 | :members: 14 | 15 | ==================== 16 | CourseStudentSummary 17 | ==================== 18 | 19 | .. autoclass:: canvasapi.course.CourseStudentSummary 20 | :members: 21 | 22 | ========== 23 | LatePolicy 24 | ========== 25 | 26 | .. autoclass:: canvasapi.course.LatePolicy 27 | :members: 28 | -------------------------------------------------------------------------------- /docs/current-user-ref.rst: -------------------------------------------------------------------------------- 1 | =========== 2 | CurrentUser 3 | =========== 4 | 5 | The :class:`canvasapi.current_user.CurrentUser` class is a subclass of :class:`canvasapi.user.User`, and thus also includes all of its methods. 6 | 7 | `Documentation for the User class `_ 8 | 9 | .. autoclass:: canvasapi.current_user.CurrentUser 10 | :members: 11 | -------------------------------------------------------------------------------- /docs/custom-gradebook-columns-ref.rst: -------------------------------------------------------------------------------- 1 | ===================== 2 | CustomGradebookColumn 3 | ===================== 4 | 5 | .. autoclass:: canvasapi.custom_gradebook_columns.CustomGradebookColumn 6 | :members: 7 | 8 | ========== 9 | ColumnData 10 | ========== 11 | 12 | .. autoclass:: canvasapi.custom_gradebook_columns.ColumnData 13 | :members: -------------------------------------------------------------------------------- /docs/debugging.rst: -------------------------------------------------------------------------------- 1 | Debugging 2 | ========== 3 | 4 | As you work with CanvasAPI, you may encounter responses that don't match your expectations. In some cases, fixing the discrepancy is as simple as realizing you've misspelled a parameter name. In others, the CanvasAPI library might be improperly handling your input, and you'll need to submit a pull request or a bug report to get it resolved. Sometimes Canvas itself may be responding erroneously. 5 | 6 | Capturing Logs 7 | ----------------- 8 | CanvasAPI emits logs for important events through the `standard logging module `_. Inspecting these logs can make debugging much easier, as they provide a closer look at the abstracted network activity of the library. In order to view them, you'll need to tell Python where to put them. 9 | 10 | A basic logging configuration might look like this: 11 | 12 | .. code-block:: python 13 | 14 | import logging 15 | import sys 16 | 17 | logger = logging.getLogger("canvasapi") 18 | handler = logging.StreamHandler(sys.stdout) 19 | formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s') 20 | 21 | handler.setLevel(logging.DEBUG) 22 | handler.setFormatter(formatter) 23 | logger.addHandler(handler) 24 | logger.setLevel(logging.DEBUG) 25 | 26 | Once configured, any log event in the ``canvasapi`` module namespace will be printed to ``sys.stdout``: 27 | 28 | .. code:: python 29 | 30 | # A previously configured `Canvas` client 31 | >>> canvas.get_current_user() 32 | 2019-07-08 14:22:24,517 - canvasapi.requester - INFO - Request: GET https://base/api/v1/users/self 33 | 2019-07-08 14:22:24,517 - canvasapi.requester - DEBUG - Headers: {'Authorization': '****4BSt'} 34 | 2019-07-08 14:22:24,748 - canvasapi.requester - INFO - Response: GET https://base/api/v1/users/self 200 35 | 2019-07-08 14:22:24,749 - canvasapi.requester - DEBUG - Headers: {'Cache-Control': 'max-age=0, private, must-revalidate', 36 | 'Connection': 'keep-alive', 37 | 'Content-Encoding': 'gzip', 38 | 'Content-Length': '329', 39 | 'Content-Type': 'application/json; charset=utf-8', 40 | 2019-07-08 14:22:24,749 - canvasapi.requester - DEBUG - Data: {'avatar_url': 'https://base/images/thumbnails/43244/Umo5dyAg0OS3tpDtDN', 41 | 'created_at': '2014-08-22T08:02:00-04:00', 42 | 'effective_locale': 'en', 43 | 'email': '', 44 | 'id': XXXX181, 45 | 'integration_id': None, 46 | 'locale': None, 47 | 'login_id': 'XXXXXXXX181', 48 | 'name': 'Some User', 49 | 'permissions': {'can_update_avatar': True, 'can_update_name': False}, 50 | 'root_account': 'xxxxxx.edu', 51 | 'short_name': 'Some User', 52 | 'sis_user_id': 'XXXXXXXX181', 53 | 'sortable_name': 'User S'} -------------------------------------------------------------------------------- /docs/discussion-entry-ref.rst: -------------------------------------------------------------------------------- 1 | =============== 2 | DiscussionEntry 3 | =============== 4 | 5 | .. autoclass:: canvasapi.discussion_topic.DiscussionEntry 6 | :members: 7 | -------------------------------------------------------------------------------- /docs/discussion-topic-ref.rst: -------------------------------------------------------------------------------- 1 | =============== 2 | DiscussionTopic 3 | =============== 4 | 5 | .. autoclass:: canvasapi.discussion_topic.DiscussionTopic 6 | :members: 7 | -------------------------------------------------------------------------------- /docs/enrollment-ref.rst: -------------------------------------------------------------------------------- 1 | ========== 2 | Enrollment 3 | ========== 4 | 5 | .. autoclass:: canvasapi.enrollment.Enrollment 6 | :members: 7 | -------------------------------------------------------------------------------- /docs/enrollment-term-ref.rst: -------------------------------------------------------------------------------- 1 | ============== 2 | EnrollmentTerm 3 | ============== 4 | 5 | .. autoclass:: canvasapi.enrollment_term.EnrollmentTerm 6 | :members: -------------------------------------------------------------------------------- /docs/eportfolio-ref.rst: -------------------------------------------------------------------------------- 1 | ========== 2 | EPortfolio 3 | ========== 4 | 5 | .. autoclass:: canvasapi.eportfolio.EPortfolio 6 | :members: 7 | 8 | ============== 9 | EPortfolioPage 10 | ============== 11 | 12 | .. autoclass:: canvasapi.eportfolio.EPortfolioPage 13 | :members: 14 | -------------------------------------------------------------------------------- /docs/external-tool-ref.rst: -------------------------------------------------------------------------------- 1 | ============ 2 | ExternalTool 3 | ============ 4 | 5 | .. autoclass:: canvasapi.external_tool.ExternalTool 6 | :members: 7 | -------------------------------------------------------------------------------- /docs/favorite-ref.rst: -------------------------------------------------------------------------------- 1 | ======== 2 | Favorite 3 | ======== 4 | 5 | .. autoclass:: canvasapi.favorite.Favorite 6 | :members: 7 | -------------------------------------------------------------------------------- /docs/feature-ref.rst: -------------------------------------------------------------------------------- 1 | ======= 2 | Feature 3 | ======= 4 | 5 | .. autoclass:: canvasapi.feature.Feature 6 | :members: 7 | 8 | =========== 9 | FeatureFlag 10 | =========== 11 | 12 | .. autoclass:: canvasapi.feature.FeatureFlag 13 | :members: 14 | -------------------------------------------------------------------------------- /docs/file-ref.rst: -------------------------------------------------------------------------------- 1 | ==== 2 | File 3 | ==== 4 | 5 | .. autoclass:: canvasapi.file.File 6 | :members: 7 | -------------------------------------------------------------------------------- /docs/folder-ref.rst: -------------------------------------------------------------------------------- 1 | ====== 2 | Folder 3 | ====== 4 | 5 | .. autoclass:: canvasapi.folder.Folder 6 | :members: 7 | -------------------------------------------------------------------------------- /docs/grade-change-log-ref.rst: -------------------------------------------------------------------------------- 1 | ================ 2 | GradeChangeEvent 3 | ================ 4 | 5 | .. autoclass:: canvasapi.grade_change_log.GradeChangeEvent 6 | :members: 7 | -------------------------------------------------------------------------------- /docs/grading-period-ref.rst: -------------------------------------------------------------------------------- 1 | ============= 2 | GradingPeriod 3 | ============= 4 | 5 | .. autoclass:: canvasapi.grading_period.GradingPeriod 6 | :members: 7 | -------------------------------------------------------------------------------- /docs/group-ref.rst: -------------------------------------------------------------------------------- 1 | ===== 2 | Group 3 | ===== 4 | 5 | .. autoclass:: canvasapi.group.Group 6 | :members: 7 | 8 | =============== 9 | GroupMembership 10 | =============== 11 | 12 | .. autoclass:: canvasapi.group.GroupMembership 13 | :members: 14 | 15 | ============= 16 | GroupCategory 17 | ============= 18 | 19 | .. autoclass:: canvasapi.group.GroupCategory 20 | :members: 21 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | .. canvasapi documentation master file, created by 2 | sphinx-quickstart on Wed Apr 27 15:40:16 2016. 3 | You can adapt this file completely to your liking, but it should at least 4 | contain the root `toctree` directive. 5 | 6 | Welcome to CanvasAPI's documentation! 7 | ====================================== 8 | 9 | Contents: 10 | 11 | .. toctree:: 12 | :maxdepth: 2 13 | 14 | getting-started 15 | examples 16 | keyword-args 17 | exceptions 18 | troubleshooting 19 | debugging 20 | class-reference 21 | internal-classes 22 | -------------------------------------------------------------------------------- /docs/internal-classes.rst: -------------------------------------------------------------------------------- 1 | Internal Classes 2 | ================ 3 | 4 | The following classes are internal to CanvasAPI and are described here for reference only. They are not intended for use by consumers of the library. 5 | 6 | .. toctree:: 7 | 8 | canvas-object-ref 9 | paginated-list-ref 10 | requester-ref 11 | util-ref 12 | -------------------------------------------------------------------------------- /docs/jwt-ref.rst: -------------------------------------------------------------------------------- 1 | === 2 | JWT 3 | === 4 | 5 | .. autoclass:: canvasapi.jwt.JWT 6 | :members: 7 | -------------------------------------------------------------------------------- /docs/license-ref.rst: -------------------------------------------------------------------------------- 1 | ======= 2 | License 3 | ======= 4 | 5 | .. autoclass:: canvasapi.license.License 6 | :members: -------------------------------------------------------------------------------- /docs/login-ref.rst: -------------------------------------------------------------------------------- 1 | ===== 2 | Login 3 | ===== 4 | 5 | .. autoclass:: canvasapi.login.Login 6 | :members: 7 | -------------------------------------------------------------------------------- /docs/lti-resource-link-ref.rst: -------------------------------------------------------------------------------- 1 | =============== 2 | LTIResourceLink 3 | =============== 4 | 5 | .. autoclass:: canvasapi.lti_resource_link.LTIResourceLink 6 | :members: 7 | -------------------------------------------------------------------------------- /docs/module-ref.rst: -------------------------------------------------------------------------------- 1 | ====== 2 | Module 3 | ====== 4 | 5 | .. autoclass:: canvasapi.module.Module 6 | :members: 7 | 8 | ========== 9 | ModuleItem 10 | ========== 11 | 12 | .. autoclass:: canvasapi.module.ModuleItem 13 | :members: -------------------------------------------------------------------------------- /docs/outcome-import-ref.rst: -------------------------------------------------------------------------------- 1 | ============== 2 | OutcomeImport 3 | ============== 4 | 5 | .. autoclass:: canvasapi.outcome_import.OutcomeImport 6 | :members: -------------------------------------------------------------------------------- /docs/outcome-ref.rst: -------------------------------------------------------------------------------- 1 | ======= 2 | Outcome 3 | ======= 4 | 5 | .. autoclass:: canvasapi.outcome.Outcome 6 | :members: 7 | 8 | =========== 9 | OutcomeLink 10 | =========== 11 | 12 | .. autoclass:: canvasapi.outcome.OutcomeLink 13 | :members: 14 | 15 | ============ 16 | OutcomeGroup 17 | ============ 18 | 19 | .. autoclass:: canvasapi.outcome.OutcomeGroup 20 | :members: 21 | 22 | ============= 23 | OutcomeResult 24 | ============= 25 | 26 | .. autoclass:: canvasapi.outcome.OutcomeResult 27 | :members: 28 | -------------------------------------------------------------------------------- /docs/page-ref.rst: -------------------------------------------------------------------------------- 1 | ==== 2 | Page 3 | ==== 4 | 5 | .. autoclass:: canvasapi.page.Page 6 | :members: 7 | 8 | 9 | ============ 10 | PageRevision 11 | ============ 12 | .. autoclass:: canvasapi.page.PageRevision 13 | :members: 14 | -------------------------------------------------------------------------------- /docs/paginated-list-ref.rst: -------------------------------------------------------------------------------- 1 | ============= 2 | PaginatedList 3 | ============= 4 | 5 | .. autoclass:: canvasapi.paginated_list.PaginatedList 6 | :members: 7 | -------------------------------------------------------------------------------- /docs/pairing-code-ref.rst: -------------------------------------------------------------------------------- 1 | =========== 2 | PairingCode 3 | =========== 4 | 5 | .. autoclass:: canvasapi.pairing_code.PairingCode 6 | :members: 7 | -------------------------------------------------------------------------------- /docs/planner-ref.rst: -------------------------------------------------------------------------------- 1 | =========== 2 | PlannerNote 3 | =========== 4 | 5 | .. autoclass:: canvasapi.planner.PlannerNote 6 | :members: 7 | 8 | =============== 9 | PlannerOverride 10 | =============== 11 | 12 | .. autoclass:: canvasapi.planner.PlannerOverride 13 | :members: 14 | -------------------------------------------------------------------------------- /docs/poll-choice-ref.rst: -------------------------------------------------------------------------------- 1 | ========== 2 | PollChoice 3 | ========== 4 | 5 | .. autoclass:: canvasapi.poll_choice.PollChoice 6 | :members: 7 | -------------------------------------------------------------------------------- /docs/poll-ref.rst: -------------------------------------------------------------------------------- 1 | ==== 2 | Poll 3 | ==== 4 | 5 | .. autoclass:: canvasapi.poll.Poll 6 | :members: 7 | -------------------------------------------------------------------------------- /docs/poll-session-ref.rst: -------------------------------------------------------------------------------- 1 | =========== 2 | PollSession 3 | =========== 4 | 5 | .. autoclass:: canvasapi.poll_session.PollSession 6 | :members: 7 | -------------------------------------------------------------------------------- /docs/poll-submission-ref.rst: -------------------------------------------------------------------------------- 1 | ============== 2 | PollSubmission 3 | ============== 4 | 5 | .. autoclass:: canvasapi.poll_submission.PollSubmission 6 | :members: 7 | -------------------------------------------------------------------------------- /docs/progress-ref.rst: -------------------------------------------------------------------------------- 1 | ======== 2 | Progress 3 | ======== 4 | 5 | .. autoclass:: canvasapi.progress.Progress 6 | :members: 7 | -------------------------------------------------------------------------------- /docs/quiz-group-ref.rst: -------------------------------------------------------------------------------- 1 | ========= 2 | QuizGroup 3 | ========= 4 | 5 | .. autoclass:: canvasapi.quiz.QuizGroup 6 | :members: 7 | -------------------------------------------------------------------------------- /docs/quiz-ref.rst: -------------------------------------------------------------------------------- 1 | ==== 2 | Quiz 3 | ==== 4 | 5 | .. autoclass:: canvasapi.quiz.Quiz 6 | :members: 7 | 8 | ========================= 9 | QuizAssignmentOverrideSet 10 | ========================= 11 | 12 | .. autoclass:: canvasapi.quiz.QuizAssignmentOverrideSet 13 | :members: 14 | 15 | ============= 16 | QuizExtension 17 | ============= 18 | 19 | .. autoclass:: canvasapi.quiz.QuizExtension 20 | :members: 21 | 22 | ============ 23 | QuizQuestion 24 | ============ 25 | 26 | .. autoclass:: canvasapi.quiz.QuizQuestion 27 | :members: 28 | 29 | ========== 30 | QuizReport 31 | ========== 32 | 33 | .. autoclass:: canvasapi.quiz.QuizReport 34 | :members: 35 | 36 | ============== 37 | QuizSubmission 38 | ============== 39 | 40 | .. autoclass:: canvasapi.quiz.QuizSubmission 41 | :members: 42 | 43 | =================== 44 | QuizSubmissionEvent 45 | =================== 46 | 47 | .. autoclass:: canvasapi.quiz.QuizSubmissionEvent 48 | :members: 49 | 50 | ====================== 51 | QuizSubmissionQuestion 52 | ====================== 53 | 54 | .. autoclass:: canvasapi.quiz.QuizSubmissionQuestion 55 | :members: 56 | -------------------------------------------------------------------------------- /docs/requester-ref.rst: -------------------------------------------------------------------------------- 1 | ========= 2 | Requester 3 | ========= 4 | 5 | .. autoclass:: canvasapi.requester.Requester 6 | :members: -------------------------------------------------------------------------------- /docs/rubric-ref.rst: -------------------------------------------------------------------------------- 1 | ====== 2 | Rubric 3 | ====== 4 | 5 | .. autoclass:: canvasapi.rubric.Rubric 6 | :members: 7 | 8 | ================ 9 | RubricAssessment 10 | ================ 11 | 12 | .. autoclass:: canvasapi.rubric.RubricAssessment 13 | :members: 14 | 15 | ================== 16 | Rubric Association 17 | ================== 18 | 19 | .. autoclass:: canvasapi.rubric.RubricAssociation 20 | :members: 21 | -------------------------------------------------------------------------------- /docs/scope-ref.rst: -------------------------------------------------------------------------------- 1 | ===== 2 | Scope 3 | ===== 4 | 5 | .. autoclass:: canvasapi.scope.Scope 6 | :members: 7 | -------------------------------------------------------------------------------- /docs/section-ref.rst: -------------------------------------------------------------------------------- 1 | ======= 2 | Section 3 | ======= 4 | 5 | .. autoclass:: canvasapi.section.Section 6 | :members: -------------------------------------------------------------------------------- /docs/sis-import-ref.rst: -------------------------------------------------------------------------------- 1 | ========= 2 | SisImport 3 | ========= 4 | 5 | .. autoclass:: canvasapi.sis_import.SisImport 6 | :members: 7 | -------------------------------------------------------------------------------- /docs/submission-ref.rst: -------------------------------------------------------------------------------- 1 | ========== 2 | Submission 3 | ========== 4 | 5 | .. autoclass:: canvasapi.submission.Submission 6 | :members: 7 | -------------------------------------------------------------------------------- /docs/tab-ref.rst: -------------------------------------------------------------------------------- 1 | === 2 | Tab 3 | === 4 | 5 | .. autoclass:: canvasapi.tab.Tab 6 | :members: 7 | -------------------------------------------------------------------------------- /docs/todo-ref.rst: -------------------------------------------------------------------------------- 1 | ==== 2 | Todo 3 | ==== 4 | 5 | .. autoclass:: canvasapi.todo.Todo 6 | :members: 7 | -------------------------------------------------------------------------------- /docs/upload-ref.rst: -------------------------------------------------------------------------------- 1 | ======== 2 | Uploader 3 | ======== 4 | 5 | .. autoclass:: canvasapi.upload.Uploader 6 | :members: 7 | -------------------------------------------------------------------------------- /docs/usage-rights-ref.rst: -------------------------------------------------------------------------------- 1 | =========== 2 | UsageRights 3 | =========== 4 | 5 | .. autoclass:: canvasapi.usage_rights.UsageRights 6 | :members: -------------------------------------------------------------------------------- /docs/user-ref.rst: -------------------------------------------------------------------------------- 1 | ==== 2 | User 3 | ==== 4 | 5 | .. autoclass:: canvasapi.user.User 6 | :members: 7 | -------------------------------------------------------------------------------- /docs/util-ref.rst: -------------------------------------------------------------------------------- 1 | ==== 2 | Util 3 | ==== 4 | 5 | .. automodule:: canvasapi.util 6 | :members: -------------------------------------------------------------------------------- /markdown-style.rb: -------------------------------------------------------------------------------- 1 | all 2 | 3 | exclude_rule 'first-line-h1' 4 | exclude_rule 'line-length' 5 | exclude_rule 'no-duplicate-header' 6 | 7 | rule 'no-trailing-punctuation', :punctuation => '.,;:!' 8 | rule 'ol-prefix', :style => 'ordered' 9 | rule 'ul-indent', :indent => 2 10 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | arrow 2 | pytz 3 | requests 4 | -------------------------------------------------------------------------------- /scripts/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ucfopen/canvasapi/45ba5ac92eb5ced513f58fab180379ae02a3c0be/scripts/__init__.py -------------------------------------------------------------------------------- /scripts/alphabetic.py: -------------------------------------------------------------------------------- 1 | import inspect 2 | import operator 3 | import os 4 | import sys 5 | 6 | sys.path.append(os.path.join(sys.path[0], "..")) 7 | 8 | import canvasapi # noqa 9 | 10 | 11 | def main(): 12 | error_count = 0 13 | 14 | for _, module in inspect.getmembers(canvasapi, inspect.ismodule): 15 | # print(module.__name__) 16 | for class_name, theclass in inspect.getmembers(module, inspect.isclass): 17 | # Only process classes in this module 18 | if inspect.getmodule(theclass).__name__ != module.__name__: 19 | continue 20 | 21 | functions = list() 22 | for func_name, func in inspect.getmembers(theclass, inspect.isfunction): 23 | # Only add function if it is part of this class. 24 | # Get function's class name from qualified name. 25 | if func.__qualname__.split(".")[0] == class_name: 26 | functions.append((func_name, inspect.getsourcelines(func)[1])) 27 | 28 | error_count += check_alphabetical( 29 | functions, theclass.__module__, theclass.__name__ 30 | ) 31 | 32 | return error_count 33 | 34 | 35 | def check_alphabetical(methods, module_name, class_name): 36 | """ 37 | Verify that the methods are in alphabetical order 38 | 39 | :param methods: A list of tuples with the method name and line 40 | number of each method. 41 | :type methods: list 42 | :param module_name: The name of the module being checked 43 | :type module_name: str 44 | 45 | :returns: The number of mis-ordered methods found 46 | :rtype int: 47 | """ 48 | prev_func = "" 49 | prev_line = 0 50 | error_count = 0 51 | 52 | methods = sorted(methods, key=operator.itemgetter(0)) 53 | 54 | for func_name, line_no in methods: 55 | if line_no < prev_line: 56 | if error_count == 0: 57 | print("\n" + module_name + "." + class_name + "\n-----------") 58 | 59 | print( 60 | ( 61 | "{func_name} ({module_name}:{line_no}) came before " 62 | "{prev_func} ({module_name}:{prev_line})" 63 | ).format( 64 | func_name=func_name, 65 | line_no=line_no, 66 | prev_func=prev_func, 67 | prev_line=prev_line, 68 | module_name=module_name, 69 | ) 70 | ) 71 | error_count += 1 72 | 73 | prev_func = func_name 74 | prev_line = line_no 75 | 76 | return error_count 77 | 78 | 79 | if __name__ == "__main__": 80 | error_count = main() 81 | if error_count: 82 | print( 83 | "\nFound {error_count} method(s) not in alphabetical order. 💥".format( 84 | error_count=error_count 85 | ) 86 | ) 87 | else: 88 | print("All methods are alphabetical! 👍") 89 | sys.exit(error_count > 0) 90 | -------------------------------------------------------------------------------- /scripts/find_missing_kwargs.py: -------------------------------------------------------------------------------- 1 | import inspect 2 | import os 3 | import sys 4 | 5 | sys.path.append(os.path.join(sys.path[0], "..")) 6 | 7 | import canvasapi # noqa 8 | 9 | # Qualfied names of functions that are exempt from requiring kwargs 10 | WHITELIST = ( 11 | "Canvas.get_current_user", 12 | "CanvasObject.set_attributes", 13 | "File.download", 14 | "File.get_contents", 15 | "Uploader.request_upload_token", 16 | "Uploader.start", 17 | "Uploader.upload", 18 | "OutcomeGroup.context_ref", 19 | "OutcomeLink.context_ref", 20 | ) 21 | 22 | 23 | def find_missing_kwargs(): 24 | missing_count = 0 25 | 26 | for _, module in inspect.getmembers(canvasapi, inspect.ismodule): 27 | for class_name, theclass in inspect.getmembers(module, inspect.isclass): 28 | # Only process classes in this module 29 | if inspect.getmodule(theclass).__name__ != module.__name__: 30 | continue 31 | 32 | for func_name, func in inspect.getmembers(theclass, inspect.isfunction): 33 | # Only process function if it is part of this class. 34 | # Get function's class name from qualified name. 35 | if func.__qualname__.split(".")[0] == class_name: 36 | # ignore "private" and dunder methods 37 | if func_name.startswith("_"): 38 | continue 39 | 40 | # ignore functions in whitelist 41 | if func.__qualname__ in WHITELIST: 42 | continue 43 | 44 | if not accepts_kwargs(func): 45 | print(f"{func.__qualname__} is missing **kwargs") 46 | missing_count += 1 47 | 48 | return missing_count 49 | 50 | 51 | def accepts_kwargs(function): 52 | """ 53 | Determine whether or not the provided function accepts arbritrary keyword arguments. 54 | 55 | :param function: The function to look for **kwargs in 56 | :type function: 57 | 58 | :rtype: bool 59 | :returns: True if the function signature contains **kwargs. False otherwise. 60 | """ 61 | return "**kwargs" in str(inspect.signature(function)) 62 | 63 | 64 | if __name__ == "__main__": 65 | missing_count = find_missing_kwargs() 66 | if missing_count: 67 | print(f"---\nFound {missing_count} functions missing **kwargs. 💥") 68 | else: 69 | print("All functions have **kwargs! 👍") 70 | sys.exit(missing_count > 0) 71 | -------------------------------------------------------------------------------- /scripts/find_missing_modules.py: -------------------------------------------------------------------------------- 1 | import inspect 2 | import os 3 | import sys 4 | 5 | sys.path.append(os.path.join(sys.path[0], "..")) 6 | 7 | import canvasapi # noqa 8 | 9 | exempt_files = ("__init__",) 10 | 11 | 12 | def find_missing_modules(): 13 | # get all modules visile to inspect from `canvasapi.__init__` 14 | module_names = [ 15 | module_name 16 | for module_name, module in inspect.getmembers(canvasapi, inspect.ismodule) 17 | ] 18 | 19 | # get all .py files in canvasapi dir (without .py extension) 20 | path = "canvasapi" 21 | filenames = [fname[:-3] for fname in os.listdir(path) if fname.endswith(".py")] 22 | 23 | missing_modules = list() 24 | 25 | for filename in filenames: 26 | # ignore exempt files 27 | if filename in exempt_files: 28 | continue 29 | 30 | # check for missing files against module names 31 | if filename not in module_names: 32 | missing_modules.append(filename) 33 | 34 | return missing_modules 35 | 36 | 37 | if __name__ == "__main__": 38 | missing_modules = find_missing_modules() 39 | num_missing = len(missing_modules) 40 | missing_module_str = ", ".join(missing_modules) 41 | 42 | if missing_modules: 43 | print(f"Missing {num_missing} modules from inspect. 💥") 44 | for module in missing_modules: 45 | print(f" - {module}") 46 | print("Ensure the above modules are imported (even indirectly) to __init__") 47 | else: 48 | print("All modules accounted for! 👍") 49 | sys.exit(num_missing > 0) 50 | -------------------------------------------------------------------------------- /scripts/run_tests.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | coverage run -m unittest discover 4 | coverage report 5 | coverage html 6 | black --check canvasapi tests 7 | isort --check canvasapi tests 8 | flake8 canvasapi tests 9 | mdl . .github 10 | python scripts/find_missing_modules.py 11 | python scripts/alphabetic.py 12 | python scripts/find_missing_kwargs.py 13 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [coverage:run] 2 | source = canvasapi/ 3 | 4 | [flake8] 5 | max-line-length = 99 6 | exclude = .git,__pycache__,docs/conf.py,old,build,dist 7 | statistics = True 8 | show_source = True 9 | 10 | [isort] 11 | profile=black 12 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | import re 2 | from os import path 3 | 4 | from setuptools import setup 5 | 6 | # get version number 7 | with open("canvasapi/__init__.py", "r") as fd: 8 | version = re.search( 9 | r'^__version__\s*=\s*[\'"]([^\'"]*)[\'"]', fd.read(), re.MULTILINE 10 | ).group(1) 11 | 12 | if not version: 13 | raise RuntimeError("Cannot find version information") 14 | 15 | # Get the PyPI package info from the readme 16 | this_directory = path.abspath(path.dirname(__file__)) 17 | with open(path.join(this_directory, "README.md"), encoding="utf-8") as f: 18 | long_description = f.read() 19 | 20 | setup( 21 | name="canvasapi", 22 | version=version, 23 | description="API wrapper for the Canvas LMS", 24 | long_description=long_description, 25 | long_description_content_type="text/markdown", 26 | url="https://github.com/ucfopen/canvasapi", 27 | author="University of Central Florida - Center for Distributed Learning", 28 | author_email="techrangers@ucf.edu", 29 | license="MIT License", 30 | packages=["canvasapi"], 31 | include_package_data=True, 32 | install_requires=["arrow", "pytz", "requests"], 33 | zip_safe=False, 34 | classifiers=[ 35 | "Development Status :: 5 - Production/Stable", 36 | "Intended Audience :: Developers", 37 | "Intended Audience :: Education", 38 | "Intended Audience :: Information Technology", 39 | "License :: OSI Approved :: MIT License", 40 | "Operating System :: OS Independent", 41 | "Programming Language :: Python", 42 | "Programming Language :: Python :: 3.7", 43 | "Programming Language :: Python :: 3.8", 44 | "Programming Language :: Python :: 3.9", 45 | "Programming Language :: Python :: 3.10", 46 | "Programming Language :: Python :: 3.11", 47 | "Topic :: Software Development :: Libraries", 48 | ], 49 | ) 50 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ucfopen/canvasapi/45ba5ac92eb5ced513f58fab180379ae02a3c0be/tests/__init__.py -------------------------------------------------------------------------------- /tests/fixtures/404.html: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ucfopen/canvasapi/45ba5ac92eb5ced513f58fab180379ae02a3c0be/tests/fixtures/404.html -------------------------------------------------------------------------------- /tests/fixtures/announcements.json: -------------------------------------------------------------------------------- 1 | { 2 | "list_announcements": { 3 | "method": "GET", 4 | "endpoint": "announcements", 5 | "data": [{ 6 | "id": 1, 7 | "title": "Announcment #1", 8 | "author": { 9 | "id": 1, 10 | "display_name": "John Doe" 11 | }, 12 | "message": "Announcement Message #1", 13 | "subscription_hold": "topic_is_announcement", 14 | "context_code": "course_1" 15 | }, 16 | { 17 | "id": 2, 18 | "title": "Announcement #2", 19 | "author": { 20 | "id": 2, 21 | "display_name": "John Smith" 22 | }, 23 | "message": "Announcement Message #2", 24 | "subscription_hold": "topic_is_announcement", 25 | "context_code": "course_1" 26 | }, 27 | { 28 | "id": 3, 29 | "title": "Annoucement 3", 30 | "author": { 31 | "id": 3, 32 | "display_name": "John Wick" 33 | }, 34 | "message": "Announcement Message #3", 35 | "subscription_hold": "topic_is_announcement", 36 | "context_code": "group_1" 37 | }, 38 | { 39 | "id": 4, 40 | "title": "Annoucement 4", 41 | "author": { 42 | "id": 3, 43 | "display_name": "Jon Voight" 44 | }, 45 | "message": "Announcement Message #3", 46 | "subscription_hold": "topic_is_announcement", 47 | "context_code": "course_2" 48 | } 49 | ], 50 | "status_code": 200 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /tests/fixtures/appointment_group.json: -------------------------------------------------------------------------------- 1 | { 2 | "create_appointment_group": { 3 | "method": "POST", 4 | "endpoint": "appointment_groups", 5 | "data": { 6 | "id": 234, 7 | "context_codes": ["course_123"], 8 | "title": "Test Group" 9 | }, 10 | "status_code": 200 11 | }, 12 | "delete_appointment_group": { 13 | "method": "DELETE", 14 | "endpoint": "appointment_groups/567", 15 | "data": { 16 | "id": 567, 17 | "context_codes": ["course_765"], 18 | "title": "Test Group 3", 19 | "description": "Delete this appointment group" 20 | }, 21 | "status_code": 200 22 | }, 23 | "edit_appointment_group": { 24 | "method": "PUT", 25 | "endpoint": "appointment_groups/567", 26 | "data": { 27 | "id": 567, 28 | "title": "New Name", 29 | "description": "Edit this appointment group" 30 | }, 31 | "status_code": 200 32 | }, 33 | "get_appointment_group": { 34 | "method": "GET", 35 | "endpoint": "appointment_groups/567", 36 | "data": { 37 | "id": 567, 38 | "context_codes": ["course_765"], 39 | "title": "Test Group 3" 40 | }, 41 | "status_code": 200 42 | }, 43 | "get_appointment_group_222": { 44 | "method": "GET", 45 | "endpoint": "appointment_groups/222", 46 | "data": { 47 | "id": 222, 48 | "context_codes": ["course_765"], 49 | "title": "Test Group 3" 50 | }, 51 | "status_code": 200 52 | }, 53 | "list_appointment_groups": { 54 | "method": "GET", 55 | "endpoint": "appointment_groups", 56 | "data": [ 57 | { 58 | "id": 123, 59 | "context_codes": ["course_321"], 60 | "title": "Test Group 1" 61 | }, 62 | { 63 | "id": 456, 64 | "context_codes": ["course_654"], 65 | "title": "Test Group 2" 66 | } 67 | ], 68 | "status_code": 200 69 | }, 70 | "list_user_participants": { 71 | "method": "GET", 72 | "endpoint": "appointment_groups/222/users", 73 | "data": [ 74 | { 75 | "id": 123, 76 | "name": "John Doe" 77 | }, 78 | { 79 | "id": 456, 80 | "name": "John Smith" 81 | } 82 | ], 83 | "status_code": 200 84 | }, 85 | "list_group_participants": { 86 | "method": "GET", 87 | "endpoint": "appointment_groups/222/groups", 88 | "data": [ 89 | { 90 | "id": 123, 91 | "name": "John Doe" 92 | }, 93 | { 94 | "id": 456, 95 | "name": "John Smith" 96 | } 97 | ], 98 | "status_code": 200 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /tests/fixtures/authentication_providers.json: -------------------------------------------------------------------------------- 1 | { 2 | "list_authentication_providers": { 3 | "method": "GET", 4 | "endpoint": "accounts/1/authentication_providers", 5 | "data": [ 6 | { 7 | "id": 1, 8 | "auth_type": "twitter", 9 | "position": 1 10 | }, 11 | { 12 | "id": 2, 13 | "auth_type": "facebook", 14 | "position": 1 15 | } 16 | ], 17 | "headers": { 18 | "Link": "; rel=\"next\"" 19 | }, 20 | "status_code": 200 21 | }, 22 | "list_authentication_providers_2": { 23 | "method": "GET", 24 | "endpoint": "accounts/1/authentication_providers/?page=2&per_page=2", 25 | "data": [ 26 | { 27 | "id": 3, 28 | "auth_type": "canvas", 29 | "position": 1 30 | }, 31 | { 32 | "id": 4, 33 | "auth_type": "microsoft", 34 | "position": 1 35 | } 36 | ], 37 | "status_code": 200 38 | }, 39 | "add_authentication_providers": { 40 | "method": "POST", 41 | "endpoint": "accounts/1/authentication_providers", 42 | "data":{ 43 | "id": 1, 44 | "auth_type": "saml", 45 | "position": 1 46 | }, 47 | "status_code": 200 48 | }, 49 | "update_authentication_providers": { 50 | "method": "PUT", 51 | "endpoint": "accounts/1/authentication_providers/1", 52 | "data": { 53 | "id": 1, 54 | "auth_type": "New Authentication Providers", 55 | "position": 1 56 | }, 57 | "status_code": 200 58 | }, 59 | "delete_authentication_providers": { 60 | "method": "DELETE", 61 | "endpoint": "accounts/1/authentication_providers/1", 62 | "data": { 63 | "id": 1, 64 | "auth_type": "Authentication Providers", 65 | "position": 1 66 | }, 67 | "status_code": 200 68 | } 69 | } -------------------------------------------------------------------------------- /tests/fixtures/bookmark.json: -------------------------------------------------------------------------------- 1 | { 2 | "create_bookmark": { 3 | "method": "POST", 4 | "endpoint": "users/self/bookmarks", 5 | "data": { 6 | "id": 45, 7 | "name": "Test Bookmark", 8 | "url": "https://www.google.com" 9 | }, 10 | "status_code": 200 11 | }, 12 | "delete_bookmark": { 13 | "method": "DELETE", 14 | "endpoint": "users/self/bookmarks/45", 15 | "data": { 16 | "id": 45, 17 | "name": "Test Bookmark 3" 18 | }, 19 | "status_code": 200 20 | }, 21 | "edit_bookmark": { 22 | "method": "PUT", 23 | "endpoint": "users/self/bookmarks/45", 24 | "data": { 25 | "id": 45, 26 | "name": "New Name", 27 | "url": "http://happy-place.com" 28 | }, 29 | "status_code": 200 30 | }, 31 | "get_bookmark": { 32 | "method": "GET", 33 | "endpoint": "users/self/bookmarks/45", 34 | "data": { 35 | "id": 45, 36 | "name": "Test Bookmark 3" 37 | }, 38 | "status_code": 200 39 | }, 40 | "list_bookmarks": { 41 | "method": "GET", 42 | "endpoint": "users/self/bookmarks", 43 | "data": [ 44 | { 45 | "id": 56, 46 | "name": "Test Bookmark 1" 47 | }, 48 | { 49 | "id": 67, 50 | "name": "Test Bookmark 2" 51 | } 52 | ], 53 | "status_code": 200 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /tests/fixtures/calendar_event.json: -------------------------------------------------------------------------------- 1 | { 2 | "create_calendar_event": { 3 | "method": "POST", 4 | "endpoint": "calendar_events", 5 | "data": { 6 | "id": 234, 7 | "context_code": "course_123", 8 | "title": "Test Event" 9 | }, 10 | "status_code": 200 11 | }, 12 | "delete_calendar_event": { 13 | "method": "DELETE", 14 | "endpoint": "calendar_events/567", 15 | "data": { 16 | "id": 567, 17 | "context_code": "course_765", 18 | "title": "Test Event 3", 19 | "description": "Delete this assignment" 20 | }, 21 | "status_code": 200 22 | }, 23 | "edit_calendar_event": { 24 | "method": "PUT", 25 | "endpoint": "calendar_events/567", 26 | "data": { 27 | "id": 567, 28 | "course_id": 1, 29 | "title": "New Name", 30 | "description": "Edit this assignment" 31 | }, 32 | "status_code": 200 33 | }, 34 | "get_calendar_event": { 35 | "method": "GET", 36 | "endpoint": "calendar_events/567", 37 | "data": { 38 | "id": 567, 39 | "context_code": "course_765", 40 | "title": "Test Event 3" 41 | }, 42 | "status_code": 200 43 | }, 44 | "list_calendar_events": { 45 | "method": "GET", 46 | "endpoint": "calendar_events", 47 | "data": [ 48 | { 49 | "id": 123, 50 | "context_code": "course_321", 51 | "title": "Test Event 1" 52 | }, 53 | { 54 | "id": 456, 55 | "context_code": "course_654", 56 | "title": "Test Event 2" 57 | } 58 | ], 59 | "status_code": 200 60 | }, 61 | "reserve_time_slot": { 62 | "method": "POST", 63 | "endpoint": "calendar_events/567/reservations", 64 | "data": { 65 | "id": 567, 66 | "context_code": "course_123", 67 | "title": "Test Reservation" 68 | }, 69 | "status_code": 200 70 | }, 71 | "reserve_time_slot_participant_id": { 72 | "method": "POST", 73 | "endpoint": "calendar_events/567/reservations/777", 74 | "data": { 75 | "id": 567, 76 | "context_code": "course_123", 77 | "title": "Test Reservation", 78 | "user": 777 79 | }, 80 | "status_code": 200 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /tests/fixtures/collaboration.json: -------------------------------------------------------------------------------- 1 | { 2 | "get_collaborators": { 3 | "method": "GET", 4 | "endpoint": "collaborations/1/members", 5 | "data": { 6 | "collaborators": [ 7 | { 8 | "id": 12345, 9 | "type": "user", 10 | "name": "Don Draper" 11 | } 12 | ] 13 | }, 14 | "status_code": 200 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /tests/fixtures/comm_message.json: -------------------------------------------------------------------------------- 1 | { 2 | "comm_messages": { 3 | "method": "GET", 4 | "endpoint": "comm_messages", 5 | "data": [{ 6 | "id": 42, 7 | "created_at": "2013-03-19T21:00:00Z", 8 | "sent_at": "2013-03-20T22:42:00Z", 9 | "workflow_state": "sent", 10 | "from": "notifications@example.com", 11 | "from_name": "Instructure Canvas", 12 | "to": "someone@example.com", 13 | "reply_to": "notifications+specialdata@example.com", 14 | "subject": "example subject line", 15 | "body": "This is the body of the message", 16 | "html_body": "This is the body of the message" 17 | }, 18 | { 19 | "id": 2, 20 | "created_at": "2013-03-19T21:00:00Z", 21 | "sent_at": "2013-03-20T22:42:00Z", 22 | "workflow_state": "sent", 23 | "from": "johndoe@ucf.edu", 24 | "from_name": "John Doe", 25 | "to": "janedoe@ucf.edu", 26 | "reply_to": "notifications+specialdata@example.com", 27 | "subject": "My Subject", 28 | "body": "My Message Body", 29 | "html_body": "My Message Body" 30 | } 31 | ], 32 | "status_code": 200 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /tests/fixtures/custom_gradebook_columns.json: -------------------------------------------------------------------------------- 1 | { 2 | "delete": { 3 | "method": "DELETE", 4 | "endpoint": "courses/1/custom_gradebook_columns/2", 5 | "data": { 6 | "id": 2, 7 | "teacher_notes": false, 8 | "title": "Deleted Column", 9 | "position": 1, 10 | "hidden": false, 11 | "read_only": true 12 | }, 13 | "status_code": 200 14 | }, 15 | "get_column_entries": { 16 | "method": "GET", 17 | "endpoint": "courses/1/custom_gradebook_columns/2/data", 18 | "data": [ 19 | { 20 | "content": "Example content", 21 | "user_id": 1 22 | }, 23 | { 24 | "content": "More example content", 25 | "user_id": 1 26 | } 27 | ], 28 | "status_code": 200 29 | }, 30 | "reorder_custom_columns": { 31 | "method": "POST", 32 | "endpoint": "courses/1/custom_gradebook_columns/reorder", 33 | "data": { 34 | "reorder": true, 35 | "order": [1, 2, 3] 36 | }, 37 | "status_code": 200 38 | }, 39 | "update_custom_column": { 40 | "method": "PUT", 41 | "endpoint": "courses/1/custom_gradebook_columns/2", 42 | "data": { 43 | "id": 2, 44 | "teacher_notes": false, 45 | "title": "Example title", 46 | "position": 1, 47 | "hidden": false, 48 | "read_only": true 49 | }, 50 | "status_code": 200 51 | }, 52 | "update_column_data": { 53 | "method": "PUT", 54 | "endpoint": "courses/1/custom_gradebook_columns/2/data/1", 55 | "data": { 56 | "content": "Updated content", 57 | "user_id": 1 58 | }, 59 | "status_code": 200 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /tests/fixtures/enrollment.json: -------------------------------------------------------------------------------- 1 | { 2 | "deactivate": { 3 | "method": "DELETE", 4 | "endpoint": "courses/1/enrollments/1", 5 | "data": { 6 | "id": 1, 7 | "course_id": 1, 8 | "user_id": 1, 9 | "type": "StudentEnrollment", 10 | "state": "inactive" 11 | }, 12 | "status_code": 200 13 | }, 14 | "get_by_id": { 15 | "method": "GET", 16 | "endpoint": "accounts/1/enrollments/1", 17 | "data": { 18 | "id": 1, 19 | "course_id": 1, 20 | "user_id": 1, 21 | "type": "StudentEnrollment" 22 | }, 23 | "status_code": 200 24 | }, 25 | "reactivate": { 26 | "method": "PUT", 27 | "endpoint": "courses/1/enrollments/1/reactivate", 28 | "data": { 29 | "id": 1, 30 | "course_id": 1, 31 | "user_id": 1, 32 | "type": "StudentEnrollment", 33 | "state": "active" 34 | }, 35 | "status_code": 200 36 | }, 37 | "accept": { 38 | "method": "POST", 39 | "endpoint": "courses/1/enrollments/1/accept", 40 | "data": { 41 | "success": true 42 | }, 43 | "status_code": 200 44 | }, 45 | "reject": { 46 | "method": "POST", 47 | "endpoint": "courses/1/enrollments/1/reject", 48 | "data": { 49 | "success": true 50 | }, 51 | "status_code": 200 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /tests/fixtures/enrollment_term.json: -------------------------------------------------------------------------------- 1 | { 2 | "create_enrollment_term": { 3 | "method": "POST", 4 | "endpoint": "accounts/1/terms", 5 | "data": { 6 | "id": 45, 7 | "name": "Test Enrollment Term" 8 | }, 9 | "status_code": 200 10 | }, 11 | "delete_enrollment_term": { 12 | "method": "DELETE", 13 | "endpoint": "accounts/1/terms/45", 14 | "data": { 15 | "id": 45, 16 | "name": "Test Enrollment Term" 17 | }, 18 | "status_code": 200 19 | }, 20 | "edit_enrollment_term": { 21 | "method": "PUT", 22 | "endpoint": "accounts/1/terms/45", 23 | "data": { 24 | "id": 45, 25 | "name": "New Name" 26 | }, 27 | "status_code": 200 28 | }, 29 | "get_enrollment_term": { 30 | "method": "GET", 31 | "endpoint": "accounts/1/terms/45", 32 | "data": { 33 | "id": 45, 34 | "name": "Test Enrollment Term 3" 35 | }, 36 | "status_code": 200 37 | }, 38 | "list_enrollment_terms": { 39 | "method": "GET", 40 | "endpoint": "accounts/1/terms", 41 | "data": [ 42 | { 43 | "id": 56, 44 | "name": "Test Enrollment Term 1" 45 | }, 46 | { 47 | "id": 67, 48 | "name": "Test Enrollment Term 2" 49 | } 50 | ], 51 | "status_code": 200 52 | } 53 | } -------------------------------------------------------------------------------- /tests/fixtures/eportfolio.json: -------------------------------------------------------------------------------- 1 | { 2 | "delete_eportfolio": { 3 | "method": "DELETE", 4 | "endpoint": "eportfolios/1", 5 | "data": { 6 | "id": 1, 7 | "name": "ePortfolio 1", 8 | "workflow_state": "deleted", 9 | "description": "Delete this ePortfolio", 10 | "deleted_at": "2022-07-05T21:00:00Z" 11 | }, 12 | "status_code": 200 13 | }, 14 | "get_eportfolio_by_id": { 15 | "method": "GET", 16 | "endpoint": "eportfolios/1", 17 | "data": { 18 | "id": 1, 19 | "user_id": 1, 20 | "workflow_state": "active", 21 | "name": "ePortfolio 1", 22 | "deleted_at": "null", 23 | "spam_status": "null" 24 | }, 25 | "status_code": 200 26 | }, 27 | "get_eportfolio_pages": { 28 | "method": "GET", 29 | "endpoint": "eportfolios/1/pages", 30 | "data": [ 31 | { 32 | "id": 1, 33 | "eportfolio_id": 1, 34 | "position": 1, 35 | "name": "ePortfolio 1", 36 | "content": "This is the page of content", 37 | "created_at": "2022-07-01T18:00:00Z", 38 | "updated_at": "2021-07-04T18:00:00Z" 39 | }, 40 | { 41 | "id": 2, 42 | "eportfolio_id": 1, 43 | "position": 2, 44 | "name": "ePortfolio 1", 45 | "content": "This is the second page of content", 46 | "created_at": "2022-07-02T18:00:00Z", 47 | "updated_at": "2021-07-02T18:00:00Z" 48 | } 49 | ], 50 | "status_code": 200 51 | }, 52 | "moderate_eportfolio_as_spam": { 53 | "method": "PUT", 54 | "endpoint": "eportfolios/1/moderate", 55 | "data": { 56 | "id": 1, 57 | "user_id": 1, 58 | "workflow_state": "active", 59 | "name": "ePortfolio 1", 60 | "deleted_at": "null", 61 | "spam_status": "marked_as_spam" 62 | } 63 | }, 64 | "restore_deleted_eportfolio": { 65 | "method": "PUT", 66 | "endpoint": "eportfolios/1/restore", 67 | "data": { 68 | "id": 1, 69 | "user_id": 1, 70 | "workflow_state": "active", 71 | "name": "ePortfolio 1", 72 | "deleted_at": "null" 73 | } 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /tests/fixtures/external_tool.json: -------------------------------------------------------------------------------- 1 | { 2 | "create_tool_account": { 3 | "method": "POST", 4 | "endpoint": "accounts/1/external_tools", 5 | "data": { 6 | "id": 10, 7 | "name": "External Tool - Account", 8 | "privacy_level": "public", 9 | "consumer_key": "key" 10 | } 11 | }, 12 | "create_tool_course": { 13 | "method": "POST", 14 | "endpoint": "courses/1/external_tools", 15 | "data": { 16 | "id": 20, 17 | "name": "External Tool - Course", 18 | "privacy_level": "public", 19 | "consumer_key": "key" 20 | } 21 | }, 22 | "delete_tool_course": { 23 | "method": "DELETE", 24 | "endpoint": "courses/1/external_tools/1", 25 | "data": { 26 | "id": 1, 27 | "name": "External Tool #1 (Course)", 28 | "description": "This is an external tool in a course.", 29 | "url": "http://www.example.com/ims/lti", 30 | "privacy_level": "anonymous", 31 | "created_at": "2015-01-01T01:01:01Z", 32 | "updated_at": "2016-06-17T14:20:00Z" 33 | }, 34 | "status_code": 200 35 | }, 36 | "edit_tool_course": { 37 | "method": "PUT", 38 | "endpoint": "courses/1/external_tools/1", 39 | "data": { 40 | "id": 1, 41 | "name": "New Tool Name", 42 | "description": "This is an external tool in a course.", 43 | "url": "http://www.example.com/ims/lti", 44 | "privacy_level": "anonymous", 45 | "created_at": "2015-01-01T01:01:01Z", 46 | "updated_at": "2016-06-17T14:20:00Z" 47 | }, 48 | "status_code": 200 49 | }, 50 | "get_by_id_account": { 51 | "method": "GET", 52 | "endpoint": "accounts/1/external_tools/1", 53 | "data": { 54 | "id": 1, 55 | "name": "External Tool #1 (Account)", 56 | "description": "This is an external tool for an account.", 57 | "url": "http://www.example.com/ims/lti", 58 | "privacy_level": "anonymous", 59 | "created_at": "2015-01-01T01:01:01Z", 60 | "updated_at": "2016-06-17T14:20:00Z" 61 | }, 62 | "status_code": 200 63 | }, 64 | "get_by_id_course": { 65 | "method": "GET", 66 | "endpoint": "courses/1/external_tools/1", 67 | "data": { 68 | "id": 1, 69 | "name": "External Tool #1 (Course)", 70 | "description": "This is an external tool in a course.", 71 | "url": "http://www.example.com/ims/lti", 72 | "privacy_level": "anonymous", 73 | "created_at": "2015-01-01T01:01:01Z", 74 | "updated_at": "2016-06-17T14:20:00Z" 75 | }, 76 | "status_code": 200 77 | }, 78 | "get_by_id_course_2": { 79 | "method": "GET", 80 | "endpoint": "courses/2/external_tools/2", 81 | "data": { 82 | "id": 2, 83 | "name": "External Tool #2 (Course)", 84 | "description": "This is an external tool in a course.", 85 | "url": "http://www.example.com/ims/lti", 86 | "privacy_level": "anonymous", 87 | "created_at": "2015-01-01T01:01:01Z", 88 | "updated_at": "2016-06-17T14:20:00Z" 89 | }, 90 | "status_code": 200 91 | }, 92 | "get_sessionless_launch_url_course": { 93 | "method": "GET", 94 | "endpoint": "courses/1/external_tools/sessionless_launch", 95 | "data": { 96 | "id": "1", 97 | "name": "External Tool #1 (Course)", 98 | "url": "https://example.com/courses/1/external_tools/sessionless_launch/?verifier=1337" 99 | }, 100 | "status_code": 200 101 | }, 102 | "sessionless_launch_no_url": { 103 | "method": "GET", 104 | "endpoint": "courses/2/external_tools/sessionless_launch", 105 | "data": { 106 | "id": "2", 107 | "name": "External Tool #2 (Course)" 108 | }, 109 | "status_code": 200 110 | } 111 | } -------------------------------------------------------------------------------- /tests/fixtures/favorite.json: -------------------------------------------------------------------------------- 1 | { 2 | "get_course_by_id": { 3 | "method": "GET", 4 | "endpoint": "/users/self/favorites/courses/1", 5 | "data": { 6 | "context_id": 1, 7 | "context_type": "course" 8 | }, 9 | "status_code": 200 10 | }, 11 | "get_group_by_id": { 12 | "method": "GET", 13 | "endpoint": "/users/self/favorites/groups/1", 14 | "data": { 15 | "context_id": 1, 16 | "context_type": "group" 17 | }, 18 | "status_code": 200 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /tests/fixtures/file.json: -------------------------------------------------------------------------------- 1 | { 2 | "delete_file": { 3 | "method": "DELETE", 4 | "endpoint": "files/1", 5 | "data": { 6 | "id": 1, 7 | "display_name": "Bad File.docx", 8 | "size": 5512, 9 | "url": "file_download" 10 | }, 11 | "status_code": 200 12 | }, 13 | "get_by_id": { 14 | "method": "GET", 15 | "endpoint": "files/1", 16 | "data": { 17 | "id": 1, 18 | "display_name": "File.docx", 19 | "size": 6144, 20 | "url": "file_download" 21 | }, 22 | "status_code": 200 23 | }, 24 | "file_contents": { 25 | "method": "GET", 26 | "endpoint": "ANY", 27 | "data": "Hello there", 28 | "status_code": 200 29 | }, 30 | "file_download": { 31 | "method": "GET", 32 | "endpoint": "ANY", 33 | "data": "file contents are here", 34 | "status_code": 200 35 | }, 36 | "update_file": { 37 | "method": "PUT", 38 | "endpoint": "files/1", 39 | "data": { 40 | "id": 1, 41 | "display_name": "New filename.docx" 42 | }, 43 | "status_code": 200 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /tests/fixtures/files.html: -------------------------------------------------------------------------------- 1 |

2 |

3 |

4 | DELETE /api/v1/files/:id 5 |

6 |

Remove the specified file

7 |

8 |

9 |

10 | GET /api/v1/courses/:course_id/folders/:id 11 |

12 |

13 | GET /api/v1/users/:user_id/folders/:id 14 |

15 |

16 | GET /api/v1/groups/:group_id/folders/:id 17 |

18 |

19 | GET /api/v1/folders/:id 20 |

21 |

Returns the details for a folder

22 | -------------------------------------------------------------------------------- /tests/fixtures/generic.json: -------------------------------------------------------------------------------- 1 | { 2 | "not_found": { 3 | "method": "ANY", 4 | "endpoint": "ANY", 5 | "status_code": 404 6 | }, 7 | "unauthorized": { 8 | "method": "ANY", 9 | "endpoint": "ANY", 10 | "status_code": 401 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /tests/fixtures/generic_file.txt: -------------------------------------------------------------------------------- 1 | This is a generic file for testing file uploads. 2 | -------------------------------------------------------------------------------- /tests/fixtures/grading_period.json: -------------------------------------------------------------------------------- 1 | { 2 | "update": { 3 | "method": "PUT", 4 | "endpoint": "courses/1/grading_periods/1", 5 | "data": { 6 | "grading_periods": [ 7 | { 8 | "title": "Grading period 1", 9 | "id": 1, 10 | "start_date" : "2019-05-23T06:00:00Z", 11 | "end_date" : "2019-08-23T06:00:00Z", 12 | "weight" : 1, 13 | "course_id": 1 14 | } 15 | ] 16 | }, 17 | "status_code": 200 18 | }, 19 | "delete": { 20 | "method": "DELETE", 21 | "endpoint": "courses/1/grading_periods/1", 22 | "status_code": 204 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /tests/fixtures/graphql.json: -------------------------------------------------------------------------------- 1 | { 2 | "graphql": { 3 | "method": "POST", 4 | "endpoint": "graphql", 5 | "data": { 6 | "term": { 7 | "coursesConnection": { 8 | "nodes": [ 9 | { 10 | "_id": "1", 11 | "assignmentsConnection": { 12 | "nodes": [] 13 | } 14 | } 15 | ] 16 | } 17 | } 18 | }, 19 | "status_code": 200 20 | } 21 | } -------------------------------------------------------------------------------- /tests/fixtures/jwt.json: -------------------------------------------------------------------------------- 1 | { 2 | "create_jwt": { 3 | "method": "POST", 4 | "endpoint": "jwts", 5 | "data": { 6 | "token": "ZjM0UTZmLyVNSjdqb10wLV9jQSxeUiogXUlCWUs7Tg==" 7 | } 8 | }, 9 | 10 | "refresh_jwt": { 11 | "method": "POST", 12 | "endpoint": "jwts/refresh", 13 | "data": { 14 | "token": "O3MzNjpPKWc+fmFfMXRJJiEoR1VbSDVDT1IzUF1IJUpjJ3JSe0lrMHw8OUlX" 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /tests/fixtures/login.json: -------------------------------------------------------------------------------- 1 | { 2 | "create_user_login": { 3 | "method": "POST", 4 | "endpoint": "accounts/1/logins", 5 | "data": { 6 | "account_id": 1, 7 | "id": 101, 8 | "unique_id": "belieber@example.com", 9 | "user_id": 1 10 | }, 11 | "status_code": 200 12 | }, 13 | "list_user_logins": { 14 | "method": "GET", 15 | "endpoint": "accounts/1/logins", 16 | "data": [ 17 | { 18 | "account_id": 1, 19 | "id": 101, 20 | "unique_id": "belieber@example.com", 21 | "user_id": 1 22 | } 23 | ], 24 | "headers": { 25 | "Link": "; rel=\"next\"" 26 | }, 27 | "status_code": 200 28 | }, 29 | "list_user_logins_2": { 30 | "method": "GET", 31 | "endpoint": "accounts/1/logins/?page=2&per_page=1", 32 | "data": [ 33 | { 34 | "account_id": 1, 35 | "id": 102, 36 | "sis_user_id": null, 37 | "unique_id": "facebook@example.com", 38 | "user_id": 1, 39 | "authentication_provider_id": 1, 40 | "authentication_provider_type": "facebook" 41 | } 42 | ], 43 | "status_code": 200 44 | }, 45 | "edit_user_login": { 46 | "method": "PUT", 47 | "endpoint": "accounts/1/logins/101", 48 | "data": { 49 | "account_id": 1, 50 | "id": 101, 51 | "unique_id": "newemail@example.com", 52 | "user_id": 1 53 | }, 54 | "status_code": 200 55 | }, 56 | "delete_user_login": { 57 | "method": "DELETE", 58 | "endpoint": "users/1/logins/101", 59 | "data": { 60 | "account_id": 1, 61 | "id": 101, 62 | "unique_id": "belieber@example.com", 63 | "user_id": 1 64 | } 65 | }, 66 | "get_authentication_events": { 67 | "method": "GET", 68 | "endpoint": "audit/authentication/logins/101", 69 | "data": [ 70 | { 71 | "created_at": "2012-07-19T15:00:00-06:00", 72 | "event_type": "login", 73 | "pseudonym_id": 9478 74 | }, 75 | { 76 | "created_at": "2012-07-20T15:00:00-06:00", 77 | "event_type": "logout", 78 | "pseudonym_id": 9478 79 | } 80 | ], 81 | "status_code": 200 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /tests/fixtures/lti_resource_link.json: -------------------------------------------------------------------------------- 1 | { 2 | "create_lti_resource_link": { 3 | "method": "POST", 4 | "endpoint": "courses/1/lti_resource_links", 5 | "data": { 6 | "id": 45, 7 | "context_id": 1, 8 | "context_type": "Course", 9 | "context_external_tool_id": 1, 10 | "resource_type": "assignment", 11 | "canvas_launch_url": "https://example.instructure.com/courses/1/external_tools/retrieve?resource_link_lookup_uuid=ae43ba23-d238-49bc-ab55-ba7f79f77896", 12 | "resource_link_uuid": "ae43ba23-d238-49bc-ab55-ba7f79f77896", 13 | "lookup_uuid": "c522554a-d4be-49ef-b163-9c87fdc6ad6f", 14 | "title": "Test LTI Resource Link", 15 | "url": "https://example.com/lti/launch/content_item/123" 16 | }, 17 | "status_code": 200 18 | }, 19 | 20 | "get_lti_resource_link": { 21 | "method": "GET", 22 | "endpoint": "courses/1/lti_resource_links/45", 23 | "data": { 24 | "id": 45, 25 | "title": "Test LTI Resource Link", 26 | "url": "https://example.com/lti/launch/content_item/123" 27 | }, 28 | "status_code": 200 29 | }, 30 | "list_lti_resource_links": { 31 | "method": "GET", 32 | "endpoint": "courses/1/lti_resource_links", 33 | "data": [ 34 | { 35 | "id": 45, 36 | "title": "Test LTI Resource Link" 37 | }, 38 | { 39 | "id": 56, 40 | "title": "Test LTI Resource Link 2" 41 | }, 42 | { 43 | "id": 67, 44 | "title": "Test LTI Resource Link 3" 45 | } 46 | ], 47 | "status_code": 200 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /tests/fixtures/new_quiz.json: -------------------------------------------------------------------------------- 1 | { 2 | "create_new_quiz": { 3 | "method": "POST", 4 | "endpoint": "courses/1/quizzes", 5 | "data": { 6 | "id": 1, 7 | "title": "New Quiz One", 8 | "instructions": "

This is the first New Quiz. Good luck!

" 9 | }, 10 | "status_code": 200 11 | }, 12 | "delete_new_quiz": { 13 | "method": "DELETE", 14 | "endpoint": "courses/1/quizzes/1", 15 | "data": { 16 | "id": 1, 17 | "title": "New Quiz One", 18 | "instructions": "

This is the first New Quiz. Good luck!

" 19 | }, 20 | "status_code": 200 21 | }, 22 | "get_new_quiz": { 23 | "method": "GET", 24 | "endpoint": "courses/1/quizzes/1", 25 | "data": { 26 | "id": 1, 27 | "title": "New Quiz One", 28 | "instructions": "

This is the first New Quiz. Good luck!

" 29 | }, 30 | "status_code": 200 31 | }, 32 | "get_new_quizzes": { 33 | "method": "GET", 34 | "endpoint": "courses/1/quizzes", 35 | "data": [ 36 | { 37 | "id": 1, 38 | "title": "New Quiz One", 39 | "instructions": "

This is the first New Quiz. Good luck!

" 40 | }, 41 | { 42 | "id": 2, 43 | "title": "New Quiz Two", 44 | "instructions": "

This is the second New Quiz. Good luck!

" 45 | } 46 | ], 47 | "status_code": 200 48 | }, 49 | "update_new_quiz": { 50 | "method": "PATCH", 51 | "endpoint": "courses/1/quizzes/1", 52 | "data": { 53 | "id": 1, 54 | "title": "New Quiz One - Updated!", 55 | "instructions": "

This is the updated New Quiz. You got this!

" 56 | }, 57 | "status_code": 200 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /tests/fixtures/notification_preferences.html: -------------------------------------------------------------------------------- 1 |

A NotificationPreference object looks like:

2 |

 3 | 

4 |

5 |

6 | GET /api/v1/users/:user_id/communication_channels/:communication_channel_id/notification_preferences 7 |

8 |

9 | GET /api/v1/users/:user_id/communication_channels/:type/:address/notification_preferences 10 |

11 |

Fetch all preferences for the given communication channel

12 | -------------------------------------------------------------------------------- /tests/fixtures/planner.json: -------------------------------------------------------------------------------- 1 | { 2 | "update_planner_note": { 3 | "method": "PUT", 4 | "endpoint": "planner_notes/1", 5 | "data": { 6 | "id": 1, 7 | "title": "Go to restroom", 8 | "todo_date": "2020-01-07T15:16:18Z" 9 | }, 10 | "status_code": 200 11 | }, 12 | "delete_planner_note": { 13 | "method": "DELETE", 14 | "endpoint": "planner_notes/1", 15 | "data": { 16 | "id": 1, 17 | "title": "Go to restroom", 18 | "todo_date": "2020-01-07T15:16:18Z" 19 | }, 20 | "status_code": 204 21 | }, 22 | "multiple_planner_notes": { 23 | "method": "GET", 24 | "endpoint": "planner_notes", 25 | "data": [ 26 | { 27 | "id": 2, 28 | "title": "Breathe", 29 | "todo_date": "2019-07-07T01:10:29Z" 30 | }, 31 | { 32 | "id": 3, 33 | "title": "Eat dinner", 34 | "todo_date": "2019-07-01T13:15:00Z" 35 | } 36 | ], 37 | "status_code": 200 38 | }, 39 | "single_planner_note": { 40 | "method": "GET", 41 | "endpoint": "planner_notes/1", 42 | "data": { 43 | "id": 1, 44 | "title": "Take a nap", 45 | "todo_date": "2018-05-09T10:12:00Z" 46 | }, 47 | "status_code": 200 48 | }, 49 | "create_planner_note": { 50 | "method": "POST", 51 | "endpoint": "planner_notes", 52 | "data": { 53 | "title": "Perform photosynthesis", 54 | "todo_date": "2019-09-05T12:10:30Z" 55 | }, 56 | "status_code": 200 57 | }, 58 | "update_planner_override": { 59 | "method": "PUT", 60 | "endpoint": "planner/overrides/1", 61 | "data": { 62 | "id": 1, 63 | "plannable_id": 11, 64 | "marked_complete": true 65 | }, 66 | "status_code": 200 67 | }, 68 | "delete_planner_override": { 69 | "method": "DELETE", 70 | "endpoint": "planner/overrides/1", 71 | "data": { 72 | "id": 1, 73 | "plannable_id": 11, 74 | "marked_complete": true 75 | }, 76 | "status_code": 204 77 | }, 78 | "multiple_planner_overrides": { 79 | "method": "GET", 80 | "endpoint": "planner/overrides", 81 | "data": [ 82 | { 83 | "id": 2, 84 | "plannable_id": 22, 85 | "marked_complete": false 86 | }, 87 | { 88 | "id": 3, 89 | "plannable_id": 33, 90 | "marked_complete": true 91 | } 92 | ], 93 | "status_code": 200 94 | }, 95 | "single_planner_override": { 96 | "method": "GET", 97 | "endpoint": "planner/overrides/1", 98 | "data": { 99 | "id": 1, 100 | "plannable_id": 11, 101 | "marked_complete": false 102 | }, 103 | "status_code": 200 104 | }, 105 | "create_planner_override": { 106 | "method": "POST", 107 | "endpoint": "planner/overrides", 108 | "data": { 109 | "plannable_id": 11, 110 | "marked_complete": false 111 | }, 112 | "status_code": 200 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /tests/fixtures/poll.json: -------------------------------------------------------------------------------- 1 | { 2 | "create_poll": { 3 | "method": "POST", 4 | "endpoint": "polls", 5 | "data": { 6 | "polls": [ 7 | { 8 | "id": 4, 9 | "question": "Is this a question?", 10 | "description": "This is a test.", 11 | "created_at": "2015-01-07T15:16:18Z", 12 | "user_id": 1 13 | } 14 | ] 15 | }, 16 | "status_code": 200 17 | }, 18 | "delete": { 19 | "method": "DELETE", 20 | "endpoint": "polls/1", 21 | "status_code": 204 22 | }, 23 | "get_poll": { 24 | "method": "GET", 25 | "endpoint": "polls/1", 26 | "data": { 27 | "polls": [ 28 | { 29 | "id": 1, 30 | "question": "Is this a question?", 31 | "description": "This is a test.", 32 | "created_at": "2014-01-07T13:10:19Z", 33 | "user_id": 1 34 | } 35 | ] 36 | }, 37 | "status_code": 200 38 | }, 39 | "get_polls": { 40 | "method": "GET", 41 | "endpoint": "polls", 42 | "data": { 43 | "polls": [ 44 | { 45 | "id": 2, 46 | "question": "Is this a question?", 47 | "description": "This is a test.", 48 | "created_at": "2014-01-07T15:16:18Z", 49 | "user_id": 1 50 | }, 51 | { 52 | "id": 3, 53 | "question": "Is this a question?", 54 | "description": "This is a test.", 55 | "created_at": "2014-01-07T15:18:12Z", 56 | "user_id": 1 57 | } 58 | ] 59 | }, 60 | "status_code": 200 61 | }, 62 | "update": { 63 | "method": "PUT", 64 | "endpoint": "polls/1", 65 | "data": { 66 | "polls": [ 67 | { 68 | "id": 2, 69 | "question": "Is this not a question?", 70 | "description": "This is not a test.", 71 | "created_at": "2014-01-07T15:16:18Z", 72 | "user_id": 1 73 | } 74 | ] 75 | }, 76 | "status_code": 200 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /tests/fixtures/poll_choice.json: -------------------------------------------------------------------------------- 1 | { 2 | "create_choice": { 3 | "method": "POST", 4 | "endpoint": "polls/1/poll_choices", 5 | "data": { 6 | "poll_choices": [ 7 | { 8 | "id": 1, 9 | "poll_id": 1, 10 | "is_correct": true, 11 | "text": "Example choice", 12 | "position": 1 13 | } 14 | ] 15 | }, 16 | "status_code": 200 17 | }, 18 | "delete": { 19 | "method": "DELETE", 20 | "endpoint": "polls/1/poll_choices/1", 21 | "status_code": 204 22 | }, 23 | "get_choice": { 24 | "method": "GET", 25 | "endpoint": "polls/1/poll_choices/1", 26 | "data": { 27 | "poll_choices": [ 28 | { 29 | "id": 1, 30 | "poll_id": 1, 31 | "is_correct": true, 32 | "text": "Example choice", 33 | "position": 1 34 | } 35 | ] 36 | }, 37 | "status_code": 200 38 | }, 39 | "get_choices": { 40 | "method": "GET", 41 | "endpoint": "polls/1/poll_choices", 42 | "data": { 43 | "poll_choices": [ 44 | { 45 | "id": 1, 46 | "poll_id": 1, 47 | "is_correct": true, 48 | "text": "Example choice", 49 | "position": 1 50 | }, 51 | { 52 | "id": 2, 53 | "poll_id": 1, 54 | "is_correct": false, 55 | "text": "Another example", 56 | "position": 2 57 | } 58 | ] 59 | }, 60 | "status_code": 200 61 | }, 62 | "update": { 63 | "method": "PUT", 64 | "endpoint": "polls/1/poll_choices/1", 65 | "data": { 66 | "poll_choices": [ 67 | { 68 | "id": 1, 69 | "poll_id": 1, 70 | "is_correct": false, 71 | "text": "Changed example", 72 | "position": 2 73 | } 74 | ] 75 | }, 76 | "status_code": 200 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /tests/fixtures/poll_submission.json: -------------------------------------------------------------------------------- 1 | { 2 | "create_submission": { 3 | "method": "POST", 4 | "endpoint": "polls/1/poll_sessions/1/poll_submissions", 5 | "data": { 6 | "poll_submissions": [ 7 | { 8 | "id": 1, 9 | "poll_choice_id": 1, 10 | "user_id": 1, 11 | "created_at": "2013-11-07T13:16:18Z" 12 | } 13 | ] 14 | }, 15 | "status_code": 200 16 | }, 17 | "get_submission": { 18 | "method": "GET", 19 | "endpoint": "polls/1/poll_sessions/1/poll_submissions/1", 20 | "data": { 21 | "poll_submissions": [ 22 | { 23 | "id": 1, 24 | "poll_choice_id": 1, 25 | "user_id": 1, 26 | "created_at": "2013-11-07T13:16:18Z" 27 | } 28 | ] 29 | }, 30 | "status_code": 200 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /tests/fixtures/progress.json: -------------------------------------------------------------------------------- 1 | { 2 | "progress_query": { 3 | "method": "GET", 4 | "endpoint": "progress/2", 5 | "data": { 6 | "id": 2, 7 | "context_id": 1, 8 | "context_type": "Account", 9 | "user_id": 123, 10 | "tag": "assign_unassigned_members", 11 | "completion": 100, 12 | "workflow_state": "running", 13 | "created_at": "2013-01-15T15:00:00Z", 14 | "updated_at": "2013-01-15T15:04:00Z", 15 | "message": "17 courses processed", 16 | "url": "https://canvas.example.edu/api/v1/progress/1" 17 | }, 18 | "status_code": 200 19 | }, 20 | "course_progress": { 21 | "method": "GET", 22 | "endpoint": "progress/3", 23 | "data":{ 24 | "id": 3, 25 | "context_id": 1, 26 | "context_type": "Course", 27 | "user_id": null, 28 | "tag": "submissions_update", 29 | "completion": 100, 30 | "workflow_state": "completed", 31 | "updated_at": "2013-01-15T15:04:00Z", 32 | "message": null, 33 | "url": "https://canvas.example.edu/api/v1/progress/3" 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /tests/fixtures/quiz_group.json: -------------------------------------------------------------------------------- 1 | { 2 | "update": { 3 | "method": "PUT", 4 | "endpoint": "courses/1/quizzes/5/groups/10", 5 | "data": {"quiz_groups": [ 6 | { 7 | "id": 10, 8 | "quiz_id": 5, 9 | "name": "Test Group", 10 | "pick_count": 1, 11 | "question_points": 2, 12 | "assessment_question_bank_id": 3 13 | } 14 | ]}, 15 | "status_code": 200 16 | }, 17 | "delete": { 18 | "method": "DELETE", 19 | "endpoint": "courses/1/quizzes/5/groups/10", 20 | "status_code": 204 21 | }, 22 | "reorder_question_group": { 23 | "method": "POST", 24 | "endpoint": "courses/1/quizzes/5/groups/10/reorder", 25 | "status_code": 204 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /tests/fixtures/requests.json: -------------------------------------------------------------------------------- 1 | { 2 | "400": { 3 | "method": "ANY", 4 | "endpoint": "400", 5 | "data": {}, 6 | "status_code": 400 7 | }, 8 | "401_invalid_access_token": { 9 | "method": "ANY", 10 | "endpoint": "401_invalid_access_token", 11 | "data": {}, 12 | "headers": { 13 | "WWW-Authenticate": "Bearer realm=\"canvas-lms\"" 14 | }, 15 | "status_code": 401 16 | }, 17 | "401_unauthorized": { 18 | "method": "ANY", 19 | "endpoint": "401_unauthorized", 20 | "data": {}, 21 | "status_code": 401 22 | }, 23 | "403": { 24 | "method": "ANY", 25 | "endpoint": "403", 26 | "data": {}, 27 | "status_code": 403 28 | }, 29 | "403_rate_limit": { 30 | "method": "ANY", 31 | "endpoint": "403_rate_limit", 32 | "data": "403 Forbidden (Rate Limit Exceeded)", 33 | "headers": { 34 | "X-Rate-Limit-Remaining": "3.14159265359", 35 | "X-Request-Cost": "1.61803398875" 36 | }, 37 | "status_code": 403 38 | }, 39 | "403_rate_limit_no_remaining_header": { 40 | "method": "ANY", 41 | "endpoint": "403_rate_limit_no_remaining_header", 42 | "data": "403 Forbidden (Rate Limit Exceeded)", 43 | "headers": { 44 | "X-Request-Cost": "1.61803398875" 45 | }, 46 | "status_code": 403 47 | }, 48 | "404": { 49 | "method": "ANY", 50 | "endpoint": "404", 51 | "data": {}, 52 | "status_code": 404 53 | }, 54 | "409": { 55 | "method": "ANY", 56 | "endpoint": "409", 57 | "data": {}, 58 | "status_code": 409 59 | }, 60 | "422": { 61 | "method": "ANY", 62 | "endpoint": "422", 63 | "data": {}, 64 | "status_code": 422 65 | }, 66 | "500": { 67 | "method": "ANY", 68 | "endpoint": "500", 69 | "data": {}, 70 | "status_code": 500 71 | }, 72 | "502": { 73 | "method": "ANY", 74 | "endpoint": "502", 75 | "data": {}, 76 | "status_code": 502 77 | }, 78 | "503": { 79 | "method": "ANY", 80 | "endpoint": "503", 81 | "data": {}, 82 | "status_code": 503 83 | }, 84 | "absurd": { 85 | "method": "ANY", 86 | "endpoint": "absurd", 87 | "data": {}, 88 | "status_code": 9001 89 | }, 90 | "delete": { 91 | "method": "DELETE", 92 | "endpoint": "fake_delete_request", 93 | "data": {}, 94 | "status_code": 200 95 | }, 96 | "get": { 97 | "method": "GET", 98 | "endpoint": "fake_get_request", 99 | "data": {}, 100 | "status_code": 200 101 | }, 102 | "post": { 103 | "method": "POST", 104 | "endpoint": "fake_post_request", 105 | "data": {}, 106 | "status_code": 200 107 | }, 108 | "patch": { 109 | "method": "PATCH", 110 | "endpoint": "fake_patch_request", 111 | "data": {}, 112 | "status_code": 200 113 | }, 114 | "put": { 115 | "method": "PUT", 116 | "endpoint": "fake_put_request", 117 | "data": {}, 118 | "status_code": 200 119 | } 120 | } -------------------------------------------------------------------------------- /tests/fixtures/rubric.json: -------------------------------------------------------------------------------- 1 | { 2 | "create_rubric_assessment": { 3 | "method": "POST", 4 | "endpoint": "courses/1/rubric_associations/4/rubric_assessments", 5 | "data": { 6 | "id": 1, 7 | "rubric_id": 1, 8 | "rubric_association_id": 4, 9 | "score": 5.0, 10 | "artifact_type": "Submission", 11 | "artifact_id": 3, 12 | "artifact_attempt": 2, 13 | "assessment_type": "grading", 14 | "assessor_id": 6, 15 | "provisional": "false" 16 | }, 17 | "status_code": 200 18 | }, 19 | "update_rubric_assessment": { 20 | "method": "PUT", 21 | "endpoint": "courses/4/rubric_associations/4/rubric_assessments/1", 22 | "data": { 23 | "id": 1, 24 | "rubric_id": 1, 25 | "rubric_association_id": 4, 26 | "score": 5.0, 27 | "artifact_type": "Submission", 28 | "artifact_id": 3, 29 | "artifact_attempt": 2, 30 | "assessment_type": "grading", 31 | "assessor_id": 6, 32 | "provisional": "true" 33 | }, 34 | "status_code": 200 35 | }, 36 | "delete_rubric_assessment": { 37 | "method": "DELETE", 38 | "endpoint": "courses/4/rubric_associations/4/rubric_assessments/1", 39 | "data": { 40 | "id": 1, 41 | "rubric_id": 1, 42 | "rubric_association_id": 4, 43 | "score": 5.0, 44 | "artifact_type": "Submission", 45 | "artifact_id": 3, 46 | "artifact_attempt": 2, 47 | "assessment_type": "grading", 48 | "assessor_id": 6, 49 | "provisional": "false" 50 | }, 51 | "status_code": 200 52 | }, 53 | "create_rubric_association": { 54 | "method": "POST", 55 | "endpoint": "courses/1/rubric_associations/", 56 | "data": { 57 | "id": 5, 58 | "association_type": "Assignment" 59 | }, 60 | "status_code": 200 61 | }, 62 | "update_rubric_association": { 63 | "method": "PUT", 64 | "endpoint": "courses/1/rubric_associations/4", 65 | "data": { 66 | "id": 5, 67 | "association_type": "Assignment" 68 | }, 69 | "status_code": 200 70 | }, 71 | "delete_rubric_association": { 72 | "method": "DELETE", 73 | "endpoint": "courses/1/rubric_associations/4", 74 | "data": { 75 | "id": 4, 76 | "association_type": "Assignment" 77 | }, 78 | "status_code": 200 79 | }, 80 | "delete_rubric": { 81 | "method": "DELETE", 82 | "endpoint": "courses/1/rubrics/1", 83 | "data": { 84 | "id": 1, 85 | "title": "Course Rubric 1", 86 | "context_id": 1, 87 | "context_type": "Course", 88 | "points_possible": 10.0, 89 | "reusable": false, 90 | "read_only": true, 91 | "free_form_critereon_comments": true, 92 | "hide_score_total": true 93 | }, 94 | "status_code": 200 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /tests/fixtures/sis_import.json: -------------------------------------------------------------------------------- 1 | { 2 | "get_by_id": { 3 | "method": "GET", 4 | "endpoint": "accounts/1/sis_imports/2", 5 | "data": { 6 | "id": 2, 7 | "progress": 20, 8 | "workflow_state": "importing", 9 | "data": { 10 | "import_type": "instructure_csv" 11 | }, 12 | "account_id": 1 13 | }, 14 | "status_code": 200 15 | }, 16 | "abort_sis_import": { 17 | "method": "PUT", 18 | "endpoint": "accounts/1/sis_imports/2/abort", 19 | "data": { 20 | "id": 2, 21 | "progress": 20, 22 | "workflow_state": "aborted", 23 | "data": { 24 | "import_type": "instructure_csv" 25 | }, 26 | "account_id": 1 27 | }, 28 | "status_code": 200 29 | }, 30 | "restore_sis_import_states": { 31 | "method": "PUT", 32 | "endpoint": "accounts/1/sis_imports/2/restore_states", 33 | "data":{ 34 | "id": 4, 35 | "context_id": 2, 36 | "context_type": "SisBatch", 37 | "user_id": null, 38 | "tag": "sis_batch_state_restore", 39 | "completion": 0, 40 | "workflow_state": "queued", 41 | "updated_at": "2018-01-15T15:04:00Z", 42 | "message": null, 43 | "url": "https://canvas.example.edu/api/v1/progress/4" 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /tests/fixtures/test_create_sis_import.csv: -------------------------------------------------------------------------------- 1 | section_id,course_id,sec_name,status,role,user_id 2 | 20,21,WEB-115,active,teacher,9 3 | -------------------------------------------------------------------------------- /tests/fixtures/test_import_outcome.csv: -------------------------------------------------------------------------------- 1 | vendor_guid,object_type,title,description,display_name,calculation_method,calculation_int,workflow_state,parent_guids,ratings,,,,,,, 2 | a,group,Parent group,parent group description,G-1,,,active,,,,,,,,, 3 | b,group,Child group,child group description,G-1.1,,,active,a,,,,,,,, 4 | c,outcome,Learning Standard,outcome description,LS-100,decaying_average,40,active,a b,3,Excellent,2,Better,1,Good,, 5 | -------------------------------------------------------------------------------- /tests/fixtures/uploader.json: -------------------------------------------------------------------------------- 1 | { 2 | "upload_response": { 3 | "method": "POST", 4 | "endpoint": "upload_response", 5 | "data": { 6 | "upload_url": "https://example.com/api/v1/upload_response_upload_url", 7 | "upload_params": { 8 | "some_param": "param123", 9 | "a_different_param": "param456" 10 | } 11 | } 12 | }, 13 | "upload_response_upload_url": { 14 | "method": "POST", 15 | "endpoint": "upload_response_upload_url", 16 | "data": { 17 | "url": "great_url_success" 18 | } 19 | }, 20 | "upload_response_no_upload_url": { 21 | "method": "POST", 22 | "endpoint": "upload_response_no_upload_url", 23 | "data": { 24 | "some_param": "not_upload" 25 | } 26 | }, 27 | "upload_response_no_upload_params": { 28 | "method": "POST", 29 | "endpoint": "upload_response_no_upload_params", 30 | "data": { 31 | "upload_url": "mock://example.com/api/v1/upload_response_upload_url" 32 | } 33 | }, 34 | "upload_response_fail": { 35 | "method": "POST", 36 | "endpoint": "upload_response_fail", 37 | "data": { 38 | "upload_url": "https://example.com/api/v1/upload_fail", 39 | "upload_params": { 40 | "some_param": "param123", 41 | "a_different_param": "param456" 42 | } 43 | } 44 | }, 45 | "upload_fail": { 46 | "method": "POST", 47 | "endpoint": "upload_fail", 48 | "data": { 49 | "no_url": "bad_url_failure" 50 | } 51 | } 52 | } -------------------------------------------------------------------------------- /tests/settings.py: -------------------------------------------------------------------------------- 1 | BASE_URL = "https://example.com" 2 | BASE_URL_GRAPHQL = "https://example.com/api/" 3 | BASE_URL_NEW_QUIZZES = "https://example.com/api/quiz/v1/" 4 | BASE_URL_WITH_VERSION = "https://example.com/api/v1/" 5 | BASE_URL_AS_HTTP = "http://example.com" 6 | BASE_URL_AS_BLANK = "" 7 | BASE_URL_AS_INVALID = "example.com" 8 | BASE_URL_WITH_EXTRA_SPACES = " https://example.com " 9 | API_KEY = "123" 10 | 11 | INVALID_ID = 9001 12 | -------------------------------------------------------------------------------- /tests/test_account_calendar.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | import requests_mock 4 | 5 | from canvasapi import Canvas 6 | from tests import settings 7 | from tests.util import register_uris 8 | 9 | 10 | @requests_mock.Mocker() 11 | class TestAccountCalendar(unittest.TestCase): 12 | def setUp(self): 13 | self.canvas = Canvas(settings.BASE_URL, settings.API_KEY) 14 | 15 | with requests_mock.Mocker() as m: 16 | register_uris({"account": ["get_account_calendar", "get_by_id"]}, m) 17 | 18 | self.account = self.canvas.get_account(1) 19 | self.account_calendar = self.account.get_account_calendar() 20 | 21 | # __str__() 22 | def test__str__(self, m): 23 | string = str(self.account_calendar) 24 | self.assertIsInstance(string, str) 25 | -------------------------------------------------------------------------------- /tests/test_appointment_group.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | import requests_mock 4 | 5 | from canvasapi import Canvas 6 | from canvasapi.appointment_group import AppointmentGroup 7 | from canvasapi.exceptions import RequiredFieldMissing 8 | from tests import settings 9 | from tests.util import register_uris 10 | 11 | 12 | @requests_mock.Mocker() 13 | class TestAppointmentGroup(unittest.TestCase): 14 | def setUp(self): 15 | self.canvas = Canvas(settings.BASE_URL, settings.API_KEY) 16 | 17 | with requests_mock.Mocker() as m: 18 | register_uris({"appointment_group": ["get_appointment_group"]}, m) 19 | 20 | self.appointment_group = self.canvas.get_appointment_group(567) 21 | 22 | # delete() 23 | def test_delete_appointment_group(self, m): 24 | register_uris({"appointment_group": ["delete_appointment_group"]}, m) 25 | 26 | deleted_appointment_group = self.appointment_group.delete() 27 | 28 | self.assertIsInstance(deleted_appointment_group, AppointmentGroup) 29 | self.assertTrue(hasattr(deleted_appointment_group, "title")) 30 | self.assertEqual(deleted_appointment_group.title, "Test Group 3") 31 | 32 | # edit() 33 | def test_edit_appointment_group(self, m): 34 | register_uris({"appointment_group": ["edit_appointment_group"]}, m) 35 | 36 | title = "New Name" 37 | edited_appointment_group = self.appointment_group.edit( 38 | appointment_group={"title": title, "context_codes": {"course_765"}} 39 | ) 40 | 41 | self.assertIsInstance(edited_appointment_group, AppointmentGroup) 42 | self.assertTrue(hasattr(edited_appointment_group, "title")) 43 | self.assertEqual(edited_appointment_group.title, title) 44 | 45 | def test_edit_appointment_group_fail(self, m): 46 | with self.assertRaises(RequiredFieldMissing): 47 | self.appointment_group.edit({}) 48 | 49 | # __str__() 50 | def test__str__(self, m): 51 | string = str(self.appointment_group) 52 | self.assertIsInstance(string, str) 53 | -------------------------------------------------------------------------------- /tests/test_authentication_event.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | import requests_mock 4 | 5 | from canvasapi import Canvas 6 | from tests import settings 7 | from tests.util import register_uris 8 | 9 | 10 | @requests_mock.Mocker() 11 | class TestAuthenticationEvent(unittest.TestCase): 12 | def setUp(self): 13 | self.canvas = Canvas(settings.BASE_URL, settings.API_KEY) 14 | 15 | with requests_mock.Mocker() as m: 16 | requires = { 17 | "account": ["get_by_id", "get_authentication_events"], 18 | "login": ["create_user_login", "get_authentication_events"], 19 | "user": ["get_by_id", "get_authentication_events"], 20 | } 21 | register_uris(requires, m) 22 | 23 | self.account = self.canvas.get_account(1) 24 | self.login = self.account.create_user_login( 25 | user={"id": 1}, login={"unique_id": "belieber@example.com"} 26 | ) 27 | self.user = self.canvas.get_user(1) 28 | 29 | self.authentication_event_account = ( 30 | self.account.get_authentication_events()[0] 31 | ) 32 | self.authentication_event_login = self.login.get_authentication_events()[0] 33 | self.authentication_event_user = self.user.get_authentication_events()[0] 34 | 35 | # __str__() 36 | def test__str__(self, m): 37 | string = str(self.authentication_event_account) 38 | self.assertIsInstance(string, str) 39 | -------------------------------------------------------------------------------- /tests/test_authentication_providers.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | import requests_mock 4 | 5 | from canvasapi import Canvas 6 | from canvasapi.authentication_provider import AuthenticationProvider 7 | from tests import settings 8 | from tests.util import register_uris 9 | 10 | 11 | @requests_mock.Mocker() 12 | class TestAuthenticationProvider(unittest.TestCase): 13 | def setUp(self): 14 | self.canvas = Canvas(settings.BASE_URL, settings.API_KEY) 15 | 16 | with requests_mock.Mocker() as m: 17 | register_uris({"account": ["get_by_id", "add_authentication_providers"]}, m) 18 | 19 | self.account = self.canvas.get_account(1) 20 | self.authentication_providers = self.account.add_authentication_providers( 21 | authentication_providers={"auth_type": "Authentication Providers"} 22 | ) 23 | 24 | # update() 25 | def test_update_authentication_providers(self, m): 26 | register_uris( 27 | {"authentication_providers": ["update_authentication_providers"]}, m 28 | ) 29 | 30 | new_auth_type = "New Authentication Providers" 31 | 32 | self.authentication_providers.update( 33 | authentication_providers={"auth_type": new_auth_type} 34 | ) 35 | self.assertEqual(self.authentication_providers.auth_type, new_auth_type) 36 | 37 | # delete() 38 | def test_delete_authentication_providers(self, m): 39 | register_uris( 40 | {"authentication_providers": ["delete_authentication_providers"]}, m 41 | ) 42 | 43 | deleted_authentication_providers = self.authentication_providers.delete() 44 | 45 | self.assertIsInstance(deleted_authentication_providers, AuthenticationProvider) 46 | self.assertTrue(hasattr(deleted_authentication_providers, "auth_type")) 47 | self.assertEqual( 48 | deleted_authentication_providers.auth_type, "Authentication Providers" 49 | ) 50 | 51 | # __str__() 52 | def test_str__(self, m): 53 | string = str(self.authentication_providers) 54 | self.assertIsInstance(string, str) 55 | -------------------------------------------------------------------------------- /tests/test_bookmark.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | import requests_mock 4 | 5 | from canvasapi import Canvas 6 | from canvasapi.bookmark import Bookmark 7 | from tests import settings 8 | from tests.util import register_uris 9 | 10 | 11 | @requests_mock.Mocker() 12 | class TestBookmark(unittest.TestCase): 13 | def setUp(self): 14 | self.canvas = Canvas(settings.BASE_URL, settings.API_KEY) 15 | 16 | with requests_mock.Mocker() as m: 17 | register_uris( 18 | {"bookmark": ["get_bookmark"], "current_user": ["get_by_id"]}, m 19 | ) 20 | 21 | self.user = self.canvas.get_current_user() 22 | self.bookmark = self.user.get_bookmark(45) 23 | 24 | # delete() 25 | def test_delete_bookmark(self, m): 26 | register_uris({"bookmark": ["delete_bookmark"]}, m) 27 | 28 | deleted_bookmark = self.bookmark.delete() 29 | 30 | self.assertIsInstance(deleted_bookmark, Bookmark) 31 | self.assertTrue(hasattr(deleted_bookmark, "name")) 32 | self.assertEqual(deleted_bookmark.name, "Test Bookmark 3") 33 | 34 | # edit() 35 | def test_edit_bookmark(self, m): 36 | register_uris({"bookmark": ["edit_bookmark"]}, m) 37 | 38 | name = "New Name" 39 | url = "http//happy-place.com" 40 | edited_bookmark = self.bookmark.edit(name=name, url=url) 41 | 42 | self.assertIsInstance(edited_bookmark, Bookmark) 43 | self.assertTrue(hasattr(edited_bookmark, "name")) 44 | self.assertEqual(edited_bookmark.name, name) 45 | 46 | # __str__() 47 | def test__str__(self, m): 48 | string = str(self.bookmark) 49 | self.assertIsInstance(string, str) 50 | -------------------------------------------------------------------------------- /tests/test_calendar_event.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | import requests_mock 4 | 5 | from canvasapi import Canvas 6 | from canvasapi.calendar_event import CalendarEvent 7 | from tests import settings 8 | from tests.util import register_uris 9 | 10 | 11 | @requests_mock.Mocker() 12 | class TestCalendarEvent(unittest.TestCase): 13 | def setUp(self): 14 | self.canvas = Canvas(settings.BASE_URL, settings.API_KEY) 15 | 16 | with requests_mock.Mocker() as m: 17 | register_uris({"calendar_event": ["get_calendar_event"]}, m) 18 | 19 | self.calendar_event = self.canvas.get_calendar_event(567) 20 | 21 | # delete() 22 | def test_delete_calendar_event(self, m): 23 | register_uris({"calendar_event": ["delete_calendar_event"]}, m) 24 | 25 | deleted_calendar_event = self.calendar_event.delete() 26 | 27 | self.assertIsInstance(deleted_calendar_event, CalendarEvent) 28 | self.assertTrue(hasattr(deleted_calendar_event, "title")) 29 | self.assertEqual(deleted_calendar_event.title, "Test Event 3") 30 | 31 | # edit() 32 | def test_edit_calendar_event(self, m): 33 | register_uris({"calendar_event": ["edit_calendar_event"]}, m) 34 | 35 | title = "New Name" 36 | edited_calendar_event = self.calendar_event.edit( 37 | calendar_event={"title": title} 38 | ) 39 | 40 | self.assertIsInstance(edited_calendar_event, CalendarEvent) 41 | self.assertTrue(hasattr(edited_calendar_event, "title")) 42 | self.assertEqual(edited_calendar_event.title, title) 43 | 44 | # __str__() 45 | def test__str__(self, m): 46 | string = str(self.calendar_event) 47 | self.assertIsInstance(string, str) 48 | -------------------------------------------------------------------------------- /tests/test_canvas_object.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | from datetime import datetime 3 | 4 | import pytz 5 | import requests_mock 6 | 7 | from canvasapi.canvas_object import CanvasObject 8 | from canvasapi.requester import Requester 9 | from tests import settings 10 | 11 | 12 | @requests_mock.Mocker() 13 | class TestCanvasObject(unittest.TestCase): 14 | def setUp(self): 15 | self.canvas_object = CanvasObject( 16 | Requester(settings.BASE_URL, settings.API_KEY), {} 17 | ) 18 | 19 | # set_attributes 20 | def test_set_attributes_valid_date(self, m): 21 | attributes = { 22 | "start_at": "2012-05-05T00:00:00Z", 23 | "end_at": "2012-08-05", 24 | "offset_time": "2018-05-21T10:22:25+01:00", 25 | "half_offset": "2018-05-21T13:52:25+04:30", 26 | "big_offset_time": "2018-05-21T23:22:25+14:00", 27 | "big_offset_neg": "2018-05-20T23:22:25-10:00", 28 | } 29 | 30 | start_date = datetime.strptime( 31 | attributes["start_at"], "%Y-%m-%dT%H:%M:%SZ" 32 | ).replace(tzinfo=pytz.utc) 33 | end_date = datetime.strptime(attributes["end_at"], "%Y-%m-%d").replace( 34 | tzinfo=pytz.utc 35 | ) 36 | offset_time = datetime.strptime( 37 | "2018-05-21T09:22:25Z", "%Y-%m-%dT%H:%M:%SZ" 38 | ).replace(tzinfo=pytz.utc) 39 | 40 | self.canvas_object.set_attributes(attributes) 41 | 42 | self.assertTrue(hasattr(self.canvas_object, "start_at_date")) 43 | self.assertEqual(self.canvas_object.start_at_date, start_date) 44 | self.assertTrue(hasattr(self.canvas_object, "end_at_date")) 45 | self.assertEqual(self.canvas_object.end_at_date, end_date) 46 | self.assertTrue(hasattr(self.canvas_object, "offset_time_date")) 47 | self.assertEqual(self.canvas_object.offset_time_date, offset_time) 48 | self.assertTrue(hasattr(self.canvas_object, "big_offset_time_date")) 49 | self.assertEqual(self.canvas_object.big_offset_time_date, offset_time) 50 | self.assertTrue(hasattr(self.canvas_object, "big_offset_neg_date")) 51 | self.assertEqual(self.canvas_object.big_offset_neg_date, offset_time) 52 | 53 | self.assertTrue(hasattr(self.canvas_object, "half_offset_date")) 54 | self.assertEqual(self.canvas_object.half_offset_date, offset_time) 55 | 56 | def test_set_attributes_invalid_date(self, m): 57 | attributes = {"start_at": "2017-01-01T00:00+00:00:00", "end_at": "2012-08-0"} 58 | 59 | self.canvas_object.set_attributes(attributes) 60 | 61 | self.assertFalse(hasattr(self.canvas_object, "start_at_date")) 62 | self.assertFalse(hasattr(self.canvas_object, "end_at_date")) 63 | self.assertTrue(hasattr(self.canvas_object, "start_at")) 64 | self.assertTrue(hasattr(self.canvas_object, "end_at")) 65 | -------------------------------------------------------------------------------- /tests/test_collaboration.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | import requests_mock 4 | 5 | from canvasapi import Canvas 6 | from canvasapi.collaboration import Collaboration, Collaborator 7 | from canvasapi.paginated_list import PaginatedList 8 | from tests import settings 9 | from tests.util import register_uris 10 | 11 | 12 | @requests_mock.Mocker() 13 | class TestCollaboration(unittest.TestCase): 14 | def setUp(self): 15 | self.canvas = Canvas(settings.BASE_URL, settings.API_KEY) 16 | 17 | self.collaboration = Collaboration( 18 | self.canvas._Canvas__requester, 19 | { 20 | "id": 1, 21 | "collaboration_type": "Microsoft Office", 22 | "document_id": "oinwoenfe8w8ef_onweufe89fef", 23 | "user_id": 92, 24 | "context_id": 77, 25 | "context_type": "Course", 26 | "url": "null", 27 | "created_at": "2012-06-01T00:00:00-06:00", 28 | "updated_at": "2012-06-01T00:00:00-06:00", 29 | "description": "null", 30 | "title": "null", 31 | "type": "ExternalToolCollaboration", 32 | "update_url": "null", 33 | "user_name": "John Danger", 34 | }, 35 | ) 36 | 37 | def test_str(self, m): 38 | test_str = str(self.collaboration) 39 | self.assertIsInstance(test_str, str) 40 | 41 | def test_get_collaborators(self, m): 42 | register_uris({"collaboration": ["get_collaborators"]}, m) 43 | 44 | collaborator_list = self.collaboration.get_collaborators() 45 | 46 | self.assertIsInstance(collaborator_list, PaginatedList) 47 | self.assertIsInstance(collaborator_list[0], Collaborator) 48 | self.assertEqual(collaborator_list[0].id, 12345) 49 | self.assertEqual(collaborator_list[0].name, "Don Draper") 50 | 51 | 52 | @requests_mock.Mocker() 53 | class TestCollaborator(unittest.TestCase): 54 | def setUp(self): 55 | self.canvas = Canvas(settings.BASE_URL, settings.API_KEY) 56 | 57 | self.collaborator = Collaborator( 58 | self.canvas._Canvas__requester, 59 | {"id": 12345, "type": "user", "name": "Don Draper"}, 60 | ) 61 | 62 | def test_str(self, m): 63 | test_str = str(self.collaborator) 64 | self.assertIsInstance(test_str, str) 65 | -------------------------------------------------------------------------------- /tests/test_comm_messages.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | import requests_mock 4 | 5 | from canvasapi import Canvas 6 | from tests import settings 7 | from tests.util import register_uris 8 | 9 | 10 | @requests_mock.Mocker() 11 | class TestCommMessage(unittest.TestCase): 12 | def setUp(self): 13 | self.canvas = Canvas(settings.BASE_URL, settings.API_KEY) 14 | 15 | with requests_mock.Mocker() as m: 16 | requires = {"comm_message": ["comm_messages"]} 17 | register_uris(requires, m) 18 | 19 | self.comm_message = self.canvas.get_comm_messages(2)[0] 20 | 21 | # __str__() 22 | def test__str__(self, m): 23 | string = str(self.comm_message) 24 | self.assertIsInstance(string, str) 25 | -------------------------------------------------------------------------------- /tests/test_content_export.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | import requests_mock 4 | 5 | from canvasapi import Canvas 6 | from tests import settings 7 | from tests.util import register_uris 8 | 9 | 10 | @requests_mock.Mocker() 11 | class TestContentExport(unittest.TestCase): 12 | def setUp(self): 13 | self.canvas = Canvas(settings.BASE_URL, settings.API_KEY) 14 | 15 | with requests_mock.Mocker() as m: 16 | requires = { 17 | "course": ["get_by_id", "single_content_export"], 18 | "group": ["get_by_id", "single_content_export"], 19 | "user": ["get_by_id", "single_content_export"], 20 | } 21 | register_uris(requires, m) 22 | 23 | self.course = self.canvas.get_course(1) 24 | self.group = self.canvas.get_group(1) 25 | self.user = self.canvas.get_user(1) 26 | 27 | self.content_export_course = self.course.get_content_export(11) 28 | self.content_export_group = self.group.get_content_export(11) 29 | self.content_export_user = self.user.get_content_export(11) 30 | 31 | # __str__() 32 | def test__str__(self, m): 33 | string = str(self.content_export_course) 34 | self.assertIsInstance(string, str) 35 | -------------------------------------------------------------------------------- /tests/test_conversation.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | import requests_mock 4 | 5 | from canvasapi import Canvas 6 | from canvasapi.conversation import Conversation 7 | from tests import settings 8 | from tests.util import register_uris 9 | 10 | 11 | @requests_mock.Mocker() 12 | class TestConversation(unittest.TestCase): 13 | def setUp(self): 14 | self.canvas = Canvas(settings.BASE_URL, settings.API_KEY) 15 | 16 | with requests_mock.Mocker() as m: 17 | register_uris({"conversation": ["get_by_id"]}, m) 18 | 19 | self.conversation = self.canvas.get_conversation(1) 20 | 21 | # __str__() 22 | def test__str__(self, m): 23 | string = str(self.conversation) 24 | self.assertIsInstance(string, str) 25 | 26 | # edit() 27 | def test_edit(self, m): 28 | register_uris({"conversation": ["edit_conversation"]}, m) 29 | 30 | new_subject = "conversations api example" 31 | success = self.conversation.edit(subject=new_subject) 32 | self.assertTrue(success) 33 | 34 | def test_edit_fail(self, m): 35 | requires = {"conversation": ["get_by_id_2", "edit_conversation_fail"]} 36 | register_uris(requires, m) 37 | 38 | temp_convo = self.canvas.get_conversation(2) 39 | self.assertFalse(temp_convo.edit()) 40 | 41 | # delete() 42 | def test_delete(self, m): 43 | register_uris({"conversation": ["delete_conversation"]}, m) 44 | 45 | success = self.conversation.delete() 46 | self.assertTrue(success) 47 | 48 | def test_delete_fail(self, m): 49 | requires = {"conversation": ["get_by_id_2", "delete_conversation_fail"]} 50 | register_uris(requires, m) 51 | 52 | temp_convo = self.canvas.get_conversation(2) 53 | self.assertFalse(temp_convo.delete()) 54 | 55 | # add_recipients() 56 | def test_add_recipients(self, m): 57 | register_uris({"conversation": ["add_recipients"]}, m) 58 | 59 | recipients = {"bob": 1, "joe": 2} 60 | string_bob = "Bob was added to the conversation by Hank TA" 61 | string_joe = "Joe was added to the conversation by Hank TA" 62 | result = self.conversation.add_recipients( 63 | [recipients["bob"], recipients["joe"]] 64 | ) 65 | self.assertTrue(hasattr(result, "messages")) 66 | self.assertEqual(len(result.messages), 2) 67 | self.assertEqual(result.messages[0]["body"], string_bob) 68 | self.assertEqual(result.messages[1]["body"], string_joe) 69 | 70 | # add_message() 71 | def test_add_message(self, m): 72 | register_uris({"conversation": ["add_message"]}, m) 73 | 74 | test_string = "add_message test body" 75 | result = self.conversation.add_message(test_string) 76 | self.assertIsInstance(result, Conversation) 77 | self.assertEqual(len(result.messages), 1) 78 | self.assertEqual(result.messages[0]["id"], 3) 79 | 80 | # delete_message() 81 | def test_delete_message(self, m): 82 | register_uris({"conversation": ["delete_message"]}, m) 83 | 84 | id_list = [1] 85 | result = self.conversation.delete_messages(id_list) 86 | self.assertIn("subject", result) 87 | self.assertEqual(result["id"], 1) 88 | -------------------------------------------------------------------------------- /tests/test_course_epub_export.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | import requests_mock 4 | 5 | from canvasapi import Canvas 6 | from canvasapi.course_epub_export import CourseEpubExport 7 | from tests import settings 8 | 9 | 10 | @requests_mock.Mocker() 11 | class TestCourseEpubExport(unittest.TestCase): 12 | def setUp(self): 13 | self.canvas = Canvas(settings.BASE_URL, settings.API_KEY) 14 | 15 | self.course_epub_export = CourseEpubExport( 16 | self.canvas._Canvas__requester, 17 | { 18 | "name": "course1", 19 | "id": 1, 20 | "epub_export": { 21 | "id": 1, 22 | "created_at": "2019-01-01T00:00:00Z", 23 | "progress_url": "https://dummyurl.com/api/v1/progress/4", 24 | "user_id": 4, 25 | "workflow_state": "exported", 26 | }, 27 | }, 28 | ) 29 | 30 | def test_str(self, m): 31 | test_str = str(self.course_epub_export) 32 | self.assertIsInstance(test_str, str) 33 | -------------------------------------------------------------------------------- /tests/test_course_event.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | import requests_mock 4 | 5 | from canvasapi import Canvas 6 | from tests import settings 7 | from tests.util import register_uris 8 | 9 | 10 | @requests_mock.Mocker() 11 | class TestCourseEvent(unittest.TestCase): 12 | def setUp(self): 13 | self.canvas = Canvas(settings.BASE_URL, settings.API_KEY) 14 | 15 | with requests_mock.Mocker() as m: 16 | requires = { 17 | "account": ["get_by_id", "query_audit_by_account"], 18 | "course": ["query_audit_by_course"], 19 | } 20 | register_uris(requires, m) 21 | 22 | self.account = self.canvas.get_account(1) 23 | 24 | # isolate one of the CourseEvent objects 25 | self.query_audit_by_account = self.account.query_audit_by_account()[0] 26 | 27 | # __str__() 28 | def test__str__(self, m): 29 | string = str(self.query_audit_by_account) 30 | self.assertIsInstance(string, str) 31 | -------------------------------------------------------------------------------- /tests/test_enrollment.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | import requests_mock 4 | 5 | from canvasapi.canvas import Canvas 6 | from canvasapi.enrollment import Enrollment 7 | from tests import settings 8 | from tests.util import register_uris 9 | 10 | 11 | @requests_mock.Mocker() 12 | class TestEnrollment(unittest.TestCase): 13 | def setUp(self): 14 | self.canvas = Canvas(settings.BASE_URL, settings.API_KEY) 15 | 16 | with requests_mock.Mocker() as m: 17 | requires = {"account": ["get_by_id"], "enrollment": ["get_by_id"]} 18 | register_uris(requires, m) 19 | 20 | self.account = self.canvas.get_account(1) 21 | self.enrollment = self.account.get_enrollment(1) 22 | 23 | # __str__() 24 | def test__str__(self, m): 25 | string = str(self.enrollment) 26 | self.assertIsInstance(string, str) 27 | 28 | # deactivate() 29 | def test_deactivate(self, m): 30 | register_uris({"enrollment": ["deactivate"]}, m) 31 | 32 | target_enrollment = self.enrollment.deactivate("conclude") 33 | 34 | self.assertIsInstance(target_enrollment, Enrollment) 35 | 36 | def test_deactivate_invalid_task(self, m): 37 | with self.assertRaises(ValueError): 38 | self.enrollment.deactivate("finish") 39 | 40 | # reactivate() 41 | def test_reactivate(self, m): 42 | register_uris({"enrollment": ["reactivate"]}, m) 43 | 44 | target_enrollment = self.enrollment.reactivate() 45 | 46 | self.assertIsInstance(target_enrollment, Enrollment) 47 | 48 | # accept() 49 | def test_accept(self, m): 50 | register_uris({"enrollment": ["accept"]}, m) 51 | 52 | enrollment_accepted = self.enrollment.accept() 53 | 54 | self.assertIsInstance(enrollment_accepted, bool) 55 | self.assertTrue(enrollment_accepted) 56 | 57 | # reject() 58 | def test_reject(self, m): 59 | register_uris({"enrollment": ["reject"]}, m) 60 | 61 | enrollment_rejected = self.enrollment.reject() 62 | 63 | self.assertIsInstance(enrollment_rejected, bool) 64 | self.assertTrue(enrollment_rejected) 65 | -------------------------------------------------------------------------------- /tests/test_enrollment_term.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | import requests_mock 4 | 5 | from canvasapi import Canvas 6 | from canvasapi.enrollment_term import EnrollmentTerm 7 | from tests import settings 8 | from tests.util import register_uris 9 | 10 | 11 | @requests_mock.Mocker() 12 | class TestEnrollmentTerm(unittest.TestCase): 13 | def setUp(self): 14 | self.canvas = Canvas(settings.BASE_URL, settings.API_KEY) 15 | 16 | with requests_mock.Mocker() as m: 17 | register_uris({"account": ["get_by_id", "create_enrollment_term"]}, m) 18 | 19 | self.account = self.canvas.get_account(1) 20 | self.enrollment_term = self.account.create_enrollment_term( 21 | enrollment_term={"name": "Test Enrollment Term"} 22 | ) 23 | 24 | # delete() 25 | def test_delete_enrollment_term(self, m): 26 | register_uris({"enrollment_term": ["delete_enrollment_term"]}, m) 27 | 28 | deleted_enrollment_term = self.enrollment_term.delete() 29 | 30 | self.assertIsInstance(deleted_enrollment_term, EnrollmentTerm) 31 | self.assertTrue(hasattr(deleted_enrollment_term, "name")) 32 | self.assertEqual(deleted_enrollment_term.name, "Test Enrollment Term") 33 | 34 | # edit() 35 | def test_edit_enrollment_term(self, m): 36 | register_uris({"enrollment_term": ["edit_enrollment_term"]}, m) 37 | 38 | name = "New Name" 39 | edited_enrollment_term = self.enrollment_term.edit( 40 | enrollment_term={"name": name} 41 | ) 42 | 43 | self.assertIsInstance(edited_enrollment_term, EnrollmentTerm) 44 | self.assertTrue(hasattr(edited_enrollment_term, "name")) 45 | self.assertEqual(edited_enrollment_term.name, name) 46 | 47 | # __str__() 48 | def test__str__(self, m): 49 | string = str(self.enrollment_term) 50 | self.assertIsInstance(string, str) 51 | -------------------------------------------------------------------------------- /tests/test_eportfolio.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | import requests_mock 4 | 5 | from canvasapi import Canvas 6 | from canvasapi.eportfolio import EPortfolio, EPortfolioPage 7 | from canvasapi.paginated_list import PaginatedList 8 | from tests import settings 9 | from tests.util import register_uris 10 | 11 | 12 | @requests_mock.Mocker() 13 | class TestEPortfolio(unittest.TestCase): 14 | def setUp(self): 15 | self.canvas = Canvas(settings.BASE_URL, settings.API_KEY) 16 | 17 | with requests_mock.Mocker() as m: 18 | register_uris({"eportfolio": ["get_eportfolio_by_id"]}, m) 19 | 20 | self.eportfolio = self.canvas.get_eportfolio(1) 21 | 22 | def test_str(self, m): 23 | eportfolio_string = str(self.eportfolio) 24 | self.assertEqual(eportfolio_string, "ePortfolio 1") 25 | 26 | def test_delete_eportfolio(self, m): 27 | register_uris({"eportfolio": ["delete_eportfolio"]}, m) 28 | 29 | deleted_eportfolio = self.eportfolio.delete() 30 | 31 | self.assertIsInstance(deleted_eportfolio, EPortfolio) 32 | self.assertEqual(deleted_eportfolio.deleted_at, "2022-07-05T21:00:00Z") 33 | self.assertEqual(deleted_eportfolio.workflow_state, "deleted") 34 | 35 | def test_get_eportfolio_pages(self, m): 36 | register_uris({"eportfolio": ["get_eportfolio_pages"]}, m) 37 | 38 | pages = self.eportfolio.get_eportfolio_pages() 39 | 40 | string_page = str(pages[0]) 41 | 42 | self.assertIsInstance(pages, PaginatedList) 43 | self.assertIsInstance(pages[0], EPortfolioPage) 44 | self.assertIsInstance(pages[1], EPortfolioPage) 45 | self.assertEqual(pages[0].position, 1) 46 | self.assertEqual(pages[1].position, 2) 47 | self.assertEqual(string_page, "1. ePortfolio 1") 48 | 49 | def test_moderate_eportfolio_as_spam(self, m): 50 | register_uris({"eportfolio": ["moderate_eportfolio_as_spam"]}, m) 51 | 52 | spam_eportfolio = self.eportfolio.moderate_eportfolio( 53 | spam_status="marked_as_spam" 54 | ) 55 | 56 | self.assertIsInstance(spam_eportfolio, EPortfolio) 57 | self.assertEqual(spam_eportfolio.spam_status, "marked_as_spam") 58 | 59 | def test_restore_deleted_eportfolio(self, m): 60 | register_uris( 61 | {"eportfolio": ["delete_eportfolio", "restore_deleted_eportfolio"]}, m 62 | ) 63 | 64 | eportfolio = self.eportfolio.delete() 65 | 66 | self.assertIsInstance(eportfolio, EPortfolio) 67 | self.assertEqual(eportfolio.workflow_state, "deleted") 68 | self.assertEqual(eportfolio.deleted_at, "2022-07-05T21:00:00Z") 69 | 70 | restored = eportfolio.restore() 71 | 72 | self.assertEqual(restored.workflow_state, "active") 73 | self.assertEqual(restored.deleted_at, "null") 74 | -------------------------------------------------------------------------------- /tests/test_external_feed.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | import requests_mock 4 | 5 | from canvasapi import Canvas 6 | from tests import settings 7 | from tests.util import register_uris 8 | 9 | 10 | @requests_mock.Mocker() 11 | class TestExternalFeed(unittest.TestCase): 12 | def setUp(self): 13 | self.canvas = Canvas(settings.BASE_URL, settings.API_KEY) 14 | 15 | with requests_mock.Mocker() as m: 16 | register_uris({"course": ["get_by_id", "list_external_feeds"]}, m) 17 | 18 | self.course = self.canvas.get_course(1) 19 | self.external_feed = self.course.get_external_feeds()[0] 20 | 21 | # __str__() 22 | def test__str__(self, m): 23 | string = str(self.external_feed) 24 | self.assertIsInstance(string, str) 25 | -------------------------------------------------------------------------------- /tests/test_file.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | from os.path import isfile 3 | 4 | import requests_mock 5 | 6 | from canvasapi import Canvas 7 | from canvasapi.file import File 8 | from tests import settings 9 | from tests.util import cleanup_file, register_uris 10 | 11 | 12 | @requests_mock.Mocker() 13 | class TestFile(unittest.TestCase): 14 | def setUp(self): 15 | self.canvas = Canvas(settings.BASE_URL, settings.API_KEY) 16 | 17 | with requests_mock.Mocker() as m: 18 | register_uris( 19 | {"course": ["get_by_id", "list_course_files", "list_course_files2"]}, m 20 | ) 21 | 22 | self.course = self.canvas.get_course(1) 23 | self.file = self.course.get_files()[0] 24 | 25 | # __str__() 26 | def test__str__(self, m): 27 | string = str(self.file) 28 | self.assertIsInstance(string, str) 29 | 30 | # delete() 31 | def test_delete_file(self, m): 32 | register_uris({"file": ["delete_file"]}, m) 33 | 34 | deleted_file = self.file.delete() 35 | 36 | self.assertIsInstance(deleted_file, File) 37 | self.assertTrue(hasattr(deleted_file, "display_name")) 38 | self.assertEqual(deleted_file.display_name, "Bad File.docx") 39 | 40 | # download() 41 | def test_download_file(self, m): 42 | register_uris({"file": ["file_download"]}, m) 43 | try: 44 | self.file.download("canvasapi_file_download_test.txt") 45 | self.assertTrue(isfile("canvasapi_file_download_test.txt")) 46 | with open("canvasapi_file_download_test.txt") as downloaded_file: 47 | self.assertEqual(downloaded_file.read(), '"file contents are here"') 48 | finally: 49 | cleanup_file("canvasapi_file_download_test.txt") 50 | 51 | # contents() 52 | def test_contents_file(self, m): 53 | register_uris({"file": ["file_contents"]}, m) 54 | contents = self.file.get_contents() 55 | self.assertEqual(contents, '"Hello there"') 56 | contents_binary = self.file.get_contents(binary=True) 57 | self.assertEqual(contents_binary, b'"Hello there"') 58 | 59 | # update() 60 | def test_update_file(self, m): 61 | register_uris({"file": ["update_file"]}, m) 62 | 63 | updated_file = self.file.update(name="New filename.docx") 64 | 65 | self.assertIsInstance(updated_file, File) 66 | self.assertTrue(hasattr(updated_file, "display_name")) 67 | self.assertEqual(updated_file.display_name, "New filename.docx") 68 | -------------------------------------------------------------------------------- /tests/test_folder.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import uuid 3 | 4 | import requests_mock 5 | 6 | from canvasapi import Canvas 7 | from canvasapi.file import File 8 | from canvasapi.folder import Folder 9 | from tests import settings 10 | from tests.util import cleanup_file, register_uris 11 | 12 | 13 | @requests_mock.Mocker() 14 | class TestFolder(unittest.TestCase): 15 | def setUp(self): 16 | self.canvas = Canvas(settings.BASE_URL, settings.API_KEY) 17 | 18 | with requests_mock.Mocker() as m: 19 | register_uris({"folder": ["get_by_id"]}, m) 20 | 21 | self.folder = self.canvas.get_folder(1) 22 | 23 | # __str__() 24 | def test__str__(self, m): 25 | string = str(self.folder) 26 | self.assertIsInstance(string, str) 27 | 28 | # get_files() 29 | def test_get_files(self, m): 30 | register_uris({"folder": ["list_folder_files", "list_folder_files2"]}, m) 31 | 32 | files = self.folder.get_files() 33 | file_list = [file for file in files] 34 | self.assertEqual(len(file_list), 4) 35 | self.assertIsInstance(file_list[0], File) 36 | 37 | # delete() 38 | def test_delete_file(self, m): 39 | register_uris({"folder": ["delete_folder"]}, m) 40 | 41 | deleted_folder = self.folder.delete() 42 | 43 | self.assertIsInstance(deleted_folder, Folder) 44 | self.assertTrue(hasattr(deleted_folder, "name")) 45 | self.assertEqual(deleted_folder.full_name, "course_files/Folder 1") 46 | 47 | # get_folders() 48 | def test_get_folders(self, m): 49 | register_uris({"folder": ["list_folders"]}, m) 50 | 51 | folders = self.folder.get_folders() 52 | folder_list = [folder for folder in folders] 53 | self.assertEqual(len(folder_list), 2) 54 | self.assertIsInstance(folder_list[0], Folder) 55 | 56 | # create_folder() 57 | def test_create_folder(self, m): 58 | register_uris({"folder": ["create_folder"]}, m) 59 | 60 | name_str = "Test String" 61 | response = self.folder.create_folder(name=name_str) 62 | self.assertIsInstance(response, Folder) 63 | 64 | # upload() 65 | def test_upload(self, m): 66 | register_uris({"folder": ["upload", "upload_final"]}, m) 67 | 68 | filename = "testfile_course_{}".format(uuid.uuid4().hex) 69 | 70 | try: 71 | with open(filename, "w+") as file: 72 | response = self.folder.upload(file) 73 | self.assertTrue(response[0]) 74 | self.assertIsInstance(response[1], dict) 75 | self.assertIn("url", response[1]) 76 | finally: 77 | cleanup_file(filename) 78 | 79 | # update() 80 | def test_update(self, m): 81 | register_uris({"folder": ["update"]}, m) 82 | 83 | new_name = "New Name" 84 | response = self.folder.update(name=new_name) 85 | self.assertIsInstance(response, Folder) 86 | self.assertEqual(self.folder.name, new_name) 87 | 88 | # copy_file() 89 | def test_copy_file(self, m): 90 | register_uris({"folder": ["copy_file"]}, m) 91 | 92 | new_file = self.folder.copy_file(1) 93 | self.assertIsInstance(new_file, File) 94 | self.assertEqual(new_file.display_name, "Dummy File-1") 95 | self.assertEqual(new_file.id, 1) 96 | -------------------------------------------------------------------------------- /tests/test_grade_change_log.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | import requests_mock 4 | 5 | from canvasapi import Canvas 6 | from tests import settings 7 | from tests.util import register_uris 8 | 9 | 10 | @requests_mock.Mocker() 11 | class TestGradeChangeEvent(unittest.TestCase): 12 | def setUp(self): 13 | self.canvas = Canvas(settings.BASE_URL, settings.API_KEY) 14 | 15 | with requests_mock.Mocker() as m: 16 | requires = {"course": ["get_by_id"]} 17 | register_uris(requires, m) 18 | 19 | self.course = self.canvas.get_course(1) 20 | 21 | # __str__() 22 | def test__str__(self, m): 23 | requires = {"course": ["get_grade_change_events"]} 24 | register_uris(requires, m) 25 | 26 | for event in self.course.get_grade_change_events(): 27 | self.assertIsInstance(str(event), str) 28 | -------------------------------------------------------------------------------- /tests/test_gradebook_history.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | import requests_mock 4 | 5 | from canvasapi import Canvas 6 | from tests import settings 7 | from tests.util import register_uris 8 | 9 | 10 | @requests_mock.Mocker() 11 | class TestDay(unittest.TestCase): 12 | def setUp(self): 13 | self.canvas = Canvas(settings.BASE_URL, settings.API_KEY) 14 | 15 | with requests_mock.Mocker() as m: 16 | requires = {"course": ["get_by_id", "get_gradebook_history_dates"]} 17 | register_uris(requires, m) 18 | 19 | self.course = self.canvas.get_course(1) 20 | self.gradebook_history_dates = self.course.get_gradebook_history_dates()[0] 21 | 22 | # __str__() 23 | def test__str__(self, m): 24 | string = str(self.gradebook_history_dates) 25 | self.assertIsInstance(string, str) 26 | 27 | 28 | @requests_mock.Mocker() 29 | class TestGrader(unittest.TestCase): 30 | def setUp(self): 31 | self.canvas = Canvas(settings.BASE_URL, settings.API_KEY) 32 | 33 | with requests_mock.Mocker() as m: 34 | requires = {"course": ["get_by_id", "get_gradebook_history_details"]} 35 | register_uris(requires, m) 36 | 37 | self.course = self.canvas.get_course(1) 38 | self.gradebook_history_details = self.course.get_gradebook_history_details( 39 | "03-26-2019" 40 | )[0] 41 | 42 | # __str__() 43 | def test__str__(self, m): 44 | string = str(self.gradebook_history_details) 45 | self.assertIsInstance(string, str) 46 | 47 | 48 | @requests_mock.Mocker() 49 | class TestSubmissionHistory(unittest.TestCase): 50 | def setUp(self): 51 | self.canvas = Canvas(settings.BASE_URL, settings.API_KEY) 52 | 53 | with requests_mock.Mocker() as m: 54 | requires = {"course": ["get_by_id", "get_submission_history"]} 55 | register_uris(requires, m) 56 | 57 | self.course = self.canvas.get_course(1) 58 | self.submission_history = self.course.get_submission_history( 59 | "08-23-2019", 1, 1 60 | )[0] 61 | 62 | # __str__() 63 | def test__str__(self, m): 64 | string = str(self.submission_history) 65 | self.assertIsInstance(string, str) 66 | 67 | 68 | @requests_mock.Mocker() 69 | class TestSubmissionVersion(unittest.TestCase): 70 | def setUp(self): 71 | self.canvas = Canvas(settings.BASE_URL, settings.API_KEY) 72 | 73 | with requests_mock.Mocker() as m: 74 | requires = {"course": ["get_by_id", "get_uncollated_submissions"]} 75 | register_uris(requires, m) 76 | 77 | self.course = self.canvas.get_course(1) 78 | self.uncollated_submissions = self.course.get_uncollated_submissions()[0] 79 | 80 | # __str__() 81 | def test__str__(self, m): 82 | string = str(self.uncollated_submissions) 83 | self.assertIsInstance(string, str) 84 | -------------------------------------------------------------------------------- /tests/test_grading_period.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | import requests_mock 4 | 5 | from canvasapi import Canvas 6 | from canvasapi.exceptions import RequiredFieldMissing 7 | from canvasapi.grading_period import GradingPeriod 8 | from tests import settings 9 | from tests.util import register_uris 10 | 11 | 12 | @requests_mock.Mocker() 13 | class TestGradingPeriod(unittest.TestCase): 14 | def setUp(self): 15 | self.canvas = Canvas(settings.BASE_URL, settings.API_KEY) 16 | 17 | self.grading_period = GradingPeriod( 18 | self.canvas._Canvas__requester, 19 | {"title": "grading period 1", "id": 1, "course_id": 1}, 20 | ) 21 | 22 | def test_str(self, m): 23 | test_str = str(self.grading_period) 24 | self.assertIsInstance(test_str, str) 25 | 26 | # update() 27 | def test_update(self, m): 28 | register_uris({"grading_period": ["update"]}, m) 29 | 30 | edited_grading_period = self.grading_period.update( 31 | grading_period=[ 32 | { 33 | "start_date": "2019-06-10T06:00:00Z", 34 | "end_date": "2019-06-15T06:00:00Z", 35 | } 36 | ] 37 | ) 38 | 39 | self.assertIsInstance(edited_grading_period, GradingPeriod) 40 | self.assertTrue(hasattr(edited_grading_period, "title")) 41 | self.assertTrue(hasattr(edited_grading_period, "course_id")) 42 | self.assertEqual(edited_grading_period.title, "Grading period 1") 43 | self.assertEqual(edited_grading_period.course_id, 1) 44 | self.assertTrue(hasattr(edited_grading_period, "start_date")) 45 | self.assertTrue(hasattr(edited_grading_period, "end_date")) 46 | self.assertEqual(edited_grading_period.start_date, "2019-05-23T06:00:00Z") 47 | self.assertEqual(edited_grading_period.end_date, "2019-08-23T06:00:00Z") 48 | 49 | # Check that the appropriate exception is raised when no list is given. 50 | def test_update_without_list(self, m): 51 | register_uris({"grading_period": ["update"]}, m) 52 | 53 | with self.assertRaises(RequiredFieldMissing): 54 | self.grading_period.update( 55 | grading_period={ 56 | "start_date": "2019-06-10T06:00:00Z", 57 | "end_date": "2019-06-15T06:00:00Z", 58 | } 59 | ) 60 | 61 | # Check that the grading_period that is passed has a start date 62 | def test_update_without_start_date(self, m): 63 | register_uris({"grading_period": ["update"]}, m) 64 | 65 | with self.assertRaises(RequiredFieldMissing): 66 | self.grading_period.update( 67 | grading_period=[{"end_date": "2019-06-15T06:00:00Z"}] 68 | ) 69 | 70 | # Check that the appropriate exception is raised when no list is given. 71 | def test_update_without_end_date(self, m): 72 | register_uris({"grading_period": ["update"]}, m) 73 | 74 | with self.assertRaises(RequiredFieldMissing): 75 | self.grading_period.update( 76 | grading_period=[{"start_date": "2019-06-10T06:00:00Z"}] 77 | ) 78 | 79 | # delete() 80 | def test_delete(self, m): 81 | register_uris({"grading_period": ["delete"]}, m) 82 | self.assertEqual(self.grading_period.delete(), 204) 83 | -------------------------------------------------------------------------------- /tests/test_grading_standard.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | import requests_mock 4 | 5 | from canvasapi import Canvas 6 | from tests import settings 7 | from tests.util import register_uris 8 | 9 | 10 | @requests_mock.Mocker() 11 | class TestGradingStandard(unittest.TestCase): 12 | def setUp(self): 13 | self.canvas = Canvas(settings.BASE_URL, settings.API_KEY) 14 | 15 | with requests_mock.Mocker() as m: 16 | register_uris({"course": ["get_by_id", "get_single_grading_standard"]}, m) 17 | 18 | self.course = self.canvas.get_course(1) 19 | self.grading_standard = self.course.get_single_grading_standard(1) 20 | 21 | # __str__() 22 | def test__str__(self, m): 23 | string = str(self.grading_standard) 24 | self.assertIsInstance(string, str) 25 | -------------------------------------------------------------------------------- /tests/test_jwt.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | import requests_mock 4 | 5 | from canvasapi import Canvas 6 | from tests import settings 7 | from tests.util import register_uris 8 | 9 | 10 | @requests_mock.Mocker() 11 | class TestJWT(unittest.TestCase): 12 | def setUp(self): 13 | self.canvas = Canvas(settings.BASE_URL, settings.API_KEY) 14 | 15 | with requests_mock.Mocker() as m: 16 | register_uris({"jwt": ["create_jwt"]}, m) 17 | 18 | self.jwt = self.canvas.create_jwt() 19 | 20 | # __str__() 21 | def test__str__(self, m): 22 | string = str(self.jwt) 23 | self.assertIsInstance(string, str) 24 | -------------------------------------------------------------------------------- /tests/test_licenses.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | import requests_mock 4 | 5 | from canvasapi import Canvas 6 | from tests import settings 7 | from tests.util import register_uris 8 | 9 | 10 | @requests_mock.Mocker() 11 | class TestLicenses(unittest.TestCase): 12 | def setUp(self): 13 | self.canvas = Canvas(settings.BASE_URL, settings.API_KEY) 14 | 15 | with requests_mock.Mocker() as m: 16 | requires = {"user": ["get_by_id", "get_licenses"]} 17 | 18 | register_uris(requires, m) 19 | 20 | self.user = self.canvas.get_user(1) 21 | self.licenses = list(self.user.get_licenses()) 22 | 23 | # __str__() 24 | def test__str__(self, m): 25 | string = str(self.licenses[0]) 26 | self.assertIsInstance(string, str) 27 | -------------------------------------------------------------------------------- /tests/test_login.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | import requests_mock 4 | 5 | from canvasapi import Canvas 6 | from canvasapi.authentication_event import AuthenticationEvent 7 | from canvasapi.login import Login 8 | from tests import settings 9 | from tests.util import register_uris 10 | 11 | 12 | @requests_mock.Mocker() 13 | class TestLogin(unittest.TestCase): 14 | def setUp(self): 15 | self.canvas = Canvas(settings.BASE_URL, settings.API_KEY) 16 | 17 | with requests_mock.Mocker() as m: 18 | register_uris({"account": ["get_by_id"], "login": ["create_user_login"]}, m) 19 | 20 | self.account = self.canvas.get_account(1) 21 | self.login = self.account.create_user_login( 22 | user={"id": 1}, login={"unique_id": "belieber@example.com"} 23 | ) 24 | 25 | # delete() 26 | def test_delete_user_login(self, m): 27 | register_uris({"login": ["delete_user_login"]}, m) 28 | 29 | deleted_user_login = self.login.delete() 30 | 31 | self.assertIsInstance(deleted_user_login, Login) 32 | self.assertTrue(hasattr(deleted_user_login, "unique_id")) 33 | self.assertEqual(deleted_user_login.unique_id, "belieber@example.com") 34 | 35 | # edit() 36 | def test_edit_user_login(self, m): 37 | register_uris({"login": ["edit_user_login"]}, m) 38 | 39 | unique_id = "newemail@example.com" 40 | edited_user_login = self.login.edit( 41 | user={"id": 1}, login={"unique_id": unique_id} 42 | ) 43 | 44 | self.assertIsInstance(edited_user_login, Login) 45 | self.assertTrue(hasattr(edited_user_login, "unique_id")) 46 | self.assertEqual(edited_user_login.unique_id, unique_id) 47 | 48 | # __str__() 49 | def test__str__(self, m): 50 | string = str(self.login) 51 | self.assertIsInstance(string, str) 52 | 53 | # get_authentication_events() 54 | def test_get_authentication_events(self, m): 55 | register_uris({"login": ["get_authentication_events"]}, m) 56 | 57 | authentication_event = self.login.get_authentication_events() 58 | event_list = [event for event in authentication_event] 59 | 60 | self.assertEqual(len(event_list), 2) 61 | 62 | self.assertIsInstance(event_list[0], AuthenticationEvent) 63 | self.assertEqual(event_list[0].event_type, "login") 64 | self.assertEqual(event_list[0].pseudonym_id, 9478) 65 | 66 | self.assertIsInstance(event_list[1], AuthenticationEvent) 67 | self.assertEqual(event_list[1].created_at, "2012-07-20T15:00:00-06:00") 68 | self.assertEqual(event_list[1].event_type, "logout") 69 | -------------------------------------------------------------------------------- /tests/test_lti_resource_link.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | import requests_mock 4 | 5 | from canvasapi import Canvas 6 | from tests import settings 7 | from tests.util import register_uris 8 | 9 | 10 | @requests_mock.Mocker() 11 | class TestLTIResourceLink(unittest.TestCase): 12 | def setUp(self): 13 | self.canvas = Canvas(settings.BASE_URL, settings.API_KEY) 14 | 15 | with requests_mock.Mocker() as m: 16 | register_uris( 17 | { 18 | "course": ["get_by_id"], 19 | "lti_resource_link": ["get_lti_resource_link"], 20 | }, 21 | m, 22 | ) 23 | 24 | self.course = self.canvas.get_course(1) 25 | self.resource_link = self.course.get_lti_resource_link(45) 26 | 27 | # __str__() 28 | def test__str__(self, m): 29 | string = str(self.resource_link) 30 | self.assertIsInstance(string, str) 31 | -------------------------------------------------------------------------------- /tests/test_new_quiz.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | import requests_mock 4 | 5 | from canvasapi import Canvas 6 | from canvasapi.new_quiz import NewQuiz 7 | from tests import settings 8 | from tests.util import register_uris 9 | 10 | 11 | @requests_mock.Mocker() 12 | class TestNewQuiz(unittest.TestCase): 13 | def setUp(self): 14 | self.canvas = Canvas(settings.BASE_URL, settings.API_KEY) 15 | 16 | with requests_mock.Mocker() as m: 17 | register_uris({"course": ["get_by_id"]}, m) 18 | register_uris( 19 | {"new_quiz": ["get_new_quiz"]}, 20 | m, 21 | base_url=settings.BASE_URL_NEW_QUIZZES, 22 | ) 23 | 24 | self.course = self.canvas.get_course(1) 25 | self.new_quiz = self.course.get_new_quiz(1) 26 | 27 | # __str__() 28 | def test__str__(self, m): 29 | string = str(self.new_quiz) 30 | self.assertIsInstance(string, str) 31 | 32 | # delete() 33 | def test_delete(self, m): 34 | register_uris( 35 | {"new_quiz": ["delete_new_quiz"]}, 36 | m, 37 | base_url=settings.BASE_URL_NEW_QUIZZES, 38 | ) 39 | 40 | deleted_quiz = self.new_quiz.delete() 41 | 42 | self.assertIsInstance(deleted_quiz, NewQuiz) 43 | self.assertTrue(hasattr(deleted_quiz, "title")) 44 | self.assertEqual(deleted_quiz.title, "New Quiz One") 45 | self.assertTrue(hasattr(deleted_quiz, "course_id")) 46 | self.assertEqual(deleted_quiz.course_id, self.course.id) 47 | 48 | # update() 49 | def test_update(self, m): 50 | register_uris( 51 | {"new_quiz": ["update_new_quiz"]}, 52 | m, 53 | base_url=settings.BASE_URL_NEW_QUIZZES, 54 | ) 55 | 56 | new_title = "New Quiz One - Updated!" 57 | new_instructions = "

This is the updated New Quiz. You got this!

" 58 | new_quiz = self.new_quiz.update( 59 | quiz={"title": new_title, "instructions": new_instructions} 60 | ) 61 | 62 | self.assertIsInstance(new_quiz, NewQuiz) 63 | self.assertTrue(hasattr(new_quiz, "title")) 64 | self.assertEqual(new_quiz.title, new_title) 65 | self.assertTrue(hasattr(new_quiz, "instructions")) 66 | self.assertEqual(new_quiz.instructions, new_instructions) 67 | self.assertTrue(hasattr(new_quiz, "course_id")) 68 | self.assertEqual(new_quiz.course_id, self.course.id) 69 | -------------------------------------------------------------------------------- /tests/test_notification_preference.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | import requests_mock 4 | 5 | from canvasapi import Canvas 6 | from tests import settings 7 | from tests.util import register_uris 8 | 9 | 10 | @requests_mock.Mocker() 11 | class TestNotificationPreference(unittest.TestCase): 12 | def setUp(self): 13 | self.canvas = Canvas(settings.BASE_URL, settings.API_KEY) 14 | 15 | with requests_mock.Mocker() as m: 16 | register_uris( 17 | { 18 | "user": ["get_by_id", "list_comm_channels"], 19 | "communication_channel": ["get_preference"], 20 | }, 21 | m, 22 | ) 23 | 24 | self.user = self.canvas.get_user(1) 25 | self.comm_chan = self.user.get_communication_channels()[0] 26 | self.notif_pref = self.comm_chan.get_preference("new_announcement") 27 | 28 | # __str__() 29 | def test__str__(self, m): 30 | string = str(self.notif_pref) 31 | self.assertIsInstance(string, str) 32 | -------------------------------------------------------------------------------- /tests/test_outcome_import.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | import requests_mock 4 | 5 | from canvasapi import Canvas 6 | from canvasapi.outcome_import import OutcomeImport 7 | from tests import settings 8 | 9 | 10 | @requests_mock.Mocker() 11 | class TestOutcomeImport(unittest.TestCase): 12 | def setUp(self): 13 | self.canvas = Canvas(settings.BASE_URL, settings.API_KEY) 14 | 15 | self.outcome_import = OutcomeImport( 16 | self.canvas._Canvas__requester, {"id": 1, "workflow_state": 1} 17 | ) 18 | 19 | def test_str(self, m): 20 | test_str = str(self.outcome_import) 21 | self.assertIsInstance(test_str, str) 22 | -------------------------------------------------------------------------------- /tests/test_page_view.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | import requests_mock 4 | 5 | from canvasapi import Canvas 6 | from tests import settings 7 | from tests.util import register_uris 8 | 9 | 10 | @requests_mock.Mocker() 11 | class TestPageView(unittest.TestCase): 12 | def setUp(self): 13 | self.canvas = Canvas(settings.BASE_URL, settings.API_KEY) 14 | 15 | with requests_mock.Mocker() as m: 16 | register_uris({"user": ["get_by_id", "page_views", "page_views_p2"]}, m) 17 | 18 | self.user = self.canvas.get_user(1) 19 | pageviews = self.user.get_page_views() 20 | self.pageview = pageviews[0] 21 | 22 | # __str__() 23 | def test__str__(self, m): 24 | string = str(self.pageview) 25 | self.assertIsInstance(string, str) 26 | -------------------------------------------------------------------------------- /tests/test_pairing_code.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | import requests_mock 4 | 5 | from canvasapi import Canvas 6 | from canvasapi.pairing_code import PairingCode 7 | from tests import settings 8 | from tests.util import register_uris 9 | 10 | 11 | @requests_mock.Mocker() 12 | class TestPairingCode(unittest.TestCase): 13 | def setUp(self): 14 | self.canvas = Canvas(settings.BASE_URL, settings.API_KEY) 15 | 16 | with requests_mock.Mocker() as m: 17 | requires = {"user": ["get_by_id"]} 18 | register_uris(requires, m) 19 | 20 | self.user = self.canvas.get_user(1) 21 | 22 | # __str__() 23 | def test__str__(self, m): 24 | register_uris({"user": ["observer_pairing_codes"]}, m) 25 | 26 | pairing_code = self.user.create_pairing_code() 27 | self.assertIsInstance(pairing_code, PairingCode) 28 | self.assertEqual("1 - abc123", pairing_code.__str__()) 29 | -------------------------------------------------------------------------------- /tests/test_peer_review.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | import requests_mock 4 | 5 | from canvasapi.canvas import Canvas 6 | from tests import settings 7 | from tests.util import register_uris 8 | 9 | 10 | @requests_mock.Mocker() 11 | class TestPeerReview(unittest.TestCase): 12 | def setUp(self): 13 | self.canvas = Canvas(settings.BASE_URL, settings.API_KEY) 14 | 15 | with requests_mock.Mocker() as m: 16 | requires = { 17 | "course": ["get_by_id", "get_assignment_by_id"], 18 | "assignment": ["list_peer_reviews"], 19 | } 20 | 21 | register_uris(requires, m) 22 | 23 | self.course = self.canvas.get_course(1) 24 | self.assignment = self.course.get_assignment(1) 25 | self.peer_reviews = [ 26 | peer_review for peer_review in self.assignment.get_peer_reviews() 27 | ] 28 | 29 | # __str__() 30 | def test__str__(self, m): 31 | string = str(self.peer_reviews[0]) 32 | self.assertIsInstance(string, str) 33 | -------------------------------------------------------------------------------- /tests/test_poll.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | import requests_mock 4 | 5 | from canvasapi import Canvas 6 | from canvasapi.exceptions import RequiredFieldMissing 7 | from canvasapi.poll import Poll 8 | from tests import settings 9 | from tests.util import register_uris 10 | 11 | 12 | @requests_mock.Mocker() 13 | class TestPoll(unittest.TestCase): 14 | def setUp(self): 15 | self.canvas = Canvas(settings.BASE_URL, settings.API_KEY) 16 | 17 | with requests_mock.Mocker() as m: 18 | register_uris({"poll": ["get_poll"]}, m) 19 | self.poll = self.canvas.get_poll(1) 20 | 21 | # __str__() 22 | def test__str__(self, m): 23 | string = str(self.poll) 24 | self.assertIsInstance(string, str) 25 | 26 | # get_polls() 27 | def test_get_polls(self, m): 28 | register_uris({"poll": ["get_polls"]}, m) 29 | 30 | polls_list = self.canvas.get_polls() 31 | 32 | self.assertIsInstance(polls_list[0], Poll) 33 | self.assertIsInstance(polls_list[1], Poll) 34 | 35 | # get_poll() 36 | def test_get_poll(self, m): 37 | register_uris({"poll": ["get_poll"]}, m) 38 | 39 | poll_by_id = self.canvas.get_poll(1) 40 | self.assertIsInstance(poll_by_id, Poll) 41 | self.assertEqual(poll_by_id.question, "Is this a question?") 42 | self.assertEqual(poll_by_id.description, "This is a test.") 43 | self.assertEqual(poll_by_id.created_at, "2014-01-07T13:10:19Z") 44 | 45 | poll_by_obj = self.canvas.get_poll(poll_by_id) 46 | self.assertIsInstance(poll_by_obj, Poll) 47 | self.assertEqual(poll_by_obj.question, "Is this a question?") 48 | self.assertEqual(poll_by_obj.description, "This is a test.") 49 | self.assertEqual(poll_by_obj.created_at, "2014-01-07T13:10:19Z") 50 | 51 | # update() 52 | def test_update(self, m): 53 | register_uris({"poll": ["update"]}, m) 54 | 55 | updated_poll_q = self.poll.update([{"question": "Is this not a question?"}]) 56 | self.assertIsInstance(updated_poll_q, Poll) 57 | self.assertEqual(updated_poll_q.question, "Is this not a question?") 58 | 59 | updated_poll_q_and_d = self.poll.update( 60 | [ 61 | {"question": "Is this not a question?"}, 62 | {"description": "This is not a test."}, 63 | ] 64 | ) 65 | self.assertIsInstance(updated_poll_q_and_d, Poll) 66 | self.assertEqual(updated_poll_q_and_d.question, "Is this not a question?") 67 | self.assertEqual(updated_poll_q_and_d.description, "This is not a test.") 68 | 69 | # update 70 | def test_update_fail(self, m): 71 | with self.assertRaises(RequiredFieldMissing): 72 | self.poll.update(poll={}) 73 | 74 | # delete_poll() 75 | def test_delete(self, m): 76 | register_uris({"poll": ["delete"]}, m) 77 | 78 | result = self.poll.delete() 79 | self.assertTrue(result) 80 | -------------------------------------------------------------------------------- /tests/test_poll_submission.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | import requests_mock 4 | 5 | from canvasapi import Canvas 6 | from canvasapi.exceptions import RequiredFieldMissing 7 | from canvasapi.poll_submission import PollSubmission 8 | from tests import settings 9 | from tests.util import register_uris 10 | 11 | 12 | @requests_mock.Mocker() 13 | class TestPollSubmission(unittest.TestCase): 14 | def setUp(self): 15 | self.canvas = Canvas(settings.BASE_URL, settings.API_KEY) 16 | 17 | with requests_mock.Mocker() as m: 18 | requires = { 19 | "poll": ["get_poll"], 20 | "poll_session": ["get_session"], 21 | "poll_submission": ["get_submission"], 22 | } 23 | register_uris(requires, m) 24 | 25 | self.poll = self.canvas.get_poll(1) 26 | self.poll.poll_session = self.poll.get_session(1) 27 | self.poll.poll_session.poll_submission = ( 28 | self.poll.poll_session.get_submission(1) 29 | ) 30 | 31 | # __str__() 32 | def test__str__(self, m): 33 | string = str(self.poll.poll_session.poll_submission) 34 | self.assertIsInstance(string, str) 35 | 36 | # get_submission() 37 | def test_get_submission(self, m): 38 | register_uris({"poll_submission": ["get_submission"]}, m) 39 | 40 | choice_by_id = self.poll.poll_session.get_submission(1) 41 | self.assertIsInstance(choice_by_id, PollSubmission) 42 | self.assertTrue(hasattr(choice_by_id, "id")) 43 | self.assertTrue(hasattr(choice_by_id, "poll_choice_id")) 44 | self.assertTrue(hasattr(choice_by_id, "user_id")) 45 | self.assertTrue(hasattr(choice_by_id, "created_at")) 46 | 47 | choice_by_obj = self.poll.poll_session.get_submission(choice_by_id) 48 | self.assertIsInstance(choice_by_obj, PollSubmission) 49 | self.assertTrue(hasattr(choice_by_obj, "id")) 50 | self.assertTrue(hasattr(choice_by_obj, "poll_choice_id")) 51 | self.assertTrue(hasattr(choice_by_obj, "user_id")) 52 | self.assertTrue(hasattr(choice_by_obj, "created_at")) 53 | 54 | # create_submission() 55 | def test_create_submission(self, m): 56 | register_uris({"poll_submission": ["create_submission"]}, m) 57 | 58 | new_submission = self.poll.poll_session.create_submission( 59 | [{"poll_choice_id": 1}] 60 | ) 61 | self.assertIsInstance(new_submission, PollSubmission) 62 | self.assertEqual(new_submission.poll_choice_id, 1) 63 | 64 | # create_submission() 65 | def test_create_submission_fail(self, m): 66 | with self.assertRaises(RequiredFieldMissing): 67 | self.poll.poll_session.create_submission(poll_submissions={}) 68 | -------------------------------------------------------------------------------- /tests/test_progress.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | import requests_mock 4 | 5 | from canvasapi.canvas import Canvas 6 | from canvasapi.progress import Progress 7 | from tests import settings 8 | from tests.util import register_uris 9 | 10 | 11 | @requests_mock.Mocker() 12 | class TestProgress(unittest.TestCase): 13 | def setUp(self): 14 | self.canvas = Canvas(settings.BASE_URL, settings.API_KEY) 15 | 16 | with requests_mock.Mocker() as m: 17 | requires = { 18 | "course": ["get_by_id", "create_group_category"], 19 | "group": ["category_assign_members_false"], 20 | } 21 | 22 | register_uris(requires, m) 23 | 24 | self.course = self.canvas.get_course(1) 25 | self.group_category = self.course.create_group_category("Test String") 26 | self.progress = self.group_category.assign_members() 27 | 28 | # __str__() 29 | def test__str__(self, m): 30 | string = str(self.progress) 31 | self.assertIsInstance(string, str) 32 | 33 | # query() 34 | def test_query(self, m): 35 | register_uris({"progress": ["progress_query"]}, m) 36 | 37 | response = self.progress.query() 38 | self.assertIsInstance(response, Progress) 39 | -------------------------------------------------------------------------------- /tests/test_scope.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | import requests_mock 4 | 5 | from canvasapi import Canvas 6 | from canvasapi.scope import Scope 7 | from tests import settings 8 | 9 | 10 | @requests_mock.Mocker() 11 | class TestGradingPeriod(unittest.TestCase): 12 | def setUp(self): 13 | self.canvas = Canvas(settings.BASE_URL, settings.API_KEY) 14 | 15 | self.scope = Scope( 16 | self.canvas._Canvas__requester, {"resource": "users", "verb": "PUT"} 17 | ) 18 | 19 | def test_str(self, m): 20 | test_str = str(self.scope) 21 | self.assertIsInstance(test_str, str) 22 | -------------------------------------------------------------------------------- /tests/test_sis_import.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | import requests_mock 4 | 5 | from canvasapi import Canvas 6 | from canvasapi.progress import Progress 7 | from canvasapi.sis_import import SisImport 8 | from tests import settings 9 | from tests.util import register_uris 10 | 11 | 12 | @requests_mock.Mocker() 13 | class TestSisImportGroup(unittest.TestCase): 14 | def setUp(self): 15 | self.canvas = Canvas(settings.BASE_URL, settings.API_KEY) 16 | 17 | with requests_mock.Mocker() as m: 18 | requires = { 19 | "account": ["get_by_id", "get_role"], 20 | "sis_import": ["get_by_id"], 21 | } 22 | register_uris(requires, m) 23 | 24 | self.account = self.canvas.get_account(1) 25 | self.sis_import = self.account.get_sis_import(2) 26 | 27 | # abort() 28 | def test_abort_sis_import(self, m): 29 | register_uris({"sis_import": ["abort_sis_import"]}, m) 30 | 31 | aborted_sis_import = self.sis_import.abort() 32 | 33 | self.assertIsInstance(aborted_sis_import, SisImport) 34 | 35 | self.assertTrue( 36 | aborted_sis_import.workflow_state == "aborted" 37 | if aborted_sis_import.progress < 100 38 | else True 39 | ) 40 | 41 | # restore_states() 42 | def test_restore_states(self, m): 43 | register_uris({"sis_import": ["restore_sis_import_states"]}, m) 44 | 45 | restore_state_progress = self.sis_import.restore_states() 46 | 47 | self.assertIsInstance(restore_state_progress, Progress) 48 | self.assertEqual(restore_state_progress.context_id, self.sis_import.id) 49 | self.assertEqual(restore_state_progress.context_type, "SisBatch") 50 | self.assertEqual(restore_state_progress.tag, "sis_batch_state_restore") 51 | -------------------------------------------------------------------------------- /tests/test_tab.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | import requests_mock 4 | 5 | from canvasapi import Canvas 6 | from canvasapi.tab import Tab 7 | from tests import settings 8 | from tests.util import register_uris 9 | 10 | 11 | @requests_mock.Mocker() 12 | class TestTab(unittest.TestCase): 13 | def setUp(self): 14 | self.canvas = Canvas(settings.BASE_URL, settings.API_KEY) 15 | 16 | with requests_mock.Mocker() as m: 17 | register_uris( 18 | { 19 | "course": ["get_by_id", "list_tabs"], 20 | "group": ["get_by_id", "list_tabs"], 21 | }, 22 | m, 23 | ) 24 | 25 | self.course = self.canvas.get_course(1) 26 | 27 | tabs = self.course.get_tabs() 28 | self.tab = tabs[1] 29 | 30 | self.group = self.canvas.get_group(1) 31 | group_tabs = self.group.get_tabs() 32 | self.tab_group = group_tabs[1] 33 | 34 | # __str__() 35 | def test__str__(self, m): 36 | string = str(self.tab) 37 | self.assertIsInstance(string, str) 38 | 39 | # update() 40 | def test_update_course(self, m): 41 | register_uris({"course": ["update_tab"]}, m) 42 | 43 | new_position = 3 44 | self.tab.update(position=new_position) 45 | 46 | self.assertIsInstance(self.tab, Tab) 47 | self.assertEqual(self.tab.position, 3) 48 | 49 | def test_update_group(self, m): 50 | with self.assertRaises(ValueError): 51 | self.tab_group.update(position=1) 52 | -------------------------------------------------------------------------------- /tests/test_todo.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | import requests_mock 4 | 5 | from canvasapi import Canvas 6 | from canvasapi.todo import Todo 7 | from tests import settings 8 | 9 | 10 | @requests_mock.Mocker() 11 | class TestTodo(unittest.TestCase): 12 | def setUp(self): 13 | self.canvas = Canvas(settings.BASE_URL, settings.API_KEY) 14 | 15 | self.todo = Todo( 16 | self.canvas._Canvas__requester, 17 | { 18 | "type": "grading", 19 | "assignment": {}, 20 | "ignore": ".. url ..", 21 | "ignore_permanently": ".. url ..", 22 | "html_url": ".. url ..", 23 | "needs_grading_count": 3, 24 | "context_type": "course", 25 | "course_id": 1, 26 | "group_id": None, 27 | }, 28 | ) 29 | 30 | def test_str(self, m): 31 | test_str = str(self.todo) 32 | self.assertIsInstance(test_str, str) 33 | self.assertEqual(test_str, "Todo Item (grading)") 34 | -------------------------------------------------------------------------------- /tests/test_uploader.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import uuid 3 | from pathlib import Path 4 | 5 | import requests_mock 6 | 7 | from canvasapi.canvas import Canvas 8 | from canvasapi.upload import Uploader 9 | from tests import settings 10 | from tests.util import cleanup_file, register_uris 11 | 12 | 13 | @requests_mock.Mocker() 14 | class TestUploader(unittest.TestCase): 15 | def setUp(self): 16 | self.canvas = Canvas(settings.BASE_URL, settings.API_KEY) 17 | self.requester = self.canvas._Canvas__requester 18 | 19 | self.filename = "testfile_uploader_{}".format(uuid.uuid4().hex) 20 | self.file = open(self.filename, "w+") 21 | 22 | def tearDown(self): 23 | self.file.close() 24 | cleanup_file(self.filename) 25 | 26 | # start() 27 | def test_start(self, m): 28 | requires = {"uploader": ["upload_response", "upload_response_upload_url"]} 29 | register_uris(requires, m) 30 | 31 | uploader = Uploader(self.requester, "upload_response", self.file) 32 | result = uploader.start() 33 | 34 | self.assertTrue(result[0]) 35 | self.assertIsInstance(result[1], dict) 36 | self.assertIn("url", result[1]) 37 | 38 | def test_start_pathlib(self, m): 39 | requires = {"uploader": ["upload_response", "upload_response_upload_url"]} 40 | register_uris(requires, m) 41 | 42 | uploader = Uploader(self.requester, "upload_response", Path(self.filename)) 43 | result = uploader.start() 44 | 45 | self.assertTrue(result[0]) 46 | self.assertIsInstance(result[1], dict) 47 | self.assertIn("url", result[1]) 48 | 49 | def test_start_path(self, m): 50 | requires = {"uploader": ["upload_response", "upload_response_upload_url"]} 51 | register_uris(requires, m) 52 | 53 | uploader = Uploader(self.requester, "upload_response", self.filename) 54 | result = uploader.start() 55 | 56 | self.assertTrue(result[0]) 57 | self.assertIsInstance(result[1], dict) 58 | self.assertIn("url", result[1]) 59 | 60 | def test_start_file_does_not_exist(self, m): 61 | with self.assertRaises(IOError): 62 | Uploader(self.requester, "upload_response", "test_file_not_real.xyz") 63 | 64 | # upload() 65 | def test_upload_no_upload_url(self, m): 66 | register_uris({"uploader": ["upload_response_no_upload_url"]}, m) 67 | 68 | with self.assertRaises(ValueError): 69 | Uploader( 70 | self.requester, "upload_response_no_upload_url", self.filename 71 | ).start() 72 | 73 | def test_upload_no_upload_params(self, m): 74 | register_uris({"uploader": ["upload_response_no_upload_params"]}, m) 75 | 76 | with self.assertRaises(ValueError): 77 | Uploader( 78 | self.requester, "upload_response_no_upload_params", self.filename 79 | ).start() 80 | 81 | def test_upload_fail(self, m): 82 | requires = {"uploader": ["upload_fail", "upload_response_fail"]} 83 | register_uris(requires, m) 84 | 85 | uploader = Uploader(self.requester, "upload_response_fail", self.file) 86 | result = uploader.start() 87 | 88 | self.assertFalse(result[0]) 89 | self.assertIsInstance(result[1], dict) 90 | self.assertNotIn("url", result[1]) 91 | -------------------------------------------------------------------------------- /tests/test_usage_rights.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | import requests_mock 4 | 5 | from canvasapi import Canvas 6 | from tests import settings 7 | from tests.util import register_uris 8 | 9 | 10 | @requests_mock.Mocker() 11 | class TestUsageRights(unittest.TestCase): 12 | def setUp(self): 13 | self.canvas = Canvas(settings.BASE_URL, settings.API_KEY) 14 | 15 | with requests_mock.Mocker() as m: 16 | requires = {"user": ["get_by_id", "set_usage_rights"]} 17 | 18 | register_uris(requires, m) 19 | 20 | self.user = self.canvas.get_user(1) 21 | self.usage_rights = self.user.set_usage_rights( 22 | file_ids=[1, 2], usage_rights={"use_justification": "fair_use"} 23 | ) 24 | 25 | # __str__() 26 | def test__str__(self, m): 27 | string = str(self.usage_rights) 28 | self.assertIsInstance(string, str) 29 | -------------------------------------------------------------------------------- /tests/util.py: -------------------------------------------------------------------------------- 1 | import json 2 | import os 3 | 4 | import requests_mock 5 | 6 | from tests import settings 7 | 8 | 9 | def register_uris(requirements, requests_mocker, base_url=None): 10 | """ 11 | Given a list of required fixtures and an requests_mocker object, 12 | register each fixture as a uri with the mocker. 13 | 14 | :param base_url: str 15 | :param requirements: dict 16 | :param requests_mocker: requests_mock.mocker.Mocker 17 | """ 18 | if base_url is None: 19 | base_url = settings.BASE_URL_WITH_VERSION 20 | for fixture, objects in requirements.items(): 21 | try: 22 | with open("tests/fixtures/{}.json".format(fixture)) as file: 23 | data = json.loads(file.read()) 24 | except (IOError, ValueError): 25 | raise ValueError("Fixture {}.json contains invalid JSON.".format(fixture)) 26 | 27 | if not isinstance(objects, list): 28 | raise TypeError("{} is not a list.".format(objects)) 29 | 30 | for obj_name in objects: 31 | obj = data.get(obj_name) 32 | 33 | if obj is None: 34 | raise ValueError( 35 | "{} does not exist in {}.json".format(obj_name.__repr__(), fixture) 36 | ) 37 | 38 | method = requests_mock.ANY if obj["method"] == "ANY" else obj["method"] 39 | if obj["endpoint"] == "ANY": 40 | url = requests_mock.ANY 41 | else: 42 | url = base_url + obj["endpoint"] 43 | 44 | try: 45 | requests_mocker.register_uri( 46 | method, 47 | url, 48 | json=obj.get("data"), 49 | status_code=obj.get("status_code", 200), 50 | headers=obj.get("headers", {}), 51 | ) 52 | except Exception as e: 53 | print(e) 54 | 55 | 56 | def cleanup_file(filename): 57 | """ 58 | Remove a test file from the system. If the file doesn't exist, ignore. 59 | 60 | `Not as stupid as it looks. _` 61 | """ 62 | try: 63 | os.remove(filename) 64 | except OSError: 65 | pass 66 | -------------------------------------------------------------------------------- /tests_requirements.txt: -------------------------------------------------------------------------------- 1 | -r requirements.txt 2 | 3 | black>=23.3.0 4 | coverage 5 | flake8 6 | isort 7 | requests-mock 8 | urllib3<2 9 | --------------------------------------------------------------------------------