QuizGenerator 0.1.0__py3-none-any.whl → 0.1.2__py3-none-any.whl

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/generate.py CHANGED
@@ -7,13 +7,10 @@ import subprocess
7
7
  import tempfile
8
8
  from pathlib import Path
9
9
  from dotenv import load_dotenv
10
- from QuizGenerator.canvas.canvas_interface import CanvasInterface, CanvasCourse
10
+ from QuizGenerator.canvas.canvas_interface import CanvasInterface
11
11
 
12
12
  from QuizGenerator.quiz import Quiz
13
13
 
14
- # Load environment variables from ~/.env
15
- load_dotenv(Path.home() / '.env')
16
-
17
14
  import logging
18
15
  log = logging.getLogger(__name__)
19
16
 
@@ -23,20 +20,27 @@ from QuizGenerator.performance import PerformanceTracker
23
20
  def parse_args():
24
21
  parser = argparse.ArgumentParser()
25
22
 
26
- parser.add_argument("--prod", action="store_true")
27
- parser.add_argument("--course_id", type=int)
23
+ parser.add_argument(
24
+ "--env",
25
+ default=os.path.join(Path.home(), '.env'),
26
+ help="Path to .env file specifying canvas details"
27
+ )
28
28
 
29
29
  parser.add_argument("--quiz_yaml", default=os.path.join(os.path.dirname(os.path.abspath(__file__)), "example_files/exam_generation.yaml"))
30
- parser.add_argument("--num_canvas", default=0, type=int)
31
- parser.add_argument("--num_pdfs", default=0, type=int)
32
30
  parser.add_argument("--seed", type=int, default=None,
33
31
  help="Random seed for quiz generation (default: None for random)")
34
- parser.add_argument("--typst", action="store_true",
35
- help="Use Typst instead of LaTeX for PDF generation")
36
- parser.add_argument("--typst-measurement", action="store_true",
37
- help="Use Typst to measure question heights for optimal bin-packing (requires Typst)")
32
+
33
+ # Canvas flags
34
+ parser.add_argument("--num_canvas", default=0, type=int, help="How many variations of each question to try to upload to canvas.")
35
+ parser.add_argument("--prod", action="store_true")
36
+ parser.add_argument("--course_id", type=int)
38
37
  parser.add_argument("--delete-assignment-group", action="store_true",
39
38
  help="Delete existing assignment group before uploading new quizzes")
39
+
40
+ # PDF Flags
41
+ parser.add_argument("--num_pdfs", default=0, type=int, help="How many PDF quizzes to create")
42
+ parser.add_argument("--typst", action="store_true",
43
+ help="Use Typst instead of LaTeX for PDF generation")
40
44
 
41
45
  subparsers = parser.add_subparsers(dest='command')
42
46
  test_parser = subparsers.add_parser("TEST")
@@ -54,111 +58,6 @@ def parse_args():
54
58
  def test():
55
59
  log.info("Running test...")
56
60
 
