Skip to content

cass.apis

Typed Canvas LMS client and response schemas (msgspec.Struct types, Canvas-prefixed).

cass.apis

External service API clients and response schemas.

canvas

Canvas LMS integration — API client, roster matching, eGrades export.

auth

Canvas authentication — API token or browser session cookie.

Two ways to authenticate against the Canvas API:

  • TokenAuth: a personal API token sent as a Bearer header. Read from .canvastoken or $CANVAS_TOKEN.
  • SessionAuth: the canvas_session and _csrf_token cookies copied from a logged-in browser. Read from .canvascreds. Useful when no API token is available; cookies expire after roughly a day.

find_auth picks whichever is configured, preferring .canvastoken because a token lasts months while browser cookies last about a day.

CanvasAuthError

Bases: RuntimeError

Canvas rejected our credentials, with instructions on how to fix them.

Browser

Bases: StrEnum

Supported browser imports on macOS.

BrowserSource dataclass
BrowserSource(
    profile: str,
    origin: str,
    path: Path,
    browser: Browser = Browser.BRAVE,
)

Browser profile and Canvas origin authorized to refresh a credential file.

TokenAuth dataclass
TokenAuth(token: str, source: str)

Bearer-token authentication.

Parameters:

Name Type Description Default
token str

Canvas personal API token.

required
source str

Where the token came from (file name or $CANVAS_TOKEN).

required
description property
description: str

Short human label for status output.

SessionAuth dataclass
SessionAuth(
    session: str,
    csrf_token: str,
    source: str,
    browser_source: BrowserSource | None = None,
)

Browser session-cookie authentication.

Parameters:

Name Type Description Default
session str

Value of the canvas_session cookie.

required
csrf_token str

URL-decoded value of the _csrf_token cookie. Required for writes (Canvas checks it against the X-CSRF-Token header); may be empty for read-only use.

required
source str

File the cookies were read from.

required
browser_source BrowserSource | None

Browser and profile to refresh an imported session from.

None
description property
description: str

Short human label for status output.

parse_creds
parse_creds(
    text: str,
    source: str = CREDS_FILENAME,
    *,
    path: Path | None = None,
) -> SessionAuth

Parse a .canvascreds file of name=value cookie lines.

Values may be quoted (as copied from browser devtools) and the CSRF token may be URL-encoded or decoded; both forms are normalised.

Raises:

Type Description
SystemExit

If canvas_session is missing.

find_auth
find_auth(root: Path) -> CanvasAuth | None

Locate Canvas credentials for the project at root.

Order: .canvastoken, .canvascreds (session cookie), $CANVAS_TOKEN.

Returns:

Type Description
CanvasAuth | None

The configured auth, or None if nothing usable was found.

check_request
check_request(auth: CanvasAuth, request: Request) -> None

Fail fast before sending a request the credentials cannot satisfy.

Raises:

Type Description
CanvasAuthError

For a write with a session cookie but no CSRF token.

check_response
check_response(
    auth: CanvasAuth, response: Response
) -> None

Translate credential failures into a CanvasAuthError with guidance.

Raises:

Type Description
CanvasAuthError

On 401, or on a CSRF-rejected write under session auth.

browser

Import a Canvas session from Brave or Google Chrome on macOS.

read_browser_session
read_browser_session(source: BrowserSource) -> SessionAuth

Read Canvas cookies from one profile, using macOS Keychain access.

validate_session
validate_session(
    auth: SessionAuth,
    origin: str,
    *,
    transport: BaseTransport | None = None,
) -> None

Check authentication with a read-only request before replacing credentials.

save_browser_session
save_browser_session(auth: SessionAuth) -> None

Atomically replace the credential file with owner-only permissions.

login_from_browser
login_from_browser(
    root: Path,
    base_url: str,
    profile: str = "Default",
    *,
    browser: Browser = Browser.BRAVE,
    transport: BaseTransport | None = None,
) -> SessionAuth

Import, validate, and save a refreshable browser session for this project.

client

Canvas LMS API client — typed CRUD for every Canvas resource.

Provides CanvasClient, a typed httpx client with course_id baked in. All methods return msgspec.Struct instances, never raw dicts. The client is assembled from one mixin per resource; base holds the transport, auth lookup, pagination, and the shared _resolve helper.

CanvasClient(..., dry_run=True) records writes instead of sending them; read them from planned_requests and render with cass.apis.canvas.dry_run.format_plan.

CanvasAPIError
CanvasAPIError(response: Response)

Bases: HTTPStatusError

A Canvas request failed with a 4xx/5xx status.

Attributes:

Name Type Description
method

HTTP method of the failed request.

path

Request path without the /api/v1 prefix or query string.

status_code

HTTP status code.

canvas_message

Canvas's own error message, or the start of the body.

BaseClient
BaseClient(
    base_url: str | None = None,
    token: str | None = None,
    course_id: int | None = None,
    *,
    auth: CanvasAuth | None = None,
    transport: BaseTransport | None = None,
    time_zone: str | None = None,
    dry_run: bool = False,
)

Typed Canvas LMS API client with course_id baked in.

Parameters:

Name Type Description Default
base_url str | None

