QuizGenerator 0.1.2__tar.gz → 0.1.4__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 (73) hide show
  1. {quizgenerator-0.1.2 → quizgenerator-0.1.4}/PKG-INFO +1 -1
  2. {quizgenerator-0.1.2 → quizgenerator-0.1.4}/QuizGenerator/canvas/__init__.py +2 -2
  3. {quizgenerator-0.1.2 → quizgenerator-0.1.4}/QuizGenerator/canvas/canvas_interface.py +6 -1
  4. {quizgenerator-0.1.2 → quizgenerator-0.1.4}/QuizGenerator/contentast.py +19 -4
  5. {quizgenerator-0.1.2 → quizgenerator-0.1.4}/QuizGenerator/generate.py +18 -1
  6. {quizgenerator-0.1.2 → quizgenerator-0.1.4}/QuizGenerator/misc.py +29 -18
  7. {quizgenerator-0.1.2 → quizgenerator-0.1.4}/QuizGenerator/premade_questions/cst334/memory_questions.py +5 -3
  8. {quizgenerator-0.1.2 → quizgenerator-0.1.4}/QuizGenerator/question.py +24 -6
  9. quizgenerator-0.1.4/example_files/scratch.yaml +15 -0
  10. {quizgenerator-0.1.2 → quizgenerator-0.1.4}/pyproject.toml +1 -1
  11. {quizgenerator-0.1.2 → quizgenerator-0.1.4}/scripts/vendor_lms_interface.py +8 -7
  12. {quizgenerator-0.1.2 → quizgenerator-0.1.4}/uv.lock +1 -1
  13. quizgenerator-0.1.2/example_files/scratch.yaml +0 -72
  14. {quizgenerator-0.1.2 → quizgenerator-0.1.4}/.envrc +0 -0
  15. {quizgenerator-0.1.2 → quizgenerator-0.1.4}/.github/pull_request_template.md +0 -0
  16. {quizgenerator-0.1.2 → quizgenerator-0.1.4}/.gitignore +0 -0
  17. {quizgenerator-0.1.2 → quizgenerator-0.1.4}/CLAUDE.md +0 -0
  18. {quizgenerator-0.1.2 → quizgenerator-0.1.4}/CODEOWNERS +0 -0
  19. {quizgenerator-0.1.2 → quizgenerator-0.1.4}/LICENSE +0 -0
  20. {quizgenerator-0.1.2 → quizgenerator-0.1.4}/QuizGenerator/README.md +0 -0
  21. {quizgenerator-0.1.2 → quizgenerator-0.1.4}/QuizGenerator/__init__.py +0 -0
  22. {quizgenerator-0.1.2 → quizgenerator-0.1.4}/QuizGenerator/__main__.py +0 -0
  23. {quizgenerator-0.1.2 → quizgenerator-0.1.4}/QuizGenerator/canvas/classes.py +0 -0
  24. {quizgenerator-0.1.2 → quizgenerator-0.1.4}/QuizGenerator/constants.py +0 -0
  25. {quizgenerator-0.1.2 → quizgenerator-0.1.4}/QuizGenerator/logging.yaml +0 -0
  26. {quizgenerator-0.1.2 → quizgenerator-0.1.4}/QuizGenerator/mixins.py +0 -0
  27. {quizgenerator-0.1.2 → quizgenerator-0.1.4}/QuizGenerator/performance.py +0 -0
  28. {quizgenerator-0.1.2 → quizgenerator-0.1.4}/QuizGenerator/premade_questions/__init__.py +0 -0
  29. {quizgenerator-0.1.2 → quizgenerator-0.1.4}/QuizGenerator/premade_questions/basic.py +0 -0
  30. {quizgenerator-0.1.2 → quizgenerator-0.1.4}/QuizGenerator/premade_questions/cst334/__init__.py +0 -0
  31. {quizgenerator-0.1.2 → quizgenerator-0.1.4}/QuizGenerator/premade_questions/cst334/languages.py +0 -0
  32. {quizgenerator-0.1.2 → quizgenerator-0.1.4}/QuizGenerator/premade_questions/cst334/math_questions.py +0 -0
  33. {quizgenerator-0.1.2 → quizgenerator-0.1.4}/QuizGenerator/premade_questions/cst334/ostep13_vsfs.py +0 -0
  34. {quizgenerator-0.1.2 → quizgenerator-0.1.4}/QuizGenerator/premade_questions/cst334/persistence_questions.py +0 -0
  35. {quizgenerator-0.1.2 → quizgenerator-0.1.4}/QuizGenerator/premade_questions/cst334/process.py +0 -0
  36. {quizgenerator-0.1.2 → quizgenerator-0.1.4}/QuizGenerator/premade_questions/cst463/__init__.py +0 -0
  37. {quizgenerator-0.1.2 → quizgenerator-0.1.4}/QuizGenerator/premade_questions/cst463/gradient_descent/__init__.py +0 -0
  38. {quizgenerator-0.1.2 → quizgenerator-0.1.4}/QuizGenerator/premade_questions/cst463/gradient_descent/gradient_calculation.py +0 -0
  39. {quizgenerator-0.1.2 → quizgenerator-0.1.4}/QuizGenerator/premade_questions/cst463/gradient_descent/gradient_descent_questions.py +0 -0
  40. {quizgenerator-0.1.2 → quizgenerator-0.1.4}/QuizGenerator/premade_questions/cst463/gradient_descent/loss_calculations.py +0 -0
  41. {quizgenerator-0.1.2 → quizgenerator-0.1.4}/QuizGenerator/premade_questions/cst463/gradient_descent/misc.py +0 -0
  42. {quizgenerator-0.1.2 → quizgenerator-0.1.4}/QuizGenerator/premade_questions/cst463/math_and_data/__init__.py +0 -0
  43. {quizgenerator-0.1.2 → quizgenerator-0.1.4}/QuizGenerator/premade_questions/cst463/math_and_data/matrix_questions.py +0 -0
  44. {quizgenerator-0.1.2 → quizgenerator-0.1.4}/QuizGenerator/premade_questions/cst463/math_and_data/vector_questions.py +0 -0
  45. {quizgenerator-0.1.2 → quizgenerator-0.1.4}/QuizGenerator/premade_questions/cst463/neural-network-basics/__init__.py +0 -0
  46. {quizgenerator-0.1.2 → quizgenerator-0.1.4}/QuizGenerator/premade_questions/cst463/neural-network-basics/neural_network_questions.py +0 -0
  47. {quizgenerator-0.1.2 → quizgenerator-0.1.4}/QuizGenerator/premade_questions/cst463/tensorflow-intro/__init__.py +0 -0
  48. {quizgenerator-0.1.2 → quizgenerator-0.1.4}/QuizGenerator/premade_questions/cst463/tensorflow-intro/tensorflow_questions.py +0 -0
  49. {quizgenerator-0.1.2 → quizgenerator-0.1.4}/QuizGenerator/qrcode_generator.py +0 -0
  50. {quizgenerator-0.1.2 → quizgenerator-0.1.4}/QuizGenerator/quiz.py +0 -0
  51. {quizgenerator-0.1.2 → quizgenerator-0.1.4}/QuizGenerator/typst_utils.py +0 -0
  52. {quizgenerator-0.1.2 → quizgenerator-0.1.4}/README.md +0 -0
  53. {quizgenerator-0.1.2 → quizgenerator-0.1.4}/documentation/GRADING_GUIDE.md +0 -0
  54. {quizgenerator-0.1.2 → quizgenerator-0.1.4}/documentation/LESSONS_LEARNED-adding_questions.md +0 -0
  55. {quizgenerator-0.1.2 → quizgenerator-0.1.4}/documentation/PARAMETER_STANDARDS.md +0 -0
  56. {quizgenerator-0.1.2 → quizgenerator-0.1.4}/documentation/README.md +0 -0
  57. {quizgenerator-0.1.2 → quizgenerator-0.1.4}/documentation/WEB_UI_INTEGRATION.md +0 -0
  58. {quizgenerator-0.1.2 → quizgenerator-0.1.4}/documentation/claude_todo.md +0 -0
  59. {quizgenerator-0.1.2 → quizgenerator-0.1.4}/documentation/custom_questions.md +0 -0
  60. {quizgenerator-0.1.2 → quizgenerator-0.1.4}/documentation/pypi_release_plan.md +0 -0
  61. {quizgenerator-0.1.2 → quizgenerator-0.1.4}/documentation/typst_integration_strategy.md +0 -0
  62. {quizgenerator-0.1.2 → quizgenerator-0.1.4}/example_files/all_classes.yaml +0 -0
  63. {quizgenerator-0.1.2 → quizgenerator-0.1.4}/example_files/cst334.yaml +0 -0
  64. {quizgenerator-0.1.2 → quizgenerator-0.1.4}/example_files/cst463.yaml +0 -0
  65. {quizgenerator-0.1.2 → quizgenerator-0.1.4}/example_files/exam_generation.yaml +0 -0
  66. {quizgenerator-0.1.2 → quizgenerator-0.1.4}/example_files/specific_generators/cst334.caching.yaml +0 -0
  67. {quizgenerator-0.1.2 → quizgenerator-0.1.4}/example_files/specific_generators/cst334.lab-address_translation.yaml +0 -0
  68. {quizgenerator-0.1.2 → quizgenerator-0.1.4}/examples/README.md +0 -0
  69. {quizgenerator-0.1.2 → quizgenerator-0.1.4}/examples/web_ui_integration_example.py +0 -0
  70. {quizgenerator-0.1.2 → quizgenerator-0.1.4}/grade_from_qr.py +0 -0
  71. {quizgenerator-0.1.2 → quizgenerator-0.1.4}/scripts/README.md +0 -0
  72. {quizgenerator-0.1.2 → quizgenerator-0.1.4}/scripts/generate_practice_yaml.sh +0 -0
  73. {quizgenerator-0.1.2 → quizgenerator-0.1.4}/scripts/print.sh +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: QuizGenerator
