API wrapper for the Canvas LMS
—
Quality
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Quiz creation, question management, submissions, and both Classic Quizzes and New Quizzes support. Comprehensive assessment tools for measuring student learning and progress.
Create and manage quizzes with comprehensive configuration options.
class Course(CanvasObject):
def get_quizzes(self, **kwargs) -> PaginatedList[Quiz]:
"""
List quizzes in the course.
Parameters:
- search_term: Search term to filter quizzes
- include: Additional data ('assignment', 'overrides', 'all_dates', 'quiz_reports', 'submission_questions', 'html_url', 'mobile_url')
Returns:
Paginated list of Quiz objects
"""
def create_quiz(self, quiz: dict, **kwargs) -> Quiz:
"""
Create a new quiz.
Parameters:
- quiz: Dictionary with quiz attributes:
- title: Quiz title (required)
- description: Quiz description (HTML)
- quiz_type: Quiz type ('practice_quiz', 'assignment', 'graded_survey', 'survey')
- assignment_group_id: Assignment group ID
- time_limit: Time limit in minutes
- shuffle_answers: Shuffle answer choices
- hide_results: Hide results ('always', 'until_after_last_attempt', None)
- show_correct_answers: Show correct answers after submission
- show_correct_answers_last_attempt: Show correct answers only after last attempt
- show_correct_answers_at: Date to show correct answers
- hide_correct_answers_at: Date to hide correct answers
- allowed_attempts: Number of allowed attempts (-1 for unlimited)
- scoring_policy: Scoring policy ('keep_highest', 'keep_latest')
- one_question_at_a_time: Show one question at a time
- cant_go_back: Prevent going back to previous questions
- access_code: Access code required to take quiz
- ip_filter: IP address filter
- due_at: Due date
- lock_at: Lock date
- unlock_at: Unlock date
- published: Whether quiz is published
- one_time_results: Show results only once
- only_visible_to_overrides: Only visible to override recipients
Returns:
Quiz object
"""
def get_quiz(self, quiz, **kwargs) -> Quiz:
"""
Get a single quiz by ID.
Parameters:
- quiz: Quiz object or quiz ID
- include: Additional data to include
Returns:
Quiz object
"""
class Quiz(CanvasObject):
def edit(self, **kwargs) -> Quiz:
"""
Edit quiz properties.
Parameters:
- quiz: Dictionary with quiz updates (same structure as create_quiz)
Returns:
Updated Quiz object
"""
def delete(self, **kwargs) -> Quiz:
"""Delete the quiz."""Create and manage quiz questions with various question types.
def get_questions(self, **kwargs) -> PaginatedList[QuizQuestion]:
"""
List questions for the quiz.
Parameters:
- quiz_submission_id: Filter by specific submission
- quiz_submission_attempt: Filter by submission attempt
Returns:
Paginated list of QuizQuestion objects
"""
def create_question(self, question: dict, **kwargs) -> QuizQuestion:
"""
Create a quiz question.
Parameters:
- question: Dictionary with question attributes:
- question_name: Question name
- question_text: Question text (HTML)
- question_type: Question type ('multiple_choice_question', 'true_false_question',
'short_answer_question', 'fill_in_multiple_blanks_question',
'multiple_answers_question', 'multiple_dropdowns_question',
'matching_question', 'numerical_question', 'calculated_question',
'essay_question', 'uploaded_data_question', 'file_upload_question',
'text_only_question')
- points_possible: Points for the question
- correct_comments: Comments for correct answers
- incorrect_comments: Comments for incorrect answers
- neutral_comments: General comments
- text_after_answers: Text to display after answers
- answers: List of answer choices (structure varies by question type)
- variables: Variables for calculated questions
- formulas: Formulas for calculated questions
- answer_tolerance: Tolerance for numerical questions
- formula_decimal_places: Decimal places for calculated questions
- matches: Matches for matching questions
- matching_answer_incorrect_matches: Incorrect matches for matching questions
Returns:
QuizQuestion object
"""
def get_question(self, question_id, **kwargs) -> QuizQuestion:
"""Get a single quiz question by ID."""
class QuizQuestion(CanvasObject):
def edit(self, **kwargs) -> QuizQuestion:
"""
Edit quiz question.
Parameters:
- question: Dictionary with question updates
Returns:
Updated QuizQuestion object
"""
def delete(self, **kwargs) -> QuizQuestion:
"""Delete the quiz question."""Handle student quiz attempts and submissions.
def get_submissions(self, **kwargs) -> PaginatedList[QuizSubmission]:
"""
Get quiz submissions.
Parameters:
- include: Additional data ('submission', 'quiz', 'user')
Returns:
Paginated list of QuizSubmission objects
"""
def get_submission(self, quiz_submission_id, **kwargs) -> QuizSubmission:
"""
Get a single quiz submission.
Parameters:
- quiz_submission_id: Quiz submission ID
- include: Additional data to include
Returns:
QuizSubmission object
"""
def create_submission(self, **kwargs) -> QuizSubmission:
"""
Start a quiz attempt.
Parameters:
- access_code: Access code if required
- preview: Whether this is a preview
Returns:
QuizSubmission object
"""
class QuizSubmission(CanvasObject):
def complete(self, **kwargs) -> QuizSubmission:
"""
Complete/submit the quiz attempt.
Parameters:
- attempt: Attempt number
- validation_token: Validation token
- access_code: Access code if required
Returns:
Completed QuizSubmission object
"""
def get_questions(self, **kwargs) -> PaginatedList[QuizSubmissionQuestion]:
"""
Get questions for this submission.
Parameters:
- include: Additional data ('quiz_question')
Returns:
Paginated list of QuizSubmissionQuestion objects
"""
def answer_submission_questions(self, **kwargs) -> QuizSubmissionQuestion:
"""
Answer quiz questions.
Parameters:
- attempt: Attempt number
- validation_token: Validation token
- access_code: Access code if required
- quiz_questions: List of question answers:
- id: Question ID
- answer: Answer value (format depends on question type)
Returns:
QuizSubmissionQuestion object
"""
def get_times(self, **kwargs) -> dict:
"""Get timing data for the quiz submission."""
def update_score_and_comments(self, **kwargs) -> QuizSubmission:
"""
Update submission score and comments (for manually graded questions).
Parameters:
- quiz_submissions: List of submission updates:
- attempt: Attempt number
- fudge_points: Fudge points to add/subtract
- questions: Dictionary of question updates with comments and scores
Returns:
Updated QuizSubmission object
"""Access quiz performance data and analytics.
def get_statistics(self, **kwargs) -> QuizStatistic:
"""
Get quiz statistics.
Parameters:
- all_versions: Include all quiz versions
Returns:
QuizStatistic object with performance data
"""
def get_reports(self, **kwargs) -> PaginatedList[QuizReport]:
"""
Get available quiz reports.
Returns:
Paginated list of QuizReport objects
"""
def create_report(self, report_type: str, **kwargs) -> QuizReport:
"""
Create a quiz report.
Parameters:
- report_type: Type of report ('student_analysis', 'item_analysis')
- quiz_report: Dictionary with report settings:
- includes_all_versions: Include all quiz versions
Returns:
QuizReport object
"""
class QuizReport(CanvasObject):
def get_file(self, **kwargs) -> File:
"""Get the report file once generation is complete."""Support for Canvas New Quizzes (Quizzes.Next) platform.
def create_new_quiz(self, **kwargs) -> NewQuiz:
"""
Create a new quiz using New Quizzes platform.
Parameters:
- title: Quiz title
- instructions: Quiz instructions
- quiz_type: Quiz type
- allowed_attempts: Number of allowed attempts
- time_limit: Time limit in minutes
- due_at: Due date
- points_possible: Total points
Returns:
NewQuiz object
"""
class NewQuiz(CanvasObject):
"""
Represents a New Quiz (Quizzes.Next) assessment.
Limited API functionality compared to Classic Quizzes.
"""
def edit(self, **kwargs) -> NewQuiz:
"""Edit new quiz properties."""
def delete(self, **kwargs) -> NewQuiz:
"""Delete the new quiz."""Organize questions into groups and banks for reuse.
def get_groups(self, **kwargs) -> PaginatedList[QuizGroup]:
"""
Get question groups for the quiz.
Returns:
Paginated list of QuizGroup objects
"""
def create_group(self, quiz_groups: list, **kwargs) -> QuizGroup:
"""
Create a question group.
Parameters:
- quiz_groups: List of group dictionaries:
- name: Group name
- pick_count: Number of questions to pick from group
- question_points: Points per question
- assessment_question_bank_id: Question bank to draw from
Returns:
QuizGroup object
"""
class QuizGroup(CanvasObject):
def edit(self, **kwargs) -> QuizGroup:
"""Edit question group properties."""
def delete(self, **kwargs) -> QuizGroup:
"""Delete the question group."""from canvasapi import Canvas
canvas = Canvas("https://canvas.example.com", "your-token")
course = canvas.get_course(12345)
# Create a quiz with comprehensive settings
quiz = course.create_quiz({
'title': 'Midterm Examination',
'description': '<p>This exam covers chapters 1-5. You have 90 minutes to complete it.</p>',
'quiz_type': 'assignment',
'assignment_group_id': exam_group.id,
'time_limit': 90,
'allowed_attempts': 1,
'scoring_policy': 'keep_highest',
'shuffle_answers': True,
'show_correct_answers': False, # Don't show answers immediately
'show_correct_answers_at': '2024-12-01T09:00:00Z', # Show after exam period
'one_question_at_a_time': True,
'cant_go_back': True, # Prevent students from going back
'due_at': '2024-11-15T23:59:59Z',
'unlock_at': '2024-11-15T08:00:00Z',
'lock_at': '2024-11-15T23:59:59Z',
'points_possible': 100,
'published': False # Keep unpublished until ready
})
print(f"Created quiz: {quiz.title} (ID: {quiz.id})")# Multiple choice question
mc_question = quiz.create_question({
'question_name': 'Python Data Types',
'question_text': 'Which of the following is a mutable data type in Python?',
'question_type': 'multiple_choice_question',
'points_possible': 5,
'answers': [
{
'answer_text': 'String',
'answer_weight': 0,
'answer_comments': 'Strings are immutable in Python.'
},
{
'answer_text': 'Tuple',
'answer_weight': 0,
'answer_comments': 'Tuples are immutable in Python.'
},
{
'answer_text': 'List',
'answer_weight': 100, # Correct answer
'answer_comments': 'Correct! Lists are mutable in Python.'
},
{
'answer_text': 'Integer',
'answer_weight': 0,
'answer_comments': 'Integers are immutable in Python.'
}
],
'correct_comments': 'Well done! Lists can be modified after creation.',
'incorrect_comments': 'Review the differences between mutable and immutable data types.'
})
# True/False question
tf_question = quiz.create_question({
'question_name': 'Python Syntax',
'question_text': 'Python uses curly braces {} to define code blocks.',
'question_type': 'true_false_question',
'points_possible': 3,
'answers': [
{
'answer_text': 'True',
'answer_weight': 0,
'answer_comments': 'Python uses indentation, not curly braces.'
},
{
'answer_text': 'False',
'answer_weight': 100,
'answer_comments': 'Correct! Python uses indentation to define code blocks.'
}
]
})
# Short answer question
sa_question = quiz.create_question({
'question_name': 'Function Definition',
'question_text': 'What keyword is used to define a function in Python?',
'question_type': 'short_answer_question',
'points_possible': 2,
'answers': [
{
'answer_text': 'def',
'answer_weight': 100
},
{
'answer_text': 'define',
'answer_weight': 0
}
]
})
# Essay question
essay_question = quiz.create_question({
'question_name': 'Algorithm Analysis',
'question_text': 'Explain the difference between O(n) and O(log n) time complexity. Provide an example of each.',
'question_type': 'essay_question',
'points_possible': 15,
'correct_comments': 'Review your answer for completeness and accuracy.'
})
# Numerical question
num_question = quiz.create_question({
'question_name': 'Calculation',
'question_text': 'What is the result of 2^10?',
'question_type': 'numerical_question',
'points_possible': 3,
'answers': [
{
'answer_exact': 1024,
'answer_error_margin': 0,
'answer_weight': 100
}
],
'answer_tolerance': 0
})# Get all submissions for grading
submissions = quiz.get_submissions(include=['submission', 'user'])
print(f"Found {len(submissions)} submissions")
for submission in submissions:
user = submission.user
print(f"Student: {user['name']}")
print(f"Score: {submission.score}/{quiz.points_possible}")
print(f"Attempt: {submission.attempt}")
print(f"Time taken: {submission.time_spent} seconds")
print(f"Submitted: {submission.submitted_at}")
# Get submission questions for detailed analysis
questions = submission.get_questions(include=['quiz_question'])
for question in questions:
print(f" Q: {question.question_text[:50]}...")
print(f" Answer: {question.answer}")
print(f" Correct: {question.correct}")
print(f" Points: {question.points_awarded}/{question.points_possible}")
# Grade essay questions manually
for submission in submissions:
if submission.workflow_state == 'pending_review':
# Update scores for manually graded questions
submission.update_score_and_comments(
quiz_submissions=[{
'attempt': submission.attempt,
'fudge_points': 2, # Add 2 bonus points
'questions': {
essay_question.id: {
'score': 12, # Out of 15 points
'comment': 'Good explanation of time complexity. Could provide more detailed examples.'
}
}
}]
)# Create a question group that randomly selects from a question bank
question_group = quiz.create_group([{
'name': 'Random Multiple Choice',
'pick_count': 5, # Pick 5 questions randomly
'question_points': 4, # Each question worth 4 points
'assessment_question_bank_id': question_bank.id
}])
# Create a group with specific questions
specific_group = quiz.create_group([{
'name': 'Required Questions',
'pick_count': 3,
'question_points': 5
}])
# Add specific questions to the group
important_questions = [mc_question.id, tf_question.id, sa_question.id]
for question_id in important_questions:
# Move existing questions to the group
question = quiz.get_question(question_id)
question.edit(quiz_group_id=specific_group.id)# Get quiz statistics
stats = quiz.get_statistics(all_versions=True)
print(f"Quiz Statistics:")
print(f"Submissions: {stats.submission_statistics['scores']['count']}")
print(f"Average Score: {stats.submission_statistics['scores']['mean']:.2f}")
print(f"High Score: {stats.submission_statistics['scores']['max']}")
print(f"Low Score: {stats.submission_statistics['scores']['min']}")
# Analyze question performance
for question_stat in stats.question_statistics:
question_id = question_stat['id']
correct_percent = question_stat['responses'] / question_stat['answered_student_count'] * 100 if question_stat['answered_student_count'] > 0 else 0
print(f"Question {question_id}:")
print(f" Answered by: {question_stat['answered_student_count']} students")
print(f" Average time: {question_stat['time_spent']} seconds")
print(f" Difficulty: {question_stat['difficulty_index']:.2f}")
# Generate detailed reports
student_report = quiz.create_report(
report_type='student_analysis',
quiz_report={'includes_all_versions': True}
)
item_report = quiz.create_report(
report_type='item_analysis',
quiz_report={'includes_all_versions': True}
)
# Check report status and download when ready
import time
while student_report.workflow_state == 'generatiing':
time.sleep(10)
student_report = quiz.get_report(student_report.id)
if student_report.workflow_state == 'complete':
report_file = student_report.get_file()
print(f"Student analysis report: {report_file.url}")# Create a New Quiz (Quizzes.Next)
new_quiz = course.create_new_quiz(
title='Interactive Assessment',
instructions='Complete all sections of this assessment.',
quiz_type='graded',
allowed_attempts=2,
time_limit=60,
due_at='2024-12-01T23:59:59Z',
points_possible=50
)
print(f"Created New Quiz: {new_quiz.title}")
print(f"Edit URL: {new_quiz.html_url}")
# Note: New Quizzes have limited API functionality
# Most question creation and detailed management happens through the UI# Create a practice quiz with immediate feedback
practice_quiz = course.create_quiz({
'title': 'Chapter 3 Practice Quiz',
'description': 'Practice quiz with immediate feedback',
'quiz_type': 'practice_quiz',
'time_limit': 30,
'allowed_attempts': -1, # Unlimited attempts
'scoring_policy': 'keep_highest',
'show_correct_answers': True,
'show_correct_answers_last_attempt': False,
'one_question_at_a_time': False,
'cant_go_back': False,
'shuffle_answers': True,
'hide_results': None, # Show results immediately
'published': True
})
# Create a secure exam with access restrictions
secure_exam = course.create_quiz({
'title': 'Final Examination',
'quiz_type': 'assignment',
'time_limit': 120,
'allowed_attempts': 1,
'access_code': 'EXAM2024',
'ip_filter': '192.168.1.0/24', # Restrict to specific IP range
'one_question_at_a_time': True,
'cant_go_back': True,
'show_correct_answers': False,
'lock_at': '2024-12-15T17:00:00Z',
'due_at': '2024-12-15T16:00:00Z',
'published': False
})
# Create quiz with date overrides for different sections
override = secure_exam.create_override({
'course_section_id': section_a.id,
'title': 'Section A Extended Time',
'due_at': '2024-12-15T17:30:00Z', # 30 minutes extra
'lock_at': '2024-12-15T17:30:00Z'
})Install with Tessl CLI
npx tessl i tessl/pypi-canvasapi