Skip to main content

canvasser

Automation for Canvas (*.instructure.com) that navigates the site as a person would, rather than through the REST API. Built against UF and running at three institutions; most need only one setting, an SSO path (see Scope). A provider whose sign-in form differs needs a row in auth.IDPS as well — there are two such rows today, Shibboleth and Microsoft Entra ID.

That choice is deliberate. UF discontinued API token support after Instructure began capping non-admin tokens at 30 days, and advises retiring token-based scripts. Browser automation is the better-supported path for this institution.

Status: in use. A full semester of dates for a real course — 37 assignments, set and cleared — pulled to CSV, edited in a spreadsheet, and written back, ending in No changes. 37 sheet row(s) match Canvas exactly. Plain assignments and classic quizzes both. Writing is opt-in (--commit), and every field is checked three times: the typed values are read back out of the form before saving, Canvas's own error messages are read after, and the stored value is re-read from the page's state afterwards.

Assignment settings — points, grading type, submission types, allowed attempts, peer review, publish state — are pulled to a second CSV. Writing them is newer than the date path: points, grading type, submission types, allowed attempts and peer review can be written, and a title with --rename. Publish state can be turned on — on a brand-new assignment or one that already exists — but not off: Canvas offers a "Save & Publish" button precisely while an assignment is unpublished, and no control at all for the other direction. Anything not writable is reported rather than silently ignored.

New assignments can be created from the settings sheet: put NEW in the id cell and the id Canvas assigns is written straight back into it, so the row becomes an ordinary edit and a second push cannot create a duplicate. Assignments only — quizzes and discussions are refused, since Canvas makes each of those somewhere else. There is no delete command, so anything created by mistake has to be removed in Canvas by hand.

Not supported: assignments with per-student or per-section overrides. Saving Canvas's edit form submits every date card, so getting that wrong deletes an accommodation date. It is refused rather than attempted — for settings writes too, since they save the same form.

What it does

A round trip: pull to CSV, edit in a spreadsheet, push it back.

canvasser status                      # is the stored session still authenticated?
canvasser login                       # authenticate (SSO, plus a second factor if your
                                      #   institution uses one)
canvasser courses                     # list courses, with ids
canvasser settings 580777             # all five displays; narrow with --sections etc.
canvasser pull 580777                 # dates AND settings -> two CSVs
canvasser pull 580777 --dates         # just dates-580777.csv
canvasser pull 580777 --info          # just info-580777.csv
canvasser push dates-580777.csv       # show what would change; writes nothing
canvasser push dates-580777.csv --commit   # actually write it
canvasser push info-580777.csv --commit    # settings, and any NEW rows
canvasser push info-580777.csv --rename    # also allow title changes (still needs --commit)
canvasser push info-580777.csv --only 7289071   # just that row; repeat --only for more
canvasser install-browser             # fetch Chromium up front (usually automatic)
canvasser --institution templeu status     # a different Canvas, with its own session
canvasser --institution ucf --passwordless login   # no password: approve in your app

push previews by default. Running it repeatedly while editing a sheet cannot touch the course; only --commit writes.

push works out which kind of sheet it was given by reading the file's first row, not its name — so push info-580777.csv reaches the settings path even if you rename the file. --dates and --info on push are assertions rather than switches: they refuse a file that says it is the other kind.

Both sheets come from the same page loads, so asking for one is no faster than asking for both.

Reading a course means loading one page per assignment, so pull and push show a progress bar while they work. Piped or redirected, they print a plain numbered list instead — no escape sequences, so a captured log stays readable. Set CANVASSER_ASCII=1 to force plain ASCII drawing on a terminal that cannot render box characters.

