canvas-pull 2.4.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- canvas_pull-2.4.0/.gitignore +181 -0
- canvas_pull-2.4.0/CHANGELOG.md +81 -0
- canvas_pull-2.4.0/LICENSE +21 -0
- canvas_pull-2.4.0/PKG-INFO +280 -0
- canvas_pull-2.4.0/README.md +245 -0
- canvas_pull-2.4.0/pyproject.toml +66 -0
- canvas_pull-2.4.0/src/canvas_pull/__init__.py +3 -0
- canvas_pull-2.4.0/src/canvas_pull/__main__.py +3 -0
- canvas_pull-2.4.0/src/canvas_pull/api.py +187 -0
- canvas_pull-2.4.0/src/canvas_pull/cli.py +531 -0
- canvas_pull-2.4.0/src/canvas_pull/collector.py +308 -0
- canvas_pull-2.4.0/src/canvas_pull/config.py +211 -0
- canvas_pull-2.4.0/src/canvas_pull/gitops.py +52 -0
- canvas_pull-2.4.0/src/canvas_pull/manifest.py +132 -0
- canvas_pull-2.4.0/src/canvas_pull/models.py +61 -0
- canvas_pull-2.4.0/src/canvas_pull/notify.py +46 -0
- canvas_pull-2.4.0/src/canvas_pull/schedule.py +215 -0
- canvas_pull-2.4.0/src/canvas_pull/summary.py +72 -0
- canvas_pull-2.4.0/src/canvas_pull/sync.py +231 -0
- canvas_pull-2.4.0/tests/__init__.py +0 -0
- canvas_pull-2.4.0/tests/fake_canvas.py +157 -0
- canvas_pull-2.4.0/tests/test_cli.py +141 -0
- canvas_pull-2.4.0/tests/test_config.py +52 -0
- canvas_pull-2.4.0/tests/test_schedule.py +90 -0
- canvas_pull-2.4.0/tests/test_summary.py +71 -0
- canvas_pull-2.4.0/tests/test_sync.py +330 -0
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
# Byte-compiled / optimized / DLL files
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*$py.class
|
|
5
|
+
|
|
6
|
+
# C extensions
|
|
7
|
+
*.so
|
|
8
|
+
|
|
9
|
+
# Distribution / packaging
|
|
10
|
+
.Python
|
|
11
|
+
build/
|
|
12
|
+
develop-eggs/
|
|
13
|
+
dist/
|
|
14
|
+
downloads/
|
|
15
|
+
eggs/
|
|
16
|
+
.eggs/
|
|
17
|
+
lib/
|
|
18
|
+
lib64/
|
|
19
|
+
parts/
|
|
20
|
+
sdist/
|
|
21
|
+
var/
|
|
22
|
+
wheels/
|
|
23
|
+
share/python-wheels/
|
|
24
|
+
*.egg-info/
|
|
25
|
+
.installed.cfg
|
|
26
|
+
*.egg
|
|
27
|
+
MANIFEST
|
|
28
|
+
|
|
29
|
+
# PyInstaller
|
|
30
|
+
# Usually these files are written by a python script from a template
|
|
31
|
+
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
|
32
|
+
*.manifest
|
|
33
|
+
*.spec
|
|
34
|
+
|
|
35
|
+
# Installer logs
|
|
36
|
+
pip-log.txt
|
|
37
|
+
pip-delete-this-directory.txt
|
|
38
|
+
|
|
39
|
+
# Unit test / coverage reports
|
|
40
|
+
htmlcov/
|
|
41
|
+
.tox/
|
|
42
|
+
.nox/
|
|
43
|
+
.coverage
|
|
44
|
+
.coverage.*
|
|
45
|
+
.cache
|
|
46
|
+
nosetests.xml
|
|
47
|
+
coverage.xml
|
|
48
|
+
*.cover
|
|
49
|
+
*.py,cover
|
|
50
|
+
.hypothesis/
|
|
51
|
+
.pytest_cache/
|
|
52
|
+
cover/
|
|
53
|
+
|
|
54
|
+
# Translations
|
|
55
|
+
*.mo
|
|
56
|
+
*.pot
|
|
57
|
+
|
|
58
|
+
# Django stuff:
|
|
59
|
+
*.log
|
|
60
|
+
local_settings.py
|
|
61
|
+
db.sqlite3
|
|
62
|
+
db.sqlite3-journal
|
|
63
|
+
|
|
64
|
+
# Flask stuff:
|
|
65
|
+
instance/
|
|
66
|
+
.webassets-cache
|
|
67
|
+
|
|
68
|
+
# Scrapy stuff:
|
|
69
|
+
.scrapy
|
|
70
|
+
|
|
71
|
+
# Sphinx documentation
|
|
72
|
+
docs/_build/
|
|
73
|
+
|
|
74
|
+
# PyBuilder
|
|
75
|
+
.pybuilder/
|
|
76
|
+
target/
|
|
77
|
+
|
|
78
|
+
# Jupyter Notebook
|
|
79
|
+
.ipynb_checkpoints
|
|
80
|
+
|
|
81
|
+
# IPython
|
|
82
|
+
profile_default/
|
|
83
|
+
ipython_config.py
|
|
84
|
+
|
|
85
|
+
# pyenv
|
|
86
|
+
# For a library or package, you might want to ignore these files since the code is
|
|
87
|
+
# intended to run in multiple environments; otherwise, check them in:
|
|
88
|
+
# .python-version
|
|
89
|
+
|
|
90
|
+
# pipenv
|
|
91
|
+
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
|
92
|
+
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
|
93
|
+
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
|
94
|
+
# install all needed dependencies.
|
|
95
|
+
#Pipfile.lock
|
|
96
|
+
|
|
97
|
+
# UV
|
|
98
|
+
# Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
|
|
99
|
+
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
|
100
|
+
# commonly ignored for libraries.
|
|
101
|
+
#uv.lock
|
|
102
|
+
|
|
103
|
+
# poetry
|
|
104
|
+
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
|
|
105
|
+
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
|
106
|
+
# commonly ignored for libraries.
|
|
107
|
+
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
|
|
108
|
+
#poetry.lock
|
|
109
|
+
|
|
110
|
+
# pdm
|
|
111
|
+
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
|
|
112
|
+
#pdm.lock
|
|
113
|
+
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
|
|
114
|
+
# in version control.
|
|
115
|
+
# https://pdm.fming.dev/latest/usage/project/#working-with-version-control
|
|
116
|
+
.pdm.toml
|
|
117
|
+
.pdm-python
|
|
118
|
+
.pdm-build/
|
|
119
|
+
|
|
120
|
+
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
|
|
121
|
+
__pypackages__/
|
|
122
|
+
|
|
123
|
+
# Celery stuff
|
|
124
|
+
celerybeat-schedule
|
|
125
|
+
celerybeat.pid
|
|
126
|
+
|
|
127
|
+
# SageMath parsed files
|
|
128
|
+
*.sage.py
|
|
129
|
+
|
|
130
|
+
# Environments
|
|
131
|
+
.env
|
|
132
|
+
.venv
|
|
133
|
+
env/
|
|
134
|
+
venv/
|
|
135
|
+
ENV/
|
|
136
|
+
env.bak/
|
|
137
|
+
venv.bak/
|
|
138
|
+
|
|
139
|
+
# Spyder project settings
|
|
140
|
+
.spyderproject
|
|
141
|
+
.spyproject
|
|
142
|
+
|
|
143
|
+
# Rope project settings
|
|
144
|
+
.ropeproject
|
|
145
|
+
|
|
146
|
+
# mkdocs documentation
|
|
147
|
+
/site
|
|
148
|
+
|
|
149
|
+
# mypy
|
|
150
|
+
.mypy_cache/
|
|
151
|
+
.dmypy.json
|
|
152
|
+
dmypy.json
|
|
153
|
+
|
|
154
|
+
# Pyre type checker
|
|
155
|
+
.pyre/
|
|
156
|
+
|
|
157
|
+
# pytype static type analyzer
|
|
158
|
+
.pytype/
|
|
159
|
+
|
|
160
|
+
# Cython debug symbols
|
|
161
|
+
cython_debug/
|
|
162
|
+
|
|
163
|
+
# PyCharm
|
|
164
|
+
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
|
|
165
|
+
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
|
|
166
|
+
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
|
167
|
+
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
|
168
|
+
#.idea/
|
|
169
|
+
|
|
170
|
+
# Ruff stuff:
|
|
171
|
+
.ruff_cache/
|
|
172
|
+
|
|
173
|
+
# PyPI configuration file
|
|
174
|
+
.pypirc
|
|
175
|
+
|
|
176
|
+
# canvas-pull local settings (may contain tokens)
|
|
177
|
+
/config/
|
|
178
|
+
/config.yml
|
|
179
|
+
.canvas-pull/
|
|
180
|
+
.DS_Store
|
|
181
|
+
.idea/
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project are documented here.
|
|
4
|
+
The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and the project
|
|
5
|
+
uses [Semantic Versioning](https://semver.org/).
|
|
6
|
+
|
|
7
|
+
## [Unreleased]
|
|
8
|
+
|
|
9
|
+
## [2.4.0] - 2026-11-01
|
|
10
|
+
|
|
11
|
+
First release on PyPI: `uv tool install canvas-pull` or `pipx install canvas-pull`.
|
|
12
|
+
|
|
13
|
+
### Added
|
|
14
|
+
- Windows: the sync lock now works there too (`msvcrt`), so a manual and a background sync can't
|
|
15
|
+
run at the same time.
|
|
16
|
+
- CI on macOS, Linux and Windows with Python 3.11–3.13; tagged releases publish to PyPI
|
|
17
|
+
automatically.
|
|
18
|
+
- Contributor guide, security policy, issue templates and a list of tested schools.
|
|
19
|
+
|
|
20
|
+
### Changed
|
|
21
|
+
- Paths under your home folder print as `~/...`.
|
|
22
|
+
- `sync` lists changes sorted by course, change type and path, not in download order.
|
|
23
|
+
|
|
24
|
+
### Fixed
|
|
25
|
+
- File and folder names that are reserved on Windows (`CON`, `NUL`, `COM1`, …) get a `_` prefix
|
|
26
|
+
so they can be saved.
|
|
27
|
+
|
|
28
|
+
## [2.3.0] - 2026-11-01
|
|
29
|
+
|
|
30
|
+
### Changed
|
|
31
|
+
- Much faster scanning. Courses are scanned in parallel, folder and file lists are fetched at
|
|
32
|
+
the same time, and linked files are resolved in parallel. On a 32-course account: 75 s → 10.6 s
|
|
33
|
+
(Files tab only), 410 s → 60 s (all sources).
|
|
34
|
+
- Module items are requested inline (`include[]=items`).
|
|
35
|
+
|
|
36
|
+
### Added
|
|
37
|
+
- `scan_concurrency` setting (default 8) caps the number of API calls in flight.
|
|
38
|
+
- Completed courses are scanned once. After a clean sync they are skipped; use `--refresh` to
|
|
39
|
+
scan them again. Enabling a new source rescans them automatically.
|
|
40
|
+
|
|
41
|
+
## [2.2.0] - 2026-11-01
|
|
42
|
+
|
|
43
|
+
### Changed
|
|
44
|
+
- Only the Files tab is synced by default. Other sources are opt-in through `sources`. When the
|
|
45
|
+
Files tab is hidden or empty, Modules is still used as a fallback.
|
|
46
|
+
|
|
47
|
+
## [2.1.0] - 2026-11-01
|
|
48
|
+
|
|
49
|
+
### Added
|
|
50
|
+
- `canvas-pull schedule enable|disable|status|run`: automatic sync through launchd (macOS) or a
|
|
51
|
+
systemd user timer (Linux).
|
|
52
|
+
- `sync --notify` shows desktop notifications for changes, failed downloads and a rejected
|
|
53
|
+
token.
|
|
54
|
+
- More file sources: syllabus, assignment descriptions, pages, announcements, discussions.
|
|
55
|
+
- `--completed` archives concluded courses.
|
|
56
|
+
- `log --since 7d`: merged summary of all changes in a time window.
|
|
57
|
+
|
|
58
|
+
## [2.0.0] - 2026-11-01
|
|
59
|
+
|
|
60
|
+
Complete rewrite as an installable CLI.
|
|
61
|
+
|
|
62
|
+
### Added
|
|
63
|
+
- Commands: `init`, `courses`, `status`, `sync`, `log`, `config`.
|
|
64
|
+
- Incremental sync tracked by Canvas file ID. It reports new, modified, moved, deleted and
|
|
65
|
+
restored files, and detects renames without downloading again.
|
|
66
|
+
- Replaced versions are kept in `.canvas-pull/history/`.
|
|
67
|
+
- Token stored in the system keychain.
|
|
68
|
+
- Optional `--git` commit after each sync.
|
|
69
|
+
|
|
70
|
+
### Fixed
|
|
71
|
+
- Pagination is followed, so courses with more than 10 files are complete.
|
|
72
|
+
- Unchanged files are no longer downloaded again on every run.
|
|
73
|
+
- HTTP errors are reported instead of ignored.
|
|
74
|
+
|
|
75
|
+
## [1.0.0] - 2025-03-02
|
|
76
|
+
|
|
77
|
+
- First version: download all files of enrolled courses.
|
|
78
|
+
|
|
79
|
+
[Unreleased]: https://github.com/gxxxr-111/canvas-pull/compare/v2.4.0...HEAD
|
|
80
|
+
[2.4.0]: https://github.com/gxxxr-111/canvas-pull/compare/v1.0.0...v2.4.0
|
|
81
|
+
[1.0.0]: https://github.com/gxxxr-111/canvas-pull/releases/tag/v1.0.0
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Mirage_
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,280 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: canvas-pull
|
|
3
|
+
Version: 2.4.0
|
|
4
|
+
Summary: Sync your Canvas LMS course files: download only what changed and see what instructors added, modified, moved or deleted.
|
|
5
|
+
Project-URL: Homepage, https://github.com/gxxxr-111/canvas-pull
|
|
6
|
+
Project-URL: Repository, https://github.com/gxxxr-111/canvas-pull
|
|
7
|
+
Project-URL: Issues, https://github.com/gxxxr-111/canvas-pull/issues
|
|
8
|
+
Project-URL: Changelog, https://github.com/gxxxr-111/canvas-pull/blob/main/CHANGELOG.md
|
|
9
|
+
Author-email: Mirage_ <gaoxuran111@gmail.com>
|
|
10
|
+
License-Expression: MIT
|
|
11
|
+
License-File: LICENSE
|
|
12
|
+
Keywords: canvas,canvas-lms,cli,download,instructure,lms,students,sync
|
|
13
|
+
Classifier: Development Status :: 4 - Beta
|
|
14
|
+
Classifier: Environment :: Console
|
|
15
|
+
Classifier: Intended Audience :: Education
|
|
16
|
+
Classifier: Intended Audience :: End Users/Desktop
|
|
17
|
+
Classifier: Operating System :: MacOS
|
|
18
|
+
Classifier: Operating System :: Microsoft :: Windows
|
|
19
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
20
|
+
Classifier: Programming Language :: Python :: 3
|
|
21
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
23
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
24
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
25
|
+
Classifier: Topic :: Education
|
|
26
|
+
Classifier: Topic :: Utilities
|
|
27
|
+
Requires-Python: >=3.11
|
|
28
|
+
Requires-Dist: httpx>=0.27
|
|
29
|
+
Requires-Dist: keyring>=25
|
|
30
|
+
Requires-Dist: platformdirs>=4.2
|
|
31
|
+
Requires-Dist: pydantic>=2.7
|
|
32
|
+
Requires-Dist: rich>=13.7
|
|
33
|
+
Requires-Dist: typer>=0.12
|
|
34
|
+
Description-Content-Type: text/markdown
|
|
35
|
+
|
|
36
|
+
<h1 align="center">Canvas Pull</h1>
|
|
37
|
+
|
|
38
|
+
<p align="center">
|
|
39
|
+
<b>Keep a local copy of your Canvas course files, and see exactly what changed.</b>
|
|
40
|
+
</p>
|
|
41
|
+
|
|
42
|
+
<p align="center">
|
|
43
|
+
<a href="https://github.com/gxxxr-111/canvas-pull/actions/workflows/ci.yml"><img src="https://github.com/gxxxr-111/canvas-pull/actions/workflows/ci.yml/badge.svg" alt="CI" /></a>
|
|
44
|
+
<a href="https://pypi.org/project/canvas-pull/"><img src="https://img.shields.io/pypi/v/canvas-pull" alt="PyPI" /></a>
|
|
45
|
+
<a href="https://pypi.org/project/canvas-pull/"><img src="https://img.shields.io/pypi/pyversions/canvas-pull" alt="Python versions" /></a>
|
|
46
|
+
<a href="LICENSE"><img src="https://img.shields.io/badge/license-MIT-green" alt="MIT license" /></a>
|
|
47
|
+
</p>
|
|
48
|
+
|
|
49
|
+
<p align="center">
|
|
50
|
+
<a href="#quick-start">Quick start</a> ·
|
|
51
|
+
<a href="#commands">Commands</a> ·
|
|
52
|
+
<a href="#faq">FAQ</a> ·
|
|
53
|
+
<a href="#中文说明">中文说明</a>
|
|
54
|
+
</p>
|
|
55
|
+
|
|
56
|
+
canvas-pull downloads the files of your Canvas LMS courses with one command. Every later run
|
|
57
|
+
downloads only what changed and tells you which files your instructors **added, modified,
|
|
58
|
+
renamed/moved or deleted**. It can run on its own every morning and notify you.
|
|
59
|
+
|
|
60
|
+
<p align="center">
|
|
61
|
+
<img src="https://raw.githubusercontent.com/gxxxr-111/canvas-pull/main/docs/demo.svg" alt="canvas-pull sync and log --since output" width="760" />
|
|
62
|
+
</p>
|
|
63
|
+
|
|
64
|
+
## Features
|
|
65
|
+
|
|
66
|
+
- **Incremental sync.** Files are tracked by their Canvas file ID, so unchanged files are never
|
|
67
|
+
downloaded again and renames are detected without re-downloading.
|
|
68
|
+
- **Change report.** `status` previews changes, `sync` applies them, `log` shows past syncs,
|
|
69
|
+
and `log --since 7d` gives a merged weekly summary.
|
|
70
|
+
- **Automatic sync.** `schedule enable` runs a sync every day (or every few hours) and shows a
|
|
71
|
+
desktop notification when something changes.
|
|
72
|
+
- **Old versions kept.** When a file changes, the previous copy is moved to
|
|
73
|
+
`.canvas-pull/history/<timestamp>/`. Files deleted on Canvas stay on your disk.
|
|
74
|
+
- **Files tab by default, more on request.** Only the Files tab is synced by default (with
|
|
75
|
+
Modules as a fallback when a course hides it). Opt in to the syllabus, assignment
|
|
76
|
+
descriptions, pages, announcements and discussions through the `sources` setting.
|
|
77
|
+
- **Current courses, or everything.** Courses whose term has ended are skipped by default;
|
|
78
|
+
`--completed` also archives concluded courses. A completed course is scanned once; later syncs
|
|
79
|
+
skip it (use `--refresh` to scan it again).
|
|
80
|
+
- **Safe token storage.** `init` stores your token in the system keychain, not in a text file.
|
|
81
|
+
- **Optional git.** `sync --git` commits each sync in the save folder.
|
|
82
|
+
- **Read-only and private.** Only reads from Canvas; no telemetry, nothing leaves your machine.
|
|
83
|
+
- **Fast.** Courses are scanned in parallel (a 32-course account scans in about 10 s instead of
|
|
84
|
+
75 s), with parallel downloads, automatic retries with back-off and full pagination.
|
|
85
|
+
|
|
86
|
+
## Install
|
|
87
|
+
|
|
88
|
+
```bash
|
|
89
|
+
uv tool install canvas-pull # or: pipx install canvas-pull
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
Try it without installing: `uvx canvas-pull --help`. Python 3.11+ on macOS, Linux or Windows.
|
|
93
|
+
To get the latest unreleased code: `uv tool install git+https://github.com/gxxxr-111/canvas-pull`.
|
|
94
|
+
|
|
95
|
+
## Quick start
|
|
96
|
+
|
|
97
|
+
1. In Canvas, open **Account → Settings → New Access Token** and copy the token.
|
|
98
|
+
Give it an expiry date. Never share it.
|
|
99
|
+
2. Run:
|
|
100
|
+
|
|
101
|
+
```bash
|
|
102
|
+
canvas-pull init # Canvas URL (e.g. oc.sjtu.edu.cn), save folder, token
|
|
103
|
+
canvas-pull courses # check which courses will be synced
|
|
104
|
+
canvas-pull sync
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
## Commands
|
|
108
|
+
|
|
109
|
+
| Command | What it does |
|
|
110
|
+
| --- | --- |
|
|
111
|
+
| `canvas-pull init` | Set Canvas URL, save folder and token (token goes to the keychain). |
|
|
112
|
+
| `canvas-pull courses [--all] [--completed]` | List courses that would be synced. |
|
|
113
|
+
| `canvas-pull status [-c COURSE] [--completed]` | Show what changed on Canvas. Downloads nothing. |
|
|
114
|
+
| `canvas-pull sync [-n] [-c COURSE] [--completed] [--refresh] [--git] [--notify]` | Download changes. `-n` is a dry run. `-c` takes a course ID or part of its name and can be repeated. `--refresh` rescans archived completed courses. `--notify` shows a desktop notification. |
|
|
115
|
+
| `canvas-pull log [-n 10]` | Show recent syncs. |
|
|
116
|
+
| `canvas-pull log --since 7d` | Merged summary since `7d`, `24h`, `2w`, `today` or a date. |
|
|
117
|
+
| `canvas-pull schedule enable [--at 08:00 ...] [--every 6h]` | Sync automatically (launchd on macOS, systemd timer on Linux). Default: daily 09:00. |
|
|
118
|
+
| `canvas-pull schedule status \| run \| disable` | Show the schedule and its log, trigger it now, or remove it. |
|
|
119
|
+
| `canvas-pull config` | Show settings in use. |
|
|
120
|
+
|
|
121
|
+
## Configuration
|
|
122
|
+
|
|
123
|
+
`init` writes a config file (path shown by `canvas-pull config`). You can edit it:
|
|
124
|
+
|
|
125
|
+
```toml
|
|
126
|
+
website = "https://oc.sjtu.edu.cn"
|
|
127
|
+
save_dir = "/Users/me/Canvas"
|
|
128
|
+
concurrency = 4 # parallel downloads
|
|
129
|
+
scan_concurrency = 8 # parallel API requests while scanning (max 16)
|
|
130
|
+
keep_history = true # keep replaced versions in .canvas-pull/history
|
|
131
|
+
git = false # commit after every sync
|
|
132
|
+
exclude = ["PE", "12345"] # course IDs or name fragments to skip
|
|
133
|
+
include_completed = false # also sync concluded courses
|
|
134
|
+
sources = ["files"] # default; add any of the sources below
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
Available sources: `files`, `modules`, `syllabus`, `assignments`, `pages`, `announcements`,
|
|
138
|
+
`discussions`. With the default, a course that hides its Files tab (or leaves it empty) falls
|
|
139
|
+
back to Modules automatically. To grab everything:
|
|
140
|
+
|
|
141
|
+
```toml
|
|
142
|
+
sources = ["files", "modules", "syllabus", "assignments", "pages", "announcements", "discussions"]
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
Environment variables override the file: `CANVAS_URL`, `CANVAS_TOKEN`, `CANVAS_SAVE_DIR`. A
|
|
146
|
+
`.env` file in the current folder is also read (v1 keys `WEBSITE`, `ACCESS_TOKEN`, `SAVE_DIR`
|
|
147
|
+
still work).
|
|
148
|
+
|
|
149
|
+
When a file appears in several places it keeps the path of the first source in that list, so a
|
|
150
|
+
file in the Files tab stays where it is even if an assignment links to it. Files found elsewhere
|
|
151
|
+
go under `Modules/<module>/`, `Assignments/<name>/`, `Pages/<title>/`, `Announcements/<title>/`,
|
|
152
|
+
`Discussions/<title>/` or `Syllabus/`.
|
|
153
|
+
|
|
154
|
+
### Automatic sync
|
|
155
|
+
|
|
156
|
+
```bash
|
|
157
|
+
canvas-pull schedule enable --at 08:00 --at 20:00 # or: --every 6h
|
|
158
|
+
canvas-pull schedule run # try it once now
|
|
159
|
+
canvas-pull schedule status # schedule + last log lines
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
The job runs `canvas-pull sync --notify` from the folder where you enabled it. A Mac that was
|
|
163
|
+
asleep at the scheduled time catches up when it wakes. Install with `uv tool install` first so
|
|
164
|
+
the job points at a stable path.
|
|
165
|
+
|
|
166
|
+
On Windows, `schedule` is not available yet: create a daily task in Task Scheduler that runs
|
|
167
|
+
`canvas-pull sync`. Desktop notifications are macOS and Linux only for now.
|
|
168
|
+
|
|
169
|
+
## How change detection works
|
|
170
|
+
|
|
171
|
+
Each synced file is recorded in `<save_dir>/.canvas-pull/manifest.json` with its Canvas ID,
|
|
172
|
+
path, size, last-modified time and SHA-256. On every run canvas-pull compares that record with
|
|
173
|
+
Canvas metadata:
|
|
174
|
+
|
|
175
|
+
| Canvas says | Result |
|
|
176
|
+
| --- | --- |
|
|
177
|
+
| ID not seen before | **new**: download |
|
|
178
|
+
| Size or modified time changed | **modified**: download; if the bytes are identical, nothing is reported |
|
|
179
|
+
| Same content, different name/folder | **moved**: renamed locally, no download |
|
|
180
|
+
| ID gone from a course that was read successfully | **deleted**: reported once; local copy kept |
|
|
181
|
+
| Recorded but missing on disk | **restored**: downloaded again |
|
|
182
|
+
|
|
183
|
+
Content-level diffs of PDF/PPTX/DOCX are out of scope for now.
|
|
184
|
+
|
|
185
|
+
## Tested schools
|
|
186
|
+
|
|
187
|
+
canvas-pull uses the standard Canvas REST API, so it should work with any Canvas site where
|
|
188
|
+
students can create access tokens.
|
|
189
|
+
|
|
190
|
+
| School | Canvas site | Status |
|
|
191
|
+
| --- | --- | --- |
|
|
192
|
+
| Shanghai Jiao Tong University (上海交通大学) | `oc.sjtu.edu.cn` | ✅ Works |
|
|
193
|
+
|
|
194
|
+
Tried it at your school? Please open a
|
|
195
|
+
[school report](https://github.com/gxxxr-111/canvas-pull/issues/new?template=school_report.yml),
|
|
196
|
+
whether it worked or not.
|
|
197
|
+
|
|
198
|
+
## FAQ
|
|
199
|
+
|
|
200
|
+
**My school doesn't let students create access tokens.**
|
|
201
|
+
That isn't supported yet. Please file a school report so we know which schools need another
|
|
202
|
+
sign-in method.
|
|
203
|
+
|
|
204
|
+
**What does "N locked file(s) not yet available" mean?**
|
|
205
|
+
The instructor has locked those files (until a date, or behind a module requirement). They are
|
|
206
|
+
downloaded automatically once they unlock.
|
|
207
|
+
|
|
208
|
+
**Where did the old version of a changed file go?**
|
|
209
|
+
To `<save_dir>/.canvas-pull/history/<timestamp>/`, keeping its folder structure. Turn this off
|
|
210
|
+
with `keep_history = false`.
|
|
211
|
+
|
|
212
|
+
**I deleted a downloaded file by accident.**
|
|
213
|
+
Run `canvas-pull sync`; it is downloaded again and reported as *restored*.
|
|
214
|
+
|
|
215
|
+
**Can canvas-pull change anything on Canvas?**
|
|
216
|
+
No. It only sends read (`GET`) requests.
|
|
217
|
+
|
|
218
|
+
**My token expired.**
|
|
219
|
+
Create a new one in Canvas and run `canvas-pull init` again. Scheduled syncs show a notification
|
|
220
|
+
when the token is rejected.
|
|
221
|
+
|
|
222
|
+
**How do I skip a course?**
|
|
223
|
+
Add its ID or part of its name to `exclude` in the config file (`canvas-pull courses` shows IDs).
|
|
224
|
+
|
|
225
|
+
## Privacy and security
|
|
226
|
+
|
|
227
|
+
- The token is kept in the OS keychain and is only sent to your Canvas site. It is not forwarded
|
|
228
|
+
when a download redirects to file storage.
|
|
229
|
+
- No telemetry and no third-party services.
|
|
230
|
+
- See [SECURITY.md](SECURITY.md) for details and how to report a vulnerability.
|
|
231
|
+
|
|
232
|
+
## Related projects
|
|
233
|
+
|
|
234
|
+
Other Canvas download tools you may also like:
|
|
235
|
+
|
|
236
|
+
- [skyzh/canvas_grab](https://github.com/skyzh/canvas_grab): sync script with a GUI, popular at SJTU.
|
|
237
|
+
- [perslev/CanvasSync](https://github.com/perslev/CanvasSync): syncs modules, assignments and files.
|
|
238
|
+
- [aik2mlj/canvas-downloader](https://github.com/aik2mlj/canvas-downloader): fast downloader written in Rust.
|
|
239
|
+
- [BrkBuilds/Canvas-Downloader](https://github.com/BrkBuilds/Canvas-Downloader): desktop app for Windows and macOS.
|
|
240
|
+
- [Beebeeoii/lominus](https://github.com/Beebeeoii/lominus): file sync for NUS Canvas.
|
|
241
|
+
|
|
242
|
+
canvas-pull focuses on the command line and automation: it tracks files by ID, reports exactly
|
|
243
|
+
what changed (including renames), keeps old versions, and can run unattended.
|
|
244
|
+
|
|
245
|
+
## Contributing
|
|
246
|
+
|
|
247
|
+
Bug reports, school reports and pull requests are welcome. See
|
|
248
|
+
[CONTRIBUTING.md](CONTRIBUTING.md). Tests run against an in-memory fake Canvas, so no account is
|
|
249
|
+
needed:
|
|
250
|
+
|
|
251
|
+
```bash
|
|
252
|
+
uv sync && uv run pytest
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
Changes are listed in [CHANGELOG.md](CHANGELOG.md). Licensed under [MIT](LICENSE).
|
|
256
|
+
|
|
257
|
+
---
|
|
258
|
+
|
|
259
|
+
## 中文说明
|
|
260
|
+
|
|
261
|
+
一条命令同步所有**正在进行的** Canvas 课程文件。之后每次运行只下载有变化的文件,并列出老师**新增、修改、改名/移动、删除**了哪些文件。
|
|
262
|
+
|
|
263
|
+
```bash
|
|
264
|
+
uv tool install canvas-pull # 或 pipx install canvas-pull
|
|
265
|
+
canvas-pull init # 填学校 Canvas 域名(如 oc.sjtu.edu.cn)、保存目录、Access Token
|
|
266
|
+
canvas-pull status # 只看变化,不下载
|
|
267
|
+
canvas-pull sync # 下载变化
|
|
268
|
+
canvas-pull log --since 7d # 本周汇总
|
|
269
|
+
canvas-pull schedule enable --at 08:00 # 每天早上自动同步,有变化时弹通知
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
- Token 在 Canvas 的「账户 → 设置 → 新建访问许可证」生成,`init` 会把它存进系统钥匙串。
|
|
273
|
+
- 文件被修改时,旧版本会移到 `.canvas-pull/history/`;Canvas 上删除的文件本地不会删除。
|
|
274
|
+
- 默认只同步「文件」栏目;课程隐藏了文件栏目时,会自动改从「模块」里找。
|
|
275
|
+
- 想要更多来源(课程大纲、作业、页面、公告、讨论里的附件和链接),在配置文件里设置 `sources`。
|
|
276
|
+
- `--completed` 可以把已结业的课程也一起归档下来。已结业的课只完整扫描一次,之后自动跳过(`--refresh` 强制重扫)。
|
|
277
|
+
- 多门课并行扫描,32 门课大约 10 秒扫完。
|
|
278
|
+
- `sync --git` 会在保存目录里自动提交一次 git,方便用 `git log` 回看。
|
|
279
|
+
- 只读取 Canvas,不会修改任何内容;没有遥测,数据不离开你的电脑。
|
|
280
|
+
- 在你的学校试过了?欢迎提交一个 [School report](https://github.com/gxxxr-111/canvas-pull/issues/new?template=school_report.yml),无论成功与否。
|