3
- Version: 0.1.2
3
+ Version: 0.1.4
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
@@ -1,7 +1,7 @@
1
1
  """
2
2
  Canvas LMS integration for QuizGenerator.
3
3
 
4
- Vendored from LMSInterface v0.1.0 (2025-11-18)
4
+ Vendored from LMSInterface v0.1.0 (2025-11-24)
5
5
 
6
6
  This module provides Canvas API integration for uploading quizzes
7
7
  and managing course content.
@@ -9,5 +9,5 @@ and managing course content.
9
9
 
10
10
  __version__ = "0.1.0"
11
11
  __vendored_from__ = "LMSInterface"
12
- __vendored_date__ = "2025-11-18"
12
+ __vendored_date__ = "2025-11-24"
13
13
 
@@ -159,7 +159,12 @@ class CanvasCourse(LMSWrapper):
159
159
 
160
160
  question_fingerprint = question_for_canvas["question_text"]
161
161
  try:
162
- question_fingerprint += ''.join([str(a["answer_text"]) for a in question_for_canvas["answers"]])
162
+ question_fingerprint += ''.join([
163
+ '|'.join([
164
+ f"{k}:{a[k]}" for k in sorted(a.keys())
165
+ ])
166
+ for a in question_for_canvas["answers"]
167
+ ])
163
168
  except TypeError as e:
164
169
  log.error(e)
165
170
  log.warning("Continuing anyway")
@@ -75,7 +75,10 @@ class ContentAST:
75
75
  html_output = section.render("html")
76
76
  """