Requirements

  • Python 3.10+ (developed and tested on 3.13)

  • A Canvas account at an *.instructure.com institution. Verified against a live login at three: UF (Shibboleth SSO with Duo MFA), Temple (Shibboleth, no second factor), and UCF (Microsoft Entra ID, passwordless — approve in Microsoft Authenticator). Each institution needs an SSO path, which has no safe default and must be configured; beyond that, the only institution-specific code is a row in auth.IDPS naming the identity provider's form. Vanity domains are not supported — UCF's Canvas must be reached as ucf.instructure.com, not webcourses.ucf.edu.

    Sign-in adapts to what the provider asks for. Entra tenants often default to a passkey, which this tool cannot answer (the credential lives in hardware); it declines truthfully and takes whatever else is offered — a password, or an authenticator app. Whether a password is wanted at all is the provider's decision, not a setting here. Use --passwordless to prefer the app even when a password is on offer, and --factor passcode to type a code instead of approving a push. One-time codes are always prompted for, never stored: automating them from a stored seed would put both factors in one file.

  • No display required — runs headless

Install

pip install canvasser

That is the whole install. The first command that needs a browser will notice one is missing and offer to fetch it:

Chromium is not installed; canvasser cannot drive Canvas without it.
Download it now (~150 MB, one time)? [y/N]

Answer y and it continues into the command you asked for. To do it ahead of time, or in a script, run canvasser install-browser (add --with-deps on Linux to pull the system libraries Chromium needs; that part needs root).

Why there is a download at all. The playwright package on PyPI ships the automation library and its driver, not the browser — browser builds are large platform-specific native binaries, not Python, so no PyPI package carries them. Playwright pins an exact build per library version, which is the whole reason page behaviour is reproducible.

The browsers land in a shared per-user cache (~/.cache/ms-playwright and equivalents), so it is once per machine rather than once per virtualenv — verified under plain venvs, pipx, uv tool install, and throwaway uvx environments. Nothing is downloaded without asking: with no terminal attached (cron, CI, a pipe) canvasser declines and prints the command instead.

From a checkout instead

python3 -m venv .venv
./.venv/bin/pip install -e .
./.venv/bin/python -m canvasser install-browser --with-deps

Where it keeps things

The saved session, browser profile, optional secrets file, and any debug snapshots share one per-user directory — never the working directory, so nothing credential-bearing can be committed by accident. (Snapshots render whole Canvas pages, so they can contain student data; the directory is created mode 700.)

%LOCALAPPDATA%\canvasser Windows
~/Library/Application Support/canvasser macOS
$XDG_STATE_HOME/canvasser otherwise — usually ~/.local/state/canvasser

Set $CANVASSER_HOME to put it somewhere else; that is the only thing that overrides the platform default. Use it to keep state on a durable or encrypted volume.

For a second institution, use --institution <subdomain> rather than a second $CANVASSER_HOME: it keeps that Canvas's credentials, session and browser profile in a subdirectory of the same state directory, so the two cannot overwrite each other's login. The account you already have stays exactly where it is.

Credentials

Resolved per field, first source that has them wins:

  1. --secrets-file PATH (--username supplies the name)
  2. $CANVAS_USERNAME / $CANVAS_PASSWORD
  3. canvas.env in the state directory above (mode 600)
  4. an interactive prompt

The former $GATORLINK_USERNAME / $GATORLINK_PASSWORD spellings are still read, so an existing canvas.env keeps working; -v reports them as deprecated.

There is deliberately no --password flag. OpenSSH is the model: argv is not private — it lands in shell history and /proc/<pid>/cmdline is world-readable. For scripted runs, sshpass answers the prompt, exactly as it does for ssh:

sshpass -f ~/.canvas-pw canvasser --username jjb pull 580777

Prompts read /dev/tty, not stdin, so this works even when stdin is a pipe. --no-prompt disables prompting entirely for unattended use; -v (or --verbose) reports which source each value came from, never the value itself.

--headed runs the browser visibly instead of headless, on a machine with a display. It is the escape hatch for a sign-in canvasser cannot complete on its own — a hardware security key, or a passkey that needs a QR scanned from the browser's own dialog. Finish it by hand in that window and the session is saved exactly as it would be otherwise.

Second factors

