├── VERSION ├── tests ├── __init__.py ├── client │ ├── __init__.py │ └── test_auth.py ├── test_exceptions.py ├── test_assignments.py └── test_modules.py ├── canvas_sdk ├── methods │ ├── __init__.py │ ├── progress.py │ ├── quiz_ip_filters.py │ ├── collaborations.py │ ├── services.py │ ├── account_domain_lookups.py │ ├── submission_comments.py │ ├── quiz_statistics.py │ ├── enrollment_terms.py │ ├── course_audit_log.py │ ├── quiz_submission_files.py │ ├── comm_messages.py │ ├── quiz_assignment_overrides.py │ ├── poll_submissions.py │ ├── quiz_extensions.py │ ├── account_notifications.py │ ├── conferences.py │ ├── authentications_log.py │ ├── favorites.py │ ├── tabs.py │ ├── live_assessments.py │ ├── outcomes.py │ ├── content_exports.py │ ├── polls.py │ ├── outcome_results.py │ ├── grading_standards.py │ ├── admins.py │ ├── account_reports.py │ ├── quiz_reports.py │ ├── communication_channels.py │ ├── user_observees.py │ ├── logins.py │ ├── poll_choices.py │ ├── grade_change_log.py │ ├── search.py │ ├── quiz_question_groups.py │ ├── gradebook_history.py │ ├── announcement_external_feeds.py │ ├── assignment_groups.py │ ├── sis_imports.py │ ├── roles.py │ ├── quiz_submission_questions.py │ ├── custom_gradebook_columns.py │ └── poll_sessions.py ├── __init__.py ├── client │ ├── __init__.py │ ├── auth.py │ ├── request_context.py │ └── base.py ├── exceptions.py └── utils.py ├── setup.cfg ├── MANIFEST.in ├── README.md ├── canvas_python_sdk.sublime-project ├── .gitignore ├── LICENSE ├── setup.py ├── static_methods ├── sections.py ├── account_reports.py ├── users.py ├── accounts.py └── modules.py └── scripts └── README.md /VERSION: -------------------------------------------------------------------------------- 1 | 1.2.1 2 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/client/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /canvas_sdk/methods/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [bdist_wheel] 2 | python-tag = py3 3 | -------------------------------------------------------------------------------- /canvas_sdk/__init__.py: -------------------------------------------------------------------------------- 1 | from .client import RequestContext 2 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include README.md 2 | include LICENSE 3 | include VERSION 4 | graft tests 5 | -------------------------------------------------------------------------------- /canvas_sdk/client/__init__.py: -------------------------------------------------------------------------------- 1 | from .request_context import RequestContext 2 | from .auth import OAuth2Bearer 3 | from .base import get, put, post, delete 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | canvas_python_sdk 2 | ================= 3 | 4 | Installation 5 | ------------- 6 | 7 | Detailed instructions for installation and configuration of Canvas are provided 8 | on our wiki. 9 | 10 | * [Quick Start](https://github.com/penzance/canvas_python_sdk/wiki) 11 | 12 | -------------------------------------------------------------------------------- /canvas_python_sdk.sublime-project: -------------------------------------------------------------------------------- 1 | { 2 | "build_systems": 3 | [ 4 | { 5 | "name": "Anaconda Python Builder", 6 | "selector": "source.python", 7 | "shell_cmd": "python -u \"$file\"" 8 | } 9 | ], 10 | "folders": 11 | [ 12 | { 13 | "follow_symlinks": true, 14 | "path": "./" 15 | } 16 | ], 17 | "settings": 18 | { 19 | "tab_size": 4, 20 | "test_command": "python -m unittest discover", 21 | "test_delimiter": ".", 22 | "test_virtualenv": "~/.virtualenvs/canvas_python_sdk", 23 | "translate_tabs_to_spaces": true 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /canvas_sdk/client/auth.py: -------------------------------------------------------------------------------- 1 | from requests.auth import AuthBase 2 | 3 | 4 | class OAuth2Bearer(AuthBase): 5 | 6 | """ Attached Oauth2 HTTP Bearer Authentication to the given Request object """ 7 | 8 | def __init__(self, oauth2_token): 9 | if not oauth2_token: 10 | raise AttributeError("OAuth2 token must be a non-empty string value.") 11 | self.oauth2_token = oauth2_token 12 | 13 | def __call__(self, r): 14 | # Modify and return the request 15 | r.headers['Authorization'] = 'Bearer ' + self.oauth2_token 16 | return r 17 | -------------------------------------------------------------------------------- /canvas_sdk/methods/progress.py: -------------------------------------------------------------------------------- 1 | from canvas_sdk import client, utils 2 | 3 | def query_progress(request_ctx, id, **request_kwargs): 4 | """ 5 | Return completion and status information about an asynchronous job 6 | 7 | :param request_ctx: The request context 8 | :type request_ctx: :class:RequestContext 9 | :param id: (required) ID 10 | :type id: string 11 | :return: Query progress 12 | :rtype: requests.Response (with Progress data) 13 | 14 | """ 15 | 16 | path = '/v1/progress/{id}' 17 | url = request_ctx.base_api_url + path.format(id=id) 18 | response = client.get(request_ctx, url, **request_kwargs) 19 | 20 | return response 21 | 22 | 23 | -------------------------------------------------------------------------------- /canvas_sdk/methods/quiz_ip_filters.py: -------------------------------------------------------------------------------- 1 | from canvas_sdk import client, utils 2 | 3 | def get_available_quiz_ip_filters(request_ctx, course_id, quiz_id, **request_kwargs): 4 | """ 5 | Get a list of available IP filters for this Quiz. 6 | 7 | 200 OK response code is returned if the request was successful. 8 | 9 | :param request_ctx: The request context 10 | :type request_ctx: :class:RequestContext 11 | :param course_id: (required) ID 12 | :type course_id: string 13 | :param quiz_id: (required) ID 14 | :type quiz_id: string 15 | :return: Get available quiz IP filters. 16 | :rtype: requests.Response (with void data) 17 | 18 | """ 19 | 20 | path = '/v1/courses/{course_id}/quizzes/{quiz_id}/ip_filters' 21 | url = request_ctx.base_api_url + path.format(course_id=course_id, quiz_id=quiz_id) 22 | response = client.get(request_ctx, url, **request_kwargs) 23 | 24 | return response 25 | 26 | 27 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | 5 | # log files 6 | *.log 7 | 8 | # secure file 9 | secure.py 10 | 11 | # C extensions 12 | *.so 13 | 14 | # Distribution / packaging 15 | .Python 16 | env/ 17 | bin/ 18 | build/ 19 | develop-eggs/ 20 | dist/ 21 | eggs/ 22 | lib/ 23 | lib64/# Backup files 24 | *.~ 25 | 26 | # Byte-compiled / optimized / DLL files 27 | __pycache__/ 28 | *.py[cod] 29 | 30 | # C extensions 31 | *.so 32 | 33 | # Distribution / packaging 34 | bin/ 35 | build/ 36 | develop-eggs/ 37 | dist/ 38 | eggs/ 39 | lib/ 40 | lib64/ 41 | parts/ 42 | sdist/ 43 | var/ 44 | *.egg-info/ 45 | .installed.cfg 46 | *.egg 47 | MANIFEST 48 | 49 | # Installer logs 50 | pip-log.txt 51 | pip-delete-this-directory.txt 52 | 53 | # Unit test / coverage reports 54 | .tox/ 55 | .coverage 56 | .cache 57 | nosetests.xml 58 | coverage.xml 59 | 60 | # Translations 61 | *.mo 62 | 63 | # Sphinx documentation 64 | docs/_build/ 65 | 66 | # Submlime 67 | *.sublime-workspace 68 | 69 | # PyCharm IDE metadata 70 | .idea/ 71 | 72 | 73 | -------------------------------------------------------------------------------- /canvas_sdk/methods/collaborations.py: -------------------------------------------------------------------------------- 1 | from canvas_sdk import client, utils 2 | 3 | def list_members_of_collaboration(request_ctx, id, per_page=None, **request_kwargs): 4 | """ 5 | Examples 6 | 7 | curl https:///api/v1/courses/1/collaborations/1/members 8 | 9 | :param request_ctx: The request context 10 | :type request_ctx: :class:RequestContext 11 | :param id: (required) ID 12 | :type id: string 13 | :param per_page: (optional) Set how many results canvas should return, defaults to config.LIMIT_PER_PAGE 14 | :type per_page: integer or None 15 | :return: List members of a collaboration. 16 | :rtype: requests.Response (with array data) 17 | 18 | """ 19 | 20 | if per_page is None: 21 | per_page = request_ctx.per_page 22 | path = '/v1/collaborations/{id}/members' 23 | payload = { 24 | 'per_page' : per_page, 25 | } 26 | url = request_ctx.base_api_url + path.format(id=id) 27 | response = client.get(request_ctx, url, payload=payload, **request_kwargs) 28 | 29 | return response 30 | 31 | 32 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2020 President and Fellows of Harvard College 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. -------------------------------------------------------------------------------- /canvas_sdk/methods/services.py: -------------------------------------------------------------------------------- 1 | from canvas_sdk import client, utils 2 | 3 | def get_kaltura_config(request_ctx, **request_kwargs): 4 | """ 5 | Return the config information for the Kaltura plugin in json format. 6 | 7 | :param request_ctx: The request context 8 | :type request_ctx: :class:RequestContext 9 | :return: Get Kaltura config 10 | :rtype: requests.Response (with void data) 11 | 12 | """ 13 | 14 | path = '/v1/services/kaltura' 15 | url = request_ctx.base_api_url + path.format() 16 | response = client.get(request_ctx, url, **request_kwargs) 17 | 18 | return response 19 | 20 | 21 | def start_kaltura_session(request_ctx, **request_kwargs): 22 | """ 23 | Start a new Kaltura session, so that new media can be recorded and uploaded 24 | to this Canvas instance's Kaltura instance. 25 | 26 | :param request_ctx: The request context 27 | :type request_ctx: :class:RequestContext 28 | :return: Start Kaltura session 29 | :rtype: requests.Response (with void data) 30 | 31 | """ 32 | 33 | path = '/v1/services/kaltura_session' 34 | url = request_ctx.base_api_url + path.format() 35 | response = client.post(request_ctx, url, **request_kwargs) 36 | 37 | return response 38 | 39 | 40 | -------------------------------------------------------------------------------- /canvas_sdk/methods/account_domain_lookups.py: -------------------------------------------------------------------------------- 1 | from canvas_sdk import client, utils 2 | 3 | def search_account_domains(request_ctx, name=None, domain=None, latitude=None, longitude=None, **request_kwargs): 4 | """ 5 | Returns a list of up to 5 matching account domains 6 | 7 | Partial match on name / domain are supported 8 | 9 | :param request_ctx: The request context 10 | :type request_ctx: :class:RequestContext 11 | :param name: (optional) campus name 12 | :type name: string or None 13 | :param domain: (optional) no description 14 | :type domain: string or None 15 | :param latitude: (optional) no description 16 | :type latitude: string or None 17 | :param longitude: (optional) no description 18 | :type longitude: string or None 19 | :return: 20 | :rtype: requests.Response (with void data) 21 | 22 | """ 23 | 24 | path = '/v1/accounts/search' 25 | payload = { 26 | 'name' : name, 27 | 'domain' : domain, 28 | 'latitude' : latitude, 29 | 'longitude' : longitude, 30 | } 31 | url = request_ctx.base_api_url + path.format() 32 | response = client.get(request_ctx, url, payload=payload, **request_kwargs) 33 | 34 | return response 35 | 36 | 37 | -------------------------------------------------------------------------------- /canvas_sdk/methods/submission_comments.py: -------------------------------------------------------------------------------- 1 | from canvas_sdk import client, utils 2 | 3 | def upload_file(request_ctx, course_id, assignment_id, user_id, **request_kwargs): 4 | """ 5 | Upload a file to attach to a submission comment 6 | 7 | See the {file:file_uploads.html File Upload Documentation} for details on the file upload workflow. 8 | 9 | The final step of the file upload workflow will return the attachment data, 10 | including the new file id. The caller can then PUT the file_id to the 11 | submission API to attach it to a comment 12 | 13 | :param request_ctx: The request context 14 | :type request_ctx: :class:RequestContext 15 | :param course_id: (required) ID 16 | :type course_id: string 17 | :param assignment_id: (required) ID 18 | :type assignment_id: string 19 | :param user_id: (required) ID 20 | :type user_id: string 21 | :return: Upload a file 22 | :rtype: requests.Response (with void data) 23 | 24 | """ 25 | 26 | path = '/v1/courses/{course_id}/assignments/{assignment_id}/submissions/{user_id}/comments/files' 27 | url = request_ctx.base_api_url + path.format(course_id=course_id, assignment_id=assignment_id, user_id=user_id) 28 | response = client.post(request_ctx, url, **request_kwargs) 29 | 30 | return response 31 | 32 | 33 | -------------------------------------------------------------------------------- /canvas_sdk/exceptions.py: -------------------------------------------------------------------------------- 1 | class SDKException(Exception): 2 | 3 | """ Base class for exceptions in the canvas python sdk """ 4 | pass 5 | 6 | 7 | class InvalidOAuthTokenError(SDKException): 8 | """ Indicates that an invalid access token made the request """ 9 | pass 10 | 11 | class CanvasAPIError(SDKException): 12 | 13 | """ 14 | Error that gets returned from Canvas API calls via request library. Contains the 15 | http status code and the raw json response from the API request. Note that the 16 | structure of the json sent back by Canvas can vary depending on the type of error. 17 | """ 18 | 19 | def __init__(self, status_code=500, msg=None, error_json=None): 20 | self.status_code = status_code 21 | self.error_msg = msg 22 | self.error_json = error_json 23 | 24 | """ 25 | NOTE: this method of fully defining unicode and having str call upon it is referenced 26 | in official pydocs (https://docs.python.org/2/howto/pyporting.html) and other porting 27 | libraries (like Django utils). If we move to Python 3, we'd rewrite this to just have 28 | a __str__ method and use futures and mixins to play nice with Python 2. 29 | """ 30 | 31 | def __str__(self): 32 | if self.error_msg: 33 | return '%s: %s' % (self.status_code, self.error_msg) 34 | else: 35 | return '%s' % self.status_code 36 | -------------------------------------------------------------------------------- /canvas_sdk/methods/quiz_statistics.py: -------------------------------------------------------------------------------- 1 | from canvas_sdk import client, utils 2 | 3 | def fetching_latest_quiz_statistics(request_ctx, course_id, quiz_id, all_versions, **request_kwargs): 4 | """ 5 | This endpoint provides statistics for all quiz versions, or for a specific 6 | quiz version, in which case the output is guaranteed to represent the 7 | _latest_ and most current version of the quiz. 8 | 9 | 200 OK response code is returned if the request was successful. 10 | 11 | :param request_ctx: The request context 12 | :type request_ctx: :class:RequestContext 13 | :param course_id: (required) ID 14 | :type course_id: string 15 | :param quiz_id: (required) ID 16 | :type quiz_id: string 17 | :param all_versions: (required) Whether the statistics report should include all submissions attempts. 18 | :type all_versions: boolean 19 | :return: Fetching the latest quiz statistics 20 | :rtype: requests.Response (with void data) 21 | 22 | """ 23 | 24 | path = '/v1/courses/{course_id}/quizzes/{quiz_id}/statistics' 25 | payload = { 26 | 'all_versions' : all_versions, 27 | } 28 | url = request_ctx.base_api_url + path.format(course_id=course_id, quiz_id=quiz_id) 29 | response = client.get(request_ctx, url, payload=payload, **request_kwargs) 30 | 31 | return response 32 | 33 | 34 | -------------------------------------------------------------------------------- /canvas_sdk/methods/enrollment_terms.py: -------------------------------------------------------------------------------- 1 | from canvas_sdk import client, utils 2 | 3 | def list_enrollment_terms(request_ctx, account_id, workflow_state=None, per_page=None, **request_kwargs): 4 | """ 5 | Return all of the terms in the account. Account must be a root account and 6 | requires permission to manage the account (when called on non-root 7 | accounts, will be directed to the appropriate root account). 8 | 9 | :param request_ctx: The request context 10 | :type request_ctx: :class:RequestContext 11 | :param account_id: (required) ID 12 | :type account_id: string 13 | :param workflow_state: (optional) no description 14 | :type workflow_state: string or None 15 | :param per_page: (optional) Set how many results canvas should return, defaults to config.LIMIT_PER_PAGE 16 | :type per_page: integer or None 17 | :return: List enrollment terms 18 | :rtype: requests.Response (with array data) 19 | 20 | """ 21 | 22 | if per_page is None: 23 | per_page = request_ctx.per_page 24 | path = '/v1/accounts/{account_id}/terms' 25 | payload = { 26 | 'workflow_state' : workflow_state, 27 | 'per_page' : per_page, 28 | } 29 | url = request_ctx.base_api_url + path.format(account_id=account_id) 30 | response = client.get(request_ctx, url, payload=payload, **request_kwargs) 31 | 32 | return response 33 | 34 | 35 | -------------------------------------------------------------------------------- /canvas_sdk/methods/course_audit_log.py: -------------------------------------------------------------------------------- 1 | from canvas_sdk import client, utils 2 | 3 | def query_by_course(request_ctx, course_id, start_time=None, end_time=None, per_page=None, **request_kwargs): 4 | """ 5 | List course change events for a given course. 6 | 7 | :param request_ctx: The request context 8 | :type request_ctx: :class:RequestContext 9 | :param course_id: (required) ID 10 | :type course_id: string 11 | :param start_time: (optional) The beginning of the time range from which you want events. 12 | :type start_time: datetime or None 13 | :param end_time: (optional) The end of the time range from which you want events. 14 | :type end_time: datetime or None 15 | :param per_page: (optional) Set how many results canvas should return, defaults to config.LIMIT_PER_PAGE 16 | :type per_page: integer or None 17 | :return: Query by course. 18 | :rtype: requests.Response (with array data) 19 | 20 | """ 21 | 22 | if per_page is None: 23 | per_page = request_ctx.per_page 24 | path = '/v1/audit/course/courses/{course_id}' 25 | payload = { 26 | 'start_time' : start_time, 27 | 'end_time' : end_time, 28 | 'per_page' : per_page, 29 | } 30 | url = request_ctx.base_api_url + path.format(course_id=course_id) 31 | response = client.get(request_ctx, url, payload=payload, **request_kwargs) 32 | 33 | return response 34 | 35 | 36 | -------------------------------------------------------------------------------- /canvas_sdk/methods/quiz_submission_files.py: -------------------------------------------------------------------------------- 1 | from canvas_sdk import client, utils 2 | 3 | def upload_file(request_ctx, course_id, quiz_id, name, on_duplicate, **request_kwargs): 4 | """ 5 | Associate a new quiz submission file 6 | 7 | This API endpoint is the first step in uploading a quiz submission file. 8 | See the {file:file_uploads.html File Upload Documentation} for details on 9 | the file upload workflow as these parameters are interpreted as per the 10 | documentation there. 11 | 12 | :param request_ctx: The request context 13 | :type request_ctx: :class:RequestContext 14 | :param course_id: (required) ID 15 | :type course_id: string 16 | :param quiz_id: (required) ID 17 | :type quiz_id: string 18 | :param name: (required) The name of the quiz submission file 19 | :type name: string 20 | :param on_duplicate: (required) How to handle duplicate names 21 | :type on_duplicate: string 22 | :return: Upload a file 23 | :rtype: requests.Response (with void data) 24 | 25 | """ 26 | 27 | path = '/v1/courses/{course_id}/quizzes/{quiz_id}/submissions/self/files' 28 | payload = { 29 | 'name' : name, 30 | 'on_duplicate' : on_duplicate, 31 | } 32 | url = request_ctx.base_api_url + path.format(course_id=course_id, quiz_id=quiz_id) 33 | response = client.post(request_ctx, url, payload=payload, **request_kwargs) 34 | 35 | return response 36 | 37 | 38 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | import os 2 | from setuptools import setup, find_packages 3 | 4 | with open(os.path.join(os.path.dirname(__file__), 'VERSION')) as v_file: 5 | version = v_file.read().strip() 6 | 7 | with open(os.path.join(os.path.dirname(__file__), 'README.md')) as readme: 8 | README = readme.read() 9 | 10 | setup( 11 | name='canvas_python_sdk', 12 | version=version, 13 | description='A python SDK for Instructure\'s Canvas LMS API', 14 | author='Harvard University', 15 | author_email='tlt-opensource@g.harvard.edu', 16 | url='https://github.com/penzance/canvas_python_sdk', 17 | packages=find_packages(exclude=["*.tests", "*.tests.*", "tests.*", "tests"]), 18 | long_description=README, 19 | classifiers=[ 20 | "License :: OSI Approved :: MIT License", 21 | 'Operating System :: OS Independent', 22 | "Programming Language :: Python :: 3.6", 23 | "Programming Language :: Python :: 3.7", 24 | "Programming Language :: Python :: 3.8", 25 | "Development Status :: 5 - Production/Stable", 26 | "Intended Audience :: Developers", 27 | "Topic :: Software Development", 28 | ], 29 | keywords='canvas api sdk LMS', 30 | license='MIT', 31 | zip_safe=False, 32 | install_requires=[ 33 | 'requests', 34 | ], 35 | extras_require={ 36 | 'docs': ['sphinx>=1.2.0'], 37 | }, 38 | python_requires='>=3.6', 39 | test_suite='tests', 40 | tests_require=[ 41 | 'requests', 42 | ], 43 | ) 44 | -------------------------------------------------------------------------------- /canvas_sdk/methods/comm_messages.py: -------------------------------------------------------------------------------- 1 | from canvas_sdk import client, utils 2 | 3 | def list_of_commmessages_for_user(request_ctx, user_id, start_time=None, end_time=None, per_page=None, **request_kwargs): 4 | """ 5 | Retrieve messages sent to a user. 6 | 7 | :param request_ctx: The request context 8 | :type request_ctx: :class:RequestContext 9 | :param user_id: (required) The user id for whom you want to retrieve CommMessages 10 | :type user_id: string 11 | :param start_time: (optional) The beginning of the time range you want to retrieve message from. 12 | :type start_time: datetime or None 13 | :param end_time: (optional) The end of the time range you want to retrieve messages for. 14 | :type end_time: datetime or None 15 | :param per_page: (optional) Set how many results canvas should return, defaults to config.LIMIT_PER_PAGE 16 | :type per_page: integer or None 17 | :return: List of CommMessages for a user 18 | :rtype: requests.Response (with array data) 19 | 20 | """ 21 | 22 | if per_page is None: 23 | per_page = request_ctx.per_page 24 | path = '/v1/comm_messages' 25 | payload = { 26 | 'user_id' : user_id, 27 | 'start_time' : start_time, 28 | 'end_time' : end_time, 29 | 'per_page' : per_page, 30 | } 31 | url = request_ctx.base_api_url + path.format() 32 | response = client.get(request_ctx, url, payload=payload, **request_kwargs) 33 | 34 | return response 35 | 36 | 37 | -------------------------------------------------------------------------------- /tests/test_exceptions.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | from canvas_sdk.exceptions import CanvasAPIError 4 | 5 | 6 | 7 | class TestExceptions(unittest.TestCase): 8 | longMessage = True 9 | 10 | def setUp(self): 11 | self.default_api_error = CanvasAPIError() 12 | 13 | def test_default_status_for_canvas_api_error(self): 14 | """ Test expected default status for instance of CanvasAPIError """ 15 | self.assertEqual(self.default_api_error.status_code, 500) 16 | 17 | def test_default_message_for_canvas_api_error(self): 18 | """ Test expected default msg attribute for instance of CanvasAPIError """ 19 | self.assertIsNone(self.default_api_error.error_msg) 20 | 21 | def test_default_error_json_for_canvas_api_error(self): 22 | """ Test expected default error_json attribute for instance of CanvasAPIError """ 23 | self.assertIsNone(self.default_api_error.error_json) 24 | 25 | def test_default_str_for_canvas_api_error(self): 26 | """ Test default CanvasAPIError instance represented as a str """ 27 | self.assertEqual('500', str(self.default_api_error)) 28 | 29 | def test_instance_str_for_canvas_api_error(self): 30 | """ Test string representation of CanvasAPIError with custom attributes """ 31 | status = 404 32 | error_msg = 'This is a test message' 33 | error_json = {'Some error json'} 34 | 35 | api_error = CanvasAPIError(status_code=status, msg=error_msg, error_json=error_json) 36 | self.assertEqual('%d: %s' % (status, error_msg), str(api_error)) 37 | -------------------------------------------------------------------------------- /canvas_sdk/methods/quiz_assignment_overrides.py: -------------------------------------------------------------------------------- 1 | from canvas_sdk import client, utils 2 | 3 | def retrieve_assignment_overridden_dates_for_quizzes(request_ctx, course_id, quiz_assignment_overrides_0_quiz_ids=None, per_page=None, **request_kwargs): 4 | """ 5 | Retrieve the actual due-at, unlock-at, and available-at dates for quizzes 6 | based on the assignment overrides active for the current API user. 7 | 8 | :param request_ctx: The request context 9 | :type request_ctx: :class:RequestContext 10 | :param course_id: (required) ID 11 | :type course_id: string 12 | :param quiz_assignment_overrides_0_quiz_ids: (optional) An array of quiz IDs. If omitted, overrides for all quizzes available to the operating user will be returned. 13 | :type quiz_assignment_overrides_0_quiz_ids: integer or None 14 | :param per_page: (optional) Set how many results canvas should return, defaults to config.LIMIT_PER_PAGE 15 | :type per_page: integer or None 16 | :return: Retrieve assignment-overridden dates for quizzes 17 | :rtype: requests.Response (with array data) 18 | 19 | """ 20 | 21 | if per_page is None: 22 | per_page = request_ctx.per_page 23 | path = '/v1/courses/{course_id}/quizzes/assignment_overrides' 24 | payload = { 25 | 'quiz_assignment_overrides[0][quiz_ids]' : quiz_assignment_overrides_0_quiz_ids, 26 | 'per_page' : per_page, 27 | } 28 | url = request_ctx.base_api_url + path.format(course_id=course_id) 29 | response = client.get(request_ctx, url, payload=payload, **request_kwargs) 30 | 31 | return response 32 | 33 | 34 | -------------------------------------------------------------------------------- /tests/client/test_auth.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | from unittest import mock 3 | from canvas_sdk.client import auth 4 | 5 | AUTH_TOKEN = 'testAuthToken' # Bogus auth token used to get call 6 | 7 | 8 | class TestAuth(unittest.TestCase): 9 | longMessage = True 10 | 11 | def test_initialize_oauth2_bearer_no_token_raises_exception(self): 12 | """ 13 | Test that global session variable is set when new session is created. 14 | """ 15 | with self.assertRaises(AttributeError): 16 | auth.OAuth2Bearer(None) 17 | 18 | def test_initialize_oauth2_bearer_empty_token_raises_exception(self): 19 | """ 20 | Test that global session variable is set when new session is created. 21 | """ 22 | with self.assertRaises(AttributeError): 23 | auth.OAuth2Bearer('') 24 | 25 | def test_initialize_oauth2_bearer_with_token_sets_attribute(self): 26 | """ 27 | Test that global session variable is set when new session is created. 28 | """ 29 | result = auth.OAuth2Bearer(AUTH_TOKEN) 30 | self.assertEqual(result.oauth2_token, AUTH_TOKEN, "Initializing OAuth2Bearer class should set oauth2_token instance attribute") 31 | 32 | def test_calling_oauth2_bearer_sets_authorization_header(self): 33 | """ 34 | Test that global session variable is set when new session is created. 35 | """ 36 | auth_bearer = auth.OAuth2Bearer(AUTH_TOKEN) 37 | request_mock = mock.MagicMock(headers={}) 38 | response = auth_bearer(request_mock) 39 | self.assertEqual(response.headers['Authorization'], 'Bearer %s' % AUTH_TOKEN) 40 | -------------------------------------------------------------------------------- /static_methods/sections.py: -------------------------------------------------------------------------------- 1 | 2 | def edit_section(request_ctx, id, course_section_name=None, course_section_sis_section_id=None, course_section_start_at=None, course_section_end_at=None, **request_kwargs): 3 | """ 4 | Modify an existing section. See the documentation for `SectionsController#create `_. 5 | :param request_ctx: The request context 6 | :type request_ctx: :class:RequestContext 7 | :param id: (required) ID 8 | :type id: string 9 | :param course_section_name: (optional) The name of the section 10 | :type course_section_name: string or None 11 | :param course_section_sis_section_id: (optional) The sis ID of the section 12 | :type course_section_sis_section_id: string or None 13 | :param course_section_start_at: (optional) Section start date in ISO8601 format, e.g. 2011-01-01T01:00Z 14 | :type course_section_start_at: datetime or None 15 | :param course_section_end_at: (optional) Section end date in ISO8601 format. e.g. 2011-01-01T01:00Z 16 | :type course_section_end_at: datetime or None 17 | :return: Edit a section 18 | :rtype: requests.Response (with Section data) 19 | """ 20 | path = '/v1/sections/{id}' 21 | payload = { 22 | 'course_section[name]' : course_section_name, 23 | 'course_section[sis_section_id]' : course_section_sis_section_id, 24 | 'course_section[start_at]' : course_section_start_at, 25 | 'course_section[end_at]' : course_section_end_at, 26 | } 27 | url = request_ctx.base_api_url + path.format(id=id) 28 | response = client.put(request_ctx, url, payload=payload, **request_kwargs) 29 | return response 30 | 31 | -------------------------------------------------------------------------------- /tests/test_assignments.py: -------------------------------------------------------------------------------- 1 | from unittest import mock 2 | import random 3 | import unittest 4 | import uuid 5 | 6 | from canvas_sdk.client import RequestContext 7 | from canvas_sdk.methods import assignments 8 | 9 | 10 | class TestAssignments(unittest.TestCase): 11 | ''' 12 | Exercises all hand-edited methods in the assignments module, specifically 13 | the parts that needed hand-editing in the first place. 14 | ''' 15 | def setUp(self): 16 | self.base_api_url = 'http://example.org/api/{}'.format(uuid.uuid4().hex) 17 | self.request_context = mock.MagicMock(spec=RequestContext, 18 | base_api_url=self.base_api_url) 19 | self.assignment_name = uuid.uuid4().hex 20 | self.course_id = uuid.uuid4().hex 21 | self.integration_id = uuid.uuid4().hex 22 | self.points_possible = random.randint(1,100) 23 | self.url = 'http://example.org/{}'.format(uuid.uuid4().hex) 24 | 25 | @mock.patch('canvas_sdk.methods.assignments.client.post') 26 | def test_create_assignment_external_tool(self, mock_client_post): 27 | response = assignments.create_assignment( 28 | self.request_context, self.course_id, 29 | self.assignment_name, 'external_tool', 30 | assignment_external_tool_tag_attributes = { 31 | 'url': self.url}, 32 | assignment_integration_id=self.integration_id, 33 | assignment_points_possible=self.points_possible) 34 | self.assertTrue(mock_client_post.called) 35 | assert ( 36 | set({ 37 | 'assignment[external_tool_tag_attributes][url]': self.url, 38 | 'assignment[submission_types][]': 'external_tool' 39 | }.items()) <= 40 | set(mock_client_post.call_args[1]['payload'].items()) 41 | ) 42 | -------------------------------------------------------------------------------- /static_methods/account_reports.py: -------------------------------------------------------------------------------- 1 | def start_report(request_ctx, account_id, report, parameters, **request_kwargs): 2 | """ 3 | Generates a report instance for the account. 4 | 5 | :param request_ctx: The request context 6 | :type request_ctx: :class:RequestContext 7 | :param account_id: (required) ID 8 | :type account_id: string 9 | :param report: (required) ID 10 | :type report: string 11 | :param parameters: (required) The parameters will vary for each report 12 | :type parameters: dict 13 | :return: Start a Report 14 | :rtype: requests.Response (with Report data) 15 | 16 | """ 17 | 18 | path = '/v1/accounts/{account_id}/reports/{report}' 19 | 20 | # if the parameters dict has keys like 'enrollments', 'xlist', 'include_deleted' 21 | # we need to translate them to be like 'parameters[enrollments]' 22 | ppat = re.compile('parameters\[.+\]') 23 | fix_key = lambda k_v: (k_v[0] if ppat.match(str(k_v[0])) else 'parameters[{}]'.format(k_v[0]), k_v[1]) 24 | payload = list(map(fix_key, list(parameters.items()))) 25 | url = request_ctx.base_api_url + path.format(account_id=account_id, report=report) 26 | response = client.post(request_ctx, url, payload=payload, **request_kwargs) 27 | 28 | return response 29 | 30 | 31 | def status_of_report(request_ctx, account_id, report, id, **request_kwargs): 32 | """ 33 | Returns the status of a report. 34 | 35 | :param request_ctx: The request context 36 | :type request_ctx: :class:RequestContext 37 | :param account_id: (required) ID 38 | :type account_id: string 39 | :param report: (required) ID 40 | :type report: string 41 | :param id: (required) ID 42 | :type id: string 43 | :return: Status of a Report 44 | :rtype: requests.Response (with Report data) 45 | 46 | """ 47 | 48 | path = '/v1/accounts/{account_id}/reports/{report}/{id}' 49 | url = request_ctx.base_api_url + path.format(account_id=account_id, report=report, id=id) 50 | response = client.get(request_ctx, url, **request_kwargs) 51 | 52 | return response 53 | -------------------------------------------------------------------------------- /static_methods/users.py: -------------------------------------------------------------------------------- 1 | def list_users_in_account(request_ctx, account_id, search_term=None, 2 | include=None, per_page=None, **request_kwargs): 3 | """ 4 | Retrieve the list of users associated with this account. 5 | 6 | @example_request 7 | curl https:///api/v1/accounts/self/users?search_term= \ 8 | -X GET \ 9 | -H 'Authorization: Bearer ' 10 | 11 | :param request_ctx: The request context 12 | :type request_ctx: :class:RequestContext 13 | :param account_id: (required) ID 14 | :type account_id: string 15 | :param search_term: (optional) The partial name or full ID of the users to match and return in the 16 | results list. Must be at least 3 characters. 17 | 18 | Note that the API will prefer matching on canonical user ID if the ID has 19 | a numeric form. It will only search against other fields if non-numeric 20 | in form, or if the numeric value doesn't yield any matches. Queries by 21 | administrative users will search on SIS ID, name, or email address; non- 22 | administrative queries will only be compared against name. 23 | :type search_term: string or None 24 | :param include: (optional) One of (avatar_url, email, last_login, time_zone) 25 | :type include: array or None 26 | :param per_page: (optional) Set how many results canvas should return, defaults to config.LIMIT_PER_PAGE 27 | :type per_page: integer or None 28 | :return: List users in account 29 | :rtype: requests.Response (with array data) 30 | 31 | """ 32 | 33 | if per_page is None: 34 | per_page = request_ctx.per_page 35 | include_types = ('avatar_url', 'email', 'last_login', 'time_zone') 36 | utils.validate_attr_is_acceptable(include, include_types) 37 | path = '/v1/accounts/{account_id}/users' 38 | payload = { 39 | 'include[]': include, 40 | 'search_term': search_term, 41 | 'per_page': per_page, 42 | } 43 | url = request_ctx.base_api_url + path.format(account_id=account_id) 44 | response = client.get(request_ctx, url, payload=payload, **request_kwargs) 45 | 46 | return response 47 | -------------------------------------------------------------------------------- /canvas_sdk/methods/poll_submissions.py: -------------------------------------------------------------------------------- 1 | from canvas_sdk import client, utils 2 | 3 | def get_single_poll_submission(request_ctx, poll_id, poll_session_id, id, **request_kwargs): 4 | """ 5 | Returns the poll submission with the given id 6 | 7 | :param request_ctx: The request context 8 | :type request_ctx: :class:RequestContext 9 | :param poll_id: (required) ID 10 | :type poll_id: string 11 | :param poll_session_id: (required) ID 12 | :type poll_session_id: string 13 | :param id: (required) ID 14 | :type id: string 15 | :return: Get a single poll submission 16 | :rtype: requests.Response (with void data) 17 | 18 | """ 19 | 20 | path = '/v1/polls/{poll_id}/poll_sessions/{poll_session_id}/poll_submissions/{id}' 21 | url = request_ctx.base_api_url + path.format(poll_id=poll_id, poll_session_id=poll_session_id, id=id) 22 | response = client.get(request_ctx, url, **request_kwargs) 23 | 24 | return response 25 | 26 | 27 | def create_single_poll_submission(request_ctx, poll_id, poll_session_id, poll_submissions_poll_choice_id, **request_kwargs): 28 | """ 29 | Create a new poll submission for this poll session 30 | 31 | :param request_ctx: The request context 32 | :type request_ctx: :class:RequestContext 33 | :param poll_id: (required) ID 34 | :type poll_id: string 35 | :param poll_session_id: (required) ID 36 | :type poll_session_id: string 37 | :param poll_submissions_poll_choice_id: (required) The chosen poll choice for this submission. 38 | :type poll_submissions_poll_choice_id: integer 39 | :return: Create a single poll submission 40 | :rtype: requests.Response (with void data) 41 | 42 | """ 43 | 44 | path = '/v1/polls/{poll_id}/poll_sessions/{poll_session_id}/poll_submissions' 45 | payload = { 46 | 'poll_submissions[poll_choice_id]' : poll_submissions_poll_choice_id, 47 | } 48 | url = request_ctx.base_api_url + path.format(poll_id=poll_id, poll_session_id=poll_session_id) 49 | response = client.post(request_ctx, url, payload=payload, **request_kwargs) 50 | 51 | return response 52 | 53 | 54 | -------------------------------------------------------------------------------- /static_methods/accounts.py: -------------------------------------------------------------------------------- 1 | from canvas_sdk import client, utils 2 | 3 | def list_accounts(request_ctx, per_page=None, as_user_id=None, **request_kwargs): 4 | """ 5 | List accounts that the current user can view or manage. Typically, 6 | students and even teachers will get an empty list in response, only 7 | account admins can view the accounts that they are in. 8 | 9 | :param request_ctx: The request context 10 | :type request_ctx: :class:RequestContext 11 | :param per_page: (optional) Set how many results canvas should return, defaults to config.LIMIT_PER_PAGE 12 | :type per_page: integer or None 13 | :param as_user_id: (optional) Masquerade as the given canvas user 14 | :type as_user_id: integer or None 15 | :return: List accounts 16 | :rtype: requests.Response (with array data) 17 | 18 | """ 19 | 20 | if per_page is None: 21 | per_page = request_ctx.per_page 22 | path = '/v1/accounts' 23 | payload = { 24 | 'per_page': per_page, 25 | } 26 | if as_user_id: 27 | payload['as_user_id'] = as_user_id 28 | url = request_ctx.base_api_url + path.format() 29 | response = client.get(request_ctx, url, payload=payload, **request_kwargs) 30 | 31 | return response 32 | 33 | 34 | def get_sub_accounts_of_account(request_ctx, account_id, recursive=None, per_page=None, as_user_id=None, **request_kwargs): 35 | """ 36 | List accounts that are sub-accounts of the given account. 37 | 38 | :param request_ctx: The request context 39 | :type request_ctx: :class:RequestContext 40 | :param account_id: (required) ID 41 | :type account_id: string 42 | :param recursive: (optional) If true, the entire account tree underneath this account will be returned (though still paginated). If false, only direct sub-accounts of this account will be returned. Defaults to false. 43 | :type recursive: boolean or None 44 | :param per_page: (optional) Set how many results canvas should return, defaults to config.LIMIT_PER_PAGE 45 | :type per_page: integer or None 46 | :return: Get the sub-accounts of an account 47 | :rtype: requests.Response (with array data) 48 | 49 | """ 50 | 51 | if per_page is None: 52 | per_page = request_ctx.per_page 53 | path = '/v1/accounts/{account_id}/sub_accounts' 54 | payload = { 55 | 'recursive': recursive, 56 | 'per_page': per_page, 57 | } 58 | if as_user_id: 59 | payload['as_user_id'] = as_user_id 60 | url = request_ctx.base_api_url + path.format(account_id=account_id) 61 | response = client.get(request_ctx, url, payload=payload, **request_kwargs) 62 | 63 | return response 64 | -------------------------------------------------------------------------------- /canvas_sdk/methods/quiz_extensions.py: -------------------------------------------------------------------------------- 1 | from canvas_sdk import client, utils 2 | 3 | def set_extensions_for_student_quiz_submissions(request_ctx, course_id, quiz_id, user_id, extra_attempts=None, extra_time=None, manually_unlocked=None, extend_from_now=None, extend_from_end_at=None, **request_kwargs): 4 | """ 5 | Responses 6 | 7 | * 200 OK if the request was successful 8 | * 403 Forbidden if you are not allowed to extend quizzes for this course 9 | 10 | :param request_ctx: The request context 11 | :type request_ctx: :class:RequestContext 12 | :param course_id: (required) ID 13 | :type course_id: string 14 | :param quiz_id: (required) ID 15 | :type quiz_id: string 16 | :param user_id: (required) The ID of the user we want to add quiz extensions for. 17 | :type user_id: integer 18 | :param extra_attempts: (optional) Number of times the student is allowed to re-take the quiz over the multiple-attempt limit. This is limited to 1000 attempts or less. 19 | :type extra_attempts: integer or None 20 | :param extra_time: (optional) The number of extra minutes to allow for all attempts. This will add to the existing time limit on the submission. This is limited to 10080 minutes (1 week) 21 | :type extra_time: integer or None 22 | :param manually_unlocked: (optional) Allow the student to take the quiz even if it's locked for everyone else. 23 | :type manually_unlocked: boolean or None 24 | :param extend_from_now: (optional) The number of minutes to extend the quiz from the current time. This is mutually exclusive to extend_from_end_at. This is limited to 1440 minutes (24 hours) 25 | :type extend_from_now: integer or None 26 | :param extend_from_end_at: (optional) The number of minutes to extend the quiz beyond the quiz's current ending time. This is mutually exclusive to extend_from_now. This is limited to 1440 minutes (24 hours) 27 | :type extend_from_end_at: integer or None 28 | :return: Set extensions for student quiz submissions 29 | :rtype: requests.Response (with void data) 30 | 31 | """ 32 | 33 | path = '/v1/courses/{course_id}/quizzes/{quiz_id}/extensions' 34 | payload = { 35 | 'user_id' : user_id, 36 | 'extra_attempts' : extra_attempts, 37 | 'extra_time' : extra_time, 38 | 'manually_unlocked' : manually_unlocked, 39 | 'extend_from_now' : extend_from_now, 40 | 'extend_from_end_at' : extend_from_end_at, 41 | } 42 | url = request_ctx.base_api_url + path.format(course_id=course_id, quiz_id=quiz_id) 43 | response = client.post(request_ctx, url, payload=payload, **request_kwargs) 44 | 45 | return response 46 | 47 | 48 | -------------------------------------------------------------------------------- /canvas_sdk/methods/account_notifications.py: -------------------------------------------------------------------------------- 1 | from canvas_sdk import client, utils 2 | 3 | def create_global_notification(request_ctx, account_id, account_notification_subject=None, account_notification_message=None, account_notification_start_at=None, account_notification_end_at=None, account_notification_icon=None, account_notification_roles=None, **request_kwargs): 4 | """ 5 | Create and return a new global notification for an account. 6 | 7 | :param request_ctx: The request context 8 | :type request_ctx: :class:RequestContext 9 | :param account_id: (required) ID 10 | :type account_id: string 11 | :param account_notification_subject: (optional) The subject of the notification. 12 | :type account_notification_subject: string or None 13 | :param account_notification_message: (optional) The message body of the notification. 14 | :type account_notification_message: string or None 15 | :param account_notification_start_at: (optional) The start date and time of the notification in ISO8601 format. e.g. 2014-01-01T01:00Z 16 | :type account_notification_start_at: datetime or None 17 | :param account_notification_end_at: (optional) The end date and time of the notification in ISO8601 format. e.g. 2014-01-01T01:00Z 18 | :type account_notification_end_at: datetime or None 19 | :param account_notification_icon: (optional) The icon to display with the notification. Note: Defaults to warning. 20 | :type account_notification_icon: string or None 21 | :param account_notification_roles: (optional) The role(s) to send global notification to. Note: ommitting this field will send to everyone Example: account_notification_roles: ["StudentEnrollment", "TeacherEnrollment"] 22 | :type account_notification_roles: string or None 23 | :return: Create a global notification 24 | :rtype: requests.Response (with void data) 25 | 26 | """ 27 | 28 | account_notification_icon_types = ('warning', 'information', 'question', 'error', 'calendar') 29 | utils.validate_attr_is_acceptable(account_notification_icon, account_notification_icon_types) 30 | path = '/v1/accounts/{account_id}/account_notifications' 31 | payload = { 32 | 'account_notification[subject]' : account_notification_subject, 33 | 'account_notification[message]' : account_notification_message, 34 | 'account_notification[start_at]' : account_notification_start_at, 35 | 'account_notification[end_at]' : account_notification_end_at, 36 | 'account_notification[icon]' : account_notification_icon, 37 | 'account_notification_roles' : account_notification_roles, 38 | } 39 | url = request_ctx.base_api_url + path.format(account_id=account_id) 40 | response = client.post(request_ctx, url, payload=payload, **request_kwargs) 41 | 42 | return response 43 | 44 | 45 | -------------------------------------------------------------------------------- /canvas_sdk/methods/conferences.py: -------------------------------------------------------------------------------- 1 | from canvas_sdk import client, utils 2 | 3 | def list_conferences_courses(request_ctx, course_id, per_page=None, **request_kwargs): 4 | """ 5 | Retrieve the list of conferences for this context 6 | 7 | This API returns a JSON object containing the list of conferences, 8 | the key for the list of conferences is "conferences" 9 | 10 | Examples: 11 | curl 'https:///api/v1/courses//conferences' \ 12 | -H "Authorization: Bearer " 13 | 14 | curl 'https:///api/v1/groups//conferences' \ 15 | -H "Authorization: Bearer " 16 | 17 | :param request_ctx: The request context 18 | :type request_ctx: :class:RequestContext 19 | :param course_id: (required) ID 20 | :type course_id: string 21 | :param per_page: (optional) Set how many results canvas should return, defaults to config.LIMIT_PER_PAGE 22 | :type per_page: integer or None 23 | :return: List conferences 24 | :rtype: requests.Response (with array data) 25 | 26 | """ 27 | 28 | if per_page is None: 29 | per_page = request_ctx.per_page 30 | path = '/v1/courses/{course_id}/conferences' 31 | payload = { 32 | 'per_page' : per_page, 33 | } 34 | url = request_ctx.base_api_url + path.format(course_id=course_id) 35 | response = client.get(request_ctx, url, payload=payload, **request_kwargs) 36 | 37 | return response 38 | 39 | 40 | def list_conferences_groups(request_ctx, group_id, per_page=None, **request_kwargs): 41 | """ 42 | Retrieve the list of conferences for this context 43 | 44 | This API returns a JSON object containing the list of conferences, 45 | the key for the list of conferences is "conferences" 46 | 47 | Examples: 48 | curl 'https:///api/v1/courses//conferences' \ 49 | -H "Authorization: Bearer " 50 | 51 | curl 'https:///api/v1/groups//conferences' \ 52 | -H "Authorization: Bearer " 53 | 54 | :param request_ctx: The request context 55 | :type request_ctx: :class:RequestContext 56 | :param group_id: (required) ID 57 | :type group_id: string 58 | :param per_page: (optional) Set how many results canvas should return, defaults to config.LIMIT_PER_PAGE 59 | :type per_page: integer or None 60 | :return: List conferences 61 | :rtype: requests.Response (with array data) 62 | 63 | """ 64 | 65 | if per_page is None: 66 | per_page = request_ctx.per_page 67 | path = '/v1/groups/{group_id}/conferences' 68 | payload = { 69 | 'per_page' : per_page, 70 | } 71 | url = request_ctx.base_api_url + path.format(group_id=group_id) 72 | response = client.get(request_ctx, url, payload=payload, **request_kwargs) 73 | 74 | return response 75 | 76 | 77 | -------------------------------------------------------------------------------- /canvas_sdk/methods/authentications_log.py: -------------------------------------------------------------------------------- 1 | from canvas_sdk import client, utils 2 | 3 | def query_by_login(request_ctx, login_id, start_time=None, end_time=None, **request_kwargs): 4 | """ 5 | List authentication events for a given login. 6 | 7 | :param request_ctx: The request context 8 | :type request_ctx: :class:RequestContext 9 | :param login_id: (required) ID 10 | :type login_id: string 11 | :param start_time: (optional) The beginning of the time range from which you want events. 12 | :type start_time: datetime or None 13 | :param end_time: (optional) The end of the time range from which you want events. 14 | :type end_time: datetime or None 15 | :return: Query by login. 16 | :rtype: requests.Response (with void data) 17 | 18 | """ 19 | 20 | path = '/v1/audit/authentication/logins/{login_id}' 21 | payload = { 22 | 'start_time' : start_time, 23 | 'end_time' : end_time, 24 | } 25 | url = request_ctx.base_api_url + path.format(login_id=login_id) 26 | response = client.get(request_ctx, url, payload=payload, **request_kwargs) 27 | 28 | return response 29 | 30 | 31 | def query_by_account(request_ctx, account_id, start_time=None, end_time=None, **request_kwargs): 32 | """ 33 | List authentication events for a given account. 34 | 35 | :param request_ctx: The request context 36 | :type request_ctx: :class:RequestContext 37 | :param account_id: (required) ID 38 | :type account_id: string 39 | :param start_time: (optional) The beginning of the time range from which you want events. 40 | :type start_time: datetime or None 41 | :param end_time: (optional) The end of the time range from which you want events. 42 | :type end_time: datetime or None 43 | :return: Query by account. 44 | :rtype: requests.Response (with void data) 45 | 46 | """ 47 | 48 | path = '/v1/audit/authentication/accounts/{account_id}' 49 | payload = { 50 | 'start_time' : start_time, 51 | 'end_time' : end_time, 52 | } 53 | url = request_ctx.base_api_url + path.format(account_id=account_id) 54 | response = client.get(request_ctx, url, payload=payload, **request_kwargs) 55 | 56 | return response 57 | 58 | 59 | def query_by_user(request_ctx, user_id, start_time=None, end_time=None, **request_kwargs): 60 | """ 61 | List authentication events for a given user. 62 | 63 | :param request_ctx: The request context 64 | :type request_ctx: :class:RequestContext 65 | :param user_id: (required) ID 66 | :type user_id: string 67 | :param start_time: (optional) The beginning of the time range from which you want events. 68 | :type start_time: datetime or None 69 | :param end_time: (optional) The end of the time range from which you want events. 70 | :type end_time: datetime or None 71 | :return: Query by user. 72 | :rtype: requests.Response (with void data) 73 | 74 | """ 75 | 76 | path = '/v1/audit/authentication/users/{user_id}' 77 | payload = { 78 | 'start_time' : start_time, 79 | 'end_time' : end_time, 80 | } 81 | url = request_ctx.base_api_url + path.format(user_id=user_id) 82 | response = client.get(request_ctx, url, payload=payload, **request_kwargs) 83 | 84 | return response 85 | 86 | 87 | -------------------------------------------------------------------------------- /canvas_sdk/methods/favorites.py: -------------------------------------------------------------------------------- 1 | from canvas_sdk import client, utils 2 | 3 | def list_favorite_courses(request_ctx, per_page=None, **request_kwargs): 4 | """ 5 | Retrieve the list of favorite courses for the current user. If the user has not chosen 6 | any favorites, then a selection of currently enrolled courses will be returned. 7 | 8 | See the `CoursesController#index `_ for details on accepted include[] parameters. 9 | 10 | :param request_ctx: The request context 11 | :type request_ctx: :class:RequestContext 12 | :param per_page: (optional) Set how many results canvas should return, defaults to config.LIMIT_PER_PAGE 13 | :type per_page: integer or None 14 | :return: List favorite courses 15 | :rtype: requests.Response (with array data) 16 | 17 | """ 18 | 19 | if per_page is None: 20 | per_page = request_ctx.per_page 21 | path = '/v1/users/self/favorites/courses' 22 | payload = { 23 | 'per_page' : per_page, 24 | } 25 | url = request_ctx.base_api_url + path.format() 26 | response = client.get(request_ctx, url, payload=payload, **request_kwargs) 27 | 28 | return response 29 | 30 | 31 | def add_course_to_favorites(request_ctx, id, **request_kwargs): 32 | """ 33 | Add a course to the current user's favorites. If the course is already 34 | in the user's favorites, nothing happens. 35 | 36 | :param request_ctx: The request context 37 | :type request_ctx: :class:RequestContext 38 | :param id: (required) The ID or SIS ID of the course to add. The current user must be registered in the course. 39 | :type id: string 40 | :return: Add course to favorites 41 | :rtype: requests.Response (with Favorite data) 42 | 43 | """ 44 | 45 | path = '/v1/users/self/favorites/courses/{id}' 46 | url = request_ctx.base_api_url + path.format(id=id) 47 | response = client.post(request_ctx, url, **request_kwargs) 48 | 49 | return response 50 | 51 | 52 | def remove_course_from_favorites(request_ctx, id, **request_kwargs): 53 | """ 54 | Remove a course from the current user's favorites. 55 | 56 | :param request_ctx: The request context 57 | :type request_ctx: :class:RequestContext 58 | :param id: (required) the ID or SIS ID of the course to remove 59 | :type id: string 60 | :return: Remove course from favorites 61 | :rtype: requests.Response (with Favorite data) 62 | 63 | """ 64 | 65 | path = '/v1/users/self/favorites/courses/{id}' 66 | url = request_ctx.base_api_url + path.format(id=id) 67 | response = client.delete(request_ctx, url, **request_kwargs) 68 | 69 | return response 70 | 71 | 72 | def reset_course_favorites(request_ctx, **request_kwargs): 73 | """ 74 | Reset the current user's course favorites to the default 75 | automatically generated list of enrolled courses 76 | 77 | :param request_ctx: The request context 78 | :type request_ctx: :class:RequestContext 79 | :return: Reset course favorites 80 | :rtype: requests.Response (with void data) 81 | 82 | """ 83 | 84 | path = '/v1/users/self/favorites/courses' 85 | url = request_ctx.base_api_url + path.format() 86 | response = client.delete(request_ctx, url, **request_kwargs) 87 | 88 | return response 89 | 90 | 91 | -------------------------------------------------------------------------------- /canvas_sdk/methods/tabs.py: -------------------------------------------------------------------------------- 1 | from canvas_sdk import client, utils 2 | 3 | def list_available_tabs_for_course_or_group_courses(request_ctx, course_id, include, **request_kwargs): 4 | """ 5 | Returns a list of navigation tabs available in the current context. 6 | 7 | :param request_ctx: The request context 8 | :type request_ctx: :class:RequestContext 9 | :param course_id: (required) ID 10 | :type course_id: string 11 | :param include: (required) Optionally include external tool tabs in the returned list of tabs (Only has effect for courses, not groups) 12 | :type include: string 13 | :return: List available tabs for a course or group 14 | :rtype: requests.Response (with void data) 15 | 16 | """ 17 | 18 | include_types = ('external') 19 | utils.validate_attr_is_acceptable(include, include_types) 20 | path = '/v1/courses/{course_id}/tabs' 21 | payload = { 22 | 'include' : include, 23 | } 24 | url = request_ctx.base_api_url + path.format(course_id=course_id) 25 | response = client.get(request_ctx, url, payload=payload, **request_kwargs) 26 | 27 | return response 28 | 29 | 30 | def list_available_tabs_for_course_or_group_groups(request_ctx, group_id, include, **request_kwargs): 31 | """ 32 | Returns a list of navigation tabs available in the current context. 33 | 34 | :param request_ctx: The request context 35 | :type request_ctx: :class:RequestContext 36 | :param group_id: (required) ID 37 | :type group_id: string 38 | :param include: (required) Optionally include external tool tabs in the returned list of tabs (Only has effect for courses, not groups) 39 | :type include: string 40 | :return: List available tabs for a course or group 41 | :rtype: requests.Response (with void data) 42 | 43 | """ 44 | 45 | include_types = ('external') 46 | utils.validate_attr_is_acceptable(include, include_types) 47 | path = '/v1/groups/{group_id}/tabs' 48 | payload = { 49 | 'include' : include, 50 | } 51 | url = request_ctx.base_api_url + path.format(group_id=group_id) 52 | response = client.get(request_ctx, url, payload=payload, **request_kwargs) 53 | 54 | return response 55 | 56 | 57 | def update_tab_for_course(request_ctx, course_id, tab_id, position, hidden, **request_kwargs): 58 | """ 59 | Home and Settings tabs are not manageable, and can't be hidden or moved 60 | 61 | Returns a tab object 62 | 63 | :param request_ctx: The request context 64 | :type request_ctx: :class:RequestContext 65 | :param course_id: (required) ID 66 | :type course_id: string 67 | :param tab_id: (required) ID 68 | :type tab_id: string 69 | :param position: (required) The new position of the tab, 1-based 70 | :type position: integer 71 | :param hidden: (required) \\ true, or false. 72 | :type hidden: string 73 | :return: Update a tab for a course 74 | :rtype: requests.Response (with Tab data) 75 | 76 | """ 77 | 78 | path = '/v1/courses/{course_id}/tabs/{tab_id}' 79 | payload = { 80 | 'position' : position, 81 | 'hidden' : hidden, 82 | } 83 | url = request_ctx.base_api_url + path.format(course_id=course_id, tab_id=tab_id) 84 | response = client.put(request_ctx, url, payload=payload, **request_kwargs) 85 | 86 | return response 87 | 88 | 89 | -------------------------------------------------------------------------------- /canvas_sdk/methods/live_assessments.py: -------------------------------------------------------------------------------- 1 | from canvas_sdk import client, utils 2 | 3 | def create_live_assessment_results(request_ctx, course_id, assessment_id, **request_kwargs): 4 | """ 5 | Creates live assessment results and adds them to a live assessment 6 | 7 | :param request_ctx: The request context 8 | :type request_ctx: :class:RequestContext 9 | :param course_id: (required) ID 10 | :type course_id: string 11 | :param assessment_id: (required) ID 12 | :type assessment_id: string 13 | :return: Create live assessment results 14 | :rtype: requests.Response (with void data) 15 | 16 | """ 17 | 18 | path = '/v1/courses/{course_id}/live_assessments/{assessment_id}/results' 19 | url = request_ctx.base_api_url + path.format(course_id=course_id, assessment_id=assessment_id) 20 | response = client.post(request_ctx, url, **request_kwargs) 21 | 22 | return response 23 | 24 | 25 | def list_live_assessment_results(request_ctx, course_id, assessment_id, user_id=None, **request_kwargs): 26 | """ 27 | Returns a list of live assessment results 28 | 29 | :param request_ctx: The request context 30 | :type request_ctx: :class:RequestContext 31 | :param course_id: (required) ID 32 | :type course_id: string 33 | :param assessment_id: (required) ID 34 | :type assessment_id: string 35 | :param user_id: (optional) If set, restrict results to those for this user 36 | :type user_id: integer or None 37 | :return: List live assessment results 38 | :rtype: requests.Response (with void data) 39 | 40 | """ 41 | 42 | path = '/v1/courses/{course_id}/live_assessments/{assessment_id}/results' 43 | payload = { 44 | 'user_id' : user_id, 45 | } 46 | url = request_ctx.base_api_url + path.format(course_id=course_id, assessment_id=assessment_id) 47 | response = client.get(request_ctx, url, payload=payload, **request_kwargs) 48 | 49 | return response 50 | 51 | 52 | def create_or_find_live_assessment(request_ctx, course_id, **request_kwargs): 53 | """ 54 | Creates or finds an existing live assessment with the given key and aligns it with 55 | the linked outcome 56 | 57 | :param request_ctx: The request context 58 | :type request_ctx: :class:RequestContext 59 | :param course_id: (required) ID 60 | :type course_id: string 61 | :return: Create or find a live assessment 62 | :rtype: requests.Response (with void data) 63 | 64 | """ 65 | 66 | path = '/v1/courses/{course_id}/live_assessments' 67 | url = request_ctx.base_api_url + path.format(course_id=course_id) 68 | response = client.post(request_ctx, url, **request_kwargs) 69 | 70 | return response 71 | 72 | 73 | def list_live_assessments(request_ctx, course_id, **request_kwargs): 74 | """ 75 | Returns a list of live assessments. 76 | 77 | :param request_ctx: The request context 78 | :type request_ctx: :class:RequestContext 79 | :param course_id: (required) ID 80 | :type course_id: string 81 | :return: List live assessments 82 | :rtype: requests.Response (with void data) 83 | 84 | """ 85 | 86 | path = '/v1/courses/{course_id}/live_assessments' 87 | url = request_ctx.base_api_url + path.format(course_id=course_id) 88 | response = client.get(request_ctx, url, **request_kwargs) 89 | 90 | return response 91 | 92 | 93 | -------------------------------------------------------------------------------- /canvas_sdk/methods/outcomes.py: -------------------------------------------------------------------------------- 1 | from canvas_sdk import client, utils 2 | 3 | def show_outcome(request_ctx, id, **request_kwargs): 4 | """ 5 | Returns the details of the outcome with the given id. 6 | 7 | :param request_ctx: The request context 8 | :type request_ctx: :class:RequestContext 9 | :param id: (required) ID 10 | :type id: string 11 | :return: Show an outcome 12 | :rtype: requests.Response (with Outcome data) 13 | 14 | """ 15 | 16 | path = '/v1/outcomes/{id}' 17 | url = request_ctx.base_api_url + path.format(id=id) 18 | response = client.get(request_ctx, url, **request_kwargs) 19 | 20 | return response 21 | 22 | 23 | def update_outcome(request_ctx, id, title=None, display_name=None, description=None, vendor_guid=None, mastery_points=None, ratings_description=None, ratings_points=None, **request_kwargs): 24 | """ 25 | Modify an existing outcome. Fields not provided are left as is; 26 | unrecognized fields are ignored. 27 | 28 | If any new ratings are provided, the combination of all new ratings 29 | provided completely replace any existing embedded rubric criterion; it is 30 | not possible to tweak the ratings of the embedded rubric criterion. 31 | 32 | A new embedded rubric criterion's mastery_points default to the maximum 33 | points in the highest rating if not specified in the mastery_points 34 | parameter. Any new ratings lacking a description are given a default of "No 35 | description". Any new ratings lacking a point value are given a default of 36 | 0. 37 | 38 | :param request_ctx: The request context 39 | :type request_ctx: :class:RequestContext 40 | :param id: (required) ID 41 | :type id: string 42 | :param title: (optional) The new outcome title. 43 | :type title: string or None 44 | :param display_name: (optional) A friendly name shown in reports for outcomes with cryptic titles, such as common core standards names. 45 | :type display_name: string or None 46 | :param description: (optional) The new outcome description. 47 | :type description: string or None 48 | :param vendor_guid: (optional) A custom GUID for the learning standard. 49 | :type vendor_guid: string or None 50 | :param mastery_points: (optional) The new mastery threshold for the embedded rubric criterion. 51 | :type mastery_points: integer or None 52 | :param ratings_description: (optional) The description of a new rating level for the embedded rubric criterion. 53 | :type ratings_description: string or None 54 | :param ratings_points: (optional) The points corresponding to a new rating level for the embedded rubric criterion. 55 | :type ratings_points: integer or None 56 | :return: Update an outcome 57 | :rtype: requests.Response (with Outcome data) 58 | 59 | """ 60 | 61 | path = '/v1/outcomes/{id}' 62 | payload = { 63 | 'title' : title, 64 | 'display_name' : display_name, 65 | 'description' : description, 66 | 'vendor_guid' : vendor_guid, 67 | 'mastery_points' : mastery_points, 68 | 'ratings[description]' : ratings_description, 69 | 'ratings[points]' : ratings_points, 70 | } 71 | url = request_ctx.base_api_url + path.format(id=id) 72 | response = client.put(request_ctx, url, payload=payload, **request_kwargs) 73 | 74 | return response 75 | 76 | 77 | -------------------------------------------------------------------------------- /canvas_sdk/methods/content_exports.py: -------------------------------------------------------------------------------- 1 | from canvas_sdk import client, utils 2 | 3 | def list_content_exports(request_ctx, course_id, per_page=None, **request_kwargs): 4 | """ 5 | List the past and pending content export jobs for a course. 6 | Exports are returned newest first. 7 | 8 | :param request_ctx: The request context 9 | :type request_ctx: :class:RequestContext 10 | :param course_id: (required) ID 11 | :type course_id: string 12 | :param per_page: (optional) Set how many results canvas should return, defaults to config.LIMIT_PER_PAGE 13 | :type per_page: integer or None 14 | :return: List content exports 15 | :rtype: requests.Response (with array data) 16 | 17 | """ 18 | 19 | if per_page is None: 20 | per_page = request_ctx.per_page 21 | path = '/v1/courses/{course_id}/content_exports' 22 | payload = { 23 | 'per_page' : per_page, 24 | } 25 | url = request_ctx.base_api_url + path.format(course_id=course_id) 26 | response = client.get(request_ctx, url, payload=payload, **request_kwargs) 27 | 28 | return response 29 | 30 | 31 | def show_content_export(request_ctx, course_id, id, **request_kwargs): 32 | """ 33 | Get information about a single content export. 34 | 35 | :param request_ctx: The request context 36 | :type request_ctx: :class:RequestContext 37 | :param course_id: (required) ID 38 | :type course_id: string 39 | :param id: (required) ID 40 | :type id: string 41 | :return: Show content export 42 | :rtype: requests.Response (with ContentExport data) 43 | 44 | """ 45 | 46 | path = '/v1/courses/{course_id}/content_exports/{id}' 47 | url = request_ctx.base_api_url + path.format(course_id=course_id, id=id) 48 | response = client.get(request_ctx, url, **request_kwargs) 49 | 50 | return response 51 | 52 | 53 | def export_course_content(request_ctx, course_id, export_type, **request_kwargs): 54 | """ 55 | Begin a content export job for a course. 56 | 57 | You can use the `ProgressController#show `_ to track the 58 | progress of the export. The migration's progress is linked to with the 59 | _progress_url_ value. 60 | 61 | When the export completes, use the `ContentExportsApiController#show `_ endpoint 62 | to retrieve a download URL for the exported content. 63 | 64 | :param request_ctx: The request context 65 | :type request_ctx: :class:RequestContext 66 | :param course_id: (required) ID 67 | :type course_id: string 68 | :param export_type: (required) "common_cartridge":: Export the contents of the course in the Common Cartridge (.imscc) format "qti":: Export quizzes in the QTI format 69 | :type export_type: string 70 | :return: Export course content 71 | :rtype: requests.Response (with ContentExport data) 72 | 73 | """ 74 | 75 | export_type_types = ('common_cartridge', 'qti') 76 | utils.validate_attr_is_acceptable(export_type, export_type_types) 77 | path = '/v1/courses/{course_id}/content_exports' 78 | payload = { 79 | 'export_type' : export_type, 80 | } 81 | url = request_ctx.base_api_url + path.format(course_id=course_id) 82 | response = client.post(request_ctx, url, payload=payload, **request_kwargs) 83 | 84 | return response 85 | 86 | 87 | -------------------------------------------------------------------------------- /scripts/README.md: -------------------------------------------------------------------------------- 1 | ##Autogenerate SDK Methods## 2 | 3 | 4 | ** USE AT YOUR OWN RISK - This script will overwrite existing files.** 5 | 6 | The script will automatically generate python modules containing SDK methods from the Canvas LMS REST API. The Canvas LMS REST API provides a meta-api interface describing each of the methods in JSON format. See the links below for an example on the instructure site: 7 | 8 | * [main api-docs page](https://canvas.instructure.com/doc/api/api-docs.json) 9 | * [view the accounts.json page](https://canvas.instructure.com/doc/api/accounts.json) 10 | * This can also be viewed as [accounts.html](https://canvas.instructure.com/doc/api/accounts.html). 11 | 12 | The script output will be python modules, the code below shows what the output looks like for the first 13 | method of the sections module, *list_course_sections*. 14 | 15 | ```python 16 | from canvas_sdk import client, utils 17 | 18 | def list_course_sections(request_ctx, course_id, include=None, per_page=None, **request_kwargs): 19 | """ 20 | Returns the list of sections for this course. 21 | 22 | :param request_ctx: The request context 23 | :type request_ctx: :class:RequestContext 24 | :param course_id: (required) ID 25 | :type course_id: string 26 | :param include: (optional) - "students": Associations to include with the group. Note: this is only available if you have permission to view users or grades in the course - "avatar_url": Include the avatar URLs for students returned. 27 | :type include: string or None 28 | :param per_page: (optional) Set how many results canvas should return, defaults to config.LIMIT_PER_PAGE 29 | :type per_page: integer or None 30 | :return: List course sections 31 | :rtype: requests.Response (with array data) 32 | 33 | """ 34 | 35 | if per_page is None: 36 | per_page = request_ctx.per_page 37 | include_types = ('students', 'avatar_url') 38 | utils.validate_attr_is_acceptable(include, include_types) 39 | path = '/v1/courses/{course_id}/sections' 40 | payload = { 41 | 'include' : include, 42 | 'per_page' : per_page, 43 | } 44 | url = request_ctx.base_api_url + path.format(course_id=course_id) 45 | response = client.get(request_ctx, url, payload=payload, **request_kwargs) 46 | 47 | return response 48 | 49 | ``` 50 | 51 | 52 | ### Where to run the script ### 53 | 54 | The script can be run from any directory. However, keep in mind that it will look for and create 55 | if needed, a directory called "../canvas_sdk/methods" relative (note the up and over '..') to the location of the script. It is recommended that you run the script from the scripts directory. 56 | 57 | ``` 58 | canvas_sdk_project 59 | | 60 | └─--canvas_sdk 61 | | └─methods (this directory will be created if needed by the script) 62 | | | 63 | | └─--accounts.py (these files will either be created or overwritten) 64 | | └─--sections.py 65 | | └─--... 66 | └─--scripts 67 | | | 68 | | └─--generate_sdk_methods.py (run the script from here) 69 | ... 70 | ``` 71 | 72 | ### Usage ### 73 | 74 | usage: generate_sdk_methods.py [-h] [-u URL] 75 | 76 | Build Canvas SDK methods 77 | 78 | optional arguments: 79 | -h, --help show this help message and exit 80 | -u URL, --url URL Base Canvas url, default is (https://canvas.instructure.com) 81 | 82 | If run with no arguments, the script will default to the instructure url https://canvas.instructure.com 83 | 84 | 85 | 86 | ### Examples ### 87 | 88 | ``` 89 | $ python generate_sdk_methods.py -h or --help ( print the help message ) 90 | ``` 91 | 92 | ``` 93 | $ python generate_sdk_methods.py -u https://canvas.instructure.com 94 | ``` 95 | 96 | creates the sdk methods from the base url canvas.instructure.com, if you run 97 | your own instance of canvas replace this url with yours. 98 | -------------------------------------------------------------------------------- /canvas_sdk/methods/polls.py: -------------------------------------------------------------------------------- 1 | from canvas_sdk import client, utils 2 | 3 | def list_polls(request_ctx, **request_kwargs): 4 | """ 5 | Returns the list of polls for the current user. 6 | 7 | :param request_ctx: The request context 8 | :type request_ctx: :class:RequestContext 9 | :return: List polls 10 | :rtype: requests.Response (with void data) 11 | 12 | """ 13 | 14 | path = '/v1/polls' 15 | url = request_ctx.base_api_url + path.format() 16 | response = client.get(request_ctx, url, **request_kwargs) 17 | 18 | return response 19 | 20 | 21 | def get_single_poll(request_ctx, id, **request_kwargs): 22 | """ 23 | Returns the poll with the given id 24 | 25 | :param request_ctx: The request context 26 | :type request_ctx: :class:RequestContext 27 | :param id: (required) ID 28 | :type id: string 29 | :return: Get a single poll 30 | :rtype: requests.Response (with void data) 31 | 32 | """ 33 | 34 | path = '/v1/polls/{id}' 35 | url = request_ctx.base_api_url + path.format(id=id) 36 | response = client.get(request_ctx, url, **request_kwargs) 37 | 38 | return response 39 | 40 | 41 | def create_single_poll(request_ctx, polls_question, polls_description=None, **request_kwargs): 42 | """ 43 | Create a new poll for the current user 44 | 45 | :param request_ctx: The request context 46 | :type request_ctx: :class:RequestContext 47 | :param polls_question: (required) The title of the poll. 48 | :type polls_question: string 49 | :param polls_description: (optional) A brief description or instructions for the poll. 50 | :type polls_description: string or None 51 | :return: Create a single poll 52 | :rtype: requests.Response (with void data) 53 | 54 | """ 55 | 56 | path = '/v1/polls' 57 | payload = { 58 | 'polls[question]' : polls_question, 59 | 'polls[description]' : polls_description, 60 | } 61 | url = request_ctx.base_api_url + path.format() 62 | response = client.post(request_ctx, url, payload=payload, **request_kwargs) 63 | 64 | return response 65 | 66 | 67 | def update_single_poll(request_ctx, id, polls_question, polls_description=None, **request_kwargs): 68 | """ 69 | Update an existing poll belonging to the current user 70 | 71 | :param request_ctx: The request context 72 | :type request_ctx: :class:RequestContext 73 | :param id: (required) ID 74 | :type id: string 75 | :param polls_question: (required) The title of the poll. 76 | :type polls_question: string 77 | :param polls_description: (optional) A brief description or instructions for the poll. 78 | :type polls_description: string or None 79 | :return: Update a single poll 80 | :rtype: requests.Response (with void data) 81 | 82 | """ 83 | 84 | path = '/v1/polls/{id}' 85 | payload = { 86 | 'polls[question]' : polls_question, 87 | 'polls[description]' : polls_description, 88 | } 89 | url = request_ctx.base_api_url + path.format(id=id) 90 | response = client.put(request_ctx, url, payload=payload, **request_kwargs) 91 | 92 | return response 93 | 94 | 95 | def delete_poll(request_ctx, id, **request_kwargs): 96 | """ 97 | 204 No Content response code is returned if the deletion was successful. 98 | 99 | :param request_ctx: The request context 100 | :type request_ctx: :class:RequestContext 101 | :param id: (required) ID 102 | :type id: string 103 | :return: Delete a poll 104 | :rtype: requests.Response (with void data) 105 | 106 | """ 107 | 108 | path = '/v1/polls/{id}' 109 | url = request_ctx.base_api_url + path.format(id=id) 110 | response = client.delete(request_ctx, url, **request_kwargs) 111 | 112 | return response 113 | 114 | 115 | -------------------------------------------------------------------------------- /canvas_sdk/methods/outcome_results.py: -------------------------------------------------------------------------------- 1 | from canvas_sdk import client, utils 2 | 3 | def get_outcome_results(request_ctx, course_id, user_ids=None, outcome_ids=None, include=None, **request_kwargs): 4 | """ 5 | Gets the outcome results for users and outcomes in the specified context. 6 | 7 | :param request_ctx: The request context 8 | :type request_ctx: :class:RequestContext 9 | :param course_id: (required) ID 10 | :type course_id: string 11 | :param user_ids: (optional) If specified, only the users whose ids are given will be included in the results. it is an error to specify an id for a user who is not a student in the context 12 | :type user_ids: integer or None 13 | :param outcome_ids: (optional) If specified, only the outcomes whose ids are given will be included in the results. it is an error to specify an id for an outcome which is not linked to the context. 14 | :type outcome_ids: integer or None 15 | :param include: (optional) Specify additional collections to be side loaded with the result. "alignments" includes only the alignments referenced by the returned results. "outcomes.alignments" includes all alignments referenced by outcomes in the context. 16 | :type include: string or None 17 | :return: Get outcome results 18 | :rtype: requests.Response (with void data) 19 | 20 | """ 21 | 22 | include_types = ('alignments', 'outcomes', 'outcomes.alignments', 'outcome_groups', 'outcome_links', 'outcome_paths', 'users') 23 | utils.validate_attr_is_acceptable(include, include_types) 24 | path = '/v1/courses/{course_id}/outcome_results' 25 | payload = { 26 | 'user_ids' : user_ids, 27 | 'outcome_ids' : outcome_ids, 28 | 'include' : include, 29 | } 30 | url = request_ctx.base_api_url + path.format(course_id=course_id) 31 | response = client.get(request_ctx, url, payload=payload, **request_kwargs) 32 | 33 | return response 34 | 35 | 36 | def get_outcome_result_rollups(request_ctx, course_id, aggregate=None, user_ids=None, outcome_ids=None, include=None, **request_kwargs): 37 | """ 38 | Gets the outcome rollups for the users and outcomes in the specified 39 | context. 40 | 41 | :param request_ctx: The request context 42 | :type request_ctx: :class:RequestContext 43 | :param course_id: (required) ID 44 | :type course_id: string 45 | :param aggregate: (optional) If specified, instead of returning one rollup for each user, all the user rollups will be combined into one rollup for the course that will contain the average rollup score for each outcome. 46 | :type aggregate: string or None 47 | :param user_ids: (optional) If specified, only the users whose ids are given will be included in the results or used in an aggregate result. it is an error to specify an id for a user who is not a student in the context 48 | :type user_ids: integer or None 49 | :param outcome_ids: (optional) If specified, only the outcomes whose ids are given will be included in the results. it is an error to specify an id for an outcome which is not linked to the context. 50 | :type outcome_ids: integer or None 51 | :param include: (optional) Specify additional collections to be side loaded with the result. 52 | :type include: string or None 53 | :return: Get outcome result rollups 54 | :rtype: requests.Response (with void data) 55 | 56 | """ 57 | 58 | aggregate_types = ('course') 59 | include_types = ('courses', 'outcomes', 'outcomes.alignments', 'outcome_groups', 'outcome_links', 'outcome_paths', 'users') 60 | utils.validate_attr_is_acceptable(aggregate, aggregate_types) 61 | utils.validate_attr_is_acceptable(include, include_types) 62 | path = '/v1/courses/{course_id}/outcome_rollups' 63 | payload = { 64 | 'aggregate' : aggregate, 65 | 'user_ids' : user_ids, 66 | 'outcome_ids' : outcome_ids, 67 | 'include' : include, 68 | } 69 | url = request_ctx.base_api_url + path.format(course_id=course_id) 70 | response = client.get(request_ctx, url, payload=payload, **request_kwargs) 71 | 72 | return response 73 | 74 | 75 | -------------------------------------------------------------------------------- /canvas_sdk/methods/grading_standards.py: -------------------------------------------------------------------------------- 1 | from canvas_sdk import client, utils 2 | 3 | def create_new_grading_standard_accounts(request_ctx, account_id, title, grading_scheme_entry_name, grading_scheme_entry_value, **request_kwargs): 4 | """ 5 | Create a new grading standard 6 | 7 | If grading_scheme_entry arguments are omitted, then a default grading scheme 8 | will be set. The default scheme is as follows: 9 | "A" : 94, 10 | "A-" : 90, 11 | "B+" : 87, 12 | "B" : 84, 13 | "B-" : 80, 14 | "C+" : 77, 15 | "C" : 74, 16 | "C-" : 70, 17 | "D+" : 67, 18 | "D" : 64, 19 | "D-" : 61, 20 | "F" : 0, 21 | 22 | :param request_ctx: The request context 23 | :type request_ctx: :class:RequestContext 24 | :param account_id: (required) ID 25 | :type account_id: string 26 | :param title: (required) The title for the Grading Standard. 27 | :type title: string 28 | :param grading_scheme_entry_name: (required) The name for an entry value within a GradingStandard that describes the range of the value e.g. A- 29 | :type grading_scheme_entry_name: string 30 | :param grading_scheme_entry_value: (required) The value for the name of the entry within a GradingStandard. The entry represents the lower bound of the range for the entry. This range includes the value up to the next entry in the GradingStandard, or 100 if there is no upper bound. The lowest value will have a lower bound range of 0. e.g. 93 31 | :type grading_scheme_entry_value: integer 32 | :return: Create a new grading standard 33 | :rtype: requests.Response (with GradingStandard data) 34 | 35 | """ 36 | 37 | path = '/v1/accounts/{account_id}/grading_standards' 38 | payload = { 39 | 'title' : title, 40 | 'grading_scheme_entry[name]' : grading_scheme_entry_name, 41 | 'grading_scheme_entry[value]' : grading_scheme_entry_value, 42 | } 43 | url = request_ctx.base_api_url + path.format(account_id=account_id) 44 | response = client.post(request_ctx, url, payload=payload, **request_kwargs) 45 | 46 | return response 47 | 48 | 49 | def create_new_grading_standard_courses(request_ctx, course_id, title, grading_scheme_entry_name, grading_scheme_entry_value, **request_kwargs): 50 | """ 51 | Create a new grading standard 52 | 53 | If grading_scheme_entry arguments are omitted, then a default grading scheme 54 | will be set. The default scheme is as follows: 55 | "A" : 94, 56 | "A-" : 90, 57 | "B+" : 87, 58 | "B" : 84, 59 | "B-" : 80, 60 | "C+" : 77, 61 | "C" : 74, 62 | "C-" : 70, 63 | "D+" : 67, 64 | "D" : 64, 65 | "D-" : 61, 66 | "F" : 0, 67 | 68 | :param request_ctx: The request context 69 | :type request_ctx: :class:RequestContext 70 | :param course_id: (required) ID 71 | :type course_id: string 72 | :param title: (required) The title for the Grading Standard. 73 | :type title: string 74 | :param grading_scheme_entry_name: (required) The name for an entry value within a GradingStandard that describes the range of the value e.g. A- 75 | :type grading_scheme_entry_name: string 76 | :param grading_scheme_entry_value: (required) The value for the name of the entry within a GradingStandard. The entry represents the lower bound of the range for the entry. This range includes the value up to the next entry in the GradingStandard, or 100 if there is no upper bound. The lowest value will have a lower bound range of 0. e.g. 93 77 | :type grading_scheme_entry_value: integer 78 | :return: Create a new grading standard 79 | :rtype: requests.Response (with GradingStandard data) 80 | 81 | """ 82 | 83 | path = '/v1/courses/{course_id}/grading_standards' 84 | payload = { 85 | 'title' : title, 86 | 'grading_scheme_entry[name]' : grading_scheme_entry_name, 87 | 'grading_scheme_entry[value]' : grading_scheme_entry_value, 88 | } 89 | url = request_ctx.base_api_url + path.format(course_id=course_id) 90 | response = client.post(request_ctx, url, payload=payload, **request_kwargs) 91 | 92 | return response 93 | 94 | 95 | -------------------------------------------------------------------------------- /canvas_sdk/methods/admins.py: -------------------------------------------------------------------------------- 1 | from canvas_sdk import client, utils 2 | 3 | def make_account_admin(request_ctx, account_id, user_id, role=None, role_id=None, send_confirmation=None, **request_kwargs): 4 | """ 5 | Flag an existing user as an admin within the account. 6 | 7 | :param request_ctx: The request context 8 | :type request_ctx: :class:RequestContext 9 | :param account_id: (required) ID 10 | :type account_id: string 11 | :param user_id: (required) The id of the user to promote. 12 | :type user_id: integer 13 | :param role: (optional) (deprecated) 14 | The user's admin relationship with the account will be created with the 15 | given role. Defaults to 'AccountAdmin'. 16 | :type role: string or None 17 | :param role_id: (optional) The user's admin relationship with the account will be created with the 18 | given role. Defaults to the built-in role for 'AccountAdmin'. 19 | :type role_id: integer or None 20 | :param send_confirmation: (optional) Send a notification email to 21 | the new admin if true. Default is true. 22 | :type send_confirmation: boolean or None 23 | :return: Make an account admin 24 | :rtype: requests.Response (with Admin data) 25 | 26 | """ 27 | 28 | path = '/v1/accounts/{account_id}/admins' 29 | payload = { 30 | 'user_id' : user_id, 31 | 'role' : role, 32 | 'role_id' : role_id, 33 | 'send_confirmation' : send_confirmation, 34 | } 35 | url = request_ctx.base_api_url + path.format(account_id=account_id) 36 | response = client.post(request_ctx, url, payload=payload, **request_kwargs) 37 | 38 | return response 39 | 40 | 41 | def remove_account_admin(request_ctx, account_id, user_id, role=None, role_id=None, **request_kwargs): 42 | """ 43 | Remove the rights associated with an account admin role from a user. 44 | 45 | :param request_ctx: The request context 46 | :type request_ctx: :class:RequestContext 47 | :param account_id: (required) ID 48 | :type account_id: string 49 | :param user_id: (required) ID 50 | :type user_id: string 51 | :param role: (optional) (Deprecated) 52 | Account role to remove from the user. Defaults to 'AccountAdmin'. Any 53 | other account role must be specified explicitly. 54 | :type role: string or None 55 | :param role_id: (optional) The user's admin relationship with the account will be created with the 56 | given role. Defaults to the built-in role for 'AccountAdmin'. 57 | :type role_id: integer or None 58 | :return: Remove account admin 59 | :rtype: requests.Response (with Admin data) 60 | 61 | """ 62 | 63 | path = '/v1/accounts/{account_id}/admins/{user_id}' 64 | payload = { 65 | 'role' : role, 66 | 'role_id' : role_id, 67 | } 68 | url = request_ctx.base_api_url + path.format(account_id=account_id, user_id=user_id) 69 | response = client.delete(request_ctx, url, payload=payload, **request_kwargs) 70 | 71 | return response 72 | 73 | 74 | def list_account_admins(request_ctx, account_id, user_id=None, per_page=None, **request_kwargs): 75 | """ 76 | List the admins in the account 77 | 78 | :param request_ctx: The request context 79 | :type request_ctx: :class:RequestContext 80 | :param account_id: (required) ID 81 | :type account_id: string 82 | :param user_id: (optional) Scope the results to those with user IDs equal to any of the IDs specified here. 83 | :type user_id: array or None 84 | :param per_page: (optional) Set how many results canvas should return, defaults to config.LIMIT_PER_PAGE 85 | :type per_page: integer or None 86 | :return: List account admins 87 | :rtype: requests.Response (with array data) 88 | 89 | """ 90 | 91 | if per_page is None: 92 | per_page = request_ctx.per_page 93 | path = '/v1/accounts/{account_id}/admins' 94 | payload = { 95 | 'user_id' : user_id, 96 | 'per_page' : per_page, 97 | } 98 | url = request_ctx.base_api_url + path.format(account_id=account_id) 99 | response = client.get(request_ctx, url, payload=payload, **request_kwargs) 100 | 101 | return response 102 | 103 | 104 | -------------------------------------------------------------------------------- /canvas_sdk/utils.py: -------------------------------------------------------------------------------- 1 | from canvas_sdk import client 2 | from collections import defaultdict 3 | 4 | """ 5 | The util module contains helper methods for the SDK 6 | """ 7 | 8 | 9 | def validate_attr_is_acceptable(value, acceptable_values=[], allow_none=True): 10 | """ 11 | Test an input value against a list of acceptable values. A value of None may or may 12 | not be considered valid. If the input is not valid, an Attribute error is raised, otherwise 13 | nothing is returned. 14 | """ 15 | if type(value) not in (list, tuple): 16 | value = [value] 17 | for v in value: 18 | if v not in acceptable_values: 19 | # We know the value is not one of the acceptable values, but we need to also make sure 20 | # that the value is not None and that None is not an allowable value before raising 21 | # an exception 22 | if v is not None or not allow_none: 23 | raise AttributeError("%s must be one of %s" % (v, acceptable_values)) 24 | 25 | 26 | def validate_any(param_choices, *args, **kwargs): 27 | """ 28 | If all of the arguments are falsy (e.g. None or blank strings), an 29 | Attribute error is raised, otherwise nothing is returned. param_choices 30 | should be list of variable names for feedback on a validation failure. 31 | """ 32 | if not any(args): 33 | raise AttributeError( 34 | "One of the following parameters must be included: " 35 | "%s" % (param_choices,)) 36 | 37 | 38 | def get_next(request_context, response): 39 | """ 40 | Generator function that will iterate over a given response's "next" header links. 41 | 42 | :param :class:RequestContext request_context: The context required to make a "get" request 43 | :return: next response object retrieved by client 44 | :rtype: iterator 45 | """ 46 | while 'next' in response.links: 47 | response = client.get(request_context, response.links["next"]["url"]) 48 | yield response 49 | 50 | 51 | def get_all_list_data(request_context, function, *args, **kwargs): 52 | """ 53 | Make a function request with args and kwargs and iterate over the "next" responses until exhausted. 54 | Return initial response json data or all json data as a single list. Responses that have a series of 55 | next responses (as retrieved by get_next generator) are expected to have data returned as a list. 56 | If an exception is raised during the initial function call or in the process of paging over results, 57 | that exception will be bubbled back to the caller and any intermediary results will be lost. Worst case 58 | complexity O(n). 59 | 60 | 61 | :param RequestContext request_context: The context required to make an API call 62 | :param function function: The API function to call 63 | :return: A list of all json data retrieved while iterating over response links, or the initial json 64 | function response if there are no paged results 65 | :rtype: list of json data or json 66 | """ 67 | response = function(request_context, *args, **kwargs) 68 | data = response.json() 69 | for next_response in get_next(request_context, response): 70 | data.extend(next_response.json()) 71 | return data 72 | 73 | 74 | def masquerade(request_context, function, as_user_id, *args, **kwargs): 75 | """ 76 | Make a function request on behalf of another user. In order to masquerade, the calling user must 77 | have the "Become other users" permission in Canvas. If the target user is also an admin, the 78 | calling user must additionally have every permission that the target user has. 79 | 80 | :param function function: The API function to call 81 | :param str as_user_id: The Canvas user ID or an SIS user ID (SIS IDs are described at 82 | https://canvas.instructure.com/doc/api/file.object_ids.html) 83 | :return: Response from function call 84 | """ 85 | function_kwargs = defaultdict(dict, **kwargs) 86 | # Merge or create as_user_id into params kwarg - use defaultdict to reduce conditional logic 87 | function_kwargs['params'].update(as_user_id=as_user_id) 88 | return function(request_context, *args, **function_kwargs) 89 | 90 | 91 | def get_count(request_context, function, *args, **kwargs): 92 | """ 93 | Make a function request with args and kwargs and return the total result count. Worst case complexity 94 | is O(n). 95 | 96 | :param RequestContext request_context: The context required to make an API call 97 | :param function function: The API function to call 98 | :return: Total result count 99 | :rtype: int 100 | """ 101 | return len(get_all_list_data(request_context, function, *args, **kwargs)) 102 | -------------------------------------------------------------------------------- /static_methods/modules.py: -------------------------------------------------------------------------------- 1 | from canvas_sdk import client, utils 2 | 3 | def create_module(request_ctx, course_id, module_name, module_unlock_at=None, module_position=None, module_require_sequential_progress=None, module_prerequisite_module_ids=None, module_publish_final_grade=None, **request_kwargs): 4 | """ 5 | Create and return a new module 6 | 7 | :param request_ctx: The request context 8 | :type request_ctx: :class:RequestContext 9 | :param course_id: (required) ID 10 | :type course_id: string 11 | :param module_name: (required) The name of the module 12 | :type module_name: string 13 | :param module_unlock_at: (optional) The date the module will unlock 14 | :type module_unlock_at: datetime or None 15 | :param module_position: (optional) The position of this module in the course (1-based) 16 | :type module_position: integer or None 17 | :param module_require_sequential_progress: (optional) Whether module items must be unlocked in order 18 | :type module_require_sequential_progress: boolean or None 19 | :param module_prerequisite_module_ids: (optional) IDs of Modules that must be completed before this one is unlocked. Prerequisite modules must precede this module (i.e. have a lower position value), otherwise they will be ignored 20 | :type module_prerequisite_module_ids: string or None 21 | :param module_publish_final_grade: (optional) Whether to publish the student's final grade for the course upon completion of this module. 22 | :type module_publish_final_grade: boolean or None 23 | :return: Create a module 24 | :rtype: requests.Response (with Module data) 25 | 26 | """ 27 | 28 | path = '/v1/courses/{course_id}/modules' 29 | payload = { 30 | 'module[name]' : module_name, 31 | 'module[unlock_at]' : module_unlock_at, 32 | 'module[position]' : module_position, 33 | 'module[require_sequential_progress]' : module_require_sequential_progress, 34 | 'module[prerequisite_module_ids]' : module_prerequisite_module_ids, 35 | 'module[publish_final_grade]' : module_publish_final_grade, 36 | } 37 | url = request_ctx.base_api_url + path.format(course_id=course_id) 38 | response = client.post(request_ctx, url, payload=payload, **request_kwargs) 39 | 40 | return response 41 | 42 | 43 | def update_module(request_ctx, course_id, id, module_name=None, module_unlock_at=None, module_position=None, module_require_sequential_progress=None, module_prerequisite_module_ids=None, module_publish_final_grade=None, module_published=None, **request_kwargs): 44 | """ 45 | Update and return an existing module 46 | 47 | :param request_ctx: The request context 48 | :type request_ctx: :class:RequestContext 49 | :param course_id: (required) ID 50 | :type course_id: string 51 | :param id: (required) ID 52 | :type id: string 53 | :param module_name: (optional) The name of the module 54 | :type module_name: string or None 55 | :param module_unlock_at: (optional) The date the module will unlock 56 | :type module_unlock_at: datetime or None 57 | :param module_position: (optional) The position of the module in the course (1-based) 58 | :type module_position: integer or None 59 | :param module_require_sequential_progress: (optional) Whether module items must be unlocked in order 60 | :type module_require_sequential_progress: boolean or None 61 | :param module_prerequisite_module_ids: (optional) IDs of Modules that must be completed before this one is unlocked Prerequisite modules must precede this module (i.e. have a lower position value), otherwise they will be ignored 62 | :type module_prerequisite_module_ids: string or None 63 | :param module_publish_final_grade: (optional) Whether to publish the student's final grade for the course upon completion of this module. 64 | :type module_publish_final_grade: boolean or None 65 | :param module_published: (optional) Whether the module is published and visible to students 66 | :type module_published: boolean or None 67 | :return: Update a module 68 | :rtype: requests.Response (with Module data) 69 | 70 | """ 71 | 72 | path = '/v1/courses/{course_id}/modules/{id}' 73 | payload = { 74 | 'module[name]' : module_name, 75 | 'module[unlock_at]' : module_unlock_at, 76 | 'module[position]' : module_position, 77 | 'module[require_sequential_progress]' : module_require_sequential_progress, 78 | 'module[prerequisite_module_ids]' : module_prerequisite_module_ids, 79 | 'module[publish_final_grade]' : module_publish_final_grade, 80 | 'module[published]' : module_published, 81 | } 82 | url = request_ctx.base_api_url + path.format(course_id=course_id, id=id) 83 | response = client.put(request_ctx, url, payload=payload, **request_kwargs) 84 | 85 | return response 86 | -------------------------------------------------------------------------------- /canvas_sdk/methods/account_reports.py: -------------------------------------------------------------------------------- 1 | from canvas_sdk import client, utils 2 | import re 3 | 4 | 5 | def list_available_reports(request_ctx, account_id, **request_kwargs): 6 | """ 7 | Returns the list of reports for the current context. 8 | 9 | :param request_ctx: The request context 10 | :type request_ctx: :class:RequestContext 11 | :param account_id: (required) ID 12 | :type account_id: string 13 | :return: List Available Reports 14 | :rtype: requests.Response (with void data) 15 | 16 | """ 17 | 18 | path = '/v1/accounts/{account_id}/reports' 19 | url = request_ctx.base_api_url + path.format(account_id=account_id) 20 | response = client.get(request_ctx, url, **request_kwargs) 21 | 22 | return response 23 | 24 | 25 | def start_report(request_ctx, account_id, report, parameters, **request_kwargs): 26 | """ 27 | Generates a report instance for the account. 28 | 29 | :param request_ctx: The request context 30 | :type request_ctx: :class:RequestContext 31 | :param account_id: (required) ID 32 | :type account_id: string 33 | :param report: (required) ID 34 | :type report: string 35 | :param parameters: (required) The parameters will vary for each report 36 | :type parameters: dict 37 | :return: Start a Report 38 | :rtype: requests.Response (with Report data) 39 | 40 | """ 41 | 42 | path = '/v1/accounts/{account_id}/reports/{report}' 43 | 44 | # if the parameters dict has keys like 'enrollments', 'xlist', 'include_deleted' 45 | # we need to translate them to be like 'parameters[enrollments]' 46 | ppat = re.compile('parameters\[.+\]') 47 | fix_key = lambda k_v: (k_v[0] if ppat.match(str(k_v[0])) else 'parameters[{}]'.format(k_v[0]), k_v[1]) 48 | payload = list(map(fix_key, list(parameters.items()))) 49 | url = request_ctx.base_api_url + path.format(account_id=account_id, report=report) 50 | response = client.post(request_ctx, url, payload=payload, **request_kwargs) 51 | 52 | return response 53 | 54 | 55 | def index_of_reports(request_ctx, account_id, report, per_page=None, **request_kwargs): 56 | """ 57 | Shows all reports that have been run for the account of a specific type. 58 | 59 | :param request_ctx: The request context 60 | :type request_ctx: :class:RequestContext 61 | :param account_id: (required) ID 62 | :type account_id: string 63 | :param report: (required) ID 64 | :type report: string 65 | :param per_page: (optional) Set how many results canvas should return, defaults to config.LIMIT_PER_PAGE 66 | :type per_page: integer or None 67 | :return: Index of Reports 68 | :rtype: requests.Response (with array data) 69 | 70 | """ 71 | 72 | if per_page is None: 73 | per_page = request_ctx.per_page 74 | path = '/v1/accounts/{account_id}/reports/{report}' 75 | payload = { 76 | 'per_page' : per_page, 77 | } 78 | url = request_ctx.base_api_url + path.format(account_id=account_id, report=report) 79 | response = client.get(request_ctx, url, payload=payload, **request_kwargs) 80 | 81 | return response 82 | 83 | 84 | def status_of_report(request_ctx, account_id, report, id, **request_kwargs): 85 | """ 86 | Returns the status of a report. 87 | 88 | :param request_ctx: The request context 89 | :type request_ctx: :class:RequestContext 90 | :param account_id: (required) ID 91 | :type account_id: string 92 | :param report: (required) ID 93 | :type report: string 94 | :param id: (required) ID 95 | :type id: string 96 | :return: Status of a Report 97 | :rtype: requests.Response (with Report data) 98 | 99 | """ 100 | 101 | path = '/v1/accounts/{account_id}/reports/{report}/{id}' 102 | url = request_ctx.base_api_url + path.format(account_id=account_id, report=report, id=id) 103 | response = client.get(request_ctx, url, **request_kwargs) 104 | 105 | return response 106 | 107 | 108 | def delete_report(request_ctx, account_id, report, id, **request_kwargs): 109 | """ 110 | Deletes a generated report instance. 111 | 112 | :param request_ctx: The request context 113 | :type request_ctx: :class:RequestContext 114 | :param account_id: (required) ID 115 | :type account_id: string 116 | :param report: (required) ID 117 | :type report: string 118 | :param id: (required) ID 119 | :type id: string 120 | :return: Delete a Report 121 | :rtype: requests.Response (with Report data) 122 | 123 | """ 124 | 125 | path = '/v1/accounts/{account_id}/reports/{report}/{id}' 126 | url = request_ctx.base_api_url + path.format(account_id=account_id, report=report, id=id) 127 | response = client.delete(request_ctx, url, **request_kwargs) 128 | 129 | return response 130 | -------------------------------------------------------------------------------- /canvas_sdk/methods/quiz_reports.py: -------------------------------------------------------------------------------- 1 | from canvas_sdk import client, utils 2 | 3 | def retrieve_all_quiz_reports(request_ctx, course_id, quiz_id, includes_all_versions=None, per_page=None, **request_kwargs): 4 | """ 5 | Returns a list of all available reports. 6 | 7 | :param request_ctx: The request context 8 | :type request_ctx: :class:RequestContext 9 | :param course_id: (required) ID 10 | :type course_id: string 11 | :param quiz_id: (required) ID 12 | :type quiz_id: string 13 | :param includes_all_versions: (optional) Whether to retrieve reports that consider all the submissions or only the most recent. Defaults to false, ignored for item_analysis reports. 14 | :type includes_all_versions: boolean or None 15 | :param per_page: (optional) Set how many results canvas should return, defaults to config.LIMIT_PER_PAGE 16 | :type per_page: integer or None 17 | :return: Retrieve all quiz reports 18 | :rtype: requests.Response (with array data) 19 | 20 | """ 21 | 22 | if per_page is None: 23 | per_page = request_ctx.per_page 24 | path = '/v1/courses/{course_id}/quizzes/{quiz_id}/reports' 25 | payload = { 26 | 'includes_all_versions' : includes_all_versions, 27 | 'per_page' : per_page, 28 | } 29 | url = request_ctx.base_api_url + path.format(course_id=course_id, quiz_id=quiz_id) 30 | response = client.get(request_ctx, url, payload=payload, **request_kwargs) 31 | 32 | return response 33 | 34 | 35 | def create_quiz_report(request_ctx, course_id, quiz_id, quiz_report_report_type, quiz_report_includes_all_versions=None, include=None, **request_kwargs): 36 | """ 37 | Create and return a new report for this quiz. If a previously 38 | generated report matches the arguments and is still current (i.e. 39 | there have been no new submissions), it will be returned. 40 | 41 | :param request_ctx: The request context 42 | :type request_ctx: :class:RequestContext 43 | :param course_id: (required) ID 44 | :type course_id: string 45 | :param quiz_id: (required) ID 46 | :type quiz_id: string 47 | :param quiz_report_report_type: (required) The type of report to be generated. 48 | :type quiz_report_report_type: string 49 | :param quiz_report_includes_all_versions: (optional) Whether the report should consider all submissions or only the most recent. Defaults to false, ignored for item_analysis. 50 | :type quiz_report_includes_all_versions: boolean or None 51 | :param include: (optional) Whether the output should include documents for the file and/or progress objects associated with this report. (Note: JSON-API only) 52 | :type include: string[] or None 53 | :return: Create a quiz report 54 | :rtype: requests.Response (with QuizReport data) 55 | 56 | """ 57 | 58 | quiz_report_report_type_types = ('student_analysis', 'item_analysis') 59 | include_types = ('file', 'progress') 60 | utils.validate_attr_is_acceptable(quiz_report_report_type, quiz_report_report_type_types) 61 | utils.validate_attr_is_acceptable(include, include_types) 62 | path = '/v1/courses/{course_id}/quizzes/{quiz_id}/reports' 63 | payload = { 64 | 'quiz_report[report_type]' : quiz_report_report_type, 65 | 'quiz_report[includes_all_versions]' : quiz_report_includes_all_versions, 66 | 'include' : include, 67 | } 68 | url = request_ctx.base_api_url + path.format(course_id=course_id, quiz_id=quiz_id) 69 | response = client.post(request_ctx, url, payload=payload, **request_kwargs) 70 | 71 | return response 72 | 73 | 74 | def get_quiz_report(request_ctx, course_id, quiz_id, id, include=None, **request_kwargs): 75 | """ 76 | Returns the data for a single quiz report. 77 | 78 | :param request_ctx: The request context 79 | :type request_ctx: :class:RequestContext 80 | :param course_id: (required) ID 81 | :type course_id: string 82 | :param quiz_id: (required) ID 83 | :type quiz_id: string 84 | :param id: (required) ID 85 | :type id: string 86 | :param include: (optional) Whether the output should include documents for the file and/or progress objects associated with this report. (Note: JSON-API only) 87 | :type include: string[] or None 88 | :return: Get a quiz report 89 | :rtype: requests.Response (with QuizReport data) 90 | 91 | """ 92 | 93 | include_types = ('file', 'progress') 94 | utils.validate_attr_is_acceptable(include, include_types) 95 | path = '/v1/courses/{course_id}/quizzes/{quiz_id}/reports/{id}' 96 | payload = { 97 | 'include' : include, 98 | } 99 | url = request_ctx.base_api_url + path.format(course_id=course_id, quiz_id=quiz_id, id=id) 100 | response = client.get(request_ctx, url, payload=payload, **request_kwargs) 101 | 102 | return response 103 | 104 | 105 | -------------------------------------------------------------------------------- /canvas_sdk/methods/communication_channels.py: -------------------------------------------------------------------------------- 1 | from canvas_sdk import client, utils 2 | 3 | def list_user_communication_channels(request_ctx, user_id, per_page=None, **request_kwargs): 4 | """ 5 | Returns a list of communication channels for the specified user, sorted by 6 | position. 7 | 8 | :param request_ctx: The request context 9 | :type request_ctx: :class:RequestContext 10 | :param user_id: (required) ID 11 | :type user_id: string 12 | :param per_page: (optional) Set how many results canvas should return, defaults to config.LIMIT_PER_PAGE 13 | :type per_page: integer or None 14 | :return: List user communication channels 15 | :rtype: requests.Response (with array data) 16 | 17 | """ 18 | 19 | if per_page is None: 20 | per_page = request_ctx.per_page 21 | path = '/v1/users/{user_id}/communication_channels' 22 | payload = { 23 | 'per_page' : per_page, 24 | } 25 | url = request_ctx.base_api_url + path.format(user_id=user_id) 26 | response = client.get(request_ctx, url, payload=payload, **request_kwargs) 27 | 28 | return response 29 | 30 | 31 | def create_communication_channel(request_ctx, user_id, communication_channel_address, communication_channel_type, skip_confirmation=None, **request_kwargs): 32 | """ 33 | Creates a new communication channel for the specified user. 34 | 35 | :param request_ctx: The request context 36 | :type request_ctx: :class:RequestContext 37 | :param user_id: (required) ID 38 | :type user_id: string 39 | :param communication_channel_address: (required) An email address or SMS number. 40 | :type communication_channel_address: string 41 | :param communication_channel_type: (required) The type of communication channel. In order to enable push notification support, the server must be properly configured (via sns.yml) to communicate with Amazon Simple Notification Services, and the developer key used to create the access token from this request must have an SNS ARN configured on it. 42 | :type communication_channel_type: string 43 | :param skip_confirmation: (optional) Only valid for site admins and account admins making requests; If true, the channel is automatically validated and no confirmation email or SMS is sent. Otherwise, the user must respond to a confirmation message to confirm the channel. 44 | :type skip_confirmation: boolean or None 45 | :return: Create a communication channel 46 | :rtype: requests.Response (with CommunicationChannel data) 47 | 48 | """ 49 | 50 | communication_channel_type_types = ('email', 'sms', 'push') 51 | utils.validate_attr_is_acceptable(communication_channel_type, communication_channel_type_types) 52 | path = '/v1/users/{user_id}/communication_channels' 53 | payload = { 54 | 'communication_channel[address]' : communication_channel_address, 55 | 'communication_channel[type]' : communication_channel_type, 56 | 'skip_confirmation' : skip_confirmation, 57 | } 58 | url = request_ctx.base_api_url + path.format(user_id=user_id) 59 | response = client.post(request_ctx, url, payload=payload, **request_kwargs) 60 | 61 | return response 62 | 63 | 64 | def delete_communication_channel_id(request_ctx, user_id, id, **request_kwargs): 65 | """ 66 | Delete an existing communication channel. 67 | 68 | :param request_ctx: The request context 69 | :type request_ctx: :class:RequestContext 70 | :param user_id: (required) ID 71 | :type user_id: string 72 | :param id: (required) ID 73 | :type id: string 74 | :return: Delete a communication channel 75 | :rtype: requests.Response (with CommunicationChannel data) 76 | 77 | """ 78 | 79 | path = '/v1/users/{user_id}/communication_channels/{id}' 80 | url = request_ctx.base_api_url + path.format(user_id=user_id, id=id) 81 | response = client.delete(request_ctx, url, **request_kwargs) 82 | 83 | return response 84 | 85 | 86 | def delete_communication_channel_type(request_ctx, user_id, type, address, **request_kwargs): 87 | """ 88 | Delete an existing communication channel. 89 | 90 | :param request_ctx: The request context 91 | :type request_ctx: :class:RequestContext 92 | :param user_id: (required) ID 93 | :type user_id: string 94 | :param type: (required) ID 95 | :type type: string 96 | :param address: (required) ID 97 | :type address: string 98 | :return: Delete a communication channel 99 | :rtype: requests.Response (with CommunicationChannel data) 100 | 101 | """ 102 | 103 | path = '/v1/users/{user_id}/communication_channels/{type}/{address}' 104 | url = request_ctx.base_api_url + path.format(user_id=user_id, type=type, address=address) 105 | response = client.delete(request_ctx, url, **request_kwargs) 106 | 107 | return response 108 | 109 | 110 | -------------------------------------------------------------------------------- /tests/test_modules.py: -------------------------------------------------------------------------------- 1 | from unittest import mock 2 | import random 3 | import unittest 4 | import uuid 5 | 6 | from canvas_sdk.client import RequestContext 7 | from canvas_sdk.methods import modules 8 | 9 | 10 | class TestModules(unittest.TestCase): 11 | ''' 12 | Exercises all hand-edited methods in the modules module, specifically 13 | the parts that needed hand-editing in the first place. 14 | ''' 15 | def setUp(self): 16 | self.base_api_url = 'http://example.org/api/{}'.format(uuid.uuid4().hex) 17 | self.request_context = mock.MagicMock(spec=RequestContext, 18 | base_api_url=self.base_api_url) 19 | self.content_id = uuid.uuid4().hex 20 | self.course_id = uuid.uuid4().hex 21 | self.min_score = random.randint(0,100) 22 | self.module_id = uuid.uuid4().hex 23 | self.position = random.randint(0,100) 24 | self.title = uuid.uuid4().hex 25 | self.url = 'http://example.org/{}'.format(uuid.uuid4().hex) 26 | 27 | @mock.patch('canvas_sdk.methods.modules.client.post') 28 | def test_create_module_item_external_tool(self, mock_client_post): 29 | ''' arguments taken from tlt-1641 ''' 30 | response = modules.create_module_item( 31 | self.request_context, self.course_id, 32 | self.module_id, 'ExternalTool', self.content_id, 33 | module_item_external_url=self.url, 34 | module_item_title=self.title, 35 | module_item_position=self.position) 36 | self.assertTrue(mock_client_post.called) 37 | assert ( 38 | set({ 39 | 'module_item[completion_requirement][type]': None, 40 | 'module_item[completion_requirement][min_score]': None 41 | }.items()) <= 42 | set(mock_client_post.call_args[1]['payload'].items()) 43 | ) 44 | 45 | @mock.patch('canvas_sdk.methods.modules.client.post') 46 | def test_create_module_item_assignment(self, mock_client_post): 47 | ''' arguments taken from tlt-1641 ''' 48 | response = modules.create_module_item( 49 | self.request_context, self.course_id, 50 | self.module_id, 'Assignment', self.content_id, 51 | module_item_title=self.title, 52 | module_item_position=self.position) 53 | self.assertTrue(mock_client_post.called) 54 | assert ( 55 | set({ 56 | 'module_item[completion_requirement][type]': None, 57 | 'module_item[completion_requirement][min_score]': None 58 | }.items()) <= 59 | set(mock_client_post.call_args[1]['payload'].items()) 60 | ) 61 | 62 | @mock.patch('canvas_sdk.methods.modules.client.post') 63 | def test_create_module_item_completion_requirement_type_is_min_score(self, mock_client_post): 64 | ''' verifies that completion_requirement_type=min_score is usable ''' 65 | response = modules.create_module_item( 66 | self.request_context, self.course_id, 67 | self.module_id, 'ExternalTool', self.content_id, 68 | module_item_completion_requirement_min_score=self.min_score, 69 | module_item_completion_requirement_type='min_score', 70 | module_item_external_url=self.url, 71 | module_item_position=self.position, 72 | module_item_title=self.title) 73 | self.assertTrue(mock_client_post.called) 74 | assert ( 75 | set({ 76 | 'module_item[completion_requirement][type]': 'min_score', 77 | 'module_item[completion_requirement][min_score]': self.min_score 78 | }.items()) <= 79 | set(mock_client_post.call_args[1]['payload'].items()) 80 | ) 81 | 82 | def test_create_module_item_external_url_required(self): 83 | ''' module_item_external_url is required if type='ExternalTool' ''' 84 | with self.assertRaises(ValueError): 85 | response = modules.create_module_item( 86 | self.request_context, self.course_id, 87 | self.module_id, 'ExternalTool', self.content_id, 88 | module_item_title=self.title, 89 | module_item_position=self.position) 90 | 91 | def test_create_module_item_min_score_required(self): 92 | ''' module_item_completion_requirement_min_score is required if 93 | module_item_completion_requirement_type='ExternalTool' ''' 94 | with self.assertRaises(ValueError): 95 | response = modules.create_module_item( 96 | self.request_context, self.course_id, 97 | self.module_id, 'ExternalTool', self.content_id, 98 | module_item_completion_requirement_type='min_score', 99 | module_item_external_url=self.url, 100 | module_item_position=self.position, 101 | module_item_title=self.title) 102 | -------------------------------------------------------------------------------- /canvas_sdk/methods/user_observees.py: -------------------------------------------------------------------------------- 1 | from canvas_sdk import client, utils 2 | 3 | def list_observees(request_ctx, user_id, per_page=None, **request_kwargs): 4 | """ 5 | List the users that the given user is observing. 6 | 7 | *Note:* all users are allowed to list their own observees. Administrators can list 8 | other users' observees. 9 | 10 | :param request_ctx: The request context 11 | :type request_ctx: :class:RequestContext 12 | :param user_id: (required) ID 13 | :type user_id: string 14 | :param per_page: (optional) Set how many results canvas should return, defaults to config.LIMIT_PER_PAGE 15 | :type per_page: integer or None 16 | :return: List observees 17 | :rtype: requests.Response (with array data) 18 | 19 | """ 20 | 21 | if per_page is None: 22 | per_page = request_ctx.per_page 23 | path = '/v1/users/{user_id}/observees' 24 | payload = { 25 | 'per_page' : per_page, 26 | } 27 | url = request_ctx.base_api_url + path.format(user_id=user_id) 28 | response = client.get(request_ctx, url, payload=payload, **request_kwargs) 29 | 30 | return response 31 | 32 | 33 | def add_observee_with_credentials(request_ctx, user_id, observee_unique_id=None, observee_password=None, **request_kwargs): 34 | """ 35 | Register the given user to observe another user, given the observee's credentials. 36 | 37 | *Note:* all users are allowed to add their own observees, given the observee's 38 | credentials are provided. Administrators can add observees given credentials or 39 | the `UserObserveesController#update `_. 40 | 41 | :param request_ctx: The request context 42 | :type request_ctx: :class:RequestContext 43 | :param user_id: (required) ID 44 | :type user_id: string 45 | :param observee_unique_id: (optional) The login id for the user to observe. 46 | :type observee_unique_id: string or None 47 | :param observee_password: (optional) The password for the user to observe. 48 | :type observee_password: string or None 49 | :return: Add an observee with credentials 50 | :rtype: requests.Response (with User data) 51 | 52 | """ 53 | 54 | path = '/v1/users/{user_id}/observees' 55 | payload = { 56 | 'observee[unique_id]' : observee_unique_id, 57 | 'observee[password]' : observee_password, 58 | } 59 | url = request_ctx.base_api_url + path.format(user_id=user_id) 60 | response = client.post(request_ctx, url, payload=payload, **request_kwargs) 61 | 62 | return response 63 | 64 | 65 | def show_observee(request_ctx, user_id, observee_id, **request_kwargs): 66 | """ 67 | Gets information about an observed user. 68 | 69 | *Note:* all users are allowed to view their own observees. 70 | 71 | :param request_ctx: The request context 72 | :type request_ctx: :class:RequestContext 73 | :param user_id: (required) ID 74 | :type user_id: string 75 | :param observee_id: (required) ID 76 | :type observee_id: string 77 | :return: Show an observee 78 | :rtype: requests.Response (with User data) 79 | 80 | """ 81 | 82 | path = '/v1/users/{user_id}/observees/{observee_id}' 83 | url = request_ctx.base_api_url + path.format(user_id=user_id, observee_id=observee_id) 84 | response = client.get(request_ctx, url, **request_kwargs) 85 | 86 | return response 87 | 88 | 89 | def add_observee(request_ctx, user_id, observee_id, **request_kwargs): 90 | """ 91 | Registers a user as being observed by the given user. 92 | 93 | :param request_ctx: The request context 94 | :type request_ctx: :class:RequestContext 95 | :param user_id: (required) ID 96 | :type user_id: string 97 | :param observee_id: (required) ID 98 | :type observee_id: string 99 | :return: Add an observee 100 | :rtype: requests.Response (with User data) 101 | 102 | """ 103 | 104 | path = '/v1/users/{user_id}/observees/{observee_id}' 105 | url = request_ctx.base_api_url + path.format(user_id=user_id, observee_id=observee_id) 106 | response = client.put(request_ctx, url, **request_kwargs) 107 | 108 | return response 109 | 110 | 111 | def remove_observee(request_ctx, user_id, observee_id, **request_kwargs): 112 | """ 113 | Unregisters a user as being observed by the given user. 114 | 115 | :param request_ctx: The request context 116 | :type request_ctx: :class:RequestContext 117 | :param user_id: (required) ID 118 | :type user_id: string 119 | :param observee_id: (required) ID 120 | :type observee_id: string 121 | :return: Remove an observee 122 | :rtype: requests.Response (with User data) 123 | 124 | """ 125 | 126 | path = '/v1/users/{user_id}/observees/{observee_id}' 127 | url = request_ctx.base_api_url + path.format(user_id=user_id, observee_id=observee_id) 128 | response = client.delete(request_ctx, url, **request_kwargs) 129 | 130 | return response 131 | 132 | 133 | -------------------------------------------------------------------------------- /canvas_sdk/methods/logins.py: -------------------------------------------------------------------------------- 1 | from canvas_sdk import client, utils 2 | 3 | def list_user_logins_accounts(request_ctx, account_id, **request_kwargs): 4 | """ 5 | Given a user ID, return that user's logins for the given account. 6 | 7 | :param request_ctx: The request context 8 | :type request_ctx: :class:RequestContext 9 | :param account_id: (required) ID 10 | :type account_id: string 11 | :return: List user logins 12 | :rtype: requests.Response (with void data) 13 | 14 | """ 15 | 16 | path = '/v1/accounts/{account_id}/logins' 17 | url = request_ctx.base_api_url + path.format(account_id=account_id) 18 | response = client.get(request_ctx, url, **request_kwargs) 19 | 20 | return response 21 | 22 | 23 | def list_user_logins_users(request_ctx, user_id, **request_kwargs): 24 | """ 25 | Given a user ID, return that user's logins for the given account. 26 | 27 | :param request_ctx: The request context 28 | :type request_ctx: :class:RequestContext 29 | :param user_id: (required) ID 30 | :type user_id: string 31 | :return: List user logins 32 | :rtype: requests.Response (with void data) 33 | 34 | """ 35 | 36 | path = '/v1/users/{user_id}/logins' 37 | url = request_ctx.base_api_url + path.format(user_id=user_id) 38 | response = client.get(request_ctx, url, **request_kwargs) 39 | 40 | return response 41 | 42 | 43 | def create_user_login(request_ctx, account_id, user_id, login_unique_id, login_password=None, login_sis_user_id=None, **request_kwargs): 44 | """ 45 | Create a new login for an existing user in the given account. 46 | 47 | :param request_ctx: The request context 48 | :type request_ctx: :class:RequestContext 49 | :param account_id: (required) ID 50 | :type account_id: string 51 | :param user_id: (required) The ID of the user to create the login for. 52 | :type user_id: string 53 | :param login_unique_id: (required) The unique ID for the new login. 54 | :type login_unique_id: string 55 | :param login_password: (optional) The new login's password. 56 | :type login_password: string or None 57 | :param login_sis_user_id: (optional) SIS ID for the login. To set this parameter, the caller must be able to manage SIS permissions on the account. 58 | :type login_sis_user_id: string or None 59 | :return: Create a user login 60 | :rtype: requests.Response (with void data) 61 | 62 | """ 63 | 64 | path = '/v1/accounts/{account_id}/logins' 65 | payload = { 66 | 'user[id]' : user_id, 67 | 'login[unique_id]' : login_unique_id, 68 | 'login[password]' : login_password, 69 | 'login[sis_user_id]' : login_sis_user_id, 70 | } 71 | url = request_ctx.base_api_url + path.format(account_id=account_id) 72 | response = client.post(request_ctx, url, payload=payload, **request_kwargs) 73 | 74 | return response 75 | 76 | 77 | def edit_user_login(request_ctx, account_id, id, login_unique_id=None, login_password=None, login_sis_user_id=None, **request_kwargs): 78 | """ 79 | Update an existing login for a user in the given account. 80 | 81 | :param request_ctx: The request context 82 | :type request_ctx: :class:RequestContext 83 | :param account_id: (required) ID 84 | :type account_id: string 85 | :param id: (required) ID 86 | :type id: string 87 | :param login_unique_id: (optional) The new unique ID for the login. 88 | :type login_unique_id: string or None 89 | :param login_password: (optional) The new password for the login. Can only be set by an admin user if admins are allowed to change passwords for the account. 90 | :type login_password: string or None 91 | :param login_sis_user_id: (optional) SIS ID for the login. To set this parameter, the caller must be able to manage SIS permissions on the account. 92 | :type login_sis_user_id: string or None 93 | :return: Edit a user login 94 | :rtype: requests.Response (with void data) 95 | 96 | """ 97 | 98 | path = '/v1/accounts/{account_id}/logins/{id}' 99 | payload = { 100 | 'login[unique_id]' : login_unique_id, 101 | 'login[password]' : login_password, 102 | 'login[sis_user_id]' : login_sis_user_id, 103 | } 104 | url = request_ctx.base_api_url + path.format(account_id=account_id, id=id) 105 | response = client.put(request_ctx, url, payload=payload, **request_kwargs) 106 | 107 | return response 108 | 109 | 110 | def delete_user_login(request_ctx, user_id, id, **request_kwargs): 111 | """ 112 | Delete an existing login. 113 | 114 | :param request_ctx: The request context 115 | :type request_ctx: :class:RequestContext 116 | :param user_id: (required) ID 117 | :type user_id: string 118 | :param id: (required) ID 119 | :type id: string 120 | :return: Delete a user login 121 | :rtype: requests.Response (with void data) 122 | 123 | """ 124 | 125 | path = '/v1/users/{user_id}/logins/{id}' 126 | url = request_ctx.base_api_url + path.format(user_id=user_id, id=id) 127 | response = client.delete(request_ctx, url, **request_kwargs) 128 | 129 | return response 130 | 131 | 132 | -------------------------------------------------------------------------------- /canvas_sdk/client/request_context.py: -------------------------------------------------------------------------------- 1 | # request_context.py 2 | import requests 3 | from .auth import OAuth2Bearer 4 | from urllib.parse import urlparse 5 | 6 | 7 | class RequestContext(object): 8 | 9 | """ 10 | A class that holds the default configuration values used to make Canvas API requests. The requests 11 | library is heavily leveraged in terms of the attributes that may be passed in. Most of the attributes 12 | are used to construct a requests.Session instance that by default will be reused across requests 13 | made with a given :class:`RequestContext `. See below for a full list of parameters: 14 | 15 | :param str auth_token: OAuth2 token retrieved from a Canvas site 16 | :param str base_api_url: The api endpoint of the Canvas site in the form "http(s)://[canvas.site.com]/api" 17 | :param int per_page: (optional) For get requests that return a list of data, this will be used as the default per_page value 18 | :param int max_retries: (optional) Number of times a request that generates a certain class of HTTP exception will be retried 19 | before being raised back to the caller. See :py:mod:`client.base` for a list of those error types. 20 | :param dictionary headers: (optional) dictionary of headers to send for each request. Will be merged with a default set of headers. 21 | :param cookies: (optional) Cookies to attach to each requests. 22 | :type cookies: dictionary or CookieJar 23 | :param float timeout: (optional) The timeout of the request in seconds. 24 | :param dictionary proxies: (optional) Mapping protocol to the URL of the proxy. 25 | :param verify: (optional) if ``True``, the SSL cert will be verified. A CA_BUNDLE path can also be provided. 26 | :type verify: boolean or str 27 | :param cert: (optional) if String, path to ssl client cert file (.pem). If Tuple, ('cert', 'key') pair. 28 | :type cert: str or Tuple 29 | """ 30 | 31 | @classmethod 32 | def get_default_headers(cls): 33 | """ 34 | These are headers that are sent with every API request. Be careful of overriding these headers since the API 35 | calls may not work if these are modified. 36 | """ 37 | default_headers = { 38 | 'Accept': 'text/json' 39 | } 40 | return default_headers 41 | 42 | def __init__(self, auth_token, base_api_url, max_retries=0, per_page=None, headers=None, cookies=None, timeout=None, proxies=None, verify=True, cert=None): 43 | self._session = None 44 | self.auth_token = auth_token 45 | self.per_page = per_page 46 | parsed_url = urlparse(base_api_url) 47 | if 'http' not in parsed_url.scheme: 48 | raise AttributeError( 49 | "'%s' was provided, but base_url should be in the format http(s)://path/to/canvas/instance/api." % base_api_url) 50 | self.base_api_url = base_api_url 51 | self.headers = self.get_default_headers() 52 | # Allow overriding of default values with values passed in by user 53 | if headers: 54 | self.headers.update(headers) 55 | self.cookies = cookies 56 | self.timeout = timeout 57 | self.proxies = proxies 58 | self.verify = verify 59 | self.cert = cert 60 | self.max_retries = max_retries 61 | 62 | @property 63 | def auth(self): 64 | """ 65 | A custom OAuth2 authentication handler for the instance's auth_token 66 | """ 67 | return OAuth2Bearer(self.auth_token) 68 | 69 | @property 70 | def session(self): 71 | """ 72 | Get or set a requests.Session instance based on the session related values passed into the 73 | class. The setter should only be used if you need fine-grained control over your session. 74 | You can refer to the requests library documentation for information on available options for 75 | the session object: http://docs.python-requests.org/ 76 | NOTE: Refer to the setup.py file to match up the version of the Requests library the SDK uses 77 | with the right doc version. 78 | """ 79 | if not self._session: 80 | self._session = requests.Session() 81 | # Streaming is disabled by default when creating a requests.Session 82 | # object, but let's be explicit here to prevent connections from staying 83 | # open indefinitely 84 | self._session.stream = False 85 | self._session.auth = self.auth 86 | self._session.headers.update(self.headers or {}) 87 | self._session.timeout = self.timeout 88 | self._session.cert = self.cert 89 | self._session.verify = self.verify 90 | # We only need to set proxies and cookies if not None or empty since the 91 | # defaults are empty dicts 92 | if self.proxies: 93 | self._session.proxies = self.proxies 94 | if self.cookies: 95 | self._session.cookies = self.cookies 96 | return self._session 97 | 98 | @session.setter 99 | def session(self, sess): 100 | self._session = sess 101 | 102 | def expire_session(self): 103 | """ 104 | To expire a session, it just needs to be set to None according to requests doc. 105 | """ 106 | self.session = None 107 | -------------------------------------------------------------------------------- /canvas_sdk/methods/poll_choices.py: -------------------------------------------------------------------------------- 1 | from canvas_sdk import client, utils 2 | 3 | def list_poll_choices_in_poll(request_ctx, poll_id, **request_kwargs): 4 | """ 5 | Returns the list of PollChoices in this poll. 6 | 7 | :param request_ctx: The request context 8 | :type request_ctx: :class:RequestContext 9 | :param poll_id: (required) ID 10 | :type poll_id: string 11 | :return: List poll choices in a poll 12 | :rtype: requests.Response (with void data) 13 | 14 | """ 15 | 16 | path = '/v1/polls/{poll_id}/poll_choices' 17 | url = request_ctx.base_api_url + path.format(poll_id=poll_id) 18 | response = client.get(request_ctx, url, **request_kwargs) 19 | 20 | return response 21 | 22 | 23 | def get_single_poll_choice(request_ctx, poll_id, id, **request_kwargs): 24 | """ 25 | Returns the poll choice with the given id 26 | 27 | :param request_ctx: The request context 28 | :type request_ctx: :class:RequestContext 29 | :param poll_id: (required) ID 30 | :type poll_id: string 31 | :param id: (required) ID 32 | :type id: string 33 | :return: Get a single poll choice 34 | :rtype: requests.Response (with void data) 35 | 36 | """ 37 | 38 | path = '/v1/polls/{poll_id}/poll_choices/{id}' 39 | url = request_ctx.base_api_url + path.format(poll_id=poll_id, id=id) 40 | response = client.get(request_ctx, url, **request_kwargs) 41 | 42 | return response 43 | 44 | 45 | def create_single_poll_choice(request_ctx, poll_id, poll_choices_text, poll_choices_is_correct=None, poll_choices_position=None, **request_kwargs): 46 | """ 47 | Create a new poll choice for this poll 48 | 49 | :param request_ctx: The request context 50 | :type request_ctx: :class:RequestContext 51 | :param poll_id: (required) ID 52 | :type poll_id: string 53 | :param poll_choices_text: (required) The descriptive text of the poll choice. 54 | :type poll_choices_text: string 55 | :param poll_choices_is_correct: (optional) Whether this poll choice is considered correct or not. Defaults to false. 56 | :type poll_choices_is_correct: boolean or None 57 | :param poll_choices_position: (optional) The order this poll choice should be returned in the context it's sibling poll choices. 58 | :type poll_choices_position: integer or None 59 | :return: Create a single poll choice 60 | :rtype: requests.Response (with void data) 61 | 62 | """ 63 | 64 | path = '/v1/polls/{poll_id}/poll_choices' 65 | payload = { 66 | 'poll_choices[text]' : poll_choices_text, 67 | 'poll_choices[is_correct]' : poll_choices_is_correct, 68 | 'poll_choices[position]' : poll_choices_position, 69 | } 70 | url = request_ctx.base_api_url + path.format(poll_id=poll_id) 71 | response = client.post(request_ctx, url, payload=payload, **request_kwargs) 72 | 73 | return response 74 | 75 | 76 | def update_single_poll_choice(request_ctx, poll_id, id, poll_choices_text, poll_choices_is_correct=None, poll_choices_position=None, **request_kwargs): 77 | """ 78 | Update an existing poll choice for this poll 79 | 80 | :param request_ctx: The request context 81 | :type request_ctx: :class:RequestContext 82 | :param poll_id: (required) ID 83 | :type poll_id: string 84 | :param id: (required) ID 85 | :type id: string 86 | :param poll_choices_text: (required) The descriptive text of the poll choice. 87 | :type poll_choices_text: string 88 | :param poll_choices_is_correct: (optional) Whether this poll choice is considered correct or not. Defaults to false. 89 | :type poll_choices_is_correct: boolean or None 90 | :param poll_choices_position: (optional) The order this poll choice should be returned in the context it's sibling poll choices. 91 | :type poll_choices_position: integer or None 92 | :return: Update a single poll choice 93 | :rtype: requests.Response (with void data) 94 | 95 | """ 96 | 97 | path = '/v1/polls/{poll_id}/poll_choices/{id}' 98 | payload = { 99 | 'poll_choices[text]' : poll_choices_text, 100 | 'poll_choices[is_correct]' : poll_choices_is_correct, 101 | 'poll_choices[position]' : poll_choices_position, 102 | } 103 | url = request_ctx.base_api_url + path.format(poll_id=poll_id, id=id) 104 | response = client.put(request_ctx, url, payload=payload, **request_kwargs) 105 | 106 | return response 107 | 108 | 109 | def delete_poll_choice(request_ctx, poll_id, id, **request_kwargs): 110 | """ 111 | 204 No Content response code is returned if the deletion was successful. 112 | 113 | :param request_ctx: The request context 114 | :type request_ctx: :class:RequestContext 115 | :param poll_id: (required) ID 116 | :type poll_id: string 117 | :param id: (required) ID 118 | :type id: string 119 | :return: Delete a poll choice 120 | :rtype: requests.Response (with void data) 121 | 122 | """ 123 | 124 | path = '/v1/polls/{poll_id}/poll_choices/{id}' 125 | url = request_ctx.base_api_url + path.format(poll_id=poll_id, id=id) 126 | response = client.delete(request_ctx, url, **request_kwargs) 127 | 128 | return response 129 | 130 | 131 | -------------------------------------------------------------------------------- /canvas_sdk/methods/grade_change_log.py: -------------------------------------------------------------------------------- 1 | from canvas_sdk import client, utils 2 | 3 | def query_by_assignment(request_ctx, assignment_id, start_time=None, end_time=None, per_page=None, **request_kwargs): 4 | """ 5 | List grade change events for a given assignment. 6 | 7 | :param request_ctx: The request context 8 | :type request_ctx: :class:RequestContext 9 | :param assignment_id: (required) ID 10 | :type assignment_id: string 11 | :param start_time: (optional) The beginning of the time range from which you want events. 12 | :type start_time: datetime or None 13 | :param end_time: (optional) The end of the time range from which you want events. 14 | :type end_time: datetime or None 15 | :param per_page: (optional) Set how many results canvas should return, defaults to config.LIMIT_PER_PAGE 16 | :type per_page: integer or None 17 | :return: Query by assignment. 18 | :rtype: requests.Response (with array data) 19 | 20 | """ 21 | 22 | if per_page is None: 23 | per_page = request_ctx.per_page 24 | path = '/v1/audit/grade_change/assignments/{assignment_id}' 25 | payload = { 26 | 'start_time' : start_time, 27 | 'end_time' : end_time, 28 | 'per_page' : per_page, 29 | } 30 | url = request_ctx.base_api_url + path.format(assignment_id=assignment_id) 31 | response = client.get(request_ctx, url, payload=payload, **request_kwargs) 32 | 33 | return response 34 | 35 | 36 | def query_by_course(request_ctx, course_id, start_time=None, end_time=None, per_page=None, **request_kwargs): 37 | """ 38 | List grade change events for a given course. 39 | 40 | :param request_ctx: The request context 41 | :type request_ctx: :class:RequestContext 42 | :param course_id: (required) ID 43 | :type course_id: string 44 | :param start_time: (optional) The beginning of the time range from which you want events. 45 | :type start_time: datetime or None 46 | :param end_time: (optional) The end of the time range from which you want events. 47 | :type end_time: datetime or None 48 | :param per_page: (optional) Set how many results canvas should return, defaults to config.LIMIT_PER_PAGE 49 | :type per_page: integer or None 50 | :return: Query by course. 51 | :rtype: requests.Response (with array data) 52 | 53 | """ 54 | 55 | if per_page is None: 56 | per_page = request_ctx.per_page 57 | path = '/v1/audit/grade_change/courses/{course_id}' 58 | payload = { 59 | 'start_time' : start_time, 60 | 'end_time' : end_time, 61 | 'per_page' : per_page, 62 | } 63 | url = request_ctx.base_api_url + path.format(course_id=course_id) 64 | response = client.get(request_ctx, url, payload=payload, **request_kwargs) 65 | 66 | return response 67 | 68 | 69 | def query_by_student(request_ctx, student_id, start_time=None, end_time=None, per_page=None, **request_kwargs): 70 | """ 71 | List grade change events for a given student. 72 | 73 | :param request_ctx: The request context 74 | :type request_ctx: :class:RequestContext 75 | :param student_id: (required) ID 76 | :type student_id: string 77 | :param start_time: (optional) The beginning of the time range from which you want events. 78 | :type start_time: datetime or None 79 | :param end_time: (optional) The end of the time range from which you want events. 80 | :type end_time: datetime or None 81 | :param per_page: (optional) Set how many results canvas should return, defaults to config.LIMIT_PER_PAGE 82 | :type per_page: integer or None 83 | :return: Query by student. 84 | :rtype: requests.Response (with array data) 85 | 86 | """ 87 | 88 | if per_page is None: 89 | per_page = request_ctx.per_page 90 | path = '/v1/audit/grade_change/students/{student_id}' 91 | payload = { 92 | 'start_time' : start_time, 93 | 'end_time' : end_time, 94 | 'per_page' : per_page, 95 | } 96 | url = request_ctx.base_api_url + path.format(student_id=student_id) 97 | response = client.get(request_ctx, url, payload=payload, **request_kwargs) 98 | 99 | return response 100 | 101 | 102 | def query_by_grader(request_ctx, grader_id, start_time=None, end_time=None, per_page=None, **request_kwargs): 103 | """ 104 | List grade change events for a given grader. 105 | 106 | :param request_ctx: The request context 107 | :type request_ctx: :class:RequestContext 108 | :param grader_id: (required) ID 109 | :type grader_id: string 110 | :param start_time: (optional) The beginning of the time range from which you want events. 111 | :type start_time: datetime or None 112 | :param end_time: (optional) The end of the time range from which you want events. 113 | :type end_time: datetime or None 114 | :param per_page: (optional) Set how many results canvas should return, defaults to config.LIMIT_PER_PAGE 115 | :type per_page: integer or None 116 | :return: Query by grader. 117 | :rtype: requests.Response (with array data) 118 | 119 | """ 120 | 121 | if per_page is None: 122 | per_page = request_ctx.per_page 123 | path = '/v1/audit/grade_change/graders/{grader_id}' 124 | payload = { 125 | 'start_time' : start_time, 126 | 'end_time' : end_time, 127 | 'per_page' : per_page, 128 | } 129 | url = request_ctx.base_api_url + path.format(grader_id=grader_id) 130 | response = client.get(request_ctx, url, payload=payload, **request_kwargs) 131 | 132 | return response 133 | 134 | 135 | -------------------------------------------------------------------------------- /canvas_sdk/methods/search.py: -------------------------------------------------------------------------------- 1 | from canvas_sdk import client, utils 2 | 3 | def find_recipients_conversations(request_ctx, search, context, exclude, type, user_id, from_conversation_id, permissions, **request_kwargs): 4 | """ 5 | Find valid recipients (users, courses and groups) that the current user 6 | can send messages to. The /api/v1/search/recipients path is the preferred 7 | endpoint, /api/v1/conversations/find_recipients is deprecated. 8 | 9 | Pagination is supported. 10 | 11 | :param request_ctx: The request context 12 | :type request_ctx: :class:RequestContext 13 | :param search: (required) Search terms used for matching users/courses/groups (e.g. "bob smith"). If multiple terms are given (separated via whitespace), only results matching all terms will be returned. 14 | :type search: string 15 | :param context: (required) Limit the search to a particular course/group (e.g. "course_3" or "group_4"). 16 | :type context: string 17 | :param exclude: (required) Array of ids to exclude from the search. These may be user ids or course/group ids prefixed with "course_" or "group_" respectively, e.g. exclude[]=1&exclude[]=2&exclude[]=course_3 18 | :type exclude: string 19 | :param type: (required) Limit the search just to users or contexts (groups/courses). 20 | :type type: string 21 | :param user_id: (required) Search for a specific user id. This ignores the other above parameters, and will never return more than one result. 22 | :type user_id: integer 23 | :param from_conversation_id: (required) When searching by user_id, only users that could be normally messaged by this user will be returned. This parameter allows you to specify a conversation that will be referenced for a shared context -- if both the current user and the searched user are in the conversation, the user will be returned. This is used to start new side conversations. 24 | :type from_conversation_id: integer 25 | :param permissions: (required) Array of permission strings to be checked for each matched context (e.g. "send_messages"). This argument determines which permissions may be returned in the response; it won't prevent contexts from being returned if they don't grant the permission(s). 26 | :type permissions: string 27 | :return: Find recipients 28 | :rtype: requests.Response (with void data) 29 | 30 | """ 31 | 32 | type_types = ('user', 'context') 33 | utils.validate_attr_is_acceptable(type, type_types) 34 | path = '/v1/conversations/find_recipients' 35 | payload = { 36 | 'search' : search, 37 | 'context' : context, 38 | 'exclude' : exclude, 39 | 'type' : type, 40 | 'user_id' : user_id, 41 | 'from_conversation_id' : from_conversation_id, 42 | 'permissions' : permissions, 43 | } 44 | url = request_ctx.base_api_url + path.format() 45 | response = client.get(request_ctx, url, payload=payload, **request_kwargs) 46 | 47 | return response 48 | 49 | 50 | def find_recipients_search(request_ctx, search, context, exclude, type, user_id, from_conversation_id, permissions, **request_kwargs): 51 | """ 52 | Find valid recipients (users, courses and groups) that the current user 53 | can send messages to. The /api/v1/search/recipients path is the preferred 54 | endpoint, /api/v1/conversations/find_recipients is deprecated. 55 | 56 | Pagination is supported. 57 | 58 | :param request_ctx: The request context 59 | :type request_ctx: :class:RequestContext 60 | :param search: (required) Search terms used for matching users/courses/groups (e.g. "bob smith"). If multiple terms are given (separated via whitespace), only results matching all terms will be returned. 61 | :type search: string 62 | :param context: (required) Limit the search to a particular course/group (e.g. "course_3" or "group_4"). 63 | :type context: string 64 | :param exclude: (required) Array of ids to exclude from the search. These may be user ids or course/group ids prefixed with "course_" or "group_" respectively, e.g. exclude[]=1&exclude[]=2&exclude[]=course_3 65 | :type exclude: string 66 | :param type: (required) Limit the search just to users or contexts (groups/courses). 67 | :type type: string 68 | :param user_id: (required) Search for a specific user id. This ignores the other above parameters, and will never return more than one result. 69 | :type user_id: integer 70 | :param from_conversation_id: (required) When searching by user_id, only users that could be normally messaged by this user will be returned. This parameter allows you to specify a conversation that will be referenced for a shared context -- if both the current user and the searched user are in the conversation, the user will be returned. This is used to start new side conversations. 71 | :type from_conversation_id: integer 72 | :param permissions: (required) Array of permission strings to be checked for each matched context (e.g. "send_messages"). This argument determines which permissions may be returned in the response; it won't prevent contexts from being returned if they don't grant the permission(s). 73 | :type permissions: string 74 | :return: Find recipients 75 | :rtype: requests.Response (with void data) 76 | 77 | """ 78 | 79 | type_types = ('user', 'context') 80 | utils.validate_attr_is_acceptable(type, type_types) 81 | path = '/v1/search/recipients' 82 | payload = { 83 | 'search' : search, 84 | 'context' : context, 85 | 'exclude' : exclude, 86 | 'type' : type, 87 | 'user_id' : user_id, 88 | 'from_conversation_id' : from_conversation_id, 89 | 'permissions' : permissions, 90 | } 91 | url = request_ctx.base_api_url + path.format() 92 | response = client.get(request_ctx, url, payload=payload, **request_kwargs) 93 | 94 | return response 95 | 96 | 97 | -------------------------------------------------------------------------------- /canvas_sdk/methods/quiz_question_groups.py: -------------------------------------------------------------------------------- 1 | from canvas_sdk import client, utils 2 | 3 | def create_question_group(request_ctx, course_id, quiz_id, quiz_groups_name=None, quiz_groups_pick_count=None, quiz_groups_question_points=None, quiz_groups_assessment_question_bank_id=None, **request_kwargs): 4 | """ 5 | Create a new question group for this quiz 6 | 7 | 201 Created response code is returned if the creation was successful. 8 | 9 | :param request_ctx: The request context 10 | :type request_ctx: :class:RequestContext 11 | :param course_id: (required) ID 12 | :type course_id: string 13 | :param quiz_id: (required) ID 14 | :type quiz_id: string 15 | :param quiz_groups_name: (optional) The name of the question group. 16 | :type quiz_groups_name: string or None 17 | :param quiz_groups_pick_count: (optional) The number of questions to randomly select for this group. 18 | :type quiz_groups_pick_count: integer or None 19 | :param quiz_groups_question_points: (optional) The number of points to assign to each question in the group. 20 | :type quiz_groups_question_points: integer or None 21 | :param quiz_groups_assessment_question_bank_id: (optional) The id of the assessment question bank to pull questions from. 22 | :type quiz_groups_assessment_question_bank_id: integer or None 23 | :return: Create a question group 24 | :rtype: requests.Response (with void data) 25 | 26 | """ 27 | 28 | path = '/v1/courses/{course_id}/quizzes/{quiz_id}/groups' 29 | payload = { 30 | 'quiz_groups[name]' : quiz_groups_name, 31 | 'quiz_groups[pick_count]' : quiz_groups_pick_count, 32 | 'quiz_groups[question_points]' : quiz_groups_question_points, 33 | 'quiz_groups[assessment_question_bank_id]' : quiz_groups_assessment_question_bank_id, 34 | } 35 | url = request_ctx.base_api_url + path.format(course_id=course_id, quiz_id=quiz_id) 36 | response = client.post(request_ctx, url, payload=payload, **request_kwargs) 37 | 38 | return response 39 | 40 | 41 | def update_question_group(request_ctx, course_id, quiz_id, id, quiz_groups_name=None, quiz_groups_pick_count=None, quiz_groups_question_points=None, **request_kwargs): 42 | """ 43 | Update a question group 44 | 45 | :param request_ctx: The request context 46 | :type request_ctx: :class:RequestContext 47 | :param course_id: (required) ID 48 | :type course_id: string 49 | :param quiz_id: (required) ID 50 | :type quiz_id: string 51 | :param id: (required) ID 52 | :type id: string 53 | :param quiz_groups_name: (optional) The name of the question group. 54 | :type quiz_groups_name: string or None 55 | :param quiz_groups_pick_count: (optional) The number of questions to randomly select for this group. 56 | :type quiz_groups_pick_count: integer or None 57 | :param quiz_groups_question_points: (optional) The number of points to assign to each question in the group. 58 | :type quiz_groups_question_points: integer or None 59 | :return: Update a question group 60 | :rtype: requests.Response (with void data) 61 | 62 | """ 63 | 64 | path = '/v1/courses/{course_id}/quizzes/{quiz_id}/groups/{id}' 65 | payload = { 66 | 'quiz_groups[name]' : quiz_groups_name, 67 | 'quiz_groups[pick_count]' : quiz_groups_pick_count, 68 | 'quiz_groups[question_points]' : quiz_groups_question_points, 69 | } 70 | url = request_ctx.base_api_url + path.format(course_id=course_id, quiz_id=quiz_id, id=id) 71 | response = client.put(request_ctx, url, payload=payload, **request_kwargs) 72 | 73 | return response 74 | 75 | 76 | def delete_question_group(request_ctx, course_id, quiz_id, id, **request_kwargs): 77 | """ 78 | Delete a question group 79 | 80 | 204 No Content response code is returned if the deletion was successful. 81 | 82 | :param request_ctx: The request context 83 | :type request_ctx: :class:RequestContext 84 | :param course_id: (required) ID 85 | :type course_id: string 86 | :param quiz_id: (required) ID 87 | :type quiz_id: string 88 | :param id: (required) ID 89 | :type id: string 90 | :return: Delete a question group 91 | :rtype: requests.Response (with void data) 92 | 93 | """ 94 | 95 | path = '/v1/courses/{course_id}/quizzes/{quiz_id}/groups/{id}' 96 | url = request_ctx.base_api_url + path.format(course_id=course_id, quiz_id=quiz_id, id=id) 97 | response = client.delete(request_ctx, url, **request_kwargs) 98 | 99 | return response 100 | 101 | 102 | def reorder_question_groups(request_ctx, course_id, quiz_id, id, order_id, order_type, **request_kwargs): 103 | """ 104 | Change the order of the quiz questions within the group 105 | 106 | 204 No Content response code is returned if the reorder was successful. 107 | 108 | :param request_ctx: The request context 109 | :type request_ctx: :class:RequestContext 110 | :param course_id: (required) ID 111 | :type course_id: string 112 | :param quiz_id: (required) ID 113 | :type quiz_id: string 114 | :param id: (required) ID 115 | :type id: string 116 | :param order_id: (required) The associated item's unique identifier 117 | :type order_id: integer 118 | :param order_type: (required) The type of item is always 'question' for a group 119 | :type order_type: string 120 | :return: Reorder question groups 121 | :rtype: requests.Response (with void data) 122 | 123 | """ 124 | 125 | order_type_types = ('question') 126 | utils.validate_attr_is_acceptable(order_type, order_type_types) 127 | path = '/v1/courses/{course_id}/quizzes/{quiz_id}/groups/{id}/reorder' 128 | payload = { 129 | 'order[id]' : order_id, 130 | 'order[type]' : order_type, 131 | } 132 | url = request_ctx.base_api_url + path.format(course_id=course_id, quiz_id=quiz_id, id=id) 133 | response = client.post(request_ctx, url, payload=payload, **request_kwargs) 134 | 135 | return response 136 | 137 | 138 | -------------------------------------------------------------------------------- /canvas_sdk/methods/gradebook_history.py: -------------------------------------------------------------------------------- 1 | from canvas_sdk import client, utils 2 | 3 | def days_in_gradebook_history_for_this_course(request_ctx, course_id, per_page=None, **request_kwargs): 4 | """ 5 | Returns a map of dates to grader/assignment groups 6 | 7 | :param request_ctx: The request context 8 | :type request_ctx: :class:RequestContext 9 | :param course_id: (required) The id of the contextual course for this API call 10 | :type course_id: integer 11 | :param per_page: (optional) Set how many results canvas should return, defaults to config.LIMIT_PER_PAGE 12 | :type per_page: integer or None 13 | :return: Days in gradebook history for this course 14 | :rtype: requests.Response (with array data) 15 | 16 | """ 17 | 18 | if per_page is None: 19 | per_page = request_ctx.per_page 20 | path = '/v1/courses/{course_id}/gradebook_history/days' 21 | payload = { 22 | 'per_page' : per_page, 23 | } 24 | url = request_ctx.base_api_url + path.format(course_id=course_id) 25 | response = client.get(request_ctx, url, payload=payload, **request_kwargs) 26 | 27 | return response 28 | 29 | 30 | def details_for_given_date_in_gradebook_history_for_this_course(request_ctx, course_id, date, per_page=None, **request_kwargs): 31 | """ 32 | Returns the graders who worked on this day, along with the assignments they worked on. 33 | More details can be obtained by selecting a grader and assignment and calling the 34 | 'submissions' api endpoint for a given date. 35 | 36 | :param request_ctx: The request context 37 | :type request_ctx: :class:RequestContext 38 | :param course_id: (required) The id of the contextual course for this API call 39 | :type course_id: integer 40 | :param date: (required) The date for which you would like to see detailed information 41 | :type date: string 42 | :param per_page: (optional) Set how many results canvas should return, defaults to config.LIMIT_PER_PAGE 43 | :type per_page: integer or None 44 | :return: Details for a given date in gradebook history for this course 45 | :rtype: requests.Response (with array data) 46 | 47 | """ 48 | 49 | if per_page is None: 50 | per_page = request_ctx.per_page 51 | path = '/v1/courses/{course_id}/gradebook_history/{date}' 52 | payload = { 53 | 'per_page' : per_page, 54 | } 55 | url = request_ctx.base_api_url + path.format(course_id=course_id, date=date) 56 | response = client.get(request_ctx, url, payload=payload, **request_kwargs) 57 | 58 | return response 59 | 60 | 61 | def lists_submissions(request_ctx, course_id, date, grader_id, assignment_id, per_page=None, **request_kwargs): 62 | """ 63 | Gives a nested list of submission versions 64 | 65 | :param request_ctx: The request context 66 | :type request_ctx: :class:RequestContext 67 | :param course_id: (required) The id of the contextual course for this API call 68 | :type course_id: integer 69 | :param date: (required) The date for which you would like to see submissions 70 | :type date: string 71 | :param grader_id: (required) The ID of the grader for which you want to see submissions 72 | :type grader_id: integer 73 | :param assignment_id: (required) The ID of the assignment for which you want to see submissions 74 | :type assignment_id: integer 75 | :param per_page: (optional) Set how many results canvas should return, defaults to config.LIMIT_PER_PAGE 76 | :type per_page: integer or None 77 | :return: Lists submissions 78 | :rtype: requests.Response (with array data) 79 | 80 | """ 81 | 82 | if per_page is None: 83 | per_page = request_ctx.per_page 84 | path = '/v1/courses/{course_id}/gradebook_history/{date}/graders/{grader_id}/assignments/{assignment_id}/submissions' 85 | payload = { 86 | 'per_page' : per_page, 87 | } 88 | url = request_ctx.base_api_url + path.format(course_id=course_id, date=date, grader_id=grader_id, assignment_id=assignment_id) 89 | response = client.get(request_ctx, url, payload=payload, **request_kwargs) 90 | 91 | return response 92 | 93 | 94 | def list_uncollated_submission_versions(request_ctx, course_id, assignment_id=None, user_id=None, ascending=None, per_page=None, **request_kwargs): 95 | """ 96 | Gives a paginated, uncollated list of submission versions for all matching 97 | submissions in the context. This SubmissionVersion objects will not include 98 | the +new_grade+ or +previous_grade+ keys, only the +grade+; same for 99 | +graded_at+ and +grader+. 100 | 101 | :param request_ctx: The request context 102 | :type request_ctx: :class:RequestContext 103 | :param course_id: (required) The id of the contextual course for this API call 104 | :type course_id: integer 105 | :param assignment_id: (optional) The ID of the assignment for which you want to see submissions. If absent, versions of submissions from any assignment in the course are included. 106 | :type assignment_id: integer or None 107 | :param user_id: (optional) The ID of the user for which you want to see submissions. If absent, versions of submissions from any user in the course are included. 108 | :type user_id: integer or None 109 | :param ascending: (optional) Returns submission versions in ascending date order (oldest first). If absent, returns submission versions in descending date order (newest first). 110 | :type ascending: boolean or None 111 | :param per_page: (optional) Set how many results canvas should return, defaults to config.LIMIT_PER_PAGE 112 | :type per_page: integer or None 113 | :return: List uncollated submission versions 114 | :rtype: requests.Response (with array data) 115 | 116 | """ 117 | 118 | if per_page is None: 119 | per_page = request_ctx.per_page 120 | path = '/v1/courses/{course_id}/gradebook_history/feed' 121 | payload = { 122 | 'assignment_id' : assignment_id, 123 | 'user_id' : user_id, 124 | 'ascending' : ascending, 125 | 'per_page' : per_page, 126 | } 127 | url = request_ctx.base_api_url + path.format(course_id=course_id) 128 | response = client.get(request_ctx, url, payload=payload, **request_kwargs) 129 | 130 | return response 131 | 132 | 133 | -------------------------------------------------------------------------------- /canvas_sdk/methods/announcement_external_feeds.py: -------------------------------------------------------------------------------- 1 | from canvas_sdk import client, utils 2 | 3 | def list_external_feeds_courses(request_ctx, course_id, per_page=None, **request_kwargs): 4 | """ 5 | Returns the list of External Feeds this course or group. 6 | 7 | :param request_ctx: The request context 8 | :type request_ctx: :class:RequestContext 9 | :param course_id: (required) ID 10 | :type course_id: string 11 | :param per_page: (optional) Set how many results canvas should return, defaults to config.LIMIT_PER_PAGE 12 | :type per_page: integer or None 13 | :return: List external feeds 14 | :rtype: requests.Response (with array data) 15 | 16 | """ 17 | 18 | if per_page is None: 19 | per_page = request_ctx.per_page 20 | path = '/v1/courses/{course_id}/external_feeds' 21 | payload = { 22 | 'per_page' : per_page, 23 | } 24 | url = request_ctx.base_api_url + path.format(course_id=course_id) 25 | response = client.get(request_ctx, url, payload=payload, **request_kwargs) 26 | 27 | return response 28 | 29 | 30 | def list_external_feeds_groups(request_ctx, group_id, per_page=None, **request_kwargs): 31 | """ 32 | Returns the list of External Feeds this course or group. 33 | 34 | :param request_ctx: The request context 35 | :type request_ctx: :class:RequestContext 36 | :param group_id: (required) ID 37 | :type group_id: string 38 | :param per_page: (optional) Set how many results canvas should return, defaults to config.LIMIT_PER_PAGE 39 | :type per_page: integer or None 40 | :return: List external feeds 41 | :rtype: requests.Response (with array data) 42 | 43 | """ 44 | 45 | if per_page is None: 46 | per_page = request_ctx.per_page 47 | path = '/v1/groups/{group_id}/external_feeds' 48 | payload = { 49 | 'per_page' : per_page, 50 | } 51 | url = request_ctx.base_api_url + path.format(group_id=group_id) 52 | response = client.get(request_ctx, url, payload=payload, **request_kwargs) 53 | 54 | return response 55 | 56 | 57 | def create_external_feed_courses(request_ctx, course_id, url, verbosity, header_match=None, **request_kwargs): 58 | """ 59 | Create a new external feed for the course or group. 60 | 61 | :param request_ctx: The request context 62 | :type request_ctx: :class:RequestContext 63 | :param course_id: (required) ID 64 | :type course_id: string 65 | :param url: (required) The url to the external rss or atom feed 66 | :type url: string 67 | :param verbosity: (required) no description 68 | :type verbosity: string 69 | :param header_match: (optional) If given, only feed entries that contain this string in their title will be imported 70 | :type header_match: boolean or None 71 | :return: Create an external feed 72 | :rtype: requests.Response (with ExternalFeed data) 73 | 74 | """ 75 | 76 | verbosity_types = ('full', 'truncate', 'link_only') 77 | utils.validate_attr_is_acceptable(verbosity, verbosity_types) 78 | path = '/v1/courses/{course_id}/external_feeds' 79 | payload = { 80 | 'url' : url, 81 | 'header_match' : header_match, 82 | 'verbosity' : verbosity, 83 | } 84 | url = request_ctx.base_api_url + path.format(course_id=course_id) 85 | response = client.post(request_ctx, url, payload=payload, **request_kwargs) 86 | 87 | return response 88 | 89 | 90 | def create_external_feed_groups(request_ctx, group_id, url, verbosity, header_match=None, **request_kwargs): 91 | """ 92 | Create a new external feed for the course or group. 93 | 94 | :param request_ctx: The request context 95 | :type request_ctx: :class:RequestContext 96 | :param group_id: (required) ID 97 | :type group_id: string 98 | :param url: (required) The url to the external rss or atom feed 99 | :type url: string 100 | :param verbosity: (required) no description 101 | :type verbosity: string 102 | :param header_match: (optional) If given, only feed entries that contain this string in their title will be imported 103 | :type header_match: boolean or None 104 | :return: Create an external feed 105 | :rtype: requests.Response (with ExternalFeed data) 106 | 107 | """ 108 | 109 | verbosity_types = ('full', 'truncate', 'link_only') 110 | utils.validate_attr_is_acceptable(verbosity, verbosity_types) 111 | path = '/v1/groups/{group_id}/external_feeds' 112 | payload = { 113 | 'url' : url, 114 | 'header_match' : header_match, 115 | 'verbosity' : verbosity, 116 | } 117 | url = request_ctx.base_api_url + path.format(group_id=group_id) 118 | response = client.post(request_ctx, url, payload=payload, **request_kwargs) 119 | 120 | return response 121 | 122 | 123 | def delete_external_feed_courses(request_ctx, course_id, external_feed_id, **request_kwargs): 124 | """ 125 | Deletes the external feed. 126 | 127 | :param request_ctx: The request context 128 | :type request_ctx: :class:RequestContext 129 | :param course_id: (required) ID 130 | :type course_id: string 131 | :param external_feed_id: (required) ID 132 | :type external_feed_id: string 133 | :return: Delete an external feed 134 | :rtype: requests.Response (with ExternalFeed data) 135 | 136 | """ 137 | 138 | path = '/v1/courses/{course_id}/external_feeds/{external_feed_id}' 139 | url = request_ctx.base_api_url + path.format(course_id=course_id, external_feed_id=external_feed_id) 140 | response = client.delete(request_ctx, url, **request_kwargs) 141 | 142 | return response 143 | 144 | 145 | def delete_external_feed_groups(request_ctx, group_id, external_feed_id, **request_kwargs): 146 | """ 147 | Deletes the external feed. 148 | 149 | :param request_ctx: The request context 150 | :type request_ctx: :class:RequestContext 151 | :param group_id: (required) ID 152 | :type group_id: string 153 | :param external_feed_id: (required) ID 154 | :type external_feed_id: string 155 | :return: Delete an external feed 156 | :rtype: requests.Response (with ExternalFeed data) 157 | 158 | """ 159 | 160 | path = '/v1/groups/{group_id}/external_feeds/{external_feed_id}' 161 | url = request_ctx.base_api_url + path.format(group_id=group_id, external_feed_id=external_feed_id) 162 | response = client.delete(request_ctx, url, **request_kwargs) 163 | 164 | return response 165 | 166 | 167 | -------------------------------------------------------------------------------- /canvas_sdk/methods/assignment_groups.py: -------------------------------------------------------------------------------- 1 | from canvas_sdk import client, utils 2 | 3 | def list_assignment_groups(request_ctx, course_id, include, override_assignment_dates=None, per_page=None, **request_kwargs): 4 | """ 5 | Returns the list of assignment groups for the current context. The returned 6 | groups are sorted by their position field. 7 | 8 | :param request_ctx: The request context 9 | :type request_ctx: :class:RequestContext 10 | :param course_id: (required) ID 11 | :type course_id: string 12 | :param include: (required) Associations to include with the group. both "discussion_topic" and "all_dates" is only valid are only valid if "assignments" is also included. 13 | :type include: string 14 | :param override_assignment_dates: (optional) Apply assignment overrides for each assignment, defaults to true. 15 | :type override_assignment_dates: boolean or None 16 | :param per_page: (optional) Set how many results canvas should return, defaults to config.LIMIT_PER_PAGE 17 | :type per_page: integer or None 18 | :return: List assignment groups 19 | :rtype: requests.Response (with array data) 20 | 21 | """ 22 | 23 | if per_page is None: 24 | per_page = request_ctx.per_page 25 | include_types = ('assignments', 'discussion_topic', 'all_dates') 26 | utils.validate_attr_is_acceptable(include, include_types) 27 | path = '/v1/courses/{course_id}/assignment_groups' 28 | payload = { 29 | 'include' : include, 30 | 'override_assignment_dates' : override_assignment_dates, 31 | 'per_page' : per_page, 32 | } 33 | url = request_ctx.base_api_url + path.format(course_id=course_id) 34 | response = client.get(request_ctx, url, payload=payload, **request_kwargs) 35 | 36 | return response 37 | 38 | 39 | def get_assignment_group(request_ctx, course_id, assignment_group_id, include, override_assignment_dates=None, **request_kwargs): 40 | """ 41 | Returns the assignment group with the given id. 42 | 43 | :param request_ctx: The request context 44 | :type request_ctx: :class:RequestContext 45 | :param course_id: (required) ID 46 | :type course_id: string 47 | :param assignment_group_id: (required) ID 48 | :type assignment_group_id: string 49 | :param include: (required) Associations to include with the group. "discussion_topic" is only valid if "assignments" is also included. 50 | :type include: string 51 | :param override_assignment_dates: (optional) Apply assignment overrides for each assignment, defaults to true. 52 | :type override_assignment_dates: boolean or None 53 | :return: Get an Assignment Group 54 | :rtype: requests.Response (with AssignmentGroup data) 55 | 56 | """ 57 | 58 | include_types = ('assignments', 'discussion_topic') 59 | utils.validate_attr_is_acceptable(include, include_types) 60 | path = '/v1/courses/{course_id}/assignment_groups/{assignment_group_id}' 61 | payload = { 62 | 'include' : include, 63 | 'override_assignment_dates' : override_assignment_dates, 64 | } 65 | url = request_ctx.base_api_url + path.format(course_id=course_id, assignment_group_id=assignment_group_id) 66 | response = client.get(request_ctx, url, payload=payload, **request_kwargs) 67 | 68 | return response 69 | 70 | 71 | def create_assignment_group(request_ctx, course_id, name=None, position=None, group_weight=None, rules=None, **request_kwargs): 72 | """ 73 | Create a new assignment group for this course. 74 | 75 | :param request_ctx: The request context 76 | :type request_ctx: :class:RequestContext 77 | :param course_id: (required) ID 78 | :type course_id: string 79 | :param name: (optional) The assignment group's name 80 | :type name: string or None 81 | :param position: (optional) The position of this assignment group in relation to the other assignment groups 82 | :type position: integer or None 83 | :param group_weight: (optional) The percent of the total grade that this assignment group represents 84 | :type group_weight: float or None 85 | :param rules: (optional) The grading rules that are applied within this assignment group See the Assignment Group object definition for format 86 | :type rules: string or None 87 | :return: Create an Assignment Group 88 | :rtype: requests.Response (with AssignmentGroup data) 89 | 90 | """ 91 | 92 | path = '/v1/courses/{course_id}/assignment_groups' 93 | payload = { 94 | 'name' : name, 95 | 'position' : position, 96 | 'group_weight' : group_weight, 97 | 'rules' : rules, 98 | } 99 | url = request_ctx.base_api_url + path.format(course_id=course_id) 100 | response = client.post(request_ctx, url, payload=payload, **request_kwargs) 101 | 102 | return response 103 | 104 | 105 | def edit_assignment_group(request_ctx, course_id, assignment_group_id, **request_kwargs): 106 | """ 107 | Modify an existing Assignment Group. 108 | Accepts the same parameters as Assignment Group creation 109 | 110 | :param request_ctx: The request context 111 | :type request_ctx: :class:RequestContext 112 | :param course_id: (required) ID 113 | :type course_id: string 114 | :param assignment_group_id: (required) ID 115 | :type assignment_group_id: string 116 | :return: Edit an Assignment Group 117 | :rtype: requests.Response (with AssignmentGroup data) 118 | 119 | """ 120 | 121 | path = '/v1/courses/{course_id}/assignment_groups/{assignment_group_id}' 122 | url = request_ctx.base_api_url + path.format(course_id=course_id, assignment_group_id=assignment_group_id) 123 | response = client.put(request_ctx, url, **request_kwargs) 124 | 125 | return response 126 | 127 | 128 | def destroy_assignment_group(request_ctx, course_id, assignment_group_id, move_assignment_to, **request_kwargs): 129 | """ 130 | Deletes the assignment group with the given id. 131 | 132 | :param request_ctx: The request context 133 | :type request_ctx: :class:RequestContext 134 | :param course_id: (required) ID 135 | :type course_id: string 136 | :param assignment_group_id: (required) ID 137 | :type assignment_group_id: string 138 | :param move_assignment_to: (required) The ID of an active Assignment Group to which the assignments that are currently assigned to the destroyed Assignment Group will be assigned. NOTE: If this argument is not provided, any assignments in this Assignment Group will be deleted. 139 | :type move_assignment_to: string 140 | :return: Destroy an Assignment Group 141 | :rtype: requests.Response (with AssignmentGroup data) 142 | 143 | """ 144 | 145 | path = '/v1/courses/{course_id}/assignment_groups/{assignment_group_id}' 146 | payload = { 147 | 'move_assignment_to' : move_assignment_to, 148 | } 149 | url = request_ctx.base_api_url + path.format(course_id=course_id, assignment_group_id=assignment_group_id) 150 | response = client.delete(request_ctx, url, payload=payload, **request_kwargs) 151 | 152 | return response 153 | 154 | 155 | -------------------------------------------------------------------------------- /canvas_sdk/methods/sis_imports.py: -------------------------------------------------------------------------------- 1 | from canvas_sdk import client, utils 2 | 3 | def get_sis_import_list(request_ctx, account_id, per_page=None, **request_kwargs): 4 | """ 5 | Returns the list of SIS imports for an account 6 | 7 | Examples: 8 | curl 'https:///api/v1/accounts//sis_imports' \ 9 | -H "Authorization: Bearer " 10 | 11 | :param request_ctx: The request context 12 | :type request_ctx: :class:RequestContext 13 | :param account_id: (required) ID 14 | :type account_id: string 15 | :param per_page: (optional) Set how many results canvas should return, defaults to config.LIMIT_PER_PAGE 16 | :type per_page: integer or None 17 | :return: Get SIS import list 18 | :rtype: requests.Response (with array data) 19 | 20 | """ 21 | 22 | if per_page is None: 23 | per_page = request_ctx.per_page 24 | path = '/v1/accounts/{account_id}/sis_imports' 25 | payload = { 26 | 'per_page' : per_page, 27 | } 28 | url = request_ctx.base_api_url + path.format(account_id=account_id) 29 | response = client.get(request_ctx, url, payload=payload, **request_kwargs) 30 | 31 | return response 32 | 33 | 34 | def import_sis_data(request_ctx, account_id, attachment, import_type=None, extension=None, batch_mode=None, batch_mode_term_id=None, override_sis_stickiness=None, add_sis_stickiness=None, clear_sis_stickiness=None, **request_kwargs): 35 | """ 36 | Import SIS data into Canvas. Must be on a root account with SIS imports 37 | enabled. 38 | 39 | For more information on the format that's expected here, please see the 40 | "SIS CSV" section in the API docs. 41 | 42 | :param request_ctx: The request context 43 | :type request_ctx: :class:RequestContext 44 | :param account_id: (required) ID 45 | :type account_id: string 46 | :param attachment: (required) There are two ways to post SIS import data - either via a multipart/form-data form-field-style attachment, or via a non-multipart raw post request. 'attachment' is required for multipart/form-data style posts. Assumed to be SIS data from a file upload form field named 'attachment'. Examples: curl -F attachment=@ -H "Authorization: Bearer " \ 'https:///api/v1/accounts//sis_imports.json?import_type=instructure_csv' If you decide to do a raw post, you can skip the 'attachment' argument, but you will then be required to provide a suitable Content-Type header. You are encouraged to also provide the 'extension' argument. Examples: curl -H 'Content-Type: application/octet-stream' --data-binary @.zip \ -H "Authorization: Bearer " \ 'https:///api/v1/accounts//sis_imports.json?import_type=instructure_csv&extension=zip' curl -H 'Content-Type: application/zip' --data-binary @.zip \ -H "Authorization: Bearer " \ 'https:///api/v1/accounts//sis_imports.json?import_type=instructure_csv' curl -H 'Content-Type: text/csv' --data-binary @.csv \ -H "Authorization: Bearer " \ 'https:///api/v1/accounts//sis_imports.json?import_type=instructure_csv' curl -H 'Content-Type: text/csv' --data-binary @.csv \ -H "Authorization: Bearer " \ 'https:///api/v1/accounts//sis_imports.json?import_type=instructure_csv&batch_mode=1&batch_mode_term_id=15' 47 | :type attachment: string 48 | :param import_type: (optional) Choose the data format for reading SIS data. With a standard Canvas install, this option can only be 'instructure_csv', and if unprovided, will be assumed to be so. Can be part of the query string. 49 | :type import_type: string or None 50 | :param extension: (optional) Recommended for raw post request style imports. This field will be used to distinguish between zip, xml, csv, and other file format extensions that would usually be provided with the filename in the multipart post request scenario. If not provided, this value will be inferred from the Content-Type, falling back to zip-file format if all else fails. 51 | :type extension: string or None 52 | :param batch_mode: (optional) If set, this SIS import will be run in batch mode, deleting any data previously imported via SIS that is not present in this latest import. See the SIS CSV Format page for details. 53 | :type batch_mode: boolean or None 54 | :param batch_mode_term_id: (optional) Limit deletions to only this term. Required if batch mode is enabled. 55 | :type batch_mode_term_id: string or None 56 | :param override_sis_stickiness: (optional) Many fields on records in Canvas can be marked "sticky," which means that when something changes in the UI apart from the SIS, that field gets "stuck." In this way, by default, SIS imports do not override UI changes. If this field is present, however, it will tell the SIS import to ignore "stickiness" and override all fields. 57 | :type override_sis_stickiness: boolean or None 58 | :param add_sis_stickiness: (optional) This option, if present, will process all changes as if they were UI changes. This means that "stickiness" will be added to changed fields. This option is only processed if 'override_sis_stickiness' is also provided. 59 | :type add_sis_stickiness: boolean or None 60 | :param clear_sis_stickiness: (optional) This option, if present, will clear "stickiness" from all fields touched by this import. Requires that 'override_sis_stickiness' is also provided. If 'add_sis_stickiness' is also provided, 'clear_sis_stickiness' will overrule the behavior of 'add_sis_stickiness' 61 | :type clear_sis_stickiness: boolean or None 62 | :return: Import SIS data 63 | :rtype: requests.Response (with SisImport data) 64 | 65 | """ 66 | 67 | path = '/v1/accounts/{account_id}/sis_imports' 68 | payload = { 69 | 'import_type' : import_type, 70 | 'attachment' : attachment, 71 | 'extension' : extension, 72 | 'batch_mode' : batch_mode, 73 | 'batch_mode_term_id' : batch_mode_term_id, 74 | 'override_sis_stickiness' : override_sis_stickiness, 75 | 'add_sis_stickiness' : add_sis_stickiness, 76 | 'clear_sis_stickiness' : clear_sis_stickiness, 77 | } 78 | url = request_ctx.base_api_url + path.format(account_id=account_id) 79 | response = client.post(request_ctx, url, payload=payload, **request_kwargs) 80 | 81 | return response 82 | 83 | 84 | def get_sis_import_status(request_ctx, account_id, id, **request_kwargs): 85 | """ 86 | Get the status of an already created SIS import. 87 | 88 | Examples: 89 | curl 'https:///api/v1/accounts//sis_imports/' \ 90 | -H "Authorization: Bearer " 91 | 92 | :param request_ctx: The request context 93 | :type request_ctx: :class:RequestContext 94 | :param account_id: (required) ID 95 | :type account_id: string 96 | :param id: (required) ID 97 | :type id: string 98 | :return: Get SIS import status 99 | :rtype: requests.Response (with SisImport data) 100 | 101 | """ 102 | 103 | path = '/v1/accounts/{account_id}/sis_imports/{id}' 104 | url = request_ctx.base_api_url + path.format(account_id=account_id, id=id) 105 | response = client.get(request_ctx, url, **request_kwargs) 106 | 107 | return response 108 | 109 | 110 | -------------------------------------------------------------------------------- /canvas_sdk/methods/roles.py: -------------------------------------------------------------------------------- 1 | from canvas_sdk import client, utils 2 | 3 | def list_roles(request_ctx, account_id, state, per_page=None, **request_kwargs): 4 | """ 5 | List the roles available to an account. 6 | 7 | :param request_ctx: The request context 8 | :type request_ctx: :class:RequestContext 9 | :param account_id: (required) The id of the account to retrieve roles for. 10 | :type account_id: string 11 | :param state: (required) Filter by role state. If this argument is omitted, only 'active' roles are returned. 12 | :type state: string 13 | :param per_page: (optional) Set how many results canvas should return, defaults to config.LIMIT_PER_PAGE 14 | :type per_page: integer or None 15 | :return: List roles 16 | :rtype: requests.Response (with array data) 17 | 18 | """ 19 | 20 | if per_page is None: 21 | per_page = request_ctx.per_page 22 | state_types = ('active', 'inactive') 23 | utils.validate_attr_is_acceptable(state, state_types) 24 | path = '/v1/accounts/{account_id}/roles' 25 | payload = { 26 | 'state[]' : state, 27 | 'per_page' : per_page, 28 | } 29 | url = request_ctx.base_api_url + path.format(account_id=account_id) 30 | response = client.get(request_ctx, url, payload=payload, **request_kwargs) 31 | 32 | return response 33 | 34 | 35 | def get_single_role(request_ctx, account_id, role, **request_kwargs): 36 | """ 37 | Retrieve information about a single role 38 | 39 | :param request_ctx: The request context 40 | :type request_ctx: :class:RequestContext 41 | :param account_id: (required) The id of the account containing the role 42 | :type account_id: string 43 | :param role: (required) The name and unique identifier for the role 44 | :type role: string 45 | :return: Get a single role 46 | :rtype: requests.Response (with Role data) 47 | 48 | """ 49 | 50 | path = '/v1/accounts/{account_id}/roles/{role}' 51 | url = request_ctx.base_api_url + path.format(account_id=account_id, role=role) 52 | response = client.get(request_ctx, url, **request_kwargs) 53 | 54 | return response 55 | 56 | 57 | def create_new_role(request_ctx, account_id, role, base_role_type=None, permissions={}, **request_kwargs): 58 | """ 59 | Create a new course-level or account-level role. 60 | 61 | :param request_ctx: The request context 62 | :type request_ctx: :class:RequestContext 63 | :param account_id: (required) ID 64 | :type account_id: string 65 | :param role: (required) Label and unique identifier for the role. 66 | :type role: string 67 | :param base_role_type: (optional) Specifies the role type that will be used as a base for the permissions granted to this role. Defaults to 'AccountMembership' if absent 68 | :type base_role_type: string or None 69 | :param permissions: (optional) Specifies the permissions that will be granted to this role. See Canvas API docs for details on the structure. 70 | :type permissions: dict 71 | :return: Create a new role 72 | :rtype: requests.Response (with Role data) 73 | 74 | """ 75 | 76 | base_role_type_types = ('AccountMembership', 'StudentEnrollment', 'TeacherEnrollment', 'TaEnrollment', 'ObserverEnrollment', 'DesignerEnrollment') 77 | utils.validate_attr_is_acceptable(base_role_type, base_role_type_types) 78 | path = '/v1/accounts/{account_id}/roles' 79 | payload = { 80 | 'role' : role, 81 | 'base_role_type' : base_role_type, 82 | } 83 | # flatten the permissions dict 84 | for p in permissions: 85 | for a in permissions[p]: 86 | payload['permissions[{}][{}]'.format(p, a)] = permissions[p][a] 87 | 88 | url = request_ctx.base_api_url + path.format(account_id=account_id) 89 | response = client.post(request_ctx, url, payload=payload, **request_kwargs) 90 | 91 | return response 92 | 93 | 94 | def deactivate_role(request_ctx, account_id, role, **request_kwargs): 95 | """ 96 | Deactivates a custom role. This hides it in the user interface and prevents it 97 | from being assigned to new users. Existing users assigned to the role will 98 | continue to function with the same permissions they had previously. 99 | Built-in roles cannot be deactivated. 100 | 101 | :param request_ctx: The request context 102 | :type request_ctx: :class:RequestContext 103 | :param account_id: (required) ID 104 | :type account_id: string 105 | :param role: (required) Label and unique identifier for the role. 106 | :type role: string 107 | :return: Deactivate a role 108 | :rtype: requests.Response (with Role data) 109 | 110 | """ 111 | 112 | path = '/v1/accounts/{account_id}/roles/{role}' 113 | url = request_ctx.base_api_url + path.format(account_id=account_id, role=role) 114 | response = client.delete(request_ctx, url, **request_kwargs) 115 | 116 | return response 117 | 118 | 119 | def activate_role(request_ctx, account_id, role, **request_kwargs): 120 | """ 121 | Re-activates an inactive role (allowing it to be assigned to new users) 122 | 123 | :param request_ctx: The request context 124 | :type request_ctx: :class:RequestContext 125 | :param account_id: (required) ID 126 | :type account_id: string 127 | :param role: (required) Label and unique identifier for the role. 128 | :type role: string 129 | :return: Activate a role 130 | :rtype: requests.Response (with Role data) 131 | 132 | """ 133 | 134 | path = '/v1/accounts/{account_id}/roles/{role}/activate' 135 | url = request_ctx.base_api_url + path.format(account_id=account_id, role=role) 136 | response = client.post(request_ctx, url, **request_kwargs) 137 | 138 | return response 139 | 140 | 141 | def update_role(request_ctx, account_id, role, label=None, permissions={}, **request_kwargs): 142 | """ 143 | Update permissions for an existing role. 144 | 145 | Recognized roles are: 146 | * TeacherEnrollment 147 | * StudentEnrollment 148 | * TaEnrollment 149 | * ObserverEnrollment 150 | * DesignerEnrollment 151 | * AccountAdmin 152 | * Any previously created custom role 153 | 154 | :param request_ctx: The request context 155 | :type request_ctx: :class:RequestContext 156 | :param account_id: (required) ID 157 | :type account_id: string 158 | :param role: (required) ID 159 | :type role: string 160 | :param label: The label for the role. Can only change the label of a custom role that belongs directly to the account. 161 | :type label: string 162 | :param permissions: (optional) Specifies the permissions that will be granted to this role. See Canvas API docs for details on the structure. 163 | :type permissions: dict 164 | :return: Update a role 165 | :rtype: requests.Response (with Role data) 166 | 167 | """ 168 | 169 | path = '/v1/accounts/{account_id}/roles/{role}' 170 | payload = {} 171 | if label and label != '': 172 | payload['label'] = label 173 | # flatten the permissions dict 174 | for p in permissions: 175 | for a in permissions[p]: 176 | payload['permissions[{}][{}]'.format(p, a)] = permissions[p][a] 177 | 178 | url = request_ctx.base_api_url + path.format(account_id=account_id, role=role) 179 | response = client.put(request_ctx, url, payload=payload, **request_kwargs) 180 | 181 | return response 182 | -------------------------------------------------------------------------------- /canvas_sdk/methods/quiz_submission_questions.py: -------------------------------------------------------------------------------- 1 | from canvas_sdk import client, utils 2 | 3 | def get_all_quiz_submission_questions(request_ctx, quiz_submission_id, include, **request_kwargs): 4 | """ 5 | Get a list of all the question records for this quiz submission. 6 | 7 | 200 OK response code is returned if the request was successful. 8 | 9 | :param request_ctx: The request context 10 | :type request_ctx: :class:RequestContext 11 | :param quiz_submission_id: (required) ID 12 | :type quiz_submission_id: string 13 | :param include: (required) Associations to include with the quiz submission question. 14 | :type include: string 15 | :return: Get all quiz submission questions. 16 | :rtype: requests.Response (with void data) 17 | 18 | """ 19 | 20 | include_types = ('quiz_question') 21 | utils.validate_attr_is_acceptable(include, include_types) 22 | path = '/v1/quiz_submissions/{quiz_submission_id}/questions' 23 | payload = { 24 | 'include' : include, 25 | } 26 | url = request_ctx.base_api_url + path.format(quiz_submission_id=quiz_submission_id) 27 | response = client.get(request_ctx, url, payload=payload, **request_kwargs) 28 | 29 | return response 30 | 31 | 32 | def get_single_quiz_submission_question(request_ctx, quiz_submission_id, id, include, **request_kwargs): 33 | """ 34 | Get a single question record. 35 | 36 | 200 OK response code is returned if the request was successful. 37 | 38 | :param request_ctx: The request context 39 | :type request_ctx: :class:RequestContext 40 | :param quiz_submission_id: (required) ID 41 | :type quiz_submission_id: string 42 | :param id: (required) ID 43 | :type id: string 44 | :param include: (required) Associations to include with the quiz submission question. 45 | :type include: string 46 | :return: Get a single quiz submission question. 47 | :rtype: requests.Response (with void data) 48 | 49 | """ 50 | 51 | include_types = ('quiz_question') 52 | utils.validate_attr_is_acceptable(include, include_types) 53 | path = '/v1/quiz_submissions/{quiz_submission_id}/questions/{id}' 54 | payload = { 55 | 'include' : include, 56 | } 57 | url = request_ctx.base_api_url + path.format(quiz_submission_id=quiz_submission_id, id=id) 58 | response = client.get(request_ctx, url, payload=payload, **request_kwargs) 59 | 60 | return response 61 | 62 | 63 | def answering_question(request_ctx, quiz_submission_id, id, attempt, validation_token, access_code=None, answer=None, **request_kwargs): 64 | """ 65 | Provide or modify an answer to a QuizQuestion. 66 | 67 | :param request_ctx: The request context 68 | :type request_ctx: :class:RequestContext 69 | :param quiz_submission_id: (required) ID 70 | :type quiz_submission_id: string 71 | :param id: (required) ID 72 | :type id: string 73 | :param attempt: (required) The attempt number of the quiz submission being taken. Note that this must be the latest attempt index, as questions for earlier attempts can not be modified. 74 | :type attempt: integer 75 | :param validation_token: (required) The unique validation token you received when the Quiz Submission was created. 76 | :type validation_token: string 77 | :param access_code: (optional) Access code for the Quiz, if any. 78 | :type access_code: string or None 79 | :param answer: (optional) The answer to the question. The type and format of this argument depend on the question type. See {Appendix: Question Answer Formats} for the accepted answer formats for each question type. 80 | :type answer: mixed or None 81 | :return: Answering a question. 82 | :rtype: requests.Response (with void data) 83 | 84 | """ 85 | 86 | path = '/v1/quiz_submissions/{quiz_submission_id}/questions/{id}' 87 | payload = { 88 | 'attempt' : attempt, 89 | 'validation_token' : validation_token, 90 | 'access_code' : access_code, 91 | 'answer' : answer, 92 | } 93 | url = request_ctx.base_api_url + path.format(quiz_submission_id=quiz_submission_id, id=id) 94 | response = client.put(request_ctx, url, payload=payload, **request_kwargs) 95 | 96 | return response 97 | 98 | 99 | def flagging_question(request_ctx, quiz_submission_id, id, attempt, validation_token, access_code=None, **request_kwargs): 100 | """ 101 | Set a flag on a quiz question to indicate that you want to return to it 102 | later. 103 | 104 | :param request_ctx: The request context 105 | :type request_ctx: :class:RequestContext 106 | :param quiz_submission_id: (required) ID 107 | :type quiz_submission_id: string 108 | :param id: (required) ID 109 | :type id: string 110 | :param attempt: (required) The attempt number of the quiz submission being taken. Note that this must be the latest attempt index, as questions for earlier attempts can not be modified. 111 | :type attempt: integer 112 | :param validation_token: (required) The unique validation token you received when the Quiz Submission was created. 113 | :type validation_token: string 114 | :param access_code: (optional) Access code for the Quiz, if any. 115 | :type access_code: string or None 116 | :return: Flagging a question. 117 | :rtype: requests.Response (with void data) 118 | 119 | """ 120 | 121 | path = '/v1/quiz_submissions/{quiz_submission_id}/questions/{id}/flag' 122 | payload = { 123 | 'attempt' : attempt, 124 | 'validation_token' : validation_token, 125 | 'access_code' : access_code, 126 | } 127 | url = request_ctx.base_api_url + path.format(quiz_submission_id=quiz_submission_id, id=id) 128 | response = client.put(request_ctx, url, payload=payload, **request_kwargs) 129 | 130 | return response 131 | 132 | 133 | def unflagging_question(request_ctx, quiz_submission_id, id, attempt, validation_token, access_code=None, **request_kwargs): 134 | """ 135 | Remove the flag that you previously set on a quiz question after you've 136 | returned to it. 137 | 138 | :param request_ctx: The request context 139 | :type request_ctx: :class:RequestContext 140 | :param quiz_submission_id: (required) ID 141 | :type quiz_submission_id: string 142 | :param id: (required) ID 143 | :type id: string 144 | :param attempt: (required) The attempt number of the quiz submission being taken. Note that this must be the latest attempt index, as questions for earlier attempts can not be modified. 145 | :type attempt: integer 146 | :param validation_token: (required) The unique validation token you received when the Quiz Submission was created. 147 | :type validation_token: string 148 | :param access_code: (optional) Access code for the Quiz, if any. 149 | :type access_code: string or None 150 | :return: Unflagging a question. 151 | :rtype: requests.Response (with void data) 152 | 153 | """ 154 | 155 | path = '/v1/quiz_submissions/{quiz_submission_id}/questions/{id}/unflag' 156 | payload = { 157 | 'attempt' : attempt, 158 | 'validation_token' : validation_token, 159 | 'access_code' : access_code, 160 | } 161 | url = request_ctx.base_api_url + path.format(quiz_submission_id=quiz_submission_id, id=id) 162 | response = client.put(request_ctx, url, payload=payload, **request_kwargs) 163 | 164 | return response 165 | 166 | 167 | -------------------------------------------------------------------------------- /canvas_sdk/methods/custom_gradebook_columns.py: -------------------------------------------------------------------------------- 1 | from canvas_sdk import client, utils 2 | 3 | def list_custom_gradebook_columns(request_ctx, course_id, per_page=None, **request_kwargs): 4 | """ 5 | List all custom gradebook columns for a course 6 | 7 | :param request_ctx: The request context 8 | :type request_ctx: :class:RequestContext 9 | :param course_id: (required) ID 10 | :type course_id: string 11 | :param per_page: (optional) Set how many results canvas should return, defaults to config.LIMIT_PER_PAGE 12 | :type per_page: integer or None 13 | :return: List custom gradebook columns 14 | :rtype: requests.Response (with array data) 15 | 16 | """ 17 | 18 | if per_page is None: 19 | per_page = request_ctx.per_page 20 | path = '/v1/courses/{course_id}/custom_gradebook_columns' 21 | payload = { 22 | 'per_page' : per_page, 23 | } 24 | url = request_ctx.base_api_url + path.format(course_id=course_id) 25 | response = client.get(request_ctx, url, payload=payload, **request_kwargs) 26 | 27 | return response 28 | 29 | 30 | def create_custom_gradebook_column(request_ctx, course_id, column_title, column_position, column_hidden=None, column_teacher_notes=None, **request_kwargs): 31 | """ 32 | Create a custom gradebook column 33 | 34 | :param request_ctx: The request context 35 | :type request_ctx: :class:RequestContext 36 | :param course_id: (required) ID 37 | :type course_id: string 38 | :param column_title: (required) no description 39 | :type column_title: string 40 | :param column_position: (required) The position of the column relative to other custom columns 41 | :type column_position: int 42 | :param column_hidden: (optional) Hidden columns are not displayed in the gradebook 43 | :type column_hidden: boolean or None 44 | :param column_teacher_notes: (optional) Set this if the column is created by a teacher. The gradebook only supports one teacher_notes column. 45 | :type column_teacher_notes: boolean or None 46 | :return: Create a custom gradebook column 47 | :rtype: requests.Response (with CustomColumn data) 48 | 49 | """ 50 | 51 | path = '/v1/courses/{course_id}/custom_gradebook_columns' 52 | payload = { 53 | 'column[title]' : column_title, 54 | 'column[position]' : column_position, 55 | 'column[hidden]' : column_hidden, 56 | 'column[teacher_notes]' : column_teacher_notes, 57 | } 58 | url = request_ctx.base_api_url + path.format(course_id=course_id) 59 | response = client.post(request_ctx, url, payload=payload, **request_kwargs) 60 | 61 | return response 62 | 63 | 64 | def update_custom_gradebook_column(request_ctx, course_id, id, **request_kwargs): 65 | """ 66 | Accepts the same parameters as custom gradebook column creation 67 | 68 | :param request_ctx: The request context 69 | :type request_ctx: :class:RequestContext 70 | :param course_id: (required) ID 71 | :type course_id: string 72 | :param id: (required) ID 73 | :type id: string 74 | :return: Update a custom gradebook column 75 | :rtype: requests.Response (with CustomColumn data) 76 | 77 | """ 78 | 79 | path = '/v1/courses/{course_id}/custom_gradebook_columns/{id}' 80 | url = request_ctx.base_api_url + path.format(course_id=course_id, id=id) 81 | response = client.put(request_ctx, url, **request_kwargs) 82 | 83 | return response 84 | 85 | 86 | def delete_custom_gradebook_column(request_ctx, course_id, id, **request_kwargs): 87 | """ 88 | Permanently deletes a custom column and its associated data 89 | 90 | :param request_ctx: The request context 91 | :type request_ctx: :class:RequestContext 92 | :param course_id: (required) ID 93 | :type course_id: string 94 | :param id: (required) ID 95 | :type id: string 96 | :return: Delete a custom gradebook column 97 | :rtype: requests.Response (with CustomColumn data) 98 | 99 | """ 100 | 101 | path = '/v1/courses/{course_id}/custom_gradebook_columns/{id}' 102 | url = request_ctx.base_api_url + path.format(course_id=course_id, id=id) 103 | response = client.delete(request_ctx, url, **request_kwargs) 104 | 105 | return response 106 | 107 | 108 | def reorder_custom_columns(request_ctx, course_id, order, **request_kwargs): 109 | """ 110 | Puts the given columns in the specified order 111 | 112 | 200 OK is returned if successful 113 | 114 | :param request_ctx: The request context 115 | :type request_ctx: :class:RequestContext 116 | :param course_id: (required) ID 117 | :type course_id: string 118 | :param order: (required) no description 119 | :type order: integer 120 | :return: Reorder custom columns 121 | :rtype: requests.Response (with void data) 122 | 123 | """ 124 | 125 | path = '/v1/courses/{course_id}/custom_gradebook_columns/reorder' 126 | payload = { 127 | 'order' : order, 128 | } 129 | url = request_ctx.base_api_url + path.format(course_id=course_id) 130 | response = client.post(request_ctx, url, payload=payload, **request_kwargs) 131 | 132 | return response 133 | 134 | 135 | def list_entries_for_column(request_ctx, course_id, id, per_page=None, **request_kwargs): 136 | """ 137 | This does not list entries for students without associated data. 138 | 139 | :param request_ctx: The request context 140 | :type request_ctx: :class:RequestContext 141 | :param course_id: (required) ID 142 | :type course_id: string 143 | :param id: (required) ID 144 | :type id: string 145 | :param per_page: (optional) Set how many results canvas should return, defaults to config.LIMIT_PER_PAGE 146 | :type per_page: integer or None 147 | :return: List entries for a column 148 | :rtype: requests.Response (with array data) 149 | 150 | """ 151 | 152 | if per_page is None: 153 | per_page = request_ctx.per_page 154 | path = '/v1/courses/{course_id}/custom_gradebook_columns/{id}/data' 155 | payload = { 156 | 'per_page' : per_page, 157 | } 158 | url = request_ctx.base_api_url + path.format(course_id=course_id, id=id) 159 | response = client.get(request_ctx, url, payload=payload, **request_kwargs) 160 | 161 | return response 162 | 163 | 164 | def update_column_data(request_ctx, course_id, id, user_id, column_data_content, **request_kwargs): 165 | """ 166 | Set the content of a custom column 167 | 168 | :param request_ctx: The request context 169 | :type request_ctx: :class:RequestContext 170 | :param course_id: (required) ID 171 | :type course_id: string 172 | :param id: (required) ID 173 | :type id: string 174 | :param user_id: (required) ID 175 | :type user_id: string 176 | :param column_data_content: (required) Column content. Setting this to blank will delete the datum object. 177 | :type column_data_content: string 178 | :return: Update column data 179 | :rtype: requests.Response (with ColumnDatum data) 180 | 181 | """ 182 | 183 | path = '/v1/courses/{course_id}/custom_gradebook_columns/{id}/data/{user_id}' 184 | payload = { 185 | 'column_data[content]' : column_data_content, 186 | } 187 | url = request_ctx.base_api_url + path.format(course_id=course_id, id=id, user_id=user_id) 188 | response = client.put(request_ctx, url, payload=payload, **request_kwargs) 189 | 190 | return response 191 | 192 | 193 | -------------------------------------------------------------------------------- /canvas_sdk/client/base.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | import requests 4 | from requests.exceptions import HTTPError 5 | import time 6 | 7 | from .auth import OAuth2Bearer 8 | from canvas_sdk.exceptions import (CanvasAPIError, InvalidOAuthTokenError) 9 | 10 | log = logging.getLogger(__name__) 11 | 12 | RETRY_ERROR_CODES = ( 13 | requests.codes['conflict'], # 409 14 | requests.codes['internal_server_error'], # 500 15 | requests.codes['bad_gateway'], # 502 16 | requests.codes['service_unavailable'], # 503 17 | requests.codes['gateway_timeout'] # 504 18 | ) 19 | 20 | 21 | def merge_or_create_key_value_for_dictionary(target, key, value=None): 22 | """ 23 | This helper method will attempt to update a given key on a target dictionary 24 | with a value. If the value is empty, nothing is done. If the key does not 25 | currently exist on the target, the key-value pair is created; otherwise, the 26 | given value is merged into the current key's value in the target (it's 27 | assumed an existing key is a dictionary). 28 | 29 | :param dictionary target: The dictionary to merge into 30 | :param str key: The key to merge or create on the target 31 | :param value: The value to merge into the dictionary key 32 | :type value: Dictionary or None 33 | """ 34 | if value: 35 | if key in target: 36 | target.get(key).update(value) 37 | else: 38 | target.update({key: value}) 39 | 40 | 41 | def get(request_context, url, payload=None, **optional_request_params): 42 | """ 43 | Shortcut for making a GET call to the API. Data is passed as url params. 44 | """ 45 | merge_or_create_key_value_for_dictionary(optional_request_params, 'params', payload) 46 | return call("GET", url, request_context, **optional_request_params) 47 | 48 | 49 | def put(request_context, url, payload=None, **optional_request_params): 50 | """ 51 | Shortcut for making a PUT call to the API 52 | """ 53 | merge_or_create_key_value_for_dictionary(optional_request_params, 'data', payload) 54 | return call("PUT", url, request_context, **optional_request_params) 55 | 56 | 57 | def post(request_context, url, payload=None, **optional_request_params): 58 | """ 59 | Shortcut for making a POST call to the API 60 | """ 61 | merge_or_create_key_value_for_dictionary(optional_request_params, 'data', payload) 62 | return call("POST", url, request_context, **optional_request_params) 63 | 64 | 65 | def delete(request_context, url, payload=None, **optional_request_params): 66 | """ 67 | Shortcut for making a DELETE call to the API 68 | """ 69 | merge_or_create_key_value_for_dictionary(optional_request_params, 'data', payload) 70 | return call("DELETE", url, request_context, **optional_request_params) 71 | 72 | 73 | def call(action, url, request_context, params=None, data=None, max_retries=None, 74 | auth_token=None, files=None, headers=None, cookies=None, timeout=None, 75 | proxies=None, verify=None, cert=None, allow_redirects=True): 76 | """This method servers as a pass-through to the requests library request 77 | functionality, but provides some configurable default 78 | values. Constructs and sends a :class:`requests.Request `. 79 | Returns :class:`requests.Response ` object. 80 | 81 | :param action: method for the new :class:`Request` object. 82 | :param url: Absolute url path to API method 83 | :param request_context: Instance of :class:`RequestContext` that holds a 84 | request session and other default values 85 | :param params: (optional) Dictionary or bytes to be sent in the query string 86 | for the :class:`Request`. 87 | :param data: (optional) Dictionary, bytes, or file-like object to send in 88 | the body of the :class:`Request`. 89 | :param int max_retries: (optional) Number of times a request that generates 90 | a certain class of HTTP exception will be retried before being raised 91 | back to the caller. See :py:mod:`client.base` for a list of those error 92 | types. 93 | :param files: (optional) Dictionary of 'name': file-like-objects (or 94 | {'name': ('filename', fileobj)}) for multipart encoding upload. 95 | :param dictionary headers: (optional) dictionary of headers to send for each 96 | request. Will be merged with a default set of headers. 97 | :param cookies: (optional) Cookies to attach to each requests. 98 | :type cookies: dictionary or CookieJar 99 | :param str auth_token: (optional) OAuth2 token retrieved from a Canvas site 100 | :param float timeout: (optional) The timeout of the request in seconds. 101 | :param dictionary proxies: (optional) Mapping protocol to the URL of the 102 | proxy. 103 | :param verify: (optional) if ``True``, the SSL cert will be verified. A 104 | CA_BUNDLE path can also be provided. 105 | :type verify: boolean or str 106 | :param cert: (optional) if String, path to ssl client cert file (.pem). If 107 | Tuple, ('cert', 'key') pair. 108 | :type cert: str or Tuple 109 | :param bool allow_redirects: (optional) Set to True if POST/PUT/DELETE 110 | redirect following is allowed. Defaults to True. 111 | """ 112 | # This will be a requests.Session object with defaults set for context 113 | canvas_session = request_context.session 114 | # Default back to value in request_context 115 | retries = max_retries or request_context.max_retries 116 | if retries is None: 117 | retries = 0 # Fall back if max_retries in context is explicitly None 118 | # Set up an authentication callable using OAuth2Bearer if we have a token 119 | auth = None 120 | if auth_token: 121 | auth = OAuth2Bearer(auth_token) 122 | # try the request until max_retries is reached. we need to account for the 123 | # fact that the first iteration through isn't a retry, so add 1 to max_retries 124 | for retry in range(retries + 1): 125 | st = time.time() 126 | try: 127 | # build and send the request 128 | response = canvas_session.request( 129 | action, url, params=params, data=data, headers=headers, 130 | cookies=cookies, files=files, auth=auth, timeout=timeout, 131 | proxies=proxies, verify=verify, cert=cert, 132 | allow_redirects=allow_redirects) 133 | 134 | # raise an http exception if one occured 135 | response.raise_for_status() 136 | 137 | except HTTPError as http_error: 138 | log.info("Caught an API Error returned by Canvas: %s", str(http_error)) 139 | # Need to check its an error code that can be retried 140 | status_code = response.status_code 141 | 142 | # Check to see if this is an invalid token error per 143 | # https://canvas.instructure.com/doc/api/file.oauth.html 144 | if status_code == 401 and 'WWW-Authenticate' in response.headers: 145 | raise InvalidOAuthTokenError( 146 | "OAuth Token used to make request to %s is invalid" % response.url) 147 | 148 | # If we can't retry the request, raise a CanvasAPIError 149 | if status_code not in RETRY_ERROR_CODES or retry >= retries: 150 | try: 151 | error_json = response.json() 152 | message = str(error_json) 153 | except ValueError: # no json object could be decoded, e.g. 404 154 | error_json = None 155 | message = response.text.strip() 156 | raise CanvasAPIError( 157 | status_code=status_code, 158 | msg=message, 159 | error_json=error_json, 160 | ) 161 | else: 162 | log.debug('API_CALL_DURATION {} {}'.format(url, time.time()-st)) 163 | return response 164 | -------------------------------------------------------------------------------- /canvas_sdk/methods/poll_sessions.py: -------------------------------------------------------------------------------- 1 | from canvas_sdk import client, utils 2 | 3 | def list_poll_sessions_for_poll(request_ctx, poll_id, **request_kwargs): 4 | """ 5 | Returns the list of PollSessions in this poll. 6 | 7 | :param request_ctx: The request context 8 | :type request_ctx: :class:RequestContext 9 | :param poll_id: (required) ID 10 | :type poll_id: string 11 | :return: List poll sessions for a poll 12 | :rtype: requests.Response (with void data) 13 | 14 | """ 15 | 16 | path = '/v1/polls/{poll_id}/poll_sessions' 17 | url = request_ctx.base_api_url + path.format(poll_id=poll_id) 18 | response = client.get(request_ctx, url, **request_kwargs) 19 | 20 | return response 21 | 22 | 23 | def get_results_for_single_poll_session(request_ctx, poll_id, id, **request_kwargs): 24 | """ 25 | Returns the poll session with the given id 26 | 27 | :param request_ctx: The request context 28 | :type request_ctx: :class:RequestContext 29 | :param poll_id: (required) ID 30 | :type poll_id: string 31 | :param id: (required) ID 32 | :type id: string 33 | :return: Get the results for a single poll session 34 | :rtype: requests.Response (with void data) 35 | 36 | """ 37 | 38 | path = '/v1/polls/{poll_id}/poll_sessions/{id}' 39 | url = request_ctx.base_api_url + path.format(poll_id=poll_id, id=id) 40 | response = client.get(request_ctx, url, **request_kwargs) 41 | 42 | return response 43 | 44 | 45 | def create_single_poll_session(request_ctx, poll_id, poll_sessions_course_id, poll_sessions_course_section_id=None, poll_sessions_has_public_results=None, **request_kwargs): 46 | """ 47 | Create a new poll session for this poll 48 | 49 | :param request_ctx: The request context 50 | :type request_ctx: :class:RequestContext 51 | :param poll_id: (required) ID 52 | :type poll_id: string 53 | :param poll_sessions_course_id: (required) The id of the course this session is associated with. 54 | :type poll_sessions_course_id: integer 55 | :param poll_sessions_course_section_id: (optional) The id of the course section this session is associated with. 56 | :type poll_sessions_course_section_id: integer or None 57 | :param poll_sessions_has_public_results: (optional) Whether or not results are viewable by students. 58 | :type poll_sessions_has_public_results: boolean or None 59 | :return: Create a single poll session 60 | :rtype: requests.Response (with void data) 61 | 62 | """ 63 | 64 | path = '/v1/polls/{poll_id}/poll_sessions' 65 | payload = { 66 | 'poll_sessions[course_id]' : poll_sessions_course_id, 67 | 'poll_sessions[course_section_id]' : poll_sessions_course_section_id, 68 | 'poll_sessions[has_public_results]' : poll_sessions_has_public_results, 69 | } 70 | url = request_ctx.base_api_url + path.format(poll_id=poll_id) 71 | response = client.post(request_ctx, url, payload=payload, **request_kwargs) 72 | 73 | return response 74 | 75 | 76 | def update_single_poll_session(request_ctx, poll_id, id, poll_sessions_course_id, poll_sessions_course_section_id, poll_sessions_has_public_results=None, **request_kwargs): 77 | """ 78 | Update an existing poll session for this poll 79 | 80 | :param request_ctx: The request context 81 | :type request_ctx: :class:RequestContext 82 | :param poll_id: (required) ID 83 | :type poll_id: string 84 | :param id: (required) ID 85 | :type id: string 86 | :param poll_sessions_course_id: (required) The id of the course this session is associated with. 87 | :type poll_sessions_course_id: integer 88 | :param poll_sessions_course_section_id: (required) The id of the course section this session is associated with. 89 | :type poll_sessions_course_section_id: integer 90 | :param poll_sessions_has_public_results: (optional) Whether or not results are viewable by students. 91 | :type poll_sessions_has_public_results: boolean or None 92 | :return: Update a single poll session 93 | :rtype: requests.Response (with void data) 94 | 95 | """ 96 | 97 | path = '/v1/polls/{poll_id}/poll_sessions/{id}' 98 | payload = { 99 | 'poll_sessions[course_id]' : poll_sessions_course_id, 100 | 'poll_sessions[course_section_id]' : poll_sessions_course_section_id, 101 | 'poll_sessions[has_public_results]' : poll_sessions_has_public_results, 102 | } 103 | url = request_ctx.base_api_url + path.format(poll_id=poll_id, id=id) 104 | response = client.put(request_ctx, url, payload=payload, **request_kwargs) 105 | 106 | return response 107 | 108 | 109 | def delete_poll_session(request_ctx, poll_id, id, **request_kwargs): 110 | """ 111 | 204 No Content response code is returned if the deletion was successful. 112 | 113 | :param request_ctx: The request context 114 | :type request_ctx: :class:RequestContext 115 | :param poll_id: (required) ID 116 | :type poll_id: string 117 | :param id: (required) ID 118 | :type id: string 119 | :return: Delete a poll session 120 | :rtype: requests.Response (with void data) 121 | 122 | """ 123 | 124 | path = '/v1/polls/{poll_id}/poll_sessions/{id}' 125 | url = request_ctx.base_api_url + path.format(poll_id=poll_id, id=id) 126 | response = client.delete(request_ctx, url, **request_kwargs) 127 | 128 | return response 129 | 130 | 131 | def open_poll_session(request_ctx, poll_id, id, **request_kwargs): 132 | """ 133 | 134 | :param request_ctx: The request context 135 | :type request_ctx: :class:RequestContext 136 | :param poll_id: (required) ID 137 | :type poll_id: string 138 | :param id: (required) ID 139 | :type id: string 140 | :return: Open a poll session 141 | :rtype: requests.Response (with void data) 142 | 143 | """ 144 | 145 | path = '/v1/polls/{poll_id}/poll_sessions/{id}/open' 146 | url = request_ctx.base_api_url + path.format(poll_id=poll_id, id=id) 147 | response = client.get(request_ctx, url, **request_kwargs) 148 | 149 | return response 150 | 151 | 152 | def close_opened_poll_session(request_ctx, poll_id, id, **request_kwargs): 153 | """ 154 | 155 | :param request_ctx: The request context 156 | :type request_ctx: :class:RequestContext 157 | :param poll_id: (required) ID 158 | :type poll_id: string 159 | :param id: (required) ID 160 | :type id: string 161 | :return: Close an opened poll session 162 | :rtype: requests.Response (with void data) 163 | 164 | """ 165 | 166 | path = '/v1/polls/{poll_id}/poll_sessions/{id}/close' 167 | url = request_ctx.base_api_url + path.format(poll_id=poll_id, id=id) 168 | response = client.get(request_ctx, url, **request_kwargs) 169 | 170 | return response 171 | 172 | 173 | def list_opened_poll_sessions(request_ctx, **request_kwargs): 174 | """ 175 | Lists all opened poll sessions available to the current user. 176 | 177 | :param request_ctx: The request context 178 | :type request_ctx: :class:RequestContext 179 | :return: List opened poll sessions 180 | :rtype: requests.Response (with void data) 181 | 182 | """ 183 | 184 | path = '/v1/poll_sessions/opened' 185 | url = request_ctx.base_api_url + path.format() 186 | response = client.get(request_ctx, url, **request_kwargs) 187 | 188 | return response 189 | 190 | 191 | def list_closed_poll_sessions(request_ctx, **request_kwargs): 192 | """ 193 | Lists all closed poll sessions available to the current user. 194 | 195 | :param request_ctx: The request context 196 | :type request_ctx: :class:RequestContext 197 | :return: List closed poll sessions 198 | :rtype: requests.Response (with void data) 199 | 200 | """ 201 | 202 | path = '/v1/poll_sessions/closed' 203 | url = request_ctx.base_api_url + path.format() 204 | response = client.get(request_ctx, url, **request_kwargs) 205 | 206 | return response 207 | 208 | 209 | --------------------------------------------------------------------------------