QuizGenerator 0.9.0__tar.gz → 0.10.1__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.
Files changed (80) hide show
  1. quizgenerator-0.10.1/.github/pull_request_template.md +36 -0
  2. quizgenerator-0.10.1/.github/workflows/release.yaml +146 -0
  3. {quizgenerator-0.9.0 → quizgenerator-0.10.1}/.gitignore +1 -2
  4. quizgenerator-0.10.1/AGENTS.md +58 -0
  5. quizgenerator-0.10.1/CLAUDE.md +80 -0
  6. {quizgenerator-0.9.0 → quizgenerator-0.10.1}/PKG-INFO +26 -17
  7. quizgenerator-0.10.1/QuizGenerator/README.md +5 -0
  8. {quizgenerator-0.9.0 → quizgenerator-0.10.1}/QuizGenerator/__init__.py +2 -1
  9. {quizgenerator-0.9.0 → quizgenerator-0.10.1}/QuizGenerator/canvas/canvas_interface.py +9 -6
  10. {quizgenerator-0.9.0 → quizgenerator-0.10.1}/QuizGenerator/canvas/classes.py +0 -1
  11. {quizgenerator-0.9.0 → quizgenerator-0.10.1}/QuizGenerator/contentast.py +32 -10
  12. {quizgenerator-0.9.0 → quizgenerator-0.10.1}/QuizGenerator/generate.py +57 -11
  13. quizgenerator-0.10.1/QuizGenerator/logging.yaml +55 -0
  14. {quizgenerator-0.9.0 → quizgenerator-0.10.1}/QuizGenerator/misc.py +0 -8
  15. {quizgenerator-0.9.0 → quizgenerator-0.10.1}/QuizGenerator/premade_questions/cst334/memory_questions.py +2 -3
  16. {quizgenerator-0.9.0 → quizgenerator-0.10.1}/QuizGenerator/premade_questions/cst334/process.py +0 -1
  17. quizgenerator-0.10.1/QuizGenerator/premade_questions/cst463/gradient_descent/__init__.py +12 -0
  18. {quizgenerator-0.9.0 → quizgenerator-0.10.1}/QuizGenerator/premade_questions/cst463/gradient_descent/gradient_calculation.py +0 -1
  19. {quizgenerator-0.9.0 → quizgenerator-0.10.1}/QuizGenerator/premade_questions/cst463/gradient_descent/gradient_descent_questions.py +2 -4
  20. {quizgenerator-0.9.0 → quizgenerator-0.10.1}/QuizGenerator/premade_questions/cst463/gradient_descent/loss_calculations.py +22 -20
  21. {quizgenerator-0.9.0 → quizgenerator-0.10.1}/QuizGenerator/premade_questions/cst463/gradient_descent/misc.py +1 -1
  22. quizgenerator-0.10.1/QuizGenerator/premade_questions/cst463/math_and_data/__init__.py +12 -0
  23. {quizgenerator-0.9.0 → quizgenerator-0.10.1}/QuizGenerator/premade_questions/cst463/math_and_data/matrix_questions.py +0 -1
  24. {quizgenerator-0.9.0 → quizgenerator-0.10.1}/QuizGenerator/premade_questions/cst463/math_and_data/vector_questions.py +0 -1
  25. {quizgenerator-0.9.0 → quizgenerator-0.10.1}/QuizGenerator/premade_questions/cst463/models/attention.py +1 -5
  26. {quizgenerator-0.9.0 → quizgenerator-0.10.1}/QuizGenerator/premade_questions/cst463/models/cnns.py +1 -5
  27. {quizgenerator-0.9.0 → quizgenerator-0.10.1}/QuizGenerator/premade_questions/cst463/models/rnns.py +1 -5
  28. {quizgenerator-0.9.0 → quizgenerator-0.10.1}/QuizGenerator/premade_questions/cst463/models/text.py +1 -5
  29. {quizgenerator-0.9.0 → quizgenerator-0.10.1}/QuizGenerator/premade_questions/cst463/models/weight_counting.py +20 -3
  30. {quizgenerator-0.9.0 → quizgenerator-0.10.1}/QuizGenerator/premade_questions/cst463/neural-network-basics/__init__.py +7 -0
  31. {quizgenerator-0.9.0 → quizgenerator-0.10.1}/QuizGenerator/premade_questions/cst463/neural-network-basics/neural_network_questions.py +1 -9
  32. {quizgenerator-0.9.0 → quizgenerator-0.10.1}/QuizGenerator/premade_questions/cst463/tensorflow-intro/__init__.py +7 -0
  33. {quizgenerator-0.9.0 → quizgenerator-0.10.1}/QuizGenerator/premade_questions/cst463/tensorflow-intro/tensorflow_questions.py +0 -4
  34. {quizgenerator-0.9.0 → quizgenerator-0.10.1}/QuizGenerator/qrcode_generator.py +116 -55
  35. {quizgenerator-0.9.0 → quizgenerator-0.10.1}/QuizGenerator/question.py +30 -16
  36. {quizgenerator-0.9.0 → quizgenerator-0.10.1}/QuizGenerator/quiz.py +1 -6
  37. {quizgenerator-0.9.0 → quizgenerator-0.10.1}/QuizGenerator/regenerate.py +23 -9
  38. {quizgenerator-0.9.0 → quizgenerator-0.10.1}/README.md +21 -13
  39. quizgenerator-0.10.1/documentation/GRADING_GUIDE.md +284 -0
  40. quizgenerator-0.10.1/documentation/PARAMETER_STANDARDS.md +111 -0
  41. quizgenerator-0.10.1/documentation/README.md +183 -0
  42. quizgenerator-0.10.1/documentation/WEB_UI_INTEGRATION.md +446 -0
  43. quizgenerator-0.10.1/documentation/claude_todo.md +253 -0
  44. quizgenerator-0.10.1/documentation/custom_questions.md +426 -0
  45. quizgenerator-0.10.1/documentation/getting_started.md +72 -0
  46. quizgenerator-0.10.1/documentation/quickstart.md +49 -0
  47. quizgenerator-0.10.1/documentation/yaml_config_guide.md +76 -0
  48. quizgenerator-0.10.1/example_files/cst334.yaml +223 -0
  49. quizgenerator-0.10.1/example_files/cst463.yaml +325 -0
  50. quizgenerator-0.10.1/example_files/example_exam.yaml +41 -0
  51. quizgenerator-0.10.1/examples/README.md +55 -0
  52. quizgenerator-0.10.1/examples/example_question.py +105 -0
  53. {quizgenerator-0.9.0 → quizgenerator-0.10.1}/pyproject.toml +7 -4
  54. {quizgenerator-0.9.0 → quizgenerator-0.10.1}/pyproject_prev.toml +7 -4
  55. {quizgenerator-0.9.0 → quizgenerator-0.10.1}/scripts/vendor_lms_interface.py +0 -1
  56. {quizgenerator-0.9.0 → quizgenerator-0.10.1}/uv.lock +10 -8
  57. quizgenerator-0.9.0/QuizGenerator/premade_questions/cst463/gradient_descent/__init__.py +0 -3
  58. quizgenerator-0.9.0/QuizGenerator/premade_questions/cst463/math_and_data/__init__.py +0 -2
  59. {quizgenerator-0.9.0 → quizgenerator-0.10.1}/.envrc +0 -0
  60. {quizgenerator-0.9.0 → quizgenerator-0.10.1}/CODEOWNERS +0 -0
  61. {quizgenerator-0.9.0 → quizgenerator-0.10.1}/LICENSE +0 -0
  62. {quizgenerator-0.9.0 → quizgenerator-0.10.1}/QuizGenerator/__main__.py +0 -0
  63. {quizgenerator-0.9.0 → quizgenerator-0.10.1}/QuizGenerator/canvas/__init__.py +0 -0
  64. {quizgenerator-0.9.0 → quizgenerator-0.10.1}/QuizGenerator/constants.py +0 -0
  65. {quizgenerator-0.9.0 → quizgenerator-0.10.1}/QuizGenerator/mixins.py +0 -0
  66. {quizgenerator-0.9.0 → quizgenerator-0.10.1}/QuizGenerator/performance.py +0 -0
  67. {quizgenerator-0.9.0 → quizgenerator-0.10.1}/QuizGenerator/premade_questions/__init__.py +0 -0
  68. {quizgenerator-0.9.0 → quizgenerator-0.10.1}/QuizGenerator/premade_questions/basic.py +0 -0
  69. {quizgenerator-0.9.0 → quizgenerator-0.10.1}/QuizGenerator/premade_questions/cst334/__init__.py +0 -0
  70. {quizgenerator-0.9.0 → quizgenerator-0.10.1}/QuizGenerator/premade_questions/cst334/languages.py +0 -0
  71. {quizgenerator-0.9.0 → quizgenerator-0.10.1}/QuizGenerator/premade_questions/cst334/math_questions.py +0 -0
  72. {quizgenerator-0.9.0 → quizgenerator-0.10.1}/QuizGenerator/premade_questions/cst334/ostep13_vsfs.py +0 -0
  73. {quizgenerator-0.9.0 → quizgenerator-0.10.1}/QuizGenerator/premade_questions/cst334/persistence_questions.py +0 -0
  74. {quizgenerator-0.9.0 → quizgenerator-0.10.1}/QuizGenerator/premade_questions/cst463/__init__.py +0 -0
  75. {quizgenerator-0.9.0 → quizgenerator-0.10.1}/QuizGenerator/premade_questions/cst463/models/__init__.py +0 -0
  76. {quizgenerator-0.9.0 → quizgenerator-0.10.1}/QuizGenerator/premade_questions/cst463/models/matrices.py +0 -0
  77. {quizgenerator-0.9.0 → quizgenerator-0.10.1}/QuizGenerator/typst_utils.py +0 -0
  78. {quizgenerator-0.9.0 → quizgenerator-0.10.1}/examples/web_ui_integration_example.py +0 -0
  79. {quizgenerator-0.9.0 → quizgenerator-0.10.1}/scripts/generate_practice_yaml.sh +0 -0
  80. {quizgenerator-0.9.0 → quizgenerator-0.10.1}/scripts/print.sh +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/*
@@ -11,5 +11,4 @@ debug.*
11
11
  .claude
12
12
  *.log
13
13
  dist/
14
- *.md
15
- *.yaml
14
+ agent_notes/
@@ -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.9.0
3
+ Version: 0.10.1
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
- Requires-Dist: tensorflow>=2.20.0
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
- - LaTeX distribution with `latexmk` (for PDF generation)
64
- - Optional: [Typst](https://typst.app/) (alternative to LaTeX)
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
- python -m generate_quiz --quiz_yaml my_quiz.yaml --num_pdfs 3
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
- python -m generate_quiz \
110
- --quiz_yaml my_quiz.yaml \
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) (coming soon)
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) (coming soon)
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
- python -m generate_quiz --prod --num_canvas 5 --course_id 12345
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
- Use Typst instead of LaTeX for faster compilation:
232
+ Typst is the default for faster compilation. Use `--latex` to force LaTeX:
227
233
 
228
234
  ```bash
229
- python -m generate_quiz --typst --num_pdfs 3
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
- python -m generate_quiz --seed 42 --num_pdfs 3
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
- └── generate_quiz.py # CLI entry point
270
+ └── quizgen # CLI entry point
262
271
  ```
263
272
 
264
273
  ## Contributing
@@ -0,0 +1,5 @@
1
+
2
+
3
+ ## Installation
4
+
5
+ Note, you will need to install `pandoc` prior to running since it is used to convert to HTML (for canvas) and Latex (for PDF)`
@@ -5,6 +5,7 @@ import os
5
5
  import re
6
6
 
7
7
  def setup_logging() -> None:
8
+ os.makedirs(os.path.join("out", "logs"), exist_ok=True)
8
9
  config_path = os.path.join(os.path.dirname(__file__), 'logging.yaml')
9
10
  if os.path.exists(config_path):
10
11
  with open(config_path, 'r') as f:
@@ -24,4 +25,4 @@ def setup_logging() -> None:
24
25
  logging.basicConfig(level=logging.INFO)
25
26
 
26
27
  # Call this once when your application starts
27
- setup_logging()
28
+ setup_logging()
@@ -16,17 +16,16 @@ import canvasapi.submission
16
16
  import canvasapi.exceptions
17
17
  import dotenv, os
18
18
  import requests
19
- from canvasapi.util import combine_kwargs
20
19
 
21
20
  try:
22
- from urllib3.util.retry import Retry # urllib3 v2
21
+ pass # urllib3 v2
23
22
  except Exception:
24
- from urllib3.util import Retry # urllib3 v1 fallback
23
+ pass # urllib3 v1 fallback
25
24
 
26
25
  import os
27
26
  import dotenv
28
27
 
29
- from .classes import LMSWrapper, Student, Submission, Submission__Canvas, FileSubmission__Canvas, TextSubmission__Canvas, QuizSubmission
28
+ from .classes import LMSWrapper, Student, Submission, FileSubmission__Canvas, TextSubmission__Canvas, QuizSubmission
30
29
 
31
30
  import logging
32
31
 
@@ -37,8 +36,12 @@ NUM_WORKERS = 4
37
36
 
38
37
 
39
38
  class CanvasInterface:
40
- def __init__(self, *, prod=False):
41
- dotenv.load_dotenv(os.path.join(os.path.expanduser("~"), ".env"))
39
+ def __init__(self, *, prod=False, env_path: str | None = None):
40
+ default_env = os.path.join(os.path.expanduser("~"), ".env")
41
+ if env_path and os.path.exists(env_path):
42
+ dotenv.load_dotenv(env_path)
43
+ elif os.path.exists(default_env):
44
+ dotenv.load_dotenv(default_env)
42
45
 
43
46
  self.prod = prod
44
47
  if self.prod:
@@ -6,7 +6,6 @@ import logging
6
6
  import dataclasses
7
7
  import functools
8
8
  import io
9
- import os
10
9
  import urllib.request
11
10
  from typing import Optional, List, Dict
12
11
 
@@ -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') and hasattr(
553
- self, 'question_version'
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
- extra_data['version'] = self.question_version
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') and hasattr(
619
- self, 'question_version'
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
- extra_data['version'] = self.question_version
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(