77
77
  def __init__(self, elements=None, add_spacing_before=False):
78
- self.elements : List[ContentAST.Element] = elements or []
78
+ self.elements : List[ContentAST.Element] = [
79
+ e if isinstance(e, ContentAST.Element) else ContentAST.Text(e)
80
+ for e in (elements if elements else [])
81
+ ]
79
82
  self.add_spacing_before = add_spacing_before
80
83
 
81
84
  def __str__(self):
@@ -436,7 +439,8 @@ class ContentAST:
436
439
  interest=1.0,
437
440
  spacing=0,
438
441
  topic=None,
439
- question_number=None
442
+ question_number=None,
443
+ **kwargs
440
444
  ):
441
445
  super().__init__()
442
446
  self.name = name
@@ -447,14 +451,21 @@ class ContentAST:
447
451
  self.spacing = spacing
448
452
  self.topic = topic # todo: remove this bs.
449
453
  self.question_number = question_number # For QR code generation
454
+
455
+ self.default_kwargs = kwargs
450
456
 
451
457
  def render(self, output_format, **kwargs):
458
+ updated_kwargs = self.default_kwargs
459
+ updated_kwargs.update(kwargs)
460
+
461
+ log.debug(f"updated_kwargs: {updated_kwargs}")
462
+
452
463
  # Special handling for latex and typst - use dedicated render methods
453
464
  if output_format == "typst":
454
465
  return self.render_typst(**kwargs)
455
466
 