Have your phone in hand when you run login. What you are asked for depends on the institution, and canvasser prints whatever the page shows so you can act on it — the number to match exists only inside a headless browser otherwise. It is framed in a box at both providers, because it arrives in a window of about a minute and a line of ordinary text is easy to scroll past. If the number cannot be read off the page, the box still appears with the instruction, and the page's own words are printed below it.

what appears
Duo (e.g. UF) a number to tap in the Duo app re-read every 5s in case the push is re-sent
Microsoft Authenticator (e.g. UCF) a number to match in the app the page polls until you approve
none (e.g. Temple) nothing the login simply completes

--factor passcode types a 6-digit code instead of approving a push — it works for Duo and for Microsoft Authenticator.

If you receive a push you were not expecting, deny it.

Your MFA stays intact: a stored password is something you know, the phone is something you have. No TOTP seed is ever stored — one-time codes are prompted for and forgotten, because keeping the seed beside the password would put both factors in one file. Once the provider remembers the device (Duo: ~10 hours), runs need no interaction at all.

Selecting a course

courses and pull share three independent scope axes:

Axis Default Flags
enrollment both active and archived --active / --archived
publish state any --published / --unpublished
favorite favorites only --favorite / --unmarked

--all opens all three axes at once — both sides of each, the widest possible scope. It therefore does not compose with a narrowing flag: --all --published is every course, not every published one, because --all has already named --unpublished too.

Naming both sides of an axis unions them--active --archived is every enrollment, which is what the words say. Favorites is the one deliberately narrowed default — it is the list you curate in Canvas itself, via the star on the Courses page. Canvas's own "Current Enrollments" is not a useful definition of current: on a long-lived account it is mostly sandboxes and dev shells.

pull takes a course id or a name fragment, and an ambiguous fragment is an error rather than a guess. Omit it entirely for an interactive picker:

canvasser pull 580777
canvasser pull "Comp Engr Design"
canvasser pull --course-file ~/current-class.txt
CANVASSER_COURSE=580777 canvasser pull
canvasser pull                          # numbered picker

The datesheet CSV

# canvasser datesheet v3.1,,course=580777,timezone=Eastern Time (US & Canada),,,,iana=America/New_York,,
Assignment Details,,,,unlock_at,,due_at,,lock_at,
assignment_id,override_id,title,assign_to,open_date,open_time,due_date,due_time,close_date,close_time
7289050,,01 - Equipment Demonstration,Everyone,2026-08-21,00:00,2026-08-28,23:59,2026-09-02,23:59

Three date fields, each split into a date column and a time column so a spreadsheet can bulk-shift them: open_* is Canvas's unlock_at ("Available from"), due_* is due_at, close_* is lock_at ("Until").

  • Only assignment_id is required. Delete any other column, any row, and both preamble rows — the file still reads. Columns are matched by name, so order does not matter either.
  • An absent column means "leave this alone"; an empty cell means "clear it". Deleting the close_* columns will not wipe your lock dates.
  • Rows are matched on (assignment_id, override_id), never on title — renaming an assignment in the spreadsheet cannot retarget a write.
  • The timezone is declared once, in row 1, twice over: timezone= is Canvas's familiar name for people, iana= is the identifier push resolves wall clocks through. Values themselves carry no offset.
  • The declared zone says what the sheet's own times are written in — nothing more. If it is not the course's zone, push converts every value into course time before comparing or writing, preserving the instant, and prints what it did. A sheet edited in Tokyo saying 2026-08-29 12:59 and a New York course holding 2026-08-28 23:59 are the same deadline.
  • iana= wins, but timezone= is a real fallback. Canvas is Rails, so Eastern Time (US & Canada) is an ActiveSupport::TimeZone name and maps to an IANA zone; if you delete iana= the friendly label still resolves. All 153 of Canvas's labels are recognised, including the ones IANA has since renamed (Kyiv, Greenland, Rangoon), and a label with Canvas's offset pair still attached is accepted. A label that cannot be read is reported, not silently ignored. Note EST and -05:00 deliberately do not resolve: one names half a year, the other says nothing about DST.
  • With no zone declared at all, the times are taken to be course-local already.
  • Times are minute-only. Canvas's time box has no seconds field, so seconds cannot be written; 11:59 PM is what a person types and Canvas applies its own :59. Seconds you type are accepted and dropped.
  • Dates and times are read forgivingly, because spreadsheets reformat them: 2026-02-01, 2/1/2026, 1 Feb 2026, 2026年2月1日; 23:59, 11:59 PM, 2359. A numeric date that could be US or European is resolved US-first and reported; one that is real in only one order is read that way silently; one that is real in neither is refused.

What push will not do

  • assignments with per-section or per-student overrides — saving Canvas's edit form submits every date card, so a wrong move there would silently delete a student's accommodation date. Untested, so refused outright;
  • a time that does not exist, in the hour skipped by a daylight-saving change;
  • a date with no time, when the sheet's zone differs from the course's — converting would have to assume a time, and a different assumed time lands on a different date;
  • anything Canvas itself will reject. Its rules are enforced server-side and reported only on the page, so push both checks what it can up front and reads Canvas's answer back afterwards:
4 row(s) have dates Canvas will not accept:
    7289053: lock_at 2026-10-04 23:59 is before due_at 2026-10-09 23:59 -- Canvas
             refuses this ('Until date cannot be before due date')
    Fix these cells in the sheet; they will be skipped.

Ordering is caught before any page is loaded. Other rules — dates before the term start, for instance — surface as Canvas's own wording after the save attempt. Either way the row is skipped and the rest of the push proceeds.

Clearing a date works: leave the cells empty in a column the sheet carries, and push uses the form's own Clear control.

The infosheet CSV

Everything about an assignment that is not a date:

# canvasser infosheet v1.1,,course=580777,,,,,,,,
Assignment Details,,,,Grading,,Submission,,Availability,,
,needs --rename,read-only,read-only,,,,,false->true only,,read-only
assignment_id,title,kind,assignment_group,points_possible,grading_type,submission_types,allowed_attempts,published,peer_reviews,override_count
7289050,01 - Equipment Demonstration,assignment,Intro Assignments,30,points,external_tool,-1,true,false,0

Row 3 says what push may do with each column. A blank marker means the column is writable with no ceremony; the others are read-only (a fact rather than a setting), needs --rename, and false->true only for published, which Canvas offers a control for in one direction and not the other. It is written for you to read and is never read back — edit it, delete it, or delete the whole preamble; the header row is found by searching for assignment_id.

A separate file from the datesheet, and the reason is one rule, not two. A date is the one field Canvas lets an assignment simply not have — so on the datesheet an empty cell clears the date. Nothing here can be unset: an assignment always has a publish state, always sits in exactly one group, and an empty points box is coerced to 0, which would be a silent grade change rather than an absence. So on the infosheet an empty cell means "leave this field alone", the same as deleting the column. There is no "clear" marker because there is nothing for it to mark.

  • Rows are matched on assignment_id alone. There is no override_id — these settings are per assignment, not per date card.
  • Read-only columns: kind, assignment_group, override_count. They are written so the sheet is legible and ignored on the way back in. (title is editable — see below — but only with --rename; without it a changed title is reported and skipped.) Two of them open up on a NEW row: kind and assignment_group must be settable when creating, since a new assignment has to be put in a group and the two kinds use different endpoints. Editing never needs either; creating always does.
  • kind explains the blanks. A classic quiz's page carries no grading_type, submission_types or peer_reviews at all, so those cells are empty on every quiz row — that is a fact about quizzes, not a failed read.
  • kind is one of assignment, quiz or discussion. A graded discussion is listed among your assignments and is read like any other row, dates and settings included. It cannot be written: Canvas edits those on its discussions app, which canvasser does not drive, so push names the row and refuses it — in the preview, before you commit.
  • override_count tells you which assignments carry overrides before you start editing, rather than when push refuses the row.
  • Values are written exactly as Canvas reports them, including allowed_attempts = -1 for unlimited.

What push writes from it

points_possible, grading_type, submission_types, allowed_attempts, peer_reviews, and published in one direction. Anything else an editable column can express is reported per row as NOT WRITABLE YET and skipped — never silently dropped.

Publishing goes one way, and that is Canvas's limit rather than a missing feature. There is no publish checkbox on an assignment form; Canvas offers a second submit button, "Save & Publish", exactly while an assignment is unpublished. So:

what happens
published blank left alone
false -> true published, on a NEW row and on an existing one
true -> false reported NOT WRITABLE YET — no control exists. Unpublish it in Canvas

A row asking to publish says so explicitly in the preview, and the result is read back from Canvas afterwards, because this is the one setting a student notices the moment it changes. If the button is missing when it is needed, the write is refused rather than saved unpublished — asking to publish and quietly not publishing is the failure this refuses.

Each is compared by meaning rather than text, so a spreadsheet's reformatting is not mistaken for an edit: points numerically (8.34 = 8.340), submission types as a set (order does not matter), attempts as an integer (3 = 3.0), peer review as a boolean (TRUE = true, which is what a spreadsheet writes back). Comparing these as strings would report a change nobody made, write it, and report it again on every push afterwards.

Renaming needs --rename. A changed title is reported and skipped unless you ask for it:

canvasser push info-580777.csv --rename            # preview the rename
canvasser push info-580777.csv --rename --commit   # write it

--rename un-gates the one column; it does not write anything on its own, and --commit remains the only flag that writes. The gate is deliberate: the title is also the column you read to find your row, so an edit made to keep the sheet legible should not quietly rename what students see. Nothing is retargeted either way — rows are matched on assignment_id, so a renamed cell still writes to its own row.

--only narrows a push to the rows you name, which matters because push loads one page per row and a full sheet can be fifty of them:

canvasser push info-169156.csv --only 3590538
canvasser push info-169156.csv --only 3590538 --only 3591457 --commit

It narrows the sheet before reading, so a one-row check costs one page load instead of fifty. Points to know:

  • It matches assignment_id. On the datesheet that brings a row's override cards with it — they are saved together, so they are selected together.
  • A narrowed run creates nothing. --only names ids that already exist, so it cannot select a NEW row, and --only NEW is refused rather than treated as "all the new ones". The run tells you how many new rows it set aside.
  • An id the sheet does not carry is refused by name, before anything is read or written.
  • It is a selector, not a permission: it implies neither --commit nor --rename.

Length is not pre-checked. Canvas's quiz form declares a 254-character limit and its assignment form declares none, so the limit that applies is read off the form itself: an over-long title is truncated by the box, the read-back no longer matches what was asked for, and the write refuses before saving rather than silently storing a shortened name.

submission_types takes the online sub-types (online_upload, online_text_entry, online_url, media_recording, student_annotation) or a whole mode (none, on_paper). external_tool is refused: it needs a tool URL this sheet has no column for, so writing it would leave an assignment configured for a tool it does not have.

peer_reviews takes true or false — and also yes/no and 1/0, because a spreadsheet that recognises true as a boolean re-saves it as TRUE. Anything else is refused before a page is loaded rather than guessed at either way.

allowed_attempts takes a positive count, or -1 for unlimited — Canvas's own encoding, which is what the sheet carries. Canvas hides the control unless the assignment accepts submissions, so limiting attempts on a none submission type is refused, naming submission_types as the thing to set first.

That cuts the other way too, and it is the one case where a write touches a field your sheet never mentioned. Changing an assignment's submission type to none or on_paper takes its attempts limit with it — Canvas has nowhere to keep a limit on something nobody can submit to. The limit is read before the change and reported afterwards, with what Canvas holds now, so a lost setting is never silent:

AGENT TEST  #3590538
    submission_types  online_text_entry -> none   Canvas now: none   OK
    allowed_attempts  Canvas offers no Allowed Attempts control for this submission
                      type, so its limit of 3 is probably dropped -- the line below
                      says what it actually did
                      Canvas now: -1   MISMATCH

A limit that the form drops for any other reason is put back before the save, so an edit to one field cannot quietly unset another.

grading_type takes the option values, not the words on the form: points, percent, letter_grade, gpa_scale, pass_fail, not_graded. Anything else is refused before a page is loaded, naming what is accepted:

Check-In 1   #7289061
    grading_type='Points' is not one of points, percent, letter_grade, gpa_scale,
    pass_fail, not_graded (these are the option values, not the words shown on the form)

Changing points on an assignment that already has graded submissions re-scales every student's percentage, so it is called out against the row and again before writing. It is a warning, not a refusal — the write proceeds.

Changing grading_type to not_graded is called out the same way — beside the row, in the summary, and again before writing — because it takes the assignment out of grade calculations and hides its points, and Canvas accepts it without a word:

    grading_type      points         -> not_graded
    ^ LEAVES THE GRADEBOOK -- 'not_graded' removes this assignment from grade
      calculations and hides its points; Canvas will not refuse it

Creating assignments: put NEW in the id cell

Add a row whose assignment_id reads NEW, give it a title and a submission_types, and push --commit creates it. The id Canvas assigns is written straight back into that cell, so the next push is an ordinary edit rather than a second assignment. That write-back is what makes creating safe to re-run — it is the only thing this tool does that cannot simply be repeated.

assignment_id,title,kind,assignment_group,points_possible,grading_type,submission_types,...
NEW,Homework 1,assignment,Exercises,10,points,online_upload,...
  • You must supply title and submission_types. Canvas defaults the rest — 0 points, the first assignment group, points grading — and every default you are relying on is named in the preview and again in the result, because a default is only harmless when it is expected. submission_types is required because Canvas's own default is "Online with nothing ticked", which it then refuses to save: "Please choose at least one submission type."
  • assignment_group and kind become writable on a NEW row, and only there. Editing an assignment never needs to set them; creating one always does. The group is named the way the sheet shows it — the group's own name, not its id.
  • Only assignment can be created. kind=quiz is refused rather than quietly made an assignment: classic quizzes and New Quizzes are created at different endpoints and the sheet has no column that says which you mean. kind=discussion is refused too — Canvas makes those on a page canvasser does not drive. This matters more than it used to now that pull writes discussion rows: copying one to make a new row carries the kind cell with it. Make the quiz or discussion in Canvas and pull again.
  • Close the file first. push refuses to create anything while your spreadsheet still has the sheet open, and re-reads the file after each write-back to confirm the id landed. If a spreadsheet saved NEW back over a real id, the next push would create a duplicate.
  • published works here too. Set it to true and the assignment is created already visible to students, using the "Save & Publish" button Canvas offers while an assignment is unpublished. Blank or false creates it unpublished. Either way the preview states which, because that is the one setting a student notices immediately. On an assignment that already exists, false -> true works the same way; true -> false does not, because Canvas offers no control for it.
  • A new assignment has no dates, because dates live in the other sheet. Run canvasser pull afterwards to pick the new rows up, then edit and push the datesheet.

Nothing is created without --commit. A plain push lists what it would create, with the count stated plainly — there is no delete command, so an assignment created by mistake has to be removed in Canvas by hand.

When something fails, it says why

Every failure prints its reason against the line it happened on, rather than a bare count. A post-write mismatch distinguishes Canvas still holds its previous value (the save was rejected — its message is on the edit form) from Canvas holds a third value (it accepted the write and then altered it).

After writing, push re-reads the assignment's own page state and compares date and time, so a save that silently did not take is reported rather than assumed.

Exit codes

push uses its exit code to say what it found, which matters when you are reading a log rather than a terminal:

code means
0 nothing to do, or the commit finished cleanly
1 a preview with changes pending — not an error
2 one or more items did not land (each reason is printed above), or a field the write had to give up — such as an attempts limit on a submission type that cannot hold one — or the run stopped on an error such as an unreadable sheet, a refused login or a rejected write
3 halted part-way through creating, and the message says what state Canvas is in

1 is the one to be careful with. A dry run that found work to do exits non-zero by design, so a script treating any non-zero as failure will call a healthy preview broken.

Data handling

This repository is public.

  • Credentials never live in the repo. They belong in the state directory described under "Where it keeps things", which is deliberately outside any working tree.
  • storage_state.json (the saved session) is credential-equivalent — it grants Canvas access with no password.
  • The browser profile holds live session cookies and is as sensitive as the password.
  • Debug snapshots render real Canvas pages, so they are written outside the repository on purpose — and a page that names students is not captured at all. Gradebooks, rosters, SpeedGrader, submissions and an assignment whose "Assign to" cards target individuals are refused before anything is written; a short text note recording the URL and the reason is left in place of the screenshot, so a failure there is still visible.
  • Pulled CSVs are gitignored: they will contain per-student rows once individual overrides are in scope.

License

GPL-3.0-or-later. See LICENSE.

This program is free software: you may redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. It comes with absolutely no warranty.

Scope: official Instructure hosts only

Built against UF and extended to other institutions. Point it elsewhere with --institution <subdomain>, which keeps that Canvas's credentials, saved session and browser profile in their own directory — so a second account cannot overwrite the first's session. Selection follows the usual precedence: --institution, then $CANVAS_INSTITUTION, then default_institution in the secrets file, then the account you already set up.

Two things another institution must supply, because neither is guessable:

  • CANVAS_SSO_PATH — the Canvas login route. There is deliberately no default: /login/saml/355 is UF's own SAML provider id, and using it elsewhere would send your credentials to UF's identity provider. Unset, a non-UF institution is refused.
  • A supported second factor. Two are implemented and have been used against a live login: Duo (UF) and Microsoft Authenticator (UCF), each with a push to approve or a code to type. Temple asks for none, and that works too — whether a second factor is presented at all is the provider's decision. A third would be an addition rather than a rewrite: sign-in is driven by recognising what is on screen, not by following a fixed sequence of steps.

Only *.instructure.com hosts are supported. Self-hosted and vanity-domain Canvas are refused outright: the institution is identified by its subdomain, and a host without one would make two different schools indistinguishable in the state directory.

Release files for canvasser 0.2.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for canvasser 0.2.0
File Size Uploaded
canvasser-0.2.0.tar.gz 205.8 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for canvasser 0.2.0
File Interpreter ABI Platform
canvasser-0.2.0-py3-none-any.whl Python 3 none any Details

Total release size: 398.6 kB

Release files / canvasser-0.2.0.tar.gz

Download URL canvasser-0.2.0.tar.gz
Size 205.8 kB
Tags Source
SHA-256 checksum
How to use checksums
5a98fb23389fcf9546665e01599d0f76098ad6d8222b1bbba579d67f902e22ed
BLAKE2b-256 checksum
How to use checksums
edab10657e6f67a2eaa16eb16c02bd75ad93f0019664d12c6b459e4ae5d13d23
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.13.5

Release files / canvasser-0.2.0-py3-none-any.whl

Download URL canvasser-0.2.0-py3-none-any.whl
Size 192.8 kB
Tags Python 3
SHA-256 checksum
How to use checksums
105382312717db11221ec79c86e2c8189f50b204a367663bcaed1909fbdafd98
BLAKE2b-256 checksum
How to use checksums
0133e463f945fd2827fa6036e5af67982e0f2ca436de0b6fe93e1eaef98d793d
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.13.5

Release history Release notifications | RSS feed

This release

0.2.0 This release

2 release files

0.1.9

2 release files

0.1.8

2 release files

0.1.7

2 release files

0.1.6

2 release files

0.1.5

2 release files

0.1.1

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page