57
- # Load the CST463 quiz configuration to test vector questions
58
- quiz_yaml = os.path.join(os.path.dirname(os.path.abspath(__file__)), "example_files/cst463.yaml")
59
-
60
- # Generate a quiz
61
- quizzes = Quiz.from_yaml(quiz_yaml)
62
-
63
- # Find a vector question to test
64
- from QuizGenerator.question import QuestionRegistry
65
-
66
- print("="*60)
67
- print("CANVAS ANSWER DUPLICATION TEST")
68
- print("="*60)
69
-
70
- # Test multiple question types to find which ones use VECTOR variable kind
71
- question_types = ["VectorAddition", "VectorDotProduct", "VectorMagnitude", "DerivativeBasic", "DerivativeChain"]
72
-
73
- for question_type in question_types:
74
- print(f"\n" + "="*20 + f" TESTING {question_type} " + "="*20)
75
- question = QuestionRegistry.create(question_type, name=f"Test {question_type}", points_value=1)
76
-
77
- print(f"Question answers: {len(question.answers)} answer objects")
78
-
79
- # Show all individual answers and their Canvas representations
80
- for key, answer in question.answers.items():
81
- canvas_answers = answer.get_for_canvas()
82
- print(f"\nAnswer key '{key}':")
83
- print(f" Variable kind: {answer.variable_kind}")
84
- print(f" Value: {answer.value}")
85
- print(f" Canvas entries: {len(canvas_answers)}")
86
- for i, canvas_answer in enumerate(canvas_answers):
87
- print(f" {i+1}: '{canvas_answer['answer_text']}'")
88
-
89
- # Show duplicates if they exist
90
- texts = [ca['answer_text'] for ca in canvas_answers]
91
- unique_texts = set(texts)
92
- duplicates = len(texts) - len(unique_texts)
93
- if duplicates > 0:
94
- print(f" *** {duplicates} DUPLICATE ENTRIES FOUND ***")
95
- for text in sorted(unique_texts):
96
- count = texts.count(text)
97
- if count > 1:
98
- print(f" '{text}' appears {count} times")
99
-
100
- # Check for phantom blank_ids that aren't displayed
101
- print(f"\n" + "="*20 + " PHANTOM BLANK_ID CHECK " + "="*20)
102
-
103
- for question_type in ["DerivativeBasic", "DerivativeChain"]:
104
- print(f"\n--- {question_type} ---")
105
- question = QuestionRegistry.create(question_type, name=f"Test {question_type}", points_value=1)
106
-
107
- # Get the question body to see what blank_ids are actually displayed
108
- body = question.get_body()
109
- body_html = body.render("html")
110
-
111
- print(f" HTML body preview:")
112
- print(f" {body_html[:200]}...")
113
-
114
- # Extract blank_ids from the HTML using regex
115
- import re
116
- displayed_blank_ids = set(re.findall(r'name="([^"]*)"', body_html))
117
-
118
- # Get all blank_ids from the answers
119
- question_type_enum, canvas_answers = question.get_answers()
120
- all_blank_ids = set(answer['blank_id'] for answer in canvas_answers)
121
-
122
- print(f" Total answers in self.answers: {len(question.answers)}")
123
- print(f" Total Canvas answer entries: {len(canvas_answers)}")
124
- print(f" Unique blank_ids in Canvas answers: {len(all_blank_ids)}")
125
- print(f" Blank_ids displayed in HTML: {len(displayed_blank_ids)}")
126
-
127
- # Find phantom blank_ids
128
- phantom_blank_ids = all_blank_ids - displayed_blank_ids
129
- if phantom_blank_ids:
130
- print(f" *** PHANTOM BLANK_IDS FOUND: {phantom_blank_ids} ***")
131
- for phantom_id in phantom_blank_ids:
132
- phantom_answers = [a for a in canvas_answers if a['blank_id'] == phantom_id]
133
- print(f" '{phantom_id}': {len(phantom_answers)} entries not displayed")
134
- else:
135
- print(f" No phantom blank_ids found")
136
-
137
- # Show what blank_ids are actually displayed
138
- print(f" Displayed blank_ids: {sorted(displayed_blank_ids)}")
139
- print(f" All answer blank_ids: {sorted(all_blank_ids)}")
140
-
141
- # Now create a synthetic test to demonstrate the VECTOR bug
142
- print(f"\n" + "="*20 + " SYNTHETIC VECTOR TEST " + "="*20)
143
- from QuizGenerator.misc import Answer
144
-
145
- # Create a synthetic answer with VECTOR variable kind to demonstrate the bug
146
- vector_answer = Answer(
147
- key="test_vector",
148
- value=[1, 2, 3], # 3D vector
149
- variable_kind=Answer.VariableKind.VECTOR
150
- )
151
-
152
- canvas_answers = vector_answer.get_for_canvas()
153
- print(f"Synthetic VECTOR answer:")
154
- print(f" Value: {vector_answer.value}")
155
- print(f" Canvas entries: {len(canvas_answers)}")
156
- # Only show first few to save space
157
- for i, canvas_answer in enumerate(canvas_answers[:5]):
158
- print(f" {i+1}: '{canvas_answer['answer_text']}'")
159
- if len(canvas_answers) > 5:
160
- print(f" ... and {len(canvas_answers) - 5} more entries")
161
-
162
61
  print("\n" + "="*60)
163
62
  print("TEST COMPLETE")
164
63
  print("="*60)
@@ -245,6 +144,7 @@ def generate_typst(typst_text, remove_previous=False):
245
144
  if os.path.exists(tmp_typ.name):
246
145
  os.unlink(tmp_typ.name)
247
146
 
147
+
248
148
  def generate_quiz(
249
149
  path_to_quiz_yaml,
250
150
  num_pdfs=0,
@@ -305,38 +205,12 @@ def generate_quiz(
305
205
 
306
206
  quiz.describe()
307
207
 
308
- # Generate performance report if Canvas questions were generated
309
- if num_canvas > 0:
310
- print("\n" + "="*80)
311
- print("PERFORMANCE ANALYSIS REPORT")
312
- print("="*80)
313
- PerformanceTracker.report_summary(min_duration=0.01) # Show operations taking >10ms
314
-
315
- # Show detailed breakdown for slowest operations
316
- print("\n" + "="*60)
317
- print("DETAILED TIMING BREAKDOWN")
318
- print("="*60)
319
-
320
- slow_operations = ["canvas_prepare_question", "canvas_api_upload", "ast_render_body", "question_body"]
321
- for op in slow_operations:
322
- metrics = PerformanceTracker.get_metrics_by_operation(op)
323
- if metrics:
324
- print(f"\n{op.upper()}:")
325
- # Show stats by question type
326
- by_type = {}
327
- for m in metrics:
328
- qtype = m.question_type or "unknown"
329
- if qtype not in by_type:
330
- by_type[qtype] = []
331
- by_type[qtype].append(m.duration)
332
-
333
- for qtype, durations in by_type.items():
334
- avg = sum(durations) / len(durations)
335
- print(f" {qtype}: {len(durations)} calls, avg {avg:.3f}s (range: {min(durations):.3f}s - {max(durations):.3f}s)")
336
-
337
208
  def main():
338
209
 
339
210
  args = parse_args()
211
+
212
+ # Load environment variables
213
+ load_dotenv(args.env)
340
214
 
341
215
  if args.command == "TEST":
342
216
  test()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: QuizGenerator
3
- Version: 0.1.0
3
+ Version: 0.1.2
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
@@ -3,7 +3,7 @@ QuizGenerator/__init__.py,sha256=8EV-k90A3PNC8Cm2-ZquwNyVyvnwW1gs6u-nGictyhs,840
3
3
  QuizGenerator/__main__.py,sha256=Dd9w4R0Unm3RiXztvR4Y_g9-lkWp6FHg-4VN50JbKxU,151
4
4
  QuizGenerator/constants.py,sha256=AO-UWwsWPLb1k2JW6KP8rl9fxTcdT0rW-6XC6zfnDOs,4386
5
5
  QuizGenerator/contentast.py,sha256=bHC1D_OBrDCnTuXcTiI37Muk20mBxD1gjDArUBrGv9c,63657
6
- QuizGenerator/generate.py,sha256=q5higZqQ4Q-OEC39cFWTyY89kWUtM9NMpxdD3_P4n9Q,11990
6
+ QuizGenerator/generate.py,sha256=HK8TtfQwkj59n0_BVSrej_CuQUnDwoIGlqDikI4YwK4,6613
7
7
  QuizGenerator/logging.yaml,sha256=VJCdh26D8e_PNUs4McvvP1ojz9EVjQNifJzfhEk1Mbo,1114
8
8
  QuizGenerator/misc.py,sha256=uIc1revyGZK8LL-1sJfhsoJ_MlYn75LPap6FRAiJWRQ,15635
9
9
  QuizGenerator/mixins.py,sha256=RjV76C1tkTLSvhSoMys67W7UmR6y6wAAcBM2Msxdpd0,18186
@@ -37,8 +37,8 @@ QuizGenerator/premade_questions/cst463/neural-network-basics/__init__.py,sha256=
37
37
  QuizGenerator/premade_questions/cst463/neural-network-basics/neural_network_questions.py,sha256=7ZtCQ2fXhIPSd99TstQfOKCN13GJE_56UfBQKdmzmMI,42398
38
38
  QuizGenerator/premade_questions/cst463/tensorflow-intro/__init__.py,sha256=G1gEHtG4KakYgi8ZXSYYhX6bQRtnm2tZVGx36d63Nmo,173
39
39
  QuizGenerator/premade_questions/cst463/tensorflow-intro/tensorflow_questions.py,sha256=dPn8Sj0yk4m02np62esMKZ7CvcljhYq3Tq51nY9aJnA,29781
40
- quizgenerator-0.1.0.dist-info/METADATA,sha256=p4Ahyc6-chI3WWQTlmVKIPA5i5UQ-UVTZDvzDXfsJEA,7149
41
- quizgenerator-0.1.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
42
- quizgenerator-0.1.0.dist-info/entry_points.txt,sha256=iViWMzswXGe88WKoue_Ib-ODUSiT_j_6f1us28w9pkc,56
43
- quizgenerator-0.1.0.dist-info/licenses/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
44
- quizgenerator-0.1.0.dist-info/RECORD,,
40
+ quizgenerator-0.1.2.dist-info/METADATA,sha256=GpIkM0qosTZY2p0NFI7K1Zg3g33mUOTuHtlz82awGvI,7149
41
+ quizgenerator-0.1.2.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
42
+ quizgenerator-0.1.2.dist-info/entry_points.txt,sha256=iViWMzswXGe88WKoue_Ib-ODUSiT_j_6f1us28w9pkc,56
43
+ quizgenerator-0.1.2.dist-info/licenses/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
44
+ quizgenerator-0.1.2.dist-info/RECORD,,