456
467
  # Generate content from all elements
457
- content = self.body.render(output_format, **kwargs)
468
+ content = self.body.render(output_format, **updated_kwargs)
458
469
 
459
470
  # If output format is latex, add in minipage and question environments
460
471
  if output_format == "latex":
@@ -1420,7 +1431,11 @@ class ContentAST:
1420
1431
  key_to_display = self.answer[0].key
1421
1432
  return f"{self.label + (':' if len(self.label) > 0 else '')} [{key_to_display}] {self.unit}".strip()
1422
1433
 
1423
- def render_html(self, show_answers=False, **kwargs):
1434
+ def render_html(self, show_answers=False, can_be_numerical=False, **kwargs):
1435
+ log.debug(f"can_be_numerical: {can_be_numerical}")
1436
+ log.debug(f"kwargs: {kwargs}")
1437
+ if can_be_numerical:
1438
+ return f"Calculate {self.label}"
1424
1439
  if show_answers and self.answer:
1425
1440
  # Show actual answer value using formatted display string
1426
1441
  if not isinstance(self.answer, list):
@@ -25,6 +25,8 @@ def parse_args():
25
25
  default=os.path.join(Path.home(), '.env'),
26
26
  help="Path to .env file specifying canvas details"
27
27
  )
28
+
29
+ parser.add_argument("--debug", action="store_true", help="Set logging level to debug")
28
30
 
29
31
  parser.add_argument("--quiz_yaml", default=os.path.join(os.path.dirname(os.path.abspath(__file__)), "example_files/exam_generation.yaml"))
30
32
  parser.add_argument("--seed", type=int, default=None,
@@ -211,6 +213,21 @@ def main():
211
213
 
212
214
  # Load environment variables
213
215
  load_dotenv(args.env)
216
+
217
+ if args.debug:
218
+ # Set root logger to DEBUG
219
+ logging.getLogger().setLevel(logging.DEBUG)
220
+
221
+ # Set all handlers to DEBUG level
222
+ for handler in logging.getLogger().handlers:
223
+ handler.setLevel(logging.DEBUG)
224
+
225
+ # Set named loggers to DEBUG
226
+ for logger_name in ['QuizGenerator', 'lms_interface', '__main__']:
227
+ logger = logging.getLogger(logger_name)
228
+ logger.setLevel(logging.DEBUG)
229
+ for handler in logger.handlers:
230
+ handler.setLevel(logging.DEBUG)
214
231
 
215
232
  if args.command == "TEST":
216
233
  test()
@@ -233,4 +250,4 @@ def main():
233
250
 
234
251
 
235
252
  if __name__ == "__main__":
236
- main()
253
+ main()
@@ -6,7 +6,7 @@ import enum
6
6
  import itertools
7
7
  import logging
8
8
  import math
9
- from typing import List, Dict, Tuple
9
+ from typing import List, Dict, Tuple, Any
10
10
 
11
11
  import fractions
12
12
 
@@ -23,11 +23,12 @@ class Answer:
23
23
 
24
24
  class AnswerKind(enum.Enum):
25
25
  BLANK = "fill_in_multiple_blanks_question"
26
- MULTIPLE_ANSWER = "multiple_answers_question" # todo: have baffles?
26
+ MULTIPLE_ANSWER = "multiple_answers_question"
27
27
  ESSAY = "essay_question"
28
28
  MULTIPLE_DROPDOWN = "multiple_dropdowns_question"
29
+ NUMERICAL_QUESTION = "numerical_question" # note: these can only be single answers as far as I can tell
29
30
 
30
- class VariableKind(enum.Enum): # todo: use these for generate variations?
31
+ class VariableKind(enum.Enum):
31
32
  STR = enum.auto()
32
33
  INT = enum.auto()
33
34
  FLOAT = enum.auto()
@@ -66,7 +67,7 @@ class Answer:
66
67
  self.baffles = baffles
67
68
  self.pdf_only = pdf_only
68
69
 
69
- def get_for_canvas(self) -> List[Dict]:
70
+ def get_for_canvas(self, single_answer=False) -> List[Dict]:
70
71
  # If this answer is marked as PDF-only, don't send it to Canvas
71
72
  if self.pdf_only:
72
73
  return []
@@ -131,18 +132,29 @@ class Answer:
131
132
  Answer.VariableKind.FLOAT,
132
133
  Answer.VariableKind.INT
133
134
  ]:
134
- # Use the accepted_strings helper with settings that match the original AUTOFLOAT behavior
135
- answer_strings = self.__class__.accepted_strings(
136
- self.value,
137
- allow_integer=True,
138
- allow_simple_fraction=True,
139
- max_denominator=3*4*5, # For process questions, these are the numbers of jobs we'd have
140
- allow_mixed=True,
141
- include_spaces=False,
142
- include_fixed_even_if_integer=True
143
- )
144
-
145
- canvas_answers = [
135
+ if single_answer:
136
+ canvas_answers = [
137
+ {
138
+ "numerical_answer_type": "exact_answer",
139
+ "answer_text": round(self.value, self.DEFAULT_ROUNDING_DIGITS),
140
+ "answer_exact": round(self.value, self.DEFAULT_ROUNDING_DIGITS),
141
+ "answer_error_margin": 0.1,
142
+ "answer_weight": 100 if self.correct else 0,
143
+ }
144
+ ]
145
+ else:
146
+ # Use the accepted_strings helper with settings that match the original AUTOFLOAT behavior
147
+ answer_strings = self.__class__.accepted_strings(
148
+ self.value,
149
+ allow_integer=True,
150
+ allow_simple_fraction=True,
151
+ max_denominator=3*4*5, # For process questions, these are the numbers of jobs we'd have
152
+ allow_mixed=True,
153
+ include_spaces=False,
154
+ include_fixed_even_if_integer=True
155
+ )
156
+
157
+ canvas_answers = [
146
158
  {
147
159
  "blank_id": self.key,
148
160
  "answer_text": answer_string,
@@ -476,5 +488,4 @@ class Answer:
476
488
  whole, rem = divmod(A, b)
477
489
  outs.add(f"{sign}{whole} {rem}/{b}")
478
490
 
479
- return sorted(outs, key=lambda s: (len(s), s))
480
-
491
+ return sorted(outs, key=lambda s: (len(s), s))
@@ -190,6 +190,8 @@ class CachingQuestion(MemoryQuestion, RegenerableChoiceMixin, TableQuestionMixin
190
190
  self.num_elements = self.config_params.get("num_elements", 5)
191
191
  self.cache_size = self.config_params.get("cache_size", 3)
192
192
  self.num_requests = self.config_params.get("num_requests", 10)
193
+
194
+ self.hit_rate = 0. # placeholder
193
195
 
194
196
  def refresh(self, previous: Optional[CachingQuestion] = None, *args, hard_refresh: bool = False, **kwargs):
195
197
  # Call parent refresh which seeds RNG and calls is_interesting()
@@ -200,7 +202,7 @@ class CachingQuestion(MemoryQuestion, RegenerableChoiceMixin, TableQuestionMixin
200
202
  self.cache_policy = self.get_choice('policy', self.Kind)
201
203
 
202
204
  self.requests = (
203
- list(range(self.cache_size)) # Prime the cache with the compulsory misses
205
+ list(range(self.cache_size)) # Prime the cache with the capacity misses
204
206
  + self.rng.choices(
205
207
  population=list(range(self.cache_size - 1)), k=1
206
208
  ) # Add in one request to an earlier that will differentiate clearly between FIFO and LRU
@@ -274,7 +276,7 @@ class CachingQuestion(MemoryQuestion, RegenerableChoiceMixin, TableQuestionMixin
274
276
  hit_rate_block = ContentAST.AnswerBlock(
275
277
  ContentAST.Answer(
276
278
  answer=self.answers["answer__hit_rate"],
277
- label=f"Hit rate, excluding compulsory misses. If appropriate, round to {Answer.DEFAULT_ROUNDING_DIGITS} decimal digits.",
279
+ label=f"Hit rate, excluding capacity misses. If appropriate, round to {Answer.DEFAULT_ROUNDING_DIGITS} decimal digits.",
278
280
  unit="%"
279
281
  )
280
282
  )
@@ -324,7 +326,7 @@ class CachingQuestion(MemoryQuestion, RegenerableChoiceMixin, TableQuestionMixin
324
326
  "To calculate the hit rate we calculate the percentage of requests "
325
327
  "that were cache hits out of the total number of requests. "
326
328
  f"In this case we are counting only all but {self.cache_size} requests, "
327
- f"since we are excluding compulsory misses."
329
+ f"since we are excluding capacity misses."
328
330
  ]
329
331
  )
330
332
  )
@@ -11,6 +11,7 @@ import itertools
11
11
  import os
12
12
  import pathlib
13
13
  import pkgutil
14
+ import pprint
14
15
  import random
15
16
  import re
16
17
  import uuid
@@ -522,7 +523,9 @@ class Question(abc.ABC):
522
523
  explanation=explanation,
523
524
  value=self.points_value,
524
525
  spacing=self.spacing,
525
- topic=self.topic
526
+ topic=self.topic,
527
+
528
+ can_be_numerical=self.can_be_numerical()
526
529
  )
527
530
 
528
531
  # Attach regeneration metadata to the question AST
@@ -534,7 +537,7 @@ class Question(abc.ABC):
534
537
  question_ast.config_params = dict(self.config_params)
535
538
 
536
539
  return question_ast
537
-
540
+
538
541
  @abc.abstractmethod
539
542
  def get_body(self, **kwargs) -> ContentAST.Section:
540
543
  """
@@ -555,11 +558,20 @@ class Question(abc.ABC):
555
558
  )
556
559
 
557
560
  def get_answers(self, *args, **kwargs) -> Tuple[Answer.AnswerKind, List[Dict[str,Any]]]:
561
+ if self.can_be_numerical():
562
+ return (
563
+ Answer.AnswerKind.NUMERICAL_QUESTION,
564
+ list(itertools.chain(*[a.get_for_canvas(single_answer=True) for a in self.answers.values()]))
565
+ )
566
+ elif len(self.answers.values()) > 0:
567
+ return (
568
+ self.answer_kind,
569
+ list(itertools.chain(*[a.get_for_canvas() for a in self.answers.values()]))
570
+ )
558
571
  return (
559
- self.answer_kind,
560
- list(itertools.chain(*[a.get_for_canvas() for a in self.answers.values()]))
572
+ Answer.AnswerKind.ESSAY, []
561
573
  )
562
-
574
+
563
575
  def refresh(self, rng_seed=None, *args, **kwargs):
564
576
  """If it is necessary to regenerate aspects between usages, this is the time to do it.
565
577
  This base implementation simply resets everything.
@@ -621,7 +633,13 @@ class Question(abc.ABC):
621
633
  "answers": answers,
622
634
  "neutral_comments_html": explanation_html
623
635
  }
624
-
636
+
637
+ def can_be_numerical(self):
638
+ if (len(self.answers.values()) == 1
639
+ and list(self.answers.values())[0].variable_kind in [Answer.VariableKind.FLOAT, Answer.VariableKind.AUTOFLOAT]
640
+ ):
641
+ return True
642
+ return False
625
643
 
626
644
  class QuestionGroup():
627
645
 
@@ -0,0 +1,15 @@
1
+ name: "(scratch quiz))"
2
+ questions:
3
+ # Vector Questions - Multipart
4
+ 10:
5
+ AverageMemoryAccessTime:
6
+ class: cst334.AverageMemoryAccessTime
7
+
8
+ # CachingQuestion:
9
+ # class: cst334.CachingQuestion
10
+ #
11
+ # VirtualAddressParts:
12
+ # class: cst334.VirtualAddressParts
13
+ #
14
+ # SchedulingQuestion:
15
+ # class: cst334.SchedulingQuestion
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "QuizGenerator"
7
- version = "0.1.2"
7
+ version = "0.1.4"
8
8
  description = "Generate randomized quiz questions for Canvas LMS and PDF exams"
9
9
  readme = "README.md"
10
10
  license = {text = "GPL-3.0-or-later"}
@@ -16,6 +16,7 @@ import argparse
16
16
  import re
17
17
  import shutil
18
18
  import sys
19
+ import os
19
20
  from pathlib import Path
20
21
  from datetime import datetime
21
22
 
@@ -106,15 +107,15 @@ __vendored_date__ = "{datetime.now().strftime('%Y-%m-%d')}"
106
107
  return True
107
108
 
108
109
 
109
- def update_generate_quiz_imports(repo_root: Path, dry_run: bool = False):
110
+ def update_generate_quiz_imports(target_dir: Path, dry_run: bool = False):
110
111
  """Update imports in generate_quiz.py"""
111
- generate_quiz = repo_root / "generate_quiz.py"
112
+ generate_quiz = target_dir / "generate.py"
112
113
 
113
114
  if not generate_quiz.exists():
114
115
  print(f" ❌ Warning: {generate_quiz} not found")
115
116
  return False
116
117
 
117
- print(f"\n📝 Updating imports in generate_quiz.py")
118
+ print(f"\n📝 Updating imports in generate.py")
118
119
 
119
120
  content = generate_quiz.read_text()
120
121
 
@@ -236,7 +237,7 @@ def verify_structure(repo_root: Path, dry_run: bool = False):
236
237
  "QuizGenerator/canvas/__init__.py",
237
238
  "QuizGenerator/canvas/canvas_interface.py",
238
239
  "QuizGenerator/canvas/classes.py",
239
- "generate_quiz.py",
240
+ "QuizGenerator/generate.py",
240
241
  "pyproject.toml",
241
242
  ]
242
243
 
@@ -306,7 +307,7 @@ def main():
306
307
  success = True
307
308
  success &= copy_lms_files(lms_source, target_dir, args.dry_run)
308
309
  success &= update_canvas_init(target_dir, version, args.dry_run)
309
- success &= update_generate_quiz_imports(repo_root, args.dry_run)
310
+ success &= update_generate_quiz_imports((repo_root / "QuizGenerator"), args.dry_run)
310
311
  success &= update_pyproject_toml(repo_root, lms_path, args.dry_run)
311
312
 
312
313
  if not args.dry_run:
@@ -320,8 +321,8 @@ def main():
320
321
  print("\nNext steps:")
321
322
  print(" 1. Review changes: git diff")
322
323
  print(" 2. Check dependencies in pyproject.toml")
323
- print(" 3. Test: uv sync && python generate_quiz.py --help")
324
- print(" 4. Commit: git add QuizGenerator/canvas/ generate_quiz.py pyproject.toml")
324
+ print(" 3. Test: uv sync && python QuizGenerator/generate.py --help")
325
+ print(" 4. Commit: git add QuizGenerator/canvas/ QuizGenerator/generate.py pyproject.toml")
325
326
  else:
326
327
  print("⚠️ Completed with warnings - please review output above")
327
328
  return 1
@@ -873,7 +873,7 @@ wheels = [
873
873
 
874
874
  [[package]]
875
875
  name = "quizgenerator"
876
- version = "0.1.0"
876
+ version = "0.1.4"
877
877
  source = { editable = "." }
878
878
  dependencies = [
879
879
  { name = "canvasapi" },
@@ -1,72 +0,0 @@
1
- name: "Comprehensive Vector and Matrix Questions"
2
- questions:
3
- # Vector Questions - Multipart
4
- 10:
5
- vector addition 4-part:
6
- class: VectorAddition
7
- num_subquestions: 4
8
- kwargs:
9
- spacing: 2
10
-
11
- 9:
12
- vector scalar multiplication 3-part:
13
- class: VectorScalarMultiplication
14
- num_subquestions: 3
15
- kwargs:
16
- spacing: 2
17
-
18
- 8:
19
- vector dot product 4-part:
20
- class: VectorDotProduct
21
- num_subquestions: 4
22
- kwargs:
23
- spacing: 2
24
-
25
- 7:
26
- vector magnitude 3-part:
27
- class: VectorMagnitude
28
- num_subquestions: 3
29
- kwargs:
30
- spacing: 2
31
-
32
- # Matrix Questions - Multipart
33
- 6:
34
- matrix addition 4-part:
35
- class: MatrixAddition
36
- num_subquestions: 4
37
- kwargs:
38
- spacing: 2
39
-
40
- 5:
41
- matrix scalar multiplication 3-part:
42
- class: MatrixScalarMultiplication
43
- num_subquestions: 3
44
- kwargs:
45
- spacing: 2
46
-
47
- 4:
48
- matrix multiplication 2-part:
49
- class: MatrixMultiplication
50
- num_subquestions: 2
51
- kwargs:
52
- spacing: 2
53
-
54
- # Vector Questions - Single
55
- 3:
56
- single vector addition:
57
- class: VectorAddition
58
- kwargs:
59
- spacing: 2
60
-
61
- 2:
62
- single vector dot product:
63
- class: VectorDotProduct
64
- kwargs:
65
- spacing: 2
66
-
67
- # Matrix Questions - Single
68
- 1:
69
- single matrix addition:
70
- class: MatrixAddition
71
- kwargs:
72
- spacing: 2
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes