canvasapi

This is a collection of some scripts and components I have used to interact with Canvas, the LMS used at the University of Sydney.

You can find a set of accompanying slides here.

Introduction

Infrastructure, the developers of the Canvas LMS, provide an API that allows users to interact programmatically with Canvas. Documentation here.

canvasapi is a Python wrapper for the Canvas API that was initally developed by a team at the University of Central Florida. Documentation here.

Getting started

canvasapi can be downloaded from pip:

pip install canvasapi

To use the API, you need to obtain an access token. This can be obtained at https://canvas.sydney.edu.au/profile/settings.

Basic commands

Getting a course

from canvasapi import Canvas

API_URL = 'https://canvas.sydney.edu.au'
API_KEY = '' # Your access token here as a string

# Initialize a Canvas object
canvas = Canvas(API_URL, API_KEY)

# Get your course
course_id = # Your course code here as an int
course = canvas.get_course(course_id)

Getting student information

students = course.get_users(enrollment_type=['student'])

for student in range(len(list(students))):
    # Student ID
    sid = students[student].sis_user_id
    # Canvas name
    name = students[student].name
    # Canvas ID
    cid = students[student].id
    # unikey
    email = students[student].login_id
    # Email
    unikey = students[student].email

Getting assignment information

# Getting all assignments
for a in course.get_assignments():
    print(a)

# Get the assignment
assignment = course.get_assignment(123450)
print(assignment)

Getting submission details

# Get the assignment using canvasid
submission = assignment.get_submission(cid)
# Get mark
mark = submission.score
# Get lateness
lateness = submission.seconds_late
# Number of submissions
submissions = sub.attempt
# If submission missing
missing = sub.missing

Editing submission information

# Mark changes
submission.edit(submission={'posted_grade': new_mark})
# Make a text comment
submission.edit(comment={'text_comment': comment, 'attempt': submission.attempt})
# Upload a feedback file
submission.edit(comment={'file_ids': path_to_file, 'attempt': submission.attempt})

Example use cases

External marks and feedback files

import pandas as pd

marks_df = pd.DataFrame([[123456780, 95, 'Great work!'],
                         [123456781, 55, 'You need to put in more effort!'],
                         [123456782, 75, 'Keep going!']],
                         columns=['SID', 'mark', 'comment'])
marks_df.set_index('SID', inplace=True)

for student in range(len(list(students))):
    sid = str(students[student].sis_user_id)
    cid = int(students[student].id)

    submission = assignment.get_submission(cid)

    if sid in marks_df.index:
        mark = marks_df.loc[sid, 'mark']
        submission.edit(submission={'posted_grade': mark})

        msg = marks_df.loc[sid, 'comment']
        submission.edit(comment={'text_comment': msg, 
                                 'attempt': submission.attempt})


        submission.edit(comment={'file_ids': f'Assignment1-feedback-{sid}.pdf', 
                                 'attempt': submission.attempt})
TipIndividualised assignments

This also makes it possible to mass release individualised assignments by releasing them as feedback files. You can create a release assignment with no submissions required to release an assignment through the feedback section and a separate assignment to collect assignment submissions from students.

Applying late penalties

import math

max_score = 100

for student in range(len(list(students))):
    sid = str(students[student].sis_user_id)
    cid = int(students[student].id)

    submission = assignment.get_submission(cid)
    mark = submission.score
    lateness = submission.seconds_late

    penalty_multiplier = max((math.ceil(lateness / 3600) - 5), 0)
    penalty = penalty_multiplier * 0.05 * max_score

    submission.edit(submission={'posted_grade': max(0, mark - penalty)})

    msg = f'Late penalty applied: {penalty_multiplier} day(s) late'
    submission.edit(comment={'text_comment': msg, 
                             'attempt': submission.attempt})

Updating lateness

The lateness duration can be updated to account for a grace period so that Canvas automatically compute late penalties.

# grace period
grace = 60 * 10 # 10 minutes

for student in trange(len(list(students))):
    cid = int(students[student].id)
    sub = assignment.get_submission(cid)
    
    late = sub.late
    new_late = (sub.seconds_late - grace)

    if late and (new_late <= 0):
        sub.edit(submission={'late_policy_status': 'none'})
    elif late and (new_late > 0):
        sub.edit(submission={'late_policy_status': 'late', 
                             'seconds_late_override': new_late})
    if sub.missing:
        sub.edit(submission={'late_policy_status': 'missing'})