Canvas instance URL (e.g. https://canvas.ucsd.edu).

None
token str | None

Canvas API bearer token (shortcut for auth=TokenAuth(...)).

None
course_id int | None

Canvas course ID for all requests.

None
auth CanvasAuth | None

Credentials to use; defaults to whatever get_auth finds.

None
transport BaseTransport | None

HTTP transport override (tests inject a mock here).

None
time_zone str | None

IANA course time zone; defaults to [canvas] time_zone in cass.toml and is fetched from the course when that is empty.

None
dry_run bool

Record writes instead of sending them (see planned_requests). GETs are still sent, except to paths the dry run created; each write gets a synthetic 200 response.

False
planned_requests property
planned_requests: list[PlannedRequest]

Writes a dry run recorded, in order (empty when not a dry run).

Render them with format_plan.

time_zone property
time_zone: str

IANA time zone of the course (from cass.toml, else fetched once).

from_config classmethod
from_config(path: str | Path, **kwargs: Any) -> Self

Build a client for the course a cass.toml describes.

Reads the base URL, course ID and time zone from path, and credentials (.canvastoken / .canvascreds) from its directory, without changing the project config the CLI uses.

Parameters:

Name Type Description Default
path str | Path

A cass.toml file, which may have any name.

required
**kwargs Any

Passed to the constructor (e.g. transport, dry_run).

{}

Raises:

Type Description
SystemExit

If the file or its credentials are missing.

close
close() -> None

Close the underlying HTTP client.

get_all
get_all(
    path: str,
    params: Mapping[str, object]
    | Sequence[tuple[str, object]]
    | None = None,
) -> list[dict[str, Any]]

GET every page of a Canvas list endpoint (Link header pagination).

params (and per_page=100, unless given) apply to the first request only; each later page is requested from the next URL Canvas returns, verbatim.

Parameters:

Name Type Description Default
path str

API path in any form request accepts, optionally with a query string.

required
params Mapping[str, object] | Sequence[tuple[str, object]] | None

Query parameters as a mapping or a list of pairs (repeated keys such as state[] need pairs).

None

Returns:

Type Description
list[dict[str, Any]]

Items from list pages concatenated; a non-list page is appended

list[dict[str, Any]]

as a single item.

course_path
course_path(path: str = '') -> str

Course-scoped API path.

For example, course_path('/quizzes') gives /courses/{id}/quizzes.

url_for
url_for(path: str) -> str

Full URL for an API path; absolute URLs are returned unchanged.

request
request(
    method: str, path: str, **kwargs: Any
) -> httpx.Response

Send any request with cass's auth and retry handling.

path may be /api/v1/x, /x or x; use course_path() for course-relative paths. data may be a dict or a list of pairs (repeated keys are kept); other kwargs go to httpx. Raises CanvasAPIError on a 4xx/5xx response.

publish
publish(resource: str, resource_id: int) -> None

Publish a resource (module, assignment, or quiz).

Parameters:

Name Type Description Default
resource str

Resource type (modules, assignments, quizzes).

required
resource_id int

Canvas resource ID.

required
unpublish
unpublish(resource: str, resource_id: int) -> None

Unpublish a resource (module, assignment, or quiz).

Parameters:

Name Type Description Default
resource str

Resource type (modules, assignments, quizzes).

required
resource_id int

Canvas resource ID.

required
RetryTransport
RetryTransport(
    *,
    auth: CanvasAuth,
    retries: int = 0,
    wrapped: BaseTransport | None = None,
    refresh: Callable[[], SessionAuth] | None = None,
)

Bases: BaseTransport

Wraps a transport with 429 retry, backoff, throttling, and auth checks.

Parameters:

Name Type Description Default
auth CanvasAuth

Credentials in use; credential failures (401, CSRF rejection) are raised as CanvasAuthError with instructions to fix them.

required
retries int

Connection-level retries for the underlying HTTP transport.

0
wrapped BaseTransport | None

Transport to delegate to (defaults to httpx.HTTPTransport; tests pass an httpx.MockTransport).

None
refresh Callable[[], SessionAuth] | None

Import fresh credentials after a 401, at most once per request.

None
CanvasClient
CanvasClient(
    base_url: str | None = None,
    token: str | None = None,
    course_id: int | None = None,
    *,
    auth: CanvasAuth | None = None,
    transport: BaseTransport | None = None,
    time_zone: str | None = None,
    dry_run: bool = False,
)

Bases: CourseMixin, ModulesMixin, PagesMixin, AssignmentsMixin, GradesMixin, QuizzesMixin, FilesMixin, AnnouncementsMixin, CalendarMixin, TabsMixin, BaseClient

Typed Canvas LMS API client with course_id baked in.

See BaseClient for constructor arguments.

get_auth
get_auth() -> CanvasAuth

Locate Canvas credentials for the current project.

Prefers .canvastoken, then .canvascreds (browser session cookie), then $CANVAS_TOKEN.

Raises:

Type Description
SystemExit

If no credentials are found.

get_token
get_token() -> str

Read the Canvas API token from .canvastoken or $CANVAS_TOKEN.

Raises:

Type Description
SystemExit

If no token is found.

resource_key
resource_key(resource: str) -> str

Map a plural resource name to Canvas API parameter key.

save_token
save_token(token: str) -> None

Write a Canvas API token to .canvastoken.

announcements

Announcements (discussion topics flagged as announcements).

AnnouncementsMixin
AnnouncementsMixin(
    base_url: str | None = None,
    token: str | None = None,
    course_id: int | None = None,
    *,
    auth: CanvasAuth | None = None,
    transport: BaseTransport | None = None,
    time_zone: str | None = None,
    dry_run: bool = False,
)

Bases: BaseClient

Announcements (discussion topics flagged as announcements).

list_announcements
list_announcements() -> list[CanvasAnnouncement]

List course announcements.

Returns:

Type Description
list[CanvasAnnouncement]

Announcements sorted by posted_at descending.

resolve_announcement
resolve_announcement(
    id_or_title: str,
) -> CanvasAnnouncement

Resolve an announcement by ID or case-insensitive title.

Raises:

Type Description
RuntimeError

If no announcement matches, or the title is ambiguous.

create_announcement
create_announcement(
    title: str, message: str
) -> CanvasAnnouncement

Create an announcement.

Parameters:

Name Type Description Default
title str

Announcement title.

required
message str

HTML body.

required

Returns:

Type Description
CanvasAnnouncement

The created announcement.

update_announcement
update_announcement(
    topic_id: int, **kwargs: object
) -> CanvasAnnouncement

Update an announcement.

Parameters:

Name Type Description Default
topic_id int

Discussion topic ID.

required
**kwargs object

Fields to update (title, message).

{}

Returns:

Type Description
CanvasAnnouncement

The updated announcement.

delete_announcement
delete_announcement(topic_id: int) -> None

Delete an announcement.

assignments

Assignments and assignment groups.

AssignmentsMixin
AssignmentsMixin(
    base_url: str | None = None,
    token: str | None = None,
    course_id: int | None = None,
    *,
    auth: CanvasAuth | None = None,
    transport: BaseTransport | None = None,
    time_zone: str | None = None,
    dry_run: bool = False,
)

Bases: BaseClient

Assignments and assignment groups.

list_assignments
list_assignments() -> list[CanvasAssignmentResponse]

List all course assignments.

Returns:

Type Description
list[CanvasAssignmentResponse]

Assignments from all assignment groups.

get_assignment
get_assignment(
    assignment_id: int,
) -> CanvasAssignmentResponse

Get a single assignment by ID.

create_assignment
create_assignment(
    name: str,
    *,
    points_possible: float = 0.0,
    due_at: str | None = None,
    submission_types: list[str] | None = None,
    published: bool = False,
    assignment_group_id: int | None = None,
    description: str | None = None,
    grading_type: str = "points",
) -> CanvasAssignmentResponse

Create a new assignment.

Parameters:

Name Type Description Default
name str

Assignment name.

required
points_possible float

Total points.

0.0
due_at str | None

Due date in ISO 8601 format.

None
submission_types list[str] | None

Allowed submission types.

None
published bool

Whether to publish immediately.

False
assignment_group_id int | None

Assignment group to place in.

None
description str | None

HTML description.

None
grading_type str

Grading type (points, letter_grade, etc.).

'points'

Returns:

Type Description
CanvasAssignmentResponse

The created assignment.

update_assignment
update_assignment(
    assignment_id: int, **kwargs: object
) -> CanvasAssignmentResponse

Update an assignment.

Parameters:

Name Type Description Default
assignment_id int

Canvas assignment ID.

required
**kwargs object

Fields to update (name, points_possible, due_at, published, etc.).

{}

Returns:

Type Description
CanvasAssignmentResponse

The updated assignment.

delete_assignment
delete_assignment(assignment_id: int) -> None

Delete an assignment.

list_assignment_groups
list_assignment_groups() -> list[CanvasAssignmentGroup]

List assignment groups (grade categories).

Returns:

Type Description
list[CanvasAssignmentGroup]

Assignment groups with weights and rules.

create_assignment_group
create_assignment_group(
    name: str,
    *,
    position: int | None = None,
    group_weight: float | None = None,
) -> CanvasAssignmentGroup

Create a new assignment group.

Parameters:

Name Type Description Default
name str

Group name.

required
position int | None

Optional position in the group list.

None
group_weight float | None

Optional percentage weight for weighted grades.

None

Returns:

Type Description
CanvasAssignmentGroup

The created assignment group.

delete_assignment_group
delete_assignment_group(group_id: int) -> None

Delete an assignment group (Canvas also deletes its assignments).

resolve_assignment_group
resolve_assignment_group(
    id_or_name: str,
) -> CanvasAssignmentGroup

Resolve an assignment group by numeric ID or name (case-insensitive).

Raises:

Type Description
RuntimeError

If no group matches; the message lists the available groups.

resolve_assignment
resolve_assignment(
    id_or_name: str,
) -> CanvasAssignmentResponse

Resolve an assignment by numeric ID or name (case-insensitive).

Raises:

Type Description
RuntimeError

If no assignment matches.

base

Canvas client foundation — auth lookup, HTTP transport, and BaseClient.

Resource mixins in this package subclass BaseClient and are combined into CanvasClient in __init__.

RetryTransport
RetryTransport(
    *,
    auth: CanvasAuth,
    retries: int = 0,
    wrapped: BaseTransport | None = None,
    refresh: Callable[[], SessionAuth] | None = None,
)

Bases: BaseTransport

Wraps a transport with 429 retry, backoff, throttling, and auth checks.

Parameters:

Name Type Description Default
auth CanvasAuth

Credentials in use; credential failures (401, CSRF rejection) are raised as CanvasAuthError with instructions to fix them.

required
retries int

Connection-level retries for the underlying HTTP transport.

0
wrapped BaseTransport | None

Transport to delegate to (defaults to httpx.HTTPTransport; tests pass an httpx.MockTransport).

None
refresh Callable[[], SessionAuth] | None

Import fresh credentials after a 401, at most once per request.

None
BaseClient
BaseClient(
    base_url: str | None = None,
    token: str | None = None,
    course_id: int | None = None,
    *,
    auth: CanvasAuth | None = None,
    transport: BaseTransport | None = None,
    time_zone: str | None = None,
    dry_run: bool = False,
)

Typed Canvas LMS API client with course_id baked in.

Parameters:

Name Type Description Default
base_url str | None

Canvas instance URL (e.g. https://canvas.ucsd.edu).

None
token str | None

Canvas API bearer token (shortcut for auth=TokenAuth(...)).

None
course_id int | None

Canvas course ID for all requests.

None
auth CanvasAuth | None

Credentials to use; defaults to whatever get_auth finds.

None
transport BaseTransport | None

HTTP transport override (tests inject a mock here).

None
time_zone str | None

IANA course time zone; defaults to [canvas] time_zone in cass.toml and is fetched from the course when that is empty.

None
dry_run bool

Record writes instead of sending them (see planned_requests). GETs are still sent, except to paths the dry run created; each write gets a synthetic 200 response.

False
planned_requests property
planned_requests: list[PlannedRequest]

Writes a dry run recorded, in order (empty when not a dry run).

Render them with format_plan.

time_zone property
time_zone: str

IANA time zone of the course (from cass.toml, else fetched once).

from_config classmethod
from_config(path: str | Path, **kwargs: Any) -> Self

Build a client for the course a cass.toml describes.

Reads the base URL, course ID and time zone from path, and credentials (.canvastoken / .canvascreds) from its directory, without changing the project config the CLI uses.

Parameters:

Name Type Description Default
path str | Path

A cass.toml file, which may have any name.

required
**kwargs Any

Passed to the constructor (e.g. transport, dry_run).

{}

Raises:

Type Description
SystemExit

If the file or its credentials are missing.

close
close() -> None

Close the underlying HTTP client.

get_all
get_all(
    path: str,
    params: Mapping[str, object]
    | Sequence[tuple[str, object]]
    | None = None,
) -> list[dict[str, Any]]

GET every page of a Canvas list endpoint (Link header pagination).

params (and per_page=100, unless given) apply to the first request only; each later page is requested from the next URL Canvas returns, verbatim.

Parameters:

Name Type Description Default
path str

API path in any form request accepts, optionally with a query string.

required
params Mapping[str, object] | Sequence[tuple[str, object]] | None

Query parameters as a mapping or a list of pairs (repeated keys such as state[] need pairs).

None

Returns:

Type Description
list[dict[str, Any]]

Items from list pages concatenated; a non-list page is appended

list[dict[str, Any]]

as a single item.

course_path
course_path(path: str = '') -> str

Course-scoped API path.

For example, course_path('/quizzes') gives /courses/{id}/quizzes.

url_for
url_for(path: str) -> str

Full URL for an API path; absolute URLs are returned unchanged.

request
request(
    method: str, path: str, **kwargs: Any
) -> httpx.Response

Send any request with cass's auth and retry handling.

path may be /api/v1/x, /x or x; use course_path() for course-relative paths. data may be a dict or a list of pairs (repeated keys are kept); other kwargs go to httpx. Raises CanvasAPIError on a 4xx/5xx response.

publish
publish(resource: str, resource_id: int) -> None

Publish a resource (module, assignment, or quiz).

Parameters:

Name Type Description Default
resource str

Resource type (modules, assignments, quizzes).

required
resource_id int

Canvas resource ID.

required
unpublish
unpublish(resource: str, resource_id: int) -> None

Unpublish a resource (module, assignment, or quiz).

Parameters:

Name Type Description Default
resource str

Resource type (modules, assignments, quizzes).

required
resource_id int

Canvas resource ID.

required
get_auth
get_auth() -> CanvasAuth

Locate Canvas credentials for the current project.

Prefers .canvastoken, then .canvascreds (browser session cookie), then $CANVAS_TOKEN.

Raises:

Type Description
SystemExit

If no credentials are found.

get_token
get_token() -> str

Read the Canvas API token from .canvastoken or $CANVAS_TOKEN.

Raises:

Type Description
SystemExit

If no token is found.

save_token
save_token(token: str) -> None

Write a Canvas API token to .canvastoken.

resource_key
resource_key(resource: str) -> str

Map a plural resource name to Canvas API parameter key.

calendar

Course calendar events.

CalendarMixin
CalendarMixin(
    base_url: str | None = None,
    token: str | None = None,
    course_id: int | None = None,
    *,
    auth: CanvasAuth | None = None,
    transport: BaseTransport | None = None,
    time_zone: str | None = None,
    dry_run: bool = False,
)

Bases: BaseClient

Course calendar events.

list_calendar_events
list_calendar_events(
    *,
    start_date: str | None = None,
    end_date: str | None = None,
) -> list[CanvasCalendarEvent]

List calendar events on this course's calendar.

Canvas defaults to today's events only, so without a date range this requests every event on the course calendar.

Parameters:

Name Type Description Default
start_date str | None

Inclusive lower bound (YYYY-MM-DD or ISO 8601).

None
end_date str | None

Inclusive upper bound (YYYY-MM-DD or ISO 8601).

None

Returns:

Type Description
list[CanvasCalendarEvent]

Calendar events (assignment due dates are not included).

create_calendar_event
create_calendar_event(
    title: str,
    *,
    start_at: str,
    end_at: str | None = None,
    description: str | None = None,
    location_name: str | None = None,
    all_day: bool = False,
) -> CanvasCalendarEvent

Create a calendar event on this course's calendar.

Parameters:

Name Type Description Default
title str

Event title.

required
start_at str

Start date/time (ISO 8601).

required
end_at str | None

End date/time (ISO 8601).

None
description str | None

HTML description.

None
location_name str | None

Location name.

None
all_day bool

Ignore times and span the whole day.

False

Returns:

Type Description
CanvasCalendarEvent

The created event.

update_calendar_event
update_calendar_event(
    event_id: int, **kwargs: object
) -> CanvasCalendarEvent

Update a calendar event.

Parameters:

Name Type Description Default
event_id int

Calendar event ID.

required
**kwargs object

Fields to update (title, start_at, end_at, description, location_name, all_day).

{}

Returns:

Type Description
CanvasCalendarEvent

The updated event.

delete_calendar_event
delete_calendar_event(event_id: int) -> None

Delete a calendar event.

course

Course, people, sections, enrollments, and grading standards.

CourseMixin
CourseMixin(
    base_url: str | None = None,
    token: str | None = None,
    course_id: int | None = None,
    *,
    auth: CanvasAuth | None = None,
    transport: BaseTransport | None = None,
    time_zone: str | None = None,
    dry_run: bool = False,
)

Bases: BaseClient

Course, people, sections, enrollments, and grading standards.

get_course
get_course() -> CanvasCourse

Get course details.

Returns:

Type Description
CanvasCourse

Course metadata.

list_users
list_users(
    enrollment_type: str | None = None,
    *,
    states: Sequence[str] = (),
) -> list[CanvasUser]

List course users with enrollments.

Parameters:

Name Type Description Default
enrollment_type str | None

Filter by role (e.g. student, teacher).

None
states Sequence[str]

Enrollment states to include (e.g. active, completed); empty keeps Canvas's default.

()

Returns:

Type Description
list[CanvasUser]

Users sorted by name.

list_students
list_students() -> list[CanvasStudentResponse]

List students enrolled in the course.

Returns:

Type Description
list[CanvasStudentResponse]

Students sorted by Canvas enrollment order.

list_sections
list_sections() -> list[CanvasSection]

List all course sections.

Returns:

Type Description
list[CanvasSection]

Sections with SIS IDs (if available).

list_enrollments
list_enrollments(
    *,
    enrollment_type: str | None = "StudentEnrollment",
    states: Sequence[str] = ("active",),
    include_scores: bool = True,
) -> list[CanvasEnrollment]

List enrollments, optionally with computed scores.

Parameters:

Name Type Description Default
enrollment_type str | None

Filter by type (default: StudentEnrollment); None returns every type.

'StudentEnrollment'
states Sequence[str]

Enrollment states to include (default: active).

('active',)
include_scores bool

Include computed scores (total_scores).

True

Returns:

Type Description
list[CanvasEnrollment]

Enrollments, including computed_final_score when requested.

list_grading_standards
list_grading_standards() -> list[CanvasGradingStandard]

List grading standards available for this course.

Returns:

Type Description
list[CanvasGradingStandard]

Grading standards with scheme entries.

files

Files and folders.

FilesMixin
FilesMixin(
    base_url: str | None = None,
    token: str | None = None,
    course_id: int | None = None,
    *,
    auth: CanvasAuth | None = None,
    transport: BaseTransport | None = None,
    time_zone: str | None = None,
    dry_run: bool = False,
)

Bases: BaseClient

Files and folders.

list_files
list_files(
    folder_id: int | None = None,
) -> list[CanvasFile]

List files in the course or a specific folder.

Parameters:

Name Type Description Default
folder_id int | None

Optional folder ID to scope the listing.

None

Returns:

Type Description
list[CanvasFile]

File metadata sorted by name.

resolve_file
resolve_file(id_or_name: str) -> CanvasFile

Resolve a file by ID or case-insensitive display name.

Raises:

Type Description
RuntimeError

If no file matches, or the name is ambiguous.

list_folders
list_folders() -> list[CanvasFolder]

List all folders in the course.

Returns:

Type Description
list[CanvasFolder]

Folders with hierarchy info.

find_folder
find_folder(path: str) -> CanvasFolder | None

Find a folder by path relative to the course files root.

Matching is case-insensitive.

Returns:

Type Description
CanvasFolder | None

The folder, or None if no folder has that path.

Raises:

Type Description
RuntimeError

If the path starts with the root folder's name.

resolve_folder
resolve_folder(id_or_path: str) -> CanvasFolder

Resolve a folder by exact ID, else by path.

Raises:

Type Description
CanvasNotFoundError

If nothing matches.

create_folder
create_folder(
    path: str, *, parents: bool = True
) -> CanvasFolder

Create a folder by path, creating missing parents by default.

Existing folders are returned unchanged. "" returns the root folder.

Parameters:

Name Type Description Default
path str

Folder path relative to the course files root.

required
parents bool

Create missing intermediate folders. When False, a missing intermediate folder raises.

True

Raises:

Type Description
RuntimeError

If the path starts with the root folder's name, or parents is False and an intermediate folder is missing.

delete_folder
delete_folder(
    folder_id: int, *, force: bool = False
) -> None

Delete a folder; force also deletes a non-empty folder.

update_file
update_file(
    file_id: int,
    *,
    name: str | None = None,
    folder_id: int | None = None,
    locked: bool | None = None,
    on_duplicate: Literal["rename", "overwrite"]
    | None = None,
) -> CanvasFile

Rename, move or lock a file; only the given fields are sent.

Parameters:

Name Type Description Default
file_id int

Canvas file ID.

required
name str | None

New display name.

None
folder_id int | None

ID of the folder to move the file into.

None
locked bool | None

Lock (True) or unlock (False) the file.

None
on_duplicate Literal['rename', 'overwrite'] | None

What to do if the destination already has a file with the same name.

None

Raises:

Type Description
RuntimeError

If no field is given.

upload_file
upload_file(
    local_path: str,
    *,
    folder: str = "",
    name: str | None = None,
    on_duplicate: Literal[
        "rename", "overwrite"
    ] = "overwrite",
    locked: bool = False,
) -> CanvasFile

Upload a file to the course.

Uses Canvas's 3-step file upload flow: 1. Notify Canvas to get an upload URL 2. POST the file to the upload URL 3. Confirm the upload

Parameters:

Name Type Description Default
local_path str

Path to the local file.

required
folder str

Destination folder path relative to the course files root (e.g. slides); created if missing. "" is the root.

''
name str | None

Name in Canvas; defaults to the local file name.

None
on_duplicate Literal['rename', 'overwrite']

"overwrite" (default) replaces the file of that exact name: Canvas stores the upload as a new file (new id), deletes the old one and re-points its module items and links to the new one. "rename" keeps both files.

'overwrite'
locked bool

Lock the file after uploading.

False

Returns:

Type Description
CanvasFile

The uploaded file metadata.

delete_file
delete_file(file_id: int) -> None

Delete a file.

normalize_folder_path
normalize_folder_path(path: str) -> str

Strip whitespace and slashes and drop empty segments.

Example: "/a//b/" becomes "a/b"; "" stays "".

folder_paths
folder_paths(folders: list[CanvasFolder]) -> dict[int, str]

Map each folder ID to its path relative to the course files root.

The root folder (the one without a parent) maps to "". Canvas names the root course files; that prefix is removed from every path.

grades

Submissions, grade pushes, progress polling, and grade posting.

GradesMixin
GradesMixin(
    base_url: str | None = None,
    token: str | None = None,
    course_id: int | None = None,
    *,
    auth: CanvasAuth | None = None,
    transport: BaseTransport | None = None,
    time_zone: str | None = None,
    dry_run: bool = False,
)

Bases: BaseClient

Submissions, grade pushes, progress polling, and grade posting.

list_submissions
list_submissions(
    assignment_id: int, *, include_user: bool = False
) -> list[CanvasSubmissionResponse]

List submissions for an assignment.

Parameters:

Name Type Description Default
assignment_id int

Canvas assignment ID.

required
include_user bool

Embed the submitting user in each submission.

False

Returns:

Type Description
list[CanvasSubmissionResponse]

All submissions for the assignment.

list_student_submissions
list_student_submissions(
    student_ids: Sequence[int],
    *,
    assignment_ids: Sequence[int] = (),
) -> list[CanvasSubmissionResponse]

List submissions for students across assignments.

Student IDs are sent in chunks of 100 (one request per chunk).

Parameters:

Name Type Description Default
student_ids Sequence[int]

Canvas user IDs.

required
assignment_ids Sequence[int]

Restrict to these assignments; empty means all.

()

Returns:

Type Description
list[CanvasSubmissionResponse]

Submissions for all chunks, concatenated. Empty when no students.

update_submission
update_submission(
    assignment_id: int,
    user_id: int,
    *,
    excuse: bool | None = None,
    late_policy_status: str | None = None,
) -> CanvasSubmissionResponse

Update excused or late-policy status on one submission.

Parameters:

Name Type Description Default
assignment_id int

Canvas assignment ID.

required
user_id int

Student's Canvas user ID.

required
excuse bool | None

Excuse (True) or un-excuse (False) the submission. Either way Canvas clears the grade and score.

None
late_policy_status str | None

Late policy status (e.g. none, late). none clears late and missing and restores deducted points; any status un-excuses the submission. Canvas resets the status when the student submits again.

None

Returns:

Type Description
CanvasSubmissionResponse

The updated submission.

Raises:

Type Description
ValueError

If neither field is given.

push_grade
push_grade(
    assignment_id: int, student_canvas_id: int, grade: str
) -> bool

Push a single grade to Canvas.

Parameters:

Name Type Description Default
assignment_id int

Canvas assignment ID.

required
student_canvas_id int

Student's Canvas user ID.

required
grade str

Grade string to post.

required

Returns:

Type Description
bool

True on success, False on failure.

bulk_push_grades
bulk_push_grades(
    assignment_id: int, grade_data: dict[int, str]
) -> CanvasProgress

Push grades in bulk for one assignment via the update_grades endpoint.

Parameters:

Name Type Description Default
assignment_id int

Canvas assignment ID.

required
grade_data dict[int, str]

Mapping of student_canvas_id → posted_grade string.

required

Returns:

Type Description
CanvasProgress

Progress object for tracking completion.

check_progress
check_progress(progress_id: int) -> CanvasProgress

Check the status of an async Canvas operation.

Parameters:

Name Type Description Default
progress_id int

Progress object ID.

required

Returns:

Type Description
CanvasProgress

Current progress state.

wait_for_progress
wait_for_progress(
    progress_id: int, *, timeout: float = 120.0
) -> CanvasProgress

Poll a progress object until completion or timeout.

Parameters:

Name Type Description Default
progress_id int

Progress object ID.

required
timeout float

Maximum seconds to wait.

120.0

Returns:

Type Description
CanvasProgress

Completed or failed progress object.

Raises:

Type Description
RuntimeError

If the progress times out or fails.

post_assignment_grades
post_assignment_grades(
    assignment_id: int, *, graded_only: bool = True
) -> CanvasProgress | None

Post (reveal) grades to students for a manual-post assignment.

Uses the Canvas GraphQL postAssignmentGrades mutation.

Parameters:

Name Type Description Default
assignment_id int

Canvas assignment ID.

required
graded_only bool

If True, only post grades for graded submissions.

True

Returns:

Type Description
CanvasProgress | None

Progress object for tracking, or None if no progress was started.

Raises:

Type Description
RuntimeError

If the mutation returns validation errors.

hide_assignment_grades
hide_assignment_grades(
    assignment_id: int,
) -> CanvasProgress | None

Hide grades from students for a manual-post assignment.

Uses the Canvas GraphQL hideAssignmentGrades mutation.

Parameters:

Name Type Description Default
assignment_id int

Canvas assignment ID.

required

Returns:

Type Description
CanvasProgress | None

Progress object for tracking, or None if no progress was started.

Raises:

Type Description
RuntimeError

If the mutation returns validation errors.

modules

Modules and module items.

ModulesMixin
ModulesMixin(
    base_url: str | None = None,
    token: str | None = None,
    course_id: int | None = None,
    *,
    auth: CanvasAuth | None = None,
    transport: BaseTransport | None = None,
    time_zone: str | None = None,
    dry_run: bool = False,
)

Bases: BaseClient

Modules and module items.

list_modules
list_modules() -> list[CanvasModule]

List all course modules.

Returns:

Type Description
list[CanvasModule]

Modules sorted by position.

get_module
get_module(module_id: int) -> CanvasModule

Get a single module by ID.

list_module_items
list_module_items(module_id: int) -> list[CanvasModuleItem]

List items in a module.

Parameters:

Name Type Description Default
module_id int

Canvas module ID.

required

Returns:

Type Description
list[CanvasModuleItem]

Module items sorted by position.

create_module
create_module(
    name: str, position: int | None = None
) -> CanvasModule

Create a new module.

Parameters:

Name Type Description Default
name str

Module name.

required
position int | None

Optional position in the module list.

None

Returns:

Type Description
CanvasModule

The created module.

update_module
update_module(
    module_id: int, **kwargs: object
) -> CanvasModule

Update a module.

Parameters:

Name Type Description Default
module_id int

Canvas module ID.

required
**kwargs object

Fields to update (name, position, published).

{}

Returns:

Type Description
CanvasModule

The updated module.

delete_module
delete_module(module_id: int) -> None

Delete a module.

create_module_item
create_module_item(
    module_id: int,
    *,
    item_type: str,
    content_id: int | None = None,
    title: str | None = None,
    indent: int | None = None,
    page_url: str | None = None,
    external_url: str | None = None,
) -> CanvasModuleItem

Add an item to a module.

Only the fields given are sent.

Parameters:

Name Type Description Default
module_id int

Canvas module ID.

required
item_type str

Item type (Assignment, Quiz, File, Page, SubHeader, ExternalUrl, etc.).

required
content_id int | None

ID of the linked content (Assignment, Quiz, File).

None
title str | None

Item title; Canvas uses the content's name when omitted.

None
indent int | None

Indent level.

None
page_url str | None

Slug of the linked wiki page (Page items).

None
external_url str | None

Target URL (ExternalUrl items).

None

Returns:

Type Description
CanvasModuleItem

The created module item.

update_module_item
update_module_item(
    module_id: int,
    item_id: int,
    *,
    title: str | None = None,
    indent: int | None = None,
    position: int | None = None,
    published: bool | None = None,
    current: CanvasModuleItem | None = None,
) -> CanvasModuleItem

Update a module item; only the fields given are sent.

Parameters:

Name Type Description Default
module_id int

Canvas module ID.

required
item_id int

Module item ID.

required
title str | None

New title.

None
indent int | None

New indent level.

None
position int | None

New position within the module.

None
published bool | None

New published state.

None
current CanvasModuleItem | None

The item as it is now. published is not sent when it equals current.published, and when nothing else is left to send, current is returned without a request.

None

Returns:

Type Description
CanvasModuleItem

The updated item.

Raises:

Type Description
ValueError

If no field is given.

delete_module_item
delete_module_item(module_id: int, item_id: int) -> None

Remove an item from a module (the linked content is untouched).

resolve_module_item
resolve_module_item(
    module_id: int, id_or_title: str
) -> CanvasModuleItem

Resolve a module item by ID or title (case-insensitive).

Raises:

Type Description
RuntimeError

If no item matches or several share the title.

resolve_module
resolve_module(id_or_name: str) -> CanvasModule

Resolve a module by numeric ID or name (case-insensitive).

Raises:

Type Description
RuntimeError

If no module matches.

pages

Course wiki pages.

PagesMixin
PagesMixin(
    base_url: str | None = None,
    token: str | None = None,
    course_id: int | None = None,
    *,
    auth: CanvasAuth | None = None,
    transport: BaseTransport | None = None,
    time_zone: str | None = None,
    dry_run: bool = False,
)

Bases: BaseClient

Course wiki pages.

list_pages
list_pages() -> list[CanvasPage]

List the course's wiki pages.

Returns:

Type Description
list[CanvasPage]

Pages with their URL slugs and published state.

quizzes

Classic quizzes, quiz questions, reports, and quiz submissions.

QuizzesMixin
QuizzesMixin(
    base_url: str | None = None,
    token: str | None = None,
    course_id: int | None = None,
    *,
    auth: CanvasAuth | None = None,
    transport: BaseTransport | None = None,
    time_zone: str | None = None,
    dry_run: bool = False,
)

Bases: BaseClient

Classic quizzes and quiz questions.

list_quizzes
list_quizzes() -> list[CanvasQuiz]

List all course quizzes.

Returns:

Type Description
list[CanvasQuiz]

Quizzes of all types.

get_quiz
get_quiz(quiz_id: int) -> CanvasQuiz

Get a single quiz by ID.

create_quiz
create_quiz(
    spec: QuizSpec,
    *,
    assignment_group_id: int | None = None,
) -> CanvasQuiz

Create a quiz with its questions.

Fills unset settings from QUIZ_CREATE_DEFAULTS, posts the quiz unpublished, creates the declared question groups, posts each question in file order (into its group), orders groups and entries when there are groups, then re-saves the quiz (applying spec.published) so Canvas recomputes question_count and points_possible. If a question post fails the partial quiz is deleted and the error re-raised.

Parameters:

Name Type Description Default
spec QuizSpec

Settings and questions; times are parsed in the course zone.

required
assignment_group_id int | None

Assignment group to place the quiz in.

None

Returns:

Type Description
CanvasQuiz

The quiz as re-fetched after the re-save.

update_quiz
update_quiz(
    quiz_id: int, changes: dict[str, object]
) -> CanvasQuiz

Update quiz settings.

Only the given Canvas fields are sent; questions are never touched.

Parameters:

Name Type Description Default
quiz_id int

Canvas quiz ID.

required
changes dict[str, object]

Canvas field name → new value (see quiz_settings_diff).

required

Returns:

Type Description
CanvasQuiz

The updated quiz.

list_quiz_questions
list_quiz_questions(
    quiz_id: int,
) -> tuple[list[CanvasQuizQuestion], bool]

List a quiz's questions in the order the questions API returns them.

That order follows Canvas's stored positions, but each question's position field is null for API-created questions; see canvas_layout for rebuilding the quiz order.

include[]=plain_html returns the HTML as stored. Without it, Canvas wraps each question's text in the account's theme CSS and JS and rewrites file links, so the text would never match what was sent.

Concluded courses return 403 from the questions endpoint; then the questions are rebuilt from /statistics, which carries text and which answers are correct but no answer weights or names.

Returns:

Type Description
tuple[list[CanvasQuizQuestion], bool]

(questions, used_statistics_fallback).

create_quiz_question
create_quiz_question(
    quiz_id: int,
    question: QuestionSpec,
    position: int,
    *,
    quiz_type: str = "assignment",
    quiz_group_id: int | None = None,
) -> CanvasQuizQuestion

Add one question to a quiz.

Parameters:

Name Type Description Default
quiz_id int

Canvas quiz ID.

required
question QuestionSpec

Question spec; points None uses the type default.

required
position int

1-based position.

required
quiz_type str

Decides the default points (0 for surveys, else 1).

'assignment'
quiz_group_id int | None

Question group to put the question in.

None

Returns:

Type Description
CanvasQuizQuestion

The created question.

update_quiz_question
update_quiz_question(
    quiz_id: int,
    question_id: int,
    fields: dict[str, object],
) -> CanvasQuizQuestion

Update one question (one call).

Parameters:

Name Type Description Default
quiz_id int

Canvas quiz ID.

required
question_id int

Canvas question ID.

required
fields dict[str, object]

Canvas question field → new value, e.g. question_text.

required

Returns:

Type Description
CanvasQuizQuestion

The updated question.

delete_quiz_question
delete_quiz_question(
    quiz_id: int, question_id: int
) -> None

Delete one question (one call).

create_question_group
create_question_group(
    quiz_id: int, fields: dict[str, object]
) -> CanvasQuizGroup

Create a question group (one call).

Parameters:

Name Type Description Default
quiz_id int

Canvas quiz ID.

required
fields dict[str, object]

Canvas group fields: name, pick_count, question_points.

required

Returns:

Type Description
CanvasQuizGroup

The created group.

update_question_group
update_question_group(
    quiz_id: int, group_id: int, fields: dict[str, object]
) -> CanvasQuizGroup

Update a question group (one call); same fields as the create.

delete_question_group
delete_question_group(quiz_id: int, group_id: int) -> None

Delete a question group (one call).

get_question_group
get_question_group(
    quiz_id: int, group_id: int
) -> CanvasQuizGroup

Get one question group (one call).

Canvas has no endpoint that lists a quiz's groups.

reorder_quiz
reorder_quiz(
    quiz_id: int, items: list[tuple[str, int]]
) -> None

Set the quiz's top-level order (one call).

Parameters:

Name Type Description Default
quiz_id int

Canvas quiz ID.

required
items list[tuple[str, int]]

("question" | "group", id) in the wanted order.

required
reorder_question_group
reorder_question_group(
    quiz_id: int, group_id: int, question_ids: list[int]
) -> None

Set the order of the questions inside a group (one call).

resave_quiz
resave_quiz(
    quiz_id: int, *, published: bool | None = None
) -> None

Re-save a quiz without notifying students (one call).

Classic quizzes recount questions and points only after a re-save.

Parameters:

Name Type Description Default
quiz_id int

Canvas quiz ID.

required
published bool | None

Also set the published state when given.

None
delete_quiz
delete_quiz(quiz_id: int) -> None

Delete a quiz.

create_quiz_report
create_quiz_report(
    quiz_id: int, *, all_versions: bool = False
) -> CanvasQuizReport

Ask Canvas to generate a student analysis report.

Canvas returns the existing report when one is already current, so repeating the call is cheap. Works on concluded courses.

Parameters:

Name Type Description Default
quiz_id int

Canvas quiz ID.

required
all_versions bool

Report every attempt instead of only the latest.

False

Returns:

Type Description
CanvasQuizReport

The report; file is None until generation finishes.

get_quiz_report
get_quiz_report(
    quiz_id: int, report_id: int
) -> CanvasQuizReport

Get a quiz report with its file and progress.

download_quiz_report
download_quiz_report(
    quiz_id: int,
    *,
    all_versions: bool = False,
    timeout: float = 300.0,
    interval: float = 2.0,
) -> str

Generate a student analysis report, wait for it, and download it.

Generation typically takes about 30 seconds.

Parameters:

Name Type Description Default
quiz_id int

Canvas quiz ID.

required
all_versions bool

Report every attempt instead of only the latest.

False
timeout float

Maximum seconds to wait for Canvas to build the file.

300.0
interval float

Seconds between polls.

2.0

Returns:

Type Description
str

The report CSV text.

Raises:

Type Description
RuntimeError

If generation fails or times out.

list_quiz_submissions
list_quiz_submissions(
    quiz_id: int,
) -> list[CanvasQuizSubmission]

List each student's latest attempt on a quiz.

Returns:

Type Description
list[CanvasQuizSubmission]

One quiz submission per student who opened the quiz.

start_quiz_preview
start_quiz_preview(quiz_id: int) -> CanvasQuizSubmission

Start a teacher preview attempt on a quiz.

Returns:

Type Description
CanvasQuizSubmission

The preview's quiz submission.

Raises:

Type Description
RuntimeError

Canvas returned no quiz submission.

list_submission_questions
list_submission_questions(
    submission_id: int,
) -> list[CanvasQuizQuestion]

List the questions of one quiz submission in the order it was drawn.

resolve_quiz_id
resolve_quiz_id(id_or_name: str) -> int

A quiz's ID from a numeric ID (no request) or a title.

Raises:

Type Description
RuntimeError

If no quiz has that title.

resolve_quiz
resolve_quiz(id_or_name: str) -> CanvasQuiz

Resolve a quiz by numeric ID or title (case-insensitive).

Raises:

Type Description
RuntimeError

If no quiz matches.

tabs

Course navigation tabs.

TabsMixin
TabsMixin(
    base_url: str | None = None,
    token: str | None = None,
    course_id: int | None = None,
    *,
    auth: CanvasAuth | None = None,
    transport: BaseTransport | None = None,
    time_zone: str | None = None,
    dry_run: bool = False,
)

Bases: BaseClient

Course navigation tabs.

list_tabs
list_tabs() -> list[CanvasTab]

List course navigation tabs.

Returns:

Type Description
list[CanvasTab]

Tabs with visibility and position.

update_tab
update_tab(tab_id: str, *, hidden: bool) -> CanvasTab

Show or hide a navigation tab.

Parameters:

Name Type Description Default
tab_id str

Tab ID string.

required
hidden bool

True to hide, False to show.

required

Returns:

Type Description
CanvasTab

The updated tab.

resolve_tab
resolve_tab(id_or_label: str) -> CanvasTab

Resolve a navigation tab by ID (e.g. syllabus) or label.

Raises:

Type Description
RuntimeError

If no tab matches.

dry_run

Dry run: record Canvas writes instead of sending them.

A dry-run client sends GETs as usual but records every other request and answers it with a synthetic 200 response, so chained calls (create a quiz, then add its questions) still work. New resources get placeholder IDs -1, -2, … and later requests to their paths are answered locally.

PlannedRequest dataclass
PlannedRequest(
    method: str, path: str, body: dict[str, Any]
)

One write a dry run recorded instead of sending.

Attributes:

Name Type Description
method str

HTTP method.

path str

Path relative to /api/v1, or the full URL for uploads.

body dict[str, Any]

Parsed request body (form keys nested, as in parse_form).

DryRunRecorder
DryRunRecorder()

Records planned writes and keeps the synthetic body of each written path.

stored
stored(path: str) -> dict[str, Any] | None

Synthetic body for a path this dry run created or changed, if any.

has_placeholder
has_placeholder(path: str) -> bool

Whether path contains a placeholder ID segment such as -1.

record
record(
    method: str,
    path: str,
    body: dict[str, Any],
    fetch_current: Callable[[], dict[str, Any]],
) -> dict[str, Any]

Record a write and build its synthetic response body.

A POST to a collection creates a resource with the next placeholder ID. Any other write merges the request fields into the resource's current body: the stored one, else {} under a placeholder path, else fetch_current() (called once per path). A /reorder POST is recorded and answered with {}. Endpoints in _ENVELOPES take and return their fields wrapped in a one-item list.

record_upload
record_upload(
    url: str, params: dict[str, Any], filename: str
) -> dict[str, Any]

Record the file POST to an upload URL and build the file's body.

DryRunTransport
DryRunTransport(
    recorder: DryRunRecorder,
    *,
    wrapped: BaseTransport,
    api_root: str,
)

Bases: BaseTransport

Sends GETs through wrapped; records everything else in recorder.

Parameters:

Name Type Description Default
recorder DryRunRecorder

Where planned writes and synthetic bodies are kept.

required
wrapped BaseTransport

Transport for real GETs (cass's RetryTransport).

required
api_root str

The client's API base URL, e.g. https://canvas.example.com/api/v1; recorded paths are relative to it.

required
format_plan
format_plan(
    requests: Sequence[PlannedRequest],
) -> list[str]

Render planned writes, one line each.

A line is the method, the path with placeholder IDs shown as <new N>, and the request fields as key=value pairs. Nested values are shown as compact JSON and long values are cut to 60 characters.

egrades

eGrades CSV export — UCSD final grade submission format.

Generates the 5-column CSV required by UCSD's eGrades system: Last Name, First Name, Student ID, SectionId, Final_Assigned_Egrade

Uses the Canvas enrollments API for computed_final_score and the course grading standard to convert percentages to letter grades.

score_to_letter
score_to_letter(
    pct: float | None,
    scheme: list[CanvasGradingSchemeEntry],
) -> str

Convert a percentage score to a letter grade using a grading scheme.

Canvas grading schemes are lists of {name, value} entries sorted descending by value. Each entry means "scores >= value * 100 earn this letter."

Parameters:

Name Type Description Default
pct float | None

Percentage score (0-100), or None for missing.

required
scheme list[CanvasGradingSchemeEntry]

Grading scheme entries (will be sorted internally).

required

Returns:

Type Description
str

Letter grade string, or "" if score is None or scheme is empty.

parse_sortable_name
parse_sortable_name(sortable_name: str) -> tuple[str, str]

Parse Canvas sortable_name into (last_name, first_name).

Parameters:

Name Type Description Default
sortable_name str

Name in "Last, First" format.

required

Returns:

Type Description
str

Tuple of (last_name, first_name). Falls back to full string

str

as last name if no comma found.

generate_egrades
generate_egrades(
    output: str | Path = "egrades.csv",
) -> tuple[Path, int, list[str]]

Generate an eGrades CSV file from Canvas data.

Fetches enrollments with computed_final_score, resolves the active grading scheme, and writes the UCSD eGrades format CSV.

Parameters:

Name Type Description Default
output str | Path

Output file path.

'egrades.csv'

Returns:

Type Description
tuple[Path, int, list[str]]

Tuple of (path, row_count, warnings).

Raises:

Type Description
RuntimeError

If no grading standard is configured or no students found.

errors

Canvas API errors — one exception type for every 4xx/5xx response.

CanvasNotFoundError

Bases: RuntimeError

A name or ID matched nothing (as opposed to matching several).

CanvasAPIError
CanvasAPIError(response: Response)

Bases: HTTPStatusError

A Canvas request failed with a 4xx/5xx status.

Attributes:

Name Type Description
method

HTTP method of the failed request.

path

Request path without the /api/v1 prefix or query string.

status_code

HTTP status code.

canvas_message

Canvas's own error message, or the start of the body.

raise_for_canvas_status
raise_for_canvas_status(response: Response) -> None

Raise CanvasAPIError if response is a 4xx/5xx; otherwise do nothing.

matching

Canvas LMS integration — roster fetching, submission retrieval, and grade push.

All raw API calls are delegated to CanvasClient in client.py.

get_token
get_token() -> str

Read the Canvas API token from .canvastoken or $CANVAS_TOKEN.

Raises:

Type Description
SystemExit

If no token is found.

save_token
save_token(token: str) -> None

Write a Canvas API token to .canvastoken.

fetch_students
fetch_students(
    course_id: int,
) -> list[CanvasStudentResponse]

Fetch all students enrolled in a Canvas course.

fetch_course_name
fetch_course_name(course_id: int) -> str

Fetch the course name from the Canvas API.

fetch_course_time_zone
fetch_course_time_zone() -> str

Read the course's IANA time zone from Canvas.

fetch_students_with_sections
fetch_students_with_sections(
    course_id: int,
) -> tuple[list[CanvasStudentResponse], dict[int, str]]

Fetch students and build a canvas_id -> sis_section_id mapping.

Uses the users endpoint (with enrollments) and sections endpoint to resolve each student's SIS section ID.

Returns:

Type Description
list[CanvasStudentResponse]

Tuple of (students, sis_section_map) where sis_section_map maps

dict[int, str]

canvas_id to sis_section_id.

fetch_canvas_assignments
fetch_canvas_assignments(
    course_id: int,
) -> tuple[list[CanvasAssignmentResponse], dict[int, str]]

Fetch all assignments and assignment group names from Canvas.

Returns:

Type Description
list[CanvasAssignmentResponse]

Tuple of (assignments, group_names) where group_names maps

dict[int, str]

assignment_group_id to group name.

fetch_canvas_submissions
fetch_canvas_submissions(
    course_id: int,
    canvas_assignment_id: int,
    known_canvas_ids: set[int],
) -> list[CanvasSubmission]

Fetch submissions for a Canvas assignment, filtered to known students.

Parameters:

Name Type Description Default
course_id int

Canvas course ID.

required
canvas_assignment_id int

Canvas assignment ID.

required
known_canvas_ids set[int]

Set of canvas_ids from the students table.

required

Returns:

Type Description
list[CanvasSubmission]

CanvasSubmission domain objects for known students.

push_grade
push_grade(
    course_id: int,
    assignment_id: int,
    student_canvas_id: int,
    grade: str,
) -> bool

Push a single grade to Canvas. Returns True on success, False on failure.

raw

Helpers for raw Canvas API calls: path normalization and request bodies.

api_path
api_path(path: str) -> str

Normalize a user-supplied API path to one relative to /api/v1.

An http(s):// URL is returned unchanged. Otherwise /api/v1/x, /x and x all become /x.

form_content
form_content(pairs: Iterable[tuple[str, object]]) -> bytes

URL-encode key/value pairs as a form body, keeping repeated keys.

Values are encoded like httpx does: True/False become true/false and None becomes an empty value.

parse_form
parse_form(
    pairs: Iterable[tuple[str, str]],
) -> dict[str, Any]

Parse form pairs into nested data using the Rails bracket convention.

quiz[title] nests into {"quiz": {"title": ...}}, a[] collects a list, and order[][id] builds a list of dicts. Keys without valid brackets are kept as plain keys.

parse_body
parse_body(request: Request) -> dict[str, Any]

Read a request's JSON or form body as a dict ({} for anything else).

A JSON body that is not an object is returned as {"body": value}.

request_fields
request_fields(body: dict[str, Any]) -> dict[str, Any]

Remove a resource wrapper: {"quiz": {...}} becomes {...}.

parse_pairs
parse_pairs(items: Sequence[str]) -> list[tuple[str, str]]

Split KEY=VALUE strings at the first =, keeping order and repeats.

expand_course
expand_course(path: str, course_id: int) -> str

Replace every {course} in path with the course ID.

schema

Canvas LMS API response types.

CanvasCourse

Bases: Struct

Canvas course details.

CanvasStudentResponse

Bases: Struct

Canvas student (user enrolled as student).

CanvasEnrollmentGrades

Bases: Struct

Nested grades object inside a Canvas enrollment.

CanvasEnrollment

Bases: Struct

Canvas enrollment record.

computed_final_score property
computed_final_score: float | None

Canvas returns final_score nested inside grades dict.

computed_current_score property
computed_current_score: float | None

Canvas returns current_score nested inside grades dict.

CanvasUser

Bases: Struct

Canvas user with optional enrollments.

CanvasSection

Bases: Struct

Canvas course section.

CanvasGradingSchemeEntry

Bases: Struct

Single entry in a Canvas grading scheme (e.g. A = 0.94).

CanvasGradingStandard

Bases: Struct

Canvas grading standard with scheme entries.

CanvasModuleItem

Bases: Struct

Single item within a Canvas module.

CanvasPage

Bases: Struct

Canvas wiki page.

CanvasModule

Bases: Struct

Canvas module (content grouping).

CanvasAssignmentResponse

Bases: Struct

Canvas assignment.

CanvasAssignmentGroup

Bases: Struct

Canvas assignment group (weighted grade category).

CanvasQuiz

Bases: Struct

Canvas quiz.

CanvasQuizAnswer

Bases: Struct

One answer on a Canvas quiz question.

CanvasQuizQuestion

Bases: Struct

Canvas quiz question with its answers.

CanvasQuizGroup

Bases: Struct

Canvas question group: draws pick_count of its questions per attempt.

CanvasQuizReportFile

Bases: Struct

File attached to a generated quiz report.

CanvasQuizReport

Bases: Struct

Canvas quiz report (include[]=file,progress).

file is None until Canvas finishes generating the CSV.

CanvasQuizSubmission

Bases: Struct

A student's latest classic quiz attempt (/quizzes/:id/submissions).

CanvasFile

Bases: Struct

Canvas file metadata.

CanvasFolder

Bases: Struct

Canvas folder in the file hierarchy.

CanvasAnnouncement

Bases: Struct

Canvas announcement (discussion_topic with is_announcement=true).

CanvasCalendarEvent

Bases: Struct

Canvas calendar event (type=event; assignment due dates are not included).

CanvasTab

Bases: Struct

Canvas course navigation tab.

CanvasSubmissionResponse

Bases: Struct

Canvas submission API response.

CanvasProgress

Bases: Struct

Canvas async operation progress tracker.

sync

Canvas sync primitives — push grades and assignments to Canvas LMS.

Shared by both the CLI (cass gradebook push) and the NiceGUI viewer.

same_instant
same_instant(a: str | None, b: str | None) -> bool

Compare two ISO timestamps by the moment they name, not their spelling.

Canvas returns UTC (...Z) while cass.toml may use a local offset. Unparseable values fall back to plain string comparison.

values_equal
values_equal(a: object, b: object) -> bool

Compare values loosely, handling datetime/string equivalence.

resolve_row_name
resolve_row_name(
    conn: Database, table: str, pk_key: str
) -> str

Resolve a pending-change pk_key to a human-readable row name.

is_valid_grade
is_valid_grade(grade: str | None) -> bool

Return True if grade is non-empty and not a placeholder.

build_grade_push_data
build_grade_push_data(
    grades: list[CanvasGrade],
) -> tuple[dict[int, dict[int, str]], int]

Build {aid: {uid: grade}} from CanvasGrade list, filtering placeholders.

Returns:

Type Description
tuple[dict[int, dict[int, str]], int]

Tuple of (grade_data_by_aid, skipped_count).

get_post_manually_map
get_post_manually_map(conn: Database) -> dict[int, bool]

Load {canvas_assignment_id: post_manually} from DB.

build_push_preview
build_push_preview(
    conn: Database,
    grade_data_by_aid: dict[int, dict[int, str]],
    grades: list[CanvasGrade],
) -> list[dict[str, object]]

Build a preview summary per assignment for push display.

Returns:

Type Description
list[dict[str, object]]

List of dicts with name, canvas_id, count, post_manually.

push_grades
push_grades(
    client: CanvasClient,
    conn: Database,
    grade_data_by_aid: dict[int, dict[int, str]],
) -> list[dict[str, object]]

Bulk-push grades to Canvas, handling progress tracking and post_manually.

Parameters:

Name Type Description Default
client CanvasClient

Authenticated Canvas API client.

required
conn Database

SQLite database connection (for post_manually lookup).

required
grade_data_by_aid dict[int, dict[int, str]]

{assignment_id: {user_id: grade_string}}.

required

Returns:

Type Description
list[dict[str, object]]

List of result dicts with ok, canvas_assignment_id, count,

list[dict[str, object]]

and optionally error or action keys.

resolve_group_ids
resolve_group_ids(
    client: CanvasClient, names: Iterable[str]
) -> tuple[dict[str, int], list[str]]

Map assignment group names to IDs, case-insensitively.

Returns:

Type Description
dict[str, int]

(ids keyed by lower-cased name, names with no live match). A

list[str]

missing name is listed once, as first spelled.

push_assignments
push_assignments(
    client: CanvasClient,
    updates_by_id: dict[int, dict[str, object]],
) -> list[dict[str, object]]

Push assignment field updates to Canvas.

Parameters:

Name Type Description Default
client CanvasClient

Authenticated Canvas API client.

required
updates_by_id dict[int, dict[str, object]]

{canvas_assignment_id: {field: new_value}}.

required

Returns:

Type Description
list[dict[str, object]]

List of result dicts with ok, canvas_id, and optionally error.

times

Course-local time handling for Canvas timestamps.

Canvas stores instants in UTC. Users type times in the course's own zone. parse_when turns a user string into an ISO 8601 instant with an offset; format_when renders a Canvas instant in the course zone.

parse_when
parse_when(value: str, tz: str) -> str

Parse a user-entered time into ISO 8601 with an offset.

Accepts YYYY-MM-DD, YYYY-MM-DD HH:MM, YYYY-MM-DDTHH:MM and full ISO 8601. Naive values are localized to tz; a date alone means midnight. Values that already carry an offset are returned unchanged.

Raises:

Type Description
RuntimeError

If the value cannot be parsed or tz is empty.

check_when
check_when(value: str) -> None

Fail early on a time parse_when would reject, without a time zone.

Raises:

Type Description
RuntimeError

If the value cannot be parsed.

format_when
format_when(
    iso: str | None, tz: str, *, date_only: bool = False
) -> str

Render an ISO instant in tz as YYYY-MM-DD HH:MM (or the date).

same_instant
same_instant(a: str | None, b: str | None) -> bool

Compare two ISO timestamps by the moment they name, not their spelling.

Canvas returns UTC (...Z) while cass.toml may use a local offset. Unparseable values fall back to plain string comparison.