QuizGenerator 0.9.0__tar.gz → 0.10.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.
- quizgenerator-0.10.0/.github/pull_request_template.md +36 -0
- quizgenerator-0.10.0/.github/workflows/release.yaml +146 -0
- {quizgenerator-0.9.0 → quizgenerator-0.10.0}/.gitignore +1 -2
- quizgenerator-0.10.0/AGENTS.md +58 -0
- quizgenerator-0.10.0/CLAUDE.md +80 -0
- {quizgenerator-0.9.0 → quizgenerator-0.10.0}/PKG-INFO +26 -17
- quizgenerator-0.10.0/QuizGenerator/README.md +5 -0
- {quizgenerator-0.9.0 → quizgenerator-0.10.0}/QuizGenerator/canvas/canvas_interface.py +6 -2
- {quizgenerator-0.9.0 → quizgenerator-0.10.0}/QuizGenerator/contentast.py +32 -10
- {quizgenerator-0.9.0 → quizgenerator-0.10.0}/QuizGenerator/generate.py +50 -9
- quizgenerator-0.10.0/QuizGenerator/logging.yaml +55 -0
- {quizgenerator-0.9.0 → quizgenerator-0.10.0}/QuizGenerator/premade_questions/cst463/gradient_descent/loss_calculations.py +21 -18
- {quizgenerator-0.9.0 → quizgenerator-0.10.0}/QuizGenerator/premade_questions/cst463/models/attention.py +0 -1
- {quizgenerator-0.9.0 → quizgenerator-0.10.0}/QuizGenerator/premade_questions/cst463/models/cnns.py +0 -1
- {quizgenerator-0.9.0 → quizgenerator-0.10.0}/QuizGenerator/premade_questions/cst463/models/rnns.py +0 -1
- {quizgenerator-0.9.0 → quizgenerator-0.10.0}/QuizGenerator/premade_questions/cst463/models/text.py +0 -1
- {quizgenerator-0.9.0 → quizgenerator-0.10.0}/QuizGenerator/premade_questions/cst463/models/weight_counting.py +20 -1
- {quizgenerator-0.9.0 → quizgenerator-0.10.0}/QuizGenerator/qrcode_generator.py +116 -54
- {quizgenerator-0.9.0 → quizgenerator-0.10.0}/QuizGenerator/question.py +30 -6
- {quizgenerator-0.9.0 → quizgenerator-0.10.0}/QuizGenerator/regenerate.py +23 -9
- {quizgenerator-0.9.0 → quizgenerator-0.10.0}/README.md +21 -13
- quizgenerator-0.10.0/documentation/GRADING_GUIDE.md +284 -0
- quizgenerator-0.10.0/documentation/PARAMETER_STANDARDS.md +111 -0
- quizgenerator-0.10.0/documentation/README.md +183 -0
- quizgenerator-0.10.0/documentation/WEB_UI_INTEGRATION.md +446 -0
- quizgenerator-0.10.0/documentation/claude_todo.md +253 -0
- quizgenerator-0.10.0/documentation/custom_questions.md +426 -0
- quizgenerator-0.10.0/documentation/getting_started.md +72 -0
- quizgenerator-0.10.0/documentation/quickstart.md +49 -0
- quizgenerator-0.10.0/documentation/yaml_config_guide.md +76 -0
- quizgenerator-0.10.0/example_files/cst334.yaml +223 -0
- quizgenerator-0.10.0/example_files/cst463.yaml +325 -0
- quizgenerator-0.10.0/example_files/example_exam.yaml +41 -0
- quizgenerator-0.10.0/examples/README.md +55 -0
- quizgenerator-0.10.0/examples/example_question.py +105 -0
- quizgenerator-0.9.0/pyproject_prev.toml → quizgenerator-0.10.0/pyproject.toml +7 -4
- quizgenerator-0.9.0/pyproject.toml → quizgenerator-0.10.0/pyproject_prev.toml +6 -3
- {quizgenerator-0.9.0 → quizgenerator-0.10.0}/uv.lock +10 -8
- {quizgenerator-0.9.0 → quizgenerator-0.10.0}/.envrc +0 -0
- {quizgenerator-0.9.0 → quizgenerator-0.10.0}/CODEOWNERS +0 -0
- {quizgenerator-0.9.0 → quizgenerator-0.10.0}/LICENSE +0 -0
- {quizgenerator-0.9.0 → quizgenerator-0.10.0}/QuizGenerator/__init__.py +0 -0
- {quizgenerator-0.9.0 → quizgenerator-0.10.0}/QuizGenerator/__main__.py +0 -0
- {quizgenerator-0.9.0 → quizgenerator-0.10.0}/QuizGenerator/canvas/__init__.py +0 -0
- {quizgenerator-0.9.0 → quizgenerator-0.10.0}/QuizGenerator/canvas/classes.py +0 -0
- {quizgenerator-0.9.0 → quizgenerator-0.10.0}/QuizGenerator/constants.py +0 -0
- {quizgenerator-0.9.0 → quizgenerator-0.10.0}/QuizGenerator/misc.py +0 -0
- {quizgenerator-0.9.0 → quizgenerator-0.10.0}/QuizGenerator/mixins.py +0 -0
- {quizgenerator-0.9.0 → quizgenerator-0.10.0}/QuizGenerator/performance.py +0 -0
- {quizgenerator-0.9.0 → quizgenerator-0.10.0}/QuizGenerator/premade_questions/__init__.py +0 -0
- {quizgenerator-0.9.0 → quizgenerator-0.10.0}/QuizGenerator/premade_questions/basic.py +0 -0
- {quizgenerator-0.9.0 → quizgenerator-0.10.0}/QuizGenerator/premade_questions/cst334/__init__.py +0 -0
- {quizgenerator-0.9.0 → quizgenerator-0.10.0}/QuizGenerator/premade_questions/cst334/languages.py +0 -0
- {quizgenerator-0.9.0 → quizgenerator-0.10.0}/QuizGenerator/premade_questions/cst334/math_questions.py +0 -0
- {quizgenerator-0.9.0 → quizgenerator-0.10.0}/QuizGenerator/premade_questions/cst334/memory_questions.py +0 -0
- {quizgenerator-0.9.0 → quizgenerator-0.10.0}/QuizGenerator/premade_questions/cst334/ostep13_vsfs.py +0 -0
- {quizgenerator-0.9.0 → quizgenerator-0.10.0}/QuizGenerator/premade_questions/cst334/persistence_questions.py +0 -0
- {quizgenerator-0.9.0 → quizgenerator-0.10.0}/QuizGenerator/premade_questions/cst334/process.py +0 -0
- {quizgenerator-0.9.0 → quizgenerator-0.10.0}/QuizGenerator/premade_questions/cst463/__init__.py +0 -0
- {quizgenerator-0.9.0 → quizgenerator-0.10.0}/QuizGenerator/premade_questions/cst463/gradient_descent/__init__.py +0 -0
- {quizgenerator-0.9.0 → quizgenerator-0.10.0}/QuizGenerator/premade_questions/cst463/gradient_descent/gradient_calculation.py +0 -0
- {quizgenerator-0.9.0 → quizgenerator-0.10.0}/QuizGenerator/premade_questions/cst463/gradient_descent/gradient_descent_questions.py +0 -0
- {quizgenerator-0.9.0 → quizgenerator-0.10.0}/QuizGenerator/premade_questions/cst463/gradient_descent/misc.py +0 -0
- {quizgenerator-0.9.0 → quizgenerator-0.10.0}/QuizGenerator/premade_questions/cst463/math_and_data/__init__.py +0 -0
- {quizgenerator-0.9.0 → quizgenerator-0.10.0}/QuizGenerator/premade_questions/cst463/math_and_data/matrix_questions.py +0 -0
- {quizgenerator-0.9.0 → quizgenerator-0.10.0}/QuizGenerator/premade_questions/cst463/math_and_data/vector_questions.py +0 -0
- {quizgenerator-0.9.0 → quizgenerator-0.10.0}/QuizGenerator/premade_questions/cst463/models/__init__.py +0 -0
- {quizgenerator-0.9.0 → quizgenerator-0.10.0}/QuizGenerator/premade_questions/cst463/models/matrices.py +0 -0
- {quizgenerator-0.9.0 → quizgenerator-0.10.0}/QuizGenerator/premade_questions/cst463/neural-network-basics/__init__.py +0 -0
- {quizgenerator-0.9.0 → quizgenerator-0.10.0}/QuizGenerator/premade_questions/cst463/neural-network-basics/neural_network_questions.py +0 -0
- {quizgenerator-0.9.0 → quizgenerator-0.10.0}/QuizGenerator/premade_questions/cst463/tensorflow-intro/__init__.py +0 -0
- {quizgenerator-0.9.0 → quizgenerator-0.10.0}/QuizGenerator/premade_questions/cst463/tensorflow-intro/tensorflow_questions.py +0 -0
- {quizgenerator-0.9.0 → quizgenerator-0.10.0}/QuizGenerator/quiz.py +0 -0
- {quizgenerator-0.9.0 → quizgenerator-0.10.0}/QuizGenerator/typst_utils.py +0 -0
- {quizgenerator-0.9.0 → quizgenerator-0.10.0}/examples/web_ui_integration_example.py +0 -0
- {quizgenerator-0.9.0 → quizgenerator-0.10.0}/scripts/generate_practice_yaml.sh +0 -0
- {quizgenerator-0.9.0 → quizgenerator-0.10.0}/scripts/print.sh +0 -0
- {quizgenerator-0.9.0 → quizgenerator-0.10.0}/scripts/vendor_lms_interface.py +0 -0
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# Pull Request
|
|
2
|
+
|
|
3
|
+
## Description
|
|
4
|
+
<!-- Provide a brief description of what this PR accomplishes -->
|
|
5
|
+
|
|
6
|
+
## Changes Made
|
|
7
|
+
<!-- List the key changes made in this PR -->
|
|
8
|
+
-
|
|
9
|
+
-
|
|
10
|
+
-
|
|
11
|
+
|
|
12
|
+
## Type of Change
|
|
13
|
+
<!-- Mark the appropriate option with an [x] -->
|
|
14
|
+
- [ ] Bug fix (non-breaking change that fixes an issue)
|
|
15
|
+
- [ ] New feature (non-breaking change that adds functionality)
|
|
16
|
+
- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected)
|
|
17
|
+
- [ ] Documentation update
|
|
18
|
+
- [ ] Refactoring (no functional changes)
|
|
19
|
+
|
|
20
|
+
## Testing Done
|
|
21
|
+
<!-- Describe the testing you've done to verify your changes -->
|
|
22
|
+
|
|
23
|
+
## Checklist
|
|
24
|
+
<!-- Mark the appropriate options with an [x] -->
|
|
25
|
+
- [ ] My code follows the style guidelines of this project
|
|
26
|
+
- [ ] I have performed a self-review of my own code
|
|
27
|
+
- [ ] I have commented my code, particularly in hard-to-understand areas
|
|
28
|
+
- [ ] I have updated the documentation accordingly
|
|
29
|
+
- [ ] My changes generate no new warnings
|
|
30
|
+
- [ ] Any dependent changes have been merged and published
|
|
31
|
+
|
|
32
|
+
## Screenshots (if applicable)
|
|
33
|
+
<!-- Add screenshots here if they help explain your changes -->
|
|
34
|
+
|
|
35
|
+
## Notes
|
|
36
|
+
<!-- Any additional notes or context about the PR -->
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
name: Release on version bump
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches:
|
|
6
|
+
- main # change if your default branch is different
|
|
7
|
+
workflow_dispatch: # allows manual triggering from GitHub Actions UI
|
|
8
|
+
|
|
9
|
+
jobs:
|
|
10
|
+
release:
|
|
11
|
+
runs-on: ubuntu-latest
|
|
12
|
+
environment:
|
|
13
|
+
name: pypi
|
|
14
|
+
permissions:
|
|
15
|
+
contents: write # create tags & releases
|
|
16
|
+
id-token: write # PyPI trusted publishing
|
|
17
|
+
|
|
18
|
+
steps:
|
|
19
|
+
- name: Checkout
|
|
20
|
+
uses: actions/checkout@v5
|
|
21
|
+
with:
|
|
22
|
+
fetch-depth: 0 # need history to read previous version
|
|
23
|
+
|
|
24
|
+
- name: Set up Python for tooling
|
|
25
|
+
uses: actions/setup-python@v5
|
|
26
|
+
with:
|
|
27
|
+
python-version: "3.12"
|
|
28
|
+
|
|
29
|
+
- name: Get previous version (from parent commit)
|
|
30
|
+
id: prev_version
|
|
31
|
+
run: |
|
|
32
|
+
BEFORE="${{ github.event.before }}"
|
|
33
|
+
|
|
34
|
+
# First commit / no parent? treat as "no previous version"
|
|
35
|
+
if [ "$BEFORE" = "0000000000000000000000000000000000000000" ] || [ -z "$BEFORE" ]; then
|
|
36
|
+
echo "No previous commit; setting previous version empty."
|
|
37
|
+
echo "version=" >> "$GITHUB_OUTPUT"
|
|
38
|
+
else
|
|
39
|
+
if git show "$BEFORE":pyproject.toml > pyproject_prev.toml 2>/dev/null; then
|
|
40
|
+
VERSION=$(python -c "import tomllib, pathlib; d = tomllib.loads(pathlib.Path('pyproject_prev.toml').read_text()); print(d['project']['version'])")
|
|
41
|
+
echo "previous version: $VERSION"
|
|
42
|
+
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
|
|
43
|
+
else
|
|
44
|
+
echo "No previous pyproject.toml found; setting previous version empty."
|
|
45
|
+
echo "version=" >> "$GITHUB_OUTPUT"
|
|
46
|
+
fi
|
|
47
|
+
fi
|
|
48
|
+
|
|
49
|
+
- name: Get current version
|
|
50
|
+
id: curr_version
|
|
51
|
+
run: |
|
|
52
|
+
VERSION=$(python -c "import tomllib, pathlib; d = tomllib.loads(pathlib.Path('pyproject.toml').read_text()); print(d['project']['version'])")
|
|
53
|
+
echo "current version: $VERSION"
|
|
54
|
+
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
|
|
55
|
+
|
|
56
|
+
- name: Decide whether this is a release commit
|
|
57
|
+
id: decision
|
|
58
|
+
run: |
|
|
59
|
+
PREV="${{ steps.prev_version.outputs.version }}"
|
|
60
|
+
CURR="${{ steps.curr_version.outputs.version }}"
|
|
61
|
+
MSG="${{ github.event.head_commit.message }}"
|
|
62
|
+
|
|
63
|
+
echo "Previous version: '$PREV'"
|
|
64
|
+
echo "Current version: '$CURR'"
|
|
65
|
+
echo "Commit message: '$MSG'"
|
|
66
|
+
|
|
67
|
+
SHOULD_RELEASE=false
|
|
68
|
+
|
|
69
|
+
# Release whenever the version changes
|
|
70
|
+
if [ -n "$CURR" ] && [ "$CURR" != "$PREV" ]; then
|
|
71
|
+
echo "Version changed from '$PREV' to '$CURR'; triggering release."
|
|
72
|
+
SHOULD_RELEASE=true
|
|
73
|
+
else
|
|
74
|
+
echo "Version did not change; not releasing."
|
|
75
|
+
fi
|
|
76
|
+
|
|
77
|
+
echo "release=$SHOULD_RELEASE" >> "$GITHUB_OUTPUT"
|
|
78
|
+
|
|
79
|
+
- name: Stop if not a release
|
|
80
|
+
if: steps.decision.outputs.release != 'true'
|
|
81
|
+
run: |
|
|
82
|
+
echo "Not a release commit; skipping remaining steps."
|
|
83
|
+
exit 0
|
|
84
|
+
|
|
85
|
+
- name: Ensure tag does not already exist
|
|
86
|
+
if: steps.decision.outputs.release == 'true'
|
|
87
|
+
run: |
|
|
88
|
+
VERSION="${{ steps.curr_version.outputs.version }}"
|
|
89
|
+
TAG="v${VERSION}"
|
|
90
|
+
|
|
91
|
+
echo "Checking for existing tag $TAG"
|
|
92
|
+
if git rev-parse "$TAG" >/dev/null 2>&1; then
|
|
93
|
+
echo "Tag $TAG already exists. Aborting."
|
|
94
|
+
exit 1
|
|
95
|
+
fi
|
|
96
|
+
|
|
97
|
+
- name: Create and push tag
|
|
98
|
+
if: steps.decision.outputs.release == 'true'
|
|
99
|
+
run: |
|
|
100
|
+
VERSION="${{ steps.curr_version.outputs.version }}"
|
|
101
|
+
TAG="v${VERSION}"
|
|
102
|
+
|
|
103
|
+
echo "Creating tag $TAG at $GITHUB_SHA"
|
|
104
|
+
|
|
105
|
+
git config user.name "github-actions[bot]"
|
|
106
|
+
git config user.email "github-actions[bot]@users.noreply.github.com"
|
|
107
|
+
|
|
108
|
+
git tag "$TAG" "$GITHUB_SHA"
|
|
109
|
+
git push origin "$TAG"
|
|
110
|
+
|
|
111
|
+
- name: Install uv
|
|
112
|
+
if: steps.decision.outputs.release == 'true'
|
|
113
|
+
uses: astral-sh/setup-uv@v6
|
|
114
|
+
|
|
115
|
+
- name: Install Python 3.13 for build
|
|
116
|
+
if: steps.decision.outputs.release == 'true'
|
|
117
|
+
run: uv python install 3.13
|
|
118
|
+
|
|
119
|
+
- name: Build
|
|
120
|
+
if: steps.decision.outputs.release == 'true'
|
|
121
|
+
run: uv build
|
|
122
|
+
|
|
123
|
+
- name: Upload dist artifacts
|
|
124
|
+
if: steps.decision.outputs.release == 'true'
|
|
125
|
+
uses: actions/upload-artifact@v4
|
|
126
|
+
with:
|
|
127
|
+
name: dist
|
|
128
|
+
path: dist/
|
|
129
|
+
|
|
130
|
+
- name: Publish to PyPI
|
|
131
|
+
if: steps.decision.outputs.release == 'true'
|
|
132
|
+
run: uv publish
|
|
133
|
+
|
|
134
|
+
- name: Create GitHub Release
|
|
135
|
+
if: steps.decision.outputs.release == 'true'
|
|
136
|
+
uses: softprops/action-gh-release@v2
|
|
137
|
+
with:
|
|
138
|
+
tag_name: v${{ steps.curr_version.outputs.version }}
|
|
139
|
+
name: v${{ steps.curr_version.outputs.version }}
|
|
140
|
+
body: |
|
|
141
|
+
Release v${{ steps.curr_version.outputs.version }}
|
|
142
|
+
|
|
143
|
+
- Version bumped in pyproject.toml
|
|
144
|
+
- Built with uv and published to PyPI.
|
|
145
|
+
- Artifacts attached below.
|
|
146
|
+
files: dist/*
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# Repository Guidelines
|
|
2
|
+
|
|
3
|
+
## Project Structure & Module Organization
|
|
4
|
+
|
|
5
|
+
- `QuizGenerator/` is the main Python package (question types, quiz generation, Canvas integration).
|
|
6
|
+
- `quizgen` provides the CLI entry point; the package also exposes `quizregen`.
|
|
7
|
+
- `documentation/` contains user guides and configuration references.
|
|
8
|
+
- `example_files/` and `examples/` hold sample YAML configs and outputs.
|
|
9
|
+
- `scripts/` includes maintenance utilities (see `scripts/README.md`).
|
|
10
|
+
- `out/` is the default output directory for generated PDFs and artifacts.
|
|
11
|
+
|
|
12
|
+
## Build, Test, and Development Commands
|
|
13
|
+
|
|
14
|
+
- `pip install -e .` — install the package in editable mode for local development.
|
|
15
|
+
- `quizgen --help` — show CLI options.
|
|
16
|
+
- `quizgen --yaml example_files/example_exam.yaml --num_pdfs 3` — generate PDFs in `out/` (Typst is the default renderer).
|
|
17
|
+
- `quizgen --latex --num_pdfs 3` — force LaTeX rendering when needed.
|
|
18
|
+
- `python scripts/vendor_lms_interface.py --dry-run` — preview LMSInterface vendoring changes.
|
|
19
|
+
|
|
20
|
+
## Coding Style & Naming Conventions
|
|
21
|
+
|
|
22
|
+
- Python 3.12+; follow PEP 8 with 4-space indentation.
|
|
23
|
+
- Use `snake_case` for functions/variables, `PascalCase` for classes, and `UPPER_SNAKE_CASE` for constants.
|
|
24
|
+
- Keep modules focused; new question types belong under `QuizGenerator/premade_questions/` or a custom module referenced in YAML.
|
|
25
|
+
|
|
26
|
+
## Question Authoring Pattern
|
|
27
|
+
|
|
28
|
+
- Implement `_build_context`, `_build_body`, and `_build_explanation` on every question class.
|
|
29
|
+
- `_build_context` should seed and return a `QuestionContext`; use `context.rng` (or `context["rng"]`) for deterministic randomness.
|
|
30
|
+
- Add extra data via `context["key"] = value` (stored in `context.data`); keep instance state immutable.
|
|
31
|
+
- `_build_body` and `_build_explanation` take `context` and should avoid mutating instance state.
|
|
32
|
+
- Do not implement or call `refresh()`; it has been removed.
|
|
33
|
+
|
|
34
|
+
## Testing Guidelines
|
|
35
|
+
|
|
36
|
+
- No dedicated test suite is currently committed.
|
|
37
|
+
- For changes, run a manual smoke check by generating a small quiz:
|
|
38
|
+
- `quizgen --yaml example_files/example_exam.yaml --num_pdfs 1`
|
|
39
|
+
- If adding tests, prefer `tests/` with filenames like `test_<area>.py` and document how to run them.
|
|
40
|
+
|
|
41
|
+
## Commit & Pull Request Guidelines
|
|
42
|
+
|
|
43
|
+
- Commit messages in this repo are short, sentence-case summaries (e.g., “Fixing rounding in round robin”).
|
|
44
|
+
- Keep commits focused and explain *why* when the change is non-obvious.
|
|
45
|
+
- PRs should include:
|
|
46
|
+
- A brief description of the change and rationale.
|
|
47
|
+
- Example output or screenshots for rendering/formatting changes.
|
|
48
|
+
- Any new CLI flags or YAML options documented in `documentation/`.
|
|
49
|
+
|
|
50
|
+
## Security & Configuration Tips
|
|
51
|
+
|
|
52
|
+
- Canvas credentials are read from `~/.env` (`CANVAS_API_URL`, `CANVAS_API_KEY`).
|
|
53
|
+
- PDF generation uses Typst by default; LaTeX is legacy and may be removed.
|
|
54
|
+
- Avoid committing generated files in `out/` or local secrets.
|
|
55
|
+
|
|
56
|
+
## Agent Notes Policy
|
|
57
|
+
|
|
58
|
+
- Store any agent-generated Markdown notes in `agent_notes/` at the repo root.
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
# CLAUDE.md
|
|
2
|
+
|
|
3
|
+
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
|
4
|
+
|
|
5
|
+
## Project Overview
|
|
6
|
+
|
|
7
|
+
This is a Python-based teaching tools project focused on quiz generation and Canvas LMS integration. The main entry point is `generate_quiz.py`, which generates quizzes in both PDF format and Canvas variations based on YAML configuration files.
|
|
8
|
+
|
|
9
|
+
## Architecture
|
|
10
|
+
|
|
11
|
+
### Core Modules
|
|
12
|
+
- **QuizGenerator/**: Main quiz generation engine
|
|
13
|
+
- `quiz.py`: Core Quiz class that loads from YAML and generates output
|
|
14
|
+
- `question.py`: Question classes and registry system for different question types
|
|
15
|
+
- `premade_questions/`: Collection of specialized question generators (memory, processes, persistence, etc.)
|
|
16
|
+
- `misc.py`: Utilities for output formats and content handling
|
|
17
|
+
|
|
18
|
+
- **lms_interface/**: Canvas LMS integration (submodule)
|
|
19
|
+
- `canvas_interface.py`: CanvasInterface and CanvasCourse classes for API interaction
|
|
20
|
+
- `classes.py`: Data models for LMS objects
|
|
21
|
+
|
|
22
|
+
### Question System
|
|
23
|
+
The project uses a plugin-based question system where question types are defined as classes in `premade_questions/` and registered dynamically. Questions are defined in YAML files that specify the class name and parameters.
|
|
24
|
+
|
|
25
|
+
### Configuration Files
|
|
26
|
+
Quiz configurations are stored in `example_files/` as YAML files. The default configuration is `exam_generation.yaml`, which defines question types, point values, and organization.
|
|
27
|
+
|
|
28
|
+
## Development Commands
|
|
29
|
+
|
|
30
|
+
### Environment Setup
|
|
31
|
+
```bash
|
|
32
|
+
# The project uses uv for dependency management
|
|
33
|
+
uv sync
|
|
34
|
+
source .venv/bin/activate
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
### Running the Main Script
|
|
38
|
+
```bash
|
|
39
|
+
# Generate PDFs only
|
|
40
|
+
python generate_quiz.py --num_pdfs 3
|
|
41
|
+
|
|
42
|
+
# Generate Canvas quizzes (requires course_id)
|
|
43
|
+
python generate_quiz.py --num_canvas 5 --course_id 12345
|
|
44
|
+
|
|
45
|
+
# Use production Canvas instance
|
|
46
|
+
python generate_quiz.py --prod --num_canvas 2 --course_id 12345
|
|
47
|
+
|
|
48
|
+
# Custom quiz configuration
|
|
49
|
+
python generate_quiz.py --quiz_yaml example_files/custom_quiz.yaml --num_pdfs 2
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### Quality Tools
|
|
53
|
+
The project includes development dependencies for code quality:
|
|
54
|
+
```bash
|
|
55
|
+
# Code formatting
|
|
56
|
+
black .
|
|
57
|
+
|
|
58
|
+
# Linting
|
|
59
|
+
flake8
|
|
60
|
+
|
|
61
|
+
# Type checking
|
|
62
|
+
mypy QuizGenerator/
|
|
63
|
+
|
|
64
|
+
# Testing
|
|
65
|
+
pytest
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
### LaTeX Dependencies
|
|
69
|
+
PDF generation requires LaTeX with `latexmk`. Generated PDFs are output to the `out/` directory.
|
|
70
|
+
|
|
71
|
+
## Key Files and Patterns
|
|
72
|
+
|
|
73
|
+
- Quiz configurations follow the pattern in `example_files/exam_generation.yaml`
|
|
74
|
+
- New question types should extend base classes in `question.py` and be placed in `premade_questions/`
|
|
75
|
+
- The project uses logging extensively - check `teachingtools.log` for detailed output
|
|
76
|
+
- Canvas integration requires environment variables for API keys (see `.env` symlink)
|
|
77
|
+
|
|
78
|
+
## Testing Notes
|
|
79
|
+
|
|
80
|
+
The project includes a test mode accessible via `python generate_quiz.py TEST`.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: QuizGenerator
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.10.0
|
|
4
4
|
Summary: Generate randomized quiz questions for Canvas LMS and PDF exams
|
|
5
5
|
Project-URL: Homepage, https://github.com/OtterDen-Lab/QuizGenerator
|
|
6
6
|
Project-URL: Documentation, https://github.com/OtterDen-Lab/QuizGenerator/tree/main/documentation
|
|
@@ -19,9 +19,7 @@ Classifier: Topic :: Education :: Testing
|
|
|
19
19
|
Requires-Python: >=3.12
|
|
20
20
|
Requires-Dist: canvasapi==3.2.0
|
|
21
21
|
Requires-Dist: cryptography>=41.0.0
|
|
22
|
-
Requires-Dist: graphviz>=0.21
|
|
23
22
|
Requires-Dist: jinja2==3.1.3
|
|
24
|
-
Requires-Dist: keras>=3.12.0
|
|
25
23
|
Requires-Dist: markdown>=3.9
|
|
26
24
|
Requires-Dist: matplotlib
|
|
27
25
|
Requires-Dist: pylatex>=1.4.2
|
|
@@ -32,7 +30,10 @@ Requires-Dist: pyyaml==6.0.1
|
|
|
32
30
|
Requires-Dist: requests==2.32.2
|
|
33
31
|
Requires-Dist: segno>=1.6.0
|
|
34
32
|
Requires-Dist: sympy>=1.14.0
|
|
35
|
-
|
|
33
|
+
Provides-Extra: cst463
|
|
34
|
+
Requires-Dist: graphviz>=0.21; extra == 'cst463'
|
|
35
|
+
Requires-Dist: keras>=3.12.0; extra == 'cst463'
|
|
36
|
+
Requires-Dist: tensorflow>=2.20.0; extra == 'cst463'
|
|
36
37
|
Provides-Extra: grading
|
|
37
38
|
Requires-Dist: pillow>=10.0.0; extra == 'grading'
|
|
38
39
|
Requires-Dist: pyzbar>=0.1.9; extra == 'grading'
|
|
@@ -60,18 +61,24 @@ pip install QuizGenerator
|
|
|
60
61
|
### System Requirements
|
|
61
62
|
|
|
62
63
|
- Python 3.12+
|
|
63
|
-
-
|
|
64
|
-
- Optional:
|
|
64
|
+
- [Typst](https://typst.app/) (default PDF renderer)
|
|
65
|
+
- Optional: LaTeX distribution with `latexmk` (if using `--latex`)
|
|
66
|
+
- Recommended: [Pandoc](https://pandoc.org/) (for markdown conversion)
|
|
65
67
|
|
|
66
68
|
### Optional Dependencies
|
|
67
69
|
|
|
68
70
|
```bash
|
|
69
71
|
# For QR code grading support
|
|
70
72
|
pip install QuizGenerator[grading]
|
|
73
|
+
|
|
74
|
+
# For CST463 machine learning questions
|
|
75
|
+
pip install QuizGenerator[cst463]
|
|
71
76
|
```
|
|
72
77
|
|
|
73
78
|
## Quick Start
|
|
74
79
|
|
|
80
|
+
Need a 2‑minute setup? See `documentation/quickstart.md`.
|
|
81
|
+
|
|
75
82
|
### 1. Create a quiz configuration (YAML)
|
|
76
83
|
|
|
77
84
|
```yaml
|
|
@@ -94,7 +101,7 @@ questions:
|
|
|
94
101
|
### 2. Generate PDFs
|
|
95
102
|
|
|
96
103
|
```bash
|
|
97
|
-
|
|
104
|
+
quizgen --yaml my_quiz.yaml --num_pdfs 3
|
|
98
105
|
```
|
|
99
106
|
|
|
100
107
|
PDFs will be created in the `out/` directory.
|
|
@@ -106,8 +113,8 @@ PDFs will be created in the `out/` directory.
|
|
|
106
113
|
# CANVAS_API_URL=https://canvas.instructure.com
|
|
107
114
|
# CANVAS_API_KEY=your_api_key_here
|
|
108
115
|
|
|
109
|
-
|
|
110
|
-
--
|
|
116
|
+
quizgen \
|
|
117
|
+
--yaml my_quiz.yaml \
|
|
111
118
|
--num_canvas 5 \
|
|
112
119
|
--course_id 12345
|
|
113
120
|
```
|
|
@@ -194,10 +201,9 @@ Notes:
|
|
|
194
201
|
|
|
195
202
|
## Documentation
|
|
196
203
|
|
|
197
|
-
- [Getting Started Guide](documentation/getting_started.md)
|
|
204
|
+
- [Getting Started Guide](documentation/getting_started.md)
|
|
198
205
|
- [Custom Questions Guide](documentation/custom_questions.md)
|
|
199
|
-
- [YAML Configuration Reference](documentation/yaml_config_guide.md)
|
|
200
|
-
- [PyPI Release Plan](documentation/pypi_release_plan.md)
|
|
206
|
+
- [YAML Configuration Reference](documentation/yaml_config_guide.md)
|
|
201
207
|
|
|
202
208
|
## Canvas Setup
|
|
203
209
|
|
|
@@ -216,25 +222,28 @@ CANVAS_API_KEY_prod=your_prod_api_key
|
|
|
216
222
|
2. Use `--prod` flag for production Canvas instance:
|
|
217
223
|
|
|
218
224
|
```bash
|
|
219
|
-
|
|
225
|
+
quizgen --prod --num_canvas 5 --course_id 12345
|
|
220
226
|
```
|
|
221
227
|
|
|
222
228
|
## Advanced Features
|
|
223
229
|
|
|
224
230
|
### Typst Support
|
|
225
231
|
|
|
226
|
-
|
|
232
|
+
Typst is the default for faster compilation. Use `--latex` to force LaTeX:
|
|
227
233
|
|
|
228
234
|
```bash
|
|
229
|
-
|
|
235
|
+
quizgen --latex --num_pdfs 3
|
|
230
236
|
```
|
|
231
237
|
|
|
238
|
+
Experimental: `--typst_measurement` uses Typst to measure question height for tighter layout.
|
|
239
|
+
It can change pagination and ordering, so use with care on finalized exams.
|
|
240
|
+
|
|
232
241
|
### Deterministic Generation
|
|
233
242
|
|
|
234
243
|
Use seeds for reproducible quizzes:
|
|
235
244
|
|
|
236
245
|
```bash
|
|
237
|
-
|
|
246
|
+
quizgen --seed 42 --num_pdfs 3
|
|
238
247
|
```
|
|
239
248
|
|
|
240
249
|
### QR Code Regeneration
|
|
@@ -258,7 +267,7 @@ QuizGenerator/
|
|
|
258
267
|
│ └── canvas/ # Canvas LMS integration
|
|
259
268
|
├── example_files/ # Example quiz configurations
|
|
260
269
|
├── documentation/ # User guides
|
|
261
|
-
└──
|
|
270
|
+
└── quizgen # CLI entry point
|
|
262
271
|
```
|
|
263
272
|
|
|
264
273
|
## Contributing
|
|
@@ -37,8 +37,12 @@ NUM_WORKERS = 4
|
|
|
37
37
|
|
|
38
38
|
|
|
39
39
|
class CanvasInterface:
|
|
40
|
-
def __init__(self, *, prod=False):
|
|
41
|
-
|
|
40
|
+
def __init__(self, *, prod=False, env_path: str | None = None):
|
|
41
|
+
default_env = os.path.join(os.path.expanduser("~"), ".env")
|
|
42
|
+
if env_path and os.path.exists(env_path):
|
|
43
|
+
dotenv.load_dotenv(env_path)
|
|
44
|
+
elif os.path.exists(default_env):
|
|
45
|
+
dotenv.load_dotenv(default_env)
|
|
42
46
|
|
|
43
47
|
self.prod = prod
|
|
44
48
|
if self.prod:
|
|
@@ -23,6 +23,21 @@ import numpy as np
|
|
|
23
23
|
|
|
24
24
|
log = logging.getLogger(__name__)
|
|
25
25
|
|
|
26
|
+
_PANDOC_OK = None
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def _ensure_pandoc() -> bool:
|
|
30
|
+
global _PANDOC_OK
|
|
31
|
+
if _PANDOC_OK is not None:
|
|
32
|
+
return _PANDOC_OK
|
|
33
|
+
try:
|
|
34
|
+
pypandoc.get_pandoc_version()
|
|
35
|
+
_PANDOC_OK = True
|
|
36
|
+
except Exception as exc:
|
|
37
|
+
_PANDOC_OK = False
|
|
38
|
+
log.warning(f"Pandoc not found or unusable: {exc}")
|
|
39
|
+
return _PANDOC_OK
|
|
40
|
+
|
|
26
41
|
|
|
27
42
|
"""
|
|
28
43
|
Content Abstract Syntax Tree - The core content system for quiz generation.
|
|
@@ -221,6 +236,9 @@ class Leaf(Element):
|
|
|
221
236
|
return html_output.strip()
|
|
222
237
|
|
|
223
238
|
case _:
|
|
239
|
+
if not _ensure_pandoc():
|
|
240
|
+
log.warning(f"Pandoc unavailable; returning raw markdown for {output_format} output.")
|
|
241
|
+
return str_to_convert
|
|
224
242
|
output = pypandoc.convert_text(
|
|
225
243
|
str_to_convert,
|
|
226
244
|
output_format,
|
|
@@ -549,16 +567,18 @@ class Question(Container):
|
|
|
549
567
|
|
|
550
568
|
# Build extra_data dict with regeneration metadata if available
|
|
551
569
|
extra_data = {}
|
|
552
|
-
if hasattr(self, 'question_class_name') and hasattr(self, 'generation_seed')
|
|
553
|
-
|
|
554
|
-
):
|
|
555
|
-
if self.question_class_name and self.generation_seed is not None and self.question_version:
|
|
570
|
+
if hasattr(self, 'question_class_name') and hasattr(self, 'generation_seed'):
|
|
571
|
+
if self.question_class_name and self.generation_seed is not None:
|
|
556
572
|
extra_data['question_type'] = self.question_class_name
|
|
557
573
|
extra_data['seed'] = self.generation_seed
|
|
558
|
-
|
|
574
|
+
if hasattr(self, 'question_version') and self.question_version:
|
|
575
|
+
extra_data['version'] = self.question_version
|
|
559
576
|
# Include question-specific configuration parameters if available
|
|
560
577
|
if hasattr(self, 'config_params') and self.config_params:
|
|
561
578
|
extra_data['config'] = self.config_params
|
|
579
|
+
# Include context-derived extras if available
|
|
580
|
+
if hasattr(self, 'qr_context_extras') and self.qr_context_extras:
|
|
581
|
+
extra_data['context'] = self.qr_context_extras
|
|
562
582
|
|
|
563
583
|
qr_path = QuestionQRCode.generate_qr_pdf(
|
|
564
584
|
self.question_number,
|
|
@@ -615,16 +635,18 @@ class Question(Container):
|
|
|
615
635
|
|
|
616
636
|
# Build extra_data dict with regeneration metadata if available
|
|
617
637
|
extra_data = {}
|
|
618
|
-
if hasattr(self, 'question_class_name') and hasattr(self, 'generation_seed')
|
|
619
|
-
|
|
620
|
-
):
|
|
621
|
-
if self.question_class_name and self.generation_seed is not None and self.question_version:
|
|
638
|
+
if hasattr(self, 'question_class_name') and hasattr(self, 'generation_seed'):
|
|
639
|
+
if self.question_class_name and self.generation_seed is not None:
|
|
622
640
|
extra_data['question_type'] = self.question_class_name
|
|
623
641
|
extra_data['seed'] = self.generation_seed
|
|
624
|
-
|
|
642
|
+
if hasattr(self, 'question_version') and self.question_version:
|
|
643
|
+
extra_data['version'] = self.question_version
|
|
625
644
|
# Include question-specific configuration parameters if available
|
|
626
645
|
if hasattr(self, 'config_params') and self.config_params:
|
|
627
646
|
extra_data['config'] = self.config_params
|
|
647
|
+
# Include context-derived extras if available
|
|
648
|
+
if hasattr(self, 'qr_context_extras') and self.qr_context_extras:
|
|
649
|
+
extra_data['context'] = self.qr_context_extras
|
|
628
650
|
|
|
629
651
|
# Generate QR code PNG
|
|
630
652
|
qr_path = QuestionQRCode.generate_qr_pdf(
|