QuizGenerator 0.6.2__py3-none-any.whl → 0.7.0__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/contentast.py +2198 -2213
- QuizGenerator/misc.py +1 -1
- QuizGenerator/mixins.py +64 -64
- QuizGenerator/premade_questions/basic.py +16 -16
- QuizGenerator/premade_questions/cst334/languages.py +26 -26
- QuizGenerator/premade_questions/cst334/math_questions.py +42 -42
- QuizGenerator/premade_questions/cst334/memory_questions.py +124 -124
- QuizGenerator/premade_questions/cst334/persistence_questions.py +48 -48
- QuizGenerator/premade_questions/cst334/process.py +38 -38
- QuizGenerator/premade_questions/cst463/gradient_descent/gradient_calculation.py +45 -45
- QuizGenerator/premade_questions/cst463/gradient_descent/gradient_descent_questions.py +34 -34
- QuizGenerator/premade_questions/cst463/gradient_descent/loss_calculations.py +53 -53
- QuizGenerator/premade_questions/cst463/gradient_descent/misc.py +2 -2
- QuizGenerator/premade_questions/cst463/math_and_data/matrix_questions.py +65 -65
- QuizGenerator/premade_questions/cst463/math_and_data/vector_questions.py +39 -39
- QuizGenerator/premade_questions/cst463/models/attention.py +36 -36
- QuizGenerator/premade_questions/cst463/models/cnns.py +26 -26
- QuizGenerator/premade_questions/cst463/models/rnns.py +36 -36
- QuizGenerator/premade_questions/cst463/models/text.py +32 -32
- QuizGenerator/premade_questions/cst463/models/weight_counting.py +15 -15
- QuizGenerator/premade_questions/cst463/neural-network-basics/neural_network_questions.py +124 -124
- QuizGenerator/premade_questions/cst463/tensorflow-intro/tensorflow_questions.py +161 -161
- QuizGenerator/question.py +41 -41
- QuizGenerator/quiz.py +7 -7
- QuizGenerator/typst_utils.py +2 -2
- {quizgenerator-0.6.2.dist-info → quizgenerator-0.7.0.dist-info}/METADATA +1 -1
- {quizgenerator-0.6.2.dist-info → quizgenerator-0.7.0.dist-info}/RECORD +30 -30
- {quizgenerator-0.6.2.dist-info → quizgenerator-0.7.0.dist-info}/WHEEL +0 -0
- {quizgenerator-0.6.2.dist-info → quizgenerator-0.7.0.dist-info}/entry_points.txt +0 -0
- {quizgenerator-0.6.2.dist-info → quizgenerator-0.7.0.dist-info}/licenses/LICENSE +0 -0
QuizGenerator/misc.py
CHANGED
QuizGenerator/mixins.py
CHANGED
|
@@ -6,7 +6,7 @@ These mixins provide reusable patterns for common question structures.
|
|
|
6
6
|
|
|
7
7
|
import abc
|
|
8
8
|
from typing import Dict, List, Any, Union
|
|
9
|
-
|
|
9
|
+
import QuizGenerator.contentast as ca
|
|
10
10
|
|
|
11
11
|
|
|
12
12
|
class TableQuestionMixin:
|
|
@@ -14,10 +14,10 @@ class TableQuestionMixin:
|
|
|
14
14
|
Mixin providing common table generation patterns for questions.
|
|
15
15
|
|
|
16
16
|
This mixin identifies and abstracts the most common table patterns used
|
|
17
|
-
across question types, reducing repetitive
|
|
17
|
+
across question types, reducing repetitive ca.Table creation code.
|
|
18
18
|
"""
|
|
19
19
|
|
|
20
|
-
def create_info_table(self, info_dict: Dict[str, Any], transpose: bool = False) ->
|
|
20
|
+
def create_info_table(self, info_dict: Dict[str, Any], transpose: bool = False) -> ca.Table:
|
|
21
21
|
"""
|
|
22
22
|
Creates a vertical info table (key-value pairs).
|
|
23
23
|
|
|
@@ -29,18 +29,18 @@ class TableQuestionMixin:
|
|
|
29
29
|
transpose: Whether to transpose the table (default: False)
|
|
30
30
|
|
|
31
31
|
Returns:
|
|
32
|
-
|
|
32
|
+
ca.Table with the information formatted
|
|
33
33
|
"""
|
|
34
|
-
# Don't convert
|
|
34
|
+
# Don't convert content AST elements to strings - let them render properly
|
|
35
35
|
table_data = []
|
|
36
36
|
for key, value in info_dict.items():
|
|
37
|
-
# Keep
|
|
38
|
-
if isinstance(value,
|
|
37
|
+
# Keep content AST elements as-is, convert others to strings
|
|
38
|
+
if isinstance(value, ca.Element):
|
|
39
39
|
table_data.append([key, value])
|
|
40
40
|
else:
|
|
41
41
|
table_data.append([key, str(value)])
|
|
42
42
|
|
|
43
|
-
return
|
|
43
|
+
return ca.Table(
|
|
44
44
|
data=table_data,
|
|
45
45
|
transpose=transpose
|
|
46
46
|
)
|
|
@@ -50,7 +50,7 @@ class TableQuestionMixin:
|
|
|
50
50
|
headers: List[str],
|
|
51
51
|
data_rows: List[Dict[str, Any]],
|
|
52
52
|
answer_columns: List[str] = None
|
|
53
|
-
) ->
|
|
53
|
+
) -> ca.Table:
|
|
54
54
|
"""
|
|
55
55
|
Creates a table where some cells are answer blanks.
|
|
56
56
|
|
|
@@ -63,17 +63,17 @@ class TableQuestionMixin:
|
|
|
63
63
|
answer_columns: List of column names that should be treated as answers
|
|
64
64
|
|
|
65
65
|
Returns:
|
|
66
|
-
|
|
66
|
+
ca.Table with answers embedded in appropriate cells
|
|
67
67
|
"""
|
|
68
68
|
answer_columns = answer_columns or []
|
|
69
69
|
|
|
70
|
-
def format_cell(row_data: Dict, column: str) -> Union[str,
|
|
70
|
+
def format_cell(row_data: Dict, column: str) -> Union[str, ca.Answer]:
|
|
71
71
|
"""Format a cell based on whether it should be an answer or plain data"""
|
|
72
72
|
value = row_data.get(column, "")
|
|
73
73
|
|
|
74
74
|
# If this column should contain answers and the value is an Answer object
|
|
75
|
-
# Answer extends
|
|
76
|
-
if column in answer_columns and isinstance(value,
|
|
75
|
+
# Answer extends ca.Leaf, so it can be used directly
|
|
76
|
+
if column in answer_columns and isinstance(value, ca.Answer):
|
|
77
77
|
return value
|
|
78
78
|
# If this column should contain answers but we have the answer key
|
|
79
79
|
elif column in answer_columns and isinstance(value, str) and hasattr(self, 'answers'):
|
|
@@ -89,7 +89,7 @@ class TableQuestionMixin:
|
|
|
89
89
|
for row in data_rows
|
|
90
90
|
]
|
|
91
91
|
|
|
92
|
-
return
|
|
92
|
+
return ca.Table(
|
|
93
93
|
headers=headers,
|
|
94
94
|
data=table_data
|
|
95
95
|
)
|
|
@@ -100,7 +100,7 @@ class TableQuestionMixin:
|
|
|
100
100
|
answer_label: str,
|
|
101
101
|
answer_key: str,
|
|
102
102
|
transpose: bool = True
|
|
103
|
-
) ->
|
|
103
|
+
) -> ca.Table:
|
|
104
104
|
"""
|
|
105
105
|
Creates a table combining parameters with a single answer.
|
|
106
106
|
|
|
@@ -114,18 +114,18 @@ class TableQuestionMixin:
|
|
|
114
114
|
transpose: Whether to show as vertical table (default: True)
|
|
115
115
|
|
|
116
116
|
Returns:
|
|
117
|
-
|
|
117
|
+
ca.Table with parameters and answer
|
|
118
118
|
"""
|
|
119
119
|
# Build data with parameters plus answer row
|
|
120
120
|
data = [[key, str(value)] for key, value in parameter_info.items()]
|
|
121
121
|
|
|
122
|
-
# Add answer row - Answer extends
|
|
122
|
+
# Add answer row - Answer extends ca.Leaf so it can be used directly
|
|
123
123
|
if hasattr(self, 'answers') and answer_key in self.answers:
|
|
124
124
|
data.append([answer_label, self.answers[answer_key]])
|
|
125
125
|
else:
|
|
126
126
|
data.append([answer_label, f"[{answer_key}]"]) # Fallback
|
|
127
127
|
|
|
128
|
-
return
|
|
128
|
+
return ca.Table(
|
|
129
129
|
data=data,
|
|
130
130
|
transpose=transpose
|
|
131
131
|
)
|
|
@@ -134,7 +134,7 @@ class TableQuestionMixin:
|
|
|
134
134
|
self,
|
|
135
135
|
headers: List[str],
|
|
136
136
|
template_rows: List[Dict[str, Any]]
|
|
137
|
-
) ->
|
|
137
|
+
) -> ca.Table:
|
|
138
138
|
"""
|
|
139
139
|
Creates a table where multiple cells are answer blanks to fill in.
|
|
140
140
|
|
|
@@ -146,14 +146,14 @@ class TableQuestionMixin:
|
|
|
146
146
|
template_rows: Rows where values can be data or answer keys
|
|
147
147
|
|
|
148
148
|
Returns:
|
|
149
|
-
|
|
149
|
+
ca.Table with multiple answer blanks
|
|
150
150
|
"""
|
|
151
151
|
|
|
152
|
-
def process_cell_value(value: Any) -> Union[str,
|
|
152
|
+
def process_cell_value(value: Any) -> Union[str, ca.Answer]:
|
|
153
153
|
"""Convert cell values to appropriate display format"""
|
|
154
154
|
# If it's already an Answer object, use it directly
|
|
155
|
-
# Answer extends
|
|
156
|
-
if isinstance(value,
|
|
155
|
+
# Answer extends ca.Leaf so it can be used in the AST
|
|
156
|
+
if isinstance(value, ca.Answer):
|
|
157
157
|
return value
|
|
158
158
|
# If it's a string that looks like an answer key, try to resolve it
|
|
159
159
|
elif isinstance(value, str) and value.startswith("answer__") and hasattr(self, 'answers'):
|
|
@@ -168,7 +168,7 @@ class TableQuestionMixin:
|
|
|
168
168
|
for row in template_rows
|
|
169
169
|
]
|
|
170
170
|
|
|
171
|
-
return
|
|
171
|
+
return ca.Table(
|
|
172
172
|
headers=headers,
|
|
173
173
|
data=table_data
|
|
174
174
|
)
|
|
@@ -178,23 +178,23 @@ class BodyTemplatesMixin:
|
|
|
178
178
|
"""
|
|
179
179
|
Mixin providing common body structure patterns.
|
|
180
180
|
|
|
181
|
-
These methods create complete
|
|
181
|
+
These methods create complete ca.Section objects following
|
|
182
182
|
common question layout patterns.
|
|
183
183
|
"""
|
|
184
184
|
|
|
185
185
|
def create_calculation_with_info_body(
|
|
186
186
|
self,
|
|
187
187
|
intro_text: str,
|
|
188
|
-
info_table:
|
|
189
|
-
answer_block:
|
|
190
|
-
) ->
|
|
188
|
+
info_table: ca.Table,
|
|
189
|
+
answer_block: ca.AnswerBlock
|
|
190
|
+
) -> ca.Section:
|
|
191
191
|
"""
|
|
192
192
|
Standard pattern: intro text + info table + answer block.
|
|
193
193
|
|
|
194
194
|
Used by: HardDriveAccessTime, AverageMemoryAccessTime, etc.
|
|
195
195
|
"""
|
|
196
|
-
body =
|
|
197
|
-
body.add_element(
|
|
196
|
+
body = ca.Section()
|
|
197
|
+
body.add_element(ca.Paragraph([intro_text]))
|
|
198
198
|
body.add_element(info_table)
|
|
199
199
|
body.add_element(answer_block)
|
|
200
200
|
return body
|
|
@@ -203,39 +203,39 @@ class BodyTemplatesMixin:
|
|
|
203
203
|
self,
|
|
204
204
|
intro_text: str,
|
|
205
205
|
instructions: str,
|
|
206
|
-
table:
|
|
207
|
-
) ->
|
|
206
|
+
table: ca.Table
|
|
207
|
+
) -> ca.Section:
|
|
208
208
|
"""
|
|
209
209
|
Standard pattern: intro + instructions + table with blanks.
|
|
210
210
|
|
|
211
211
|
Used by: VirtualAddressParts, CachingQuestion, etc.
|
|
212
212
|
"""
|
|
213
|
-
body =
|
|
213
|
+
body = ca.Section()
|
|
214
214
|
if intro_text:
|
|
215
|
-
body.add_element(
|
|
215
|
+
body.add_element(ca.Paragraph([intro_text]))
|
|
216
216
|
if instructions:
|
|
217
|
-
body.add_element(
|
|
217
|
+
body.add_element(ca.Paragraph([instructions]))
|
|
218
218
|
body.add_element(table)
|
|
219
219
|
return body
|
|
220
220
|
|
|
221
221
|
def create_parameter_calculation_body(
|
|
222
222
|
self,
|
|
223
223
|
intro_text: str,
|
|
224
|
-
parameter_table:
|
|
225
|
-
answer_table:
|
|
224
|
+
parameter_table: ca.Table,
|
|
225
|
+
answer_table: ca.Table = None,
|
|
226
226
|
additional_instructions: str = None
|
|
227
|
-
) ->
|
|
227
|
+
) -> ca.Section:
|
|
228
228
|
"""
|
|
229
229
|
Standard pattern: intro + parameter table + optional answer table.
|
|
230
230
|
|
|
231
231
|
Used by: BaseAndBounds, Paging, etc.
|
|
232
232
|
"""
|
|
233
|
-
body =
|
|
234
|
-
body.add_element(
|
|
233
|
+
body = ca.Section()
|
|
234
|
+
body.add_element(ca.Paragraph([intro_text]))
|
|
235
235
|
body.add_element(parameter_table)
|
|
236
236
|
|
|
237
237
|
if additional_instructions:
|
|
238
|
-
body.add_element(
|
|
238
|
+
body.add_element(ca.Paragraph([additional_instructions]))
|
|
239
239
|
|
|
240
240
|
if answer_table:
|
|
241
241
|
body.add_element(answer_table)
|
|
@@ -261,7 +261,7 @@ class MultiPartQuestionMixin:
|
|
|
261
261
|
|
|
262
262
|
Methods provided:
|
|
263
263
|
- is_multipart(): Check if this question should generate multiple subparts
|
|
264
|
-
- create_repeated_problem_part(): Create the
|
|
264
|
+
- create_repeated_problem_part(): Create the ca.RepeatedProblemPart element
|
|
265
265
|
- generate_subquestion_data(): Abstract method for subclasses to implement
|
|
266
266
|
"""
|
|
267
267
|
|
|
@@ -276,16 +276,16 @@ class MultiPartQuestionMixin:
|
|
|
276
276
|
|
|
277
277
|
def create_repeated_problem_part(self, subpart_data_list):
|
|
278
278
|
"""
|
|
279
|
-
Create a
|
|
279
|
+
Create a ca.RepeatedProblemPart element from subpart data.
|
|
280
280
|
|
|
281
281
|
Args:
|
|
282
282
|
subpart_data_list: List of data for each subpart. Each item can be:
|
|
283
283
|
- A string (LaTeX equation content)
|
|
284
|
-
- A
|
|
284
|
+
- A ca.Element
|
|
285
285
|
- A tuple/list of elements to be joined
|
|
286
286
|
|
|
287
287
|
Returns:
|
|
288
|
-
|
|
288
|
+
ca.RepeatedProblemPart: The formatted multi-part element
|
|
289
289
|
|
|
290
290
|
Example:
|
|
291
291
|
# For vector dot products
|
|
@@ -295,8 +295,8 @@ class MultiPartQuestionMixin:
|
|
|
295
295
|
]
|
|
296
296
|
return self.create_repeated_problem_part(subparts)
|
|
297
297
|
"""
|
|
298
|
-
|
|
299
|
-
return
|
|
298
|
+
import QuizGenerator.contentast as ca
|
|
299
|
+
return ca.RepeatedProblemPart(subpart_data_list)
|
|
300
300
|
|
|
301
301
|
def generate_subquestion_data(self):
|
|
302
302
|
"""
|
|
@@ -308,7 +308,7 @@ class MultiPartQuestionMixin:
|
|
|
308
308
|
Returns:
|
|
309
309
|
list: List of data for each subpart. The format depends on the
|
|
310
310
|
specific question type but should be compatible with
|
|
311
|
-
|
|
311
|
+
ca.RepeatedProblemPart.
|
|
312
312
|
|
|
313
313
|
Example implementation:
|
|
314
314
|
def generate_subquestion_data(self):
|
|
@@ -316,10 +316,10 @@ class MultiPartQuestionMixin:
|
|
|
316
316
|
for i in range(self.num_subquestions):
|
|
317
317
|
vector_a = self._generate_vector(self.dimension)
|
|
318
318
|
vector_b = self._generate_vector(self.dimension)
|
|
319
|
-
matrix_a =
|
|
319
|
+
matrix_a = ca.Matrix.to_latex(
|
|
320
320
|
[[v] for v in vector_a], "b"
|
|
321
321
|
)
|
|
322
|
-
matrix_b =
|
|
322
|
+
matrix_b = ca.Matrix.to_latex(
|
|
323
323
|
[[v] for v in vector_b], "b"
|
|
324
324
|
)
|
|
325
325
|
subparts.append((matrix_a, "\\cdot", matrix_b))
|
|
@@ -337,7 +337,7 @@ class MultiPartQuestionMixin:
|
|
|
337
337
|
intro_text: Introduction text for the question
|
|
338
338
|
|
|
339
339
|
Returns:
|
|
340
|
-
|
|
340
|
+
ca.Section: Complete question body with intro and subparts
|
|
341
341
|
|
|
342
342
|
Example:
|
|
343
343
|
def get_body(self):
|
|
@@ -346,9 +346,9 @@ class MultiPartQuestionMixin:
|
|
|
346
346
|
else:
|
|
347
347
|
return self.create_single_part_body()
|
|
348
348
|
"""
|
|
349
|
-
|
|
350
|
-
body =
|
|
351
|
-
body.add_element(
|
|
349
|
+
import QuizGenerator.contentast as ca
|
|
350
|
+
body = ca.Section()
|
|
351
|
+
body.add_element(ca.Paragraph([intro_text]))
|
|
352
352
|
|
|
353
353
|
# Generate subpart data and create the repeated problem part
|
|
354
354
|
subpart_data = self.generate_subquestion_data()
|
|
@@ -368,9 +368,9 @@ class MultiPartQuestionMixin:
|
|
|
368
368
|
Example:
|
|
369
369
|
# For a 3-part question
|
|
370
370
|
{
|
|
371
|
-
'a':
|
|
372
|
-
'b':
|
|
373
|
-
'c':
|
|
371
|
+
'a': ca.Answer.integer('a', 5),
|
|
372
|
+
'b': ca.Answer.integer('b', 12),
|
|
373
|
+
'c': ca.Answer.integer('c', -3)
|
|
374
374
|
}
|
|
375
375
|
"""
|
|
376
376
|
if not self.is_multipart():
|
|
@@ -496,10 +496,10 @@ class MathOperationQuestion(MultiPartQuestionMixin, abc.ABC):
|
|
|
496
496
|
|
|
497
497
|
def _get_body(self):
|
|
498
498
|
"""Build question body and collect answers."""
|
|
499
|
-
body =
|
|
499
|
+
body = ca.Section()
|
|
500
500
|
answers = []
|
|
501
501
|
|
|
502
|
-
body.add_element(
|
|
502
|
+
body.add_element(ca.Paragraph([self.get_intro_text()]))
|
|
503
503
|
|
|
504
504
|
if self.is_multipart():
|
|
505
505
|
# Use multipart formatting with repeated problem parts
|
|
@@ -511,7 +511,7 @@ class MathOperationQuestion(MultiPartQuestionMixin, abc.ABC):
|
|
|
511
511
|
else:
|
|
512
512
|
# Single equation display
|
|
513
513
|
equation_latex = self.format_single_equation(self.operand_a, self.operand_b)
|
|
514
|
-
body.add_element(
|
|
514
|
+
body.add_element(ca.Equation(f"{equation_latex} = ", inline=False))
|
|
515
515
|
|
|
516
516
|
# Canvas-only answer fields (hidden from PDF)
|
|
517
517
|
single_answers = self._add_single_question_answers(body)
|
|
@@ -536,9 +536,9 @@ class MathOperationQuestion(MultiPartQuestionMixin, abc.ABC):
|
|
|
536
536
|
|
|
537
537
|
def _get_explanation(self):
|
|
538
538
|
"""Default explanation structure. Subclasses should override for specific explanations."""
|
|
539
|
-
explanation =
|
|
539
|
+
explanation = ca.Section()
|
|
540
540
|
|
|
541
|
-
explanation.add_element(
|
|
541
|
+
explanation.add_element(ca.Paragraph([self.get_explanation_intro()]))
|
|
542
542
|
|
|
543
543
|
if self.is_multipart():
|
|
544
544
|
# Handle multipart explanations
|
|
@@ -562,8 +562,8 @@ class MathOperationQuestion(MultiPartQuestionMixin, abc.ABC):
|
|
|
562
562
|
|
|
563
563
|
def create_explanation_for_subpart(self, subpart_data, letter):
|
|
564
564
|
"""Create explanation for a single subpart. Subclasses should override."""
|
|
565
|
-
return
|
|
565
|
+
return ca.Paragraph([f"Part ({letter}): Calculation details would go here."])
|
|
566
566
|
|
|
567
567
|
def create_single_explanation(self):
|
|
568
568
|
"""Create explanation for single questions. Subclasses should override."""
|
|
569
|
-
return
|
|
569
|
+
return ca.Paragraph(["Single question explanation would go here."])
|
|
@@ -5,7 +5,7 @@ from typing import List, Dict, Any, Tuple
|
|
|
5
5
|
|
|
6
6
|
import logging
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
import QuizGenerator.contentast as ca
|
|
9
9
|
from QuizGenerator.question import Question, QuestionRegistry
|
|
10
10
|
from QuizGenerator.mixins import TableQuestionMixin
|
|
11
11
|
|
|
@@ -21,12 +21,12 @@ class FromText(Question):
|
|
|
21
21
|
self.answers = []
|
|
22
22
|
self.possible_variations = 1
|
|
23
23
|
|
|
24
|
-
def get_body(self, **kwargs) ->
|
|
24
|
+
def get_body(self, **kwargs) -> ca.Section:
|
|
25
25
|
|
|
26
|
-
return
|
|
26
|
+
return ca.Section([ca.Text(self.text)])
|
|
27
27
|
|
|
28
|
-
def get_answers(self, *args, **kwargs) -> Tuple[
|
|
29
|
-
return
|
|
28
|
+
def get_answers(self, *args, **kwargs) -> Tuple[ca.Answer.CanvasAnswerKind, List[Dict[str,Any]]]:
|
|
29
|
+
return ca.Answer.CanvasAnswerKind.ESSAY, []
|
|
30
30
|
|
|
31
31
|
|
|
32
32
|
@QuestionRegistry.register()
|
|
@@ -45,13 +45,13 @@ class FromGenerator(FromText, TableQuestionMixin):
|
|
|
45
45
|
def attach_function_to_object(obj, function_code, function_name='get_body_lines'):
|
|
46
46
|
function_code = "import random\n" + function_code
|
|
47
47
|
|
|
48
|
-
# Create a local namespace for exec with
|
|
48
|
+
# Create a local namespace for exec with content AST helpers available
|
|
49
49
|
local_namespace = {
|
|
50
|
-
'
|
|
51
|
-
'Section':
|
|
52
|
-
'Text':
|
|
53
|
-
'Table':
|
|
54
|
-
'Paragraph':
|
|
50
|
+
'ca': ca,
|
|
51
|
+
'Section': ca.Section,
|
|
52
|
+
'Text': ca.Text,
|
|
53
|
+
'Table': ca.Table,
|
|
54
|
+
'Paragraph': ca.Paragraph
|
|
55
55
|
}
|
|
56
56
|
|
|
57
57
|
# Define the function dynamically using exec
|
|
@@ -70,15 +70,15 @@ class FromGenerator(FromText, TableQuestionMixin):
|
|
|
70
70
|
self.answers = {}
|
|
71
71
|
|
|
72
72
|
|
|
73
|
-
def get_body(self, **kwargs) ->
|
|
73
|
+
def get_body(self, **kwargs) -> ca.Section:
|
|
74
74
|
return super().get_body()
|
|
75
75
|
|
|
76
76
|
def refresh(self, *args, **kwargs):
|
|
77
77
|
super().refresh(*args, **kwargs)
|
|
78
78
|
try:
|
|
79
79
|
generated_content = self.generator()
|
|
80
|
-
# Expect generator to return a
|
|
81
|
-
if isinstance(generated_content,
|
|
80
|
+
# Expect generator to return a ca.Section or convert string to Section
|
|
81
|
+
if isinstance(generated_content, ca.Section):
|
|
82
82
|
self.text = "" # Clear text since we'll override get_body
|
|
83
83
|
self._generated_section = generated_content
|
|
84
84
|
elif isinstance(generated_content, str):
|
|
@@ -93,11 +93,11 @@ class FromGenerator(FromText, TableQuestionMixin):
|
|
|
93
93
|
log.debug(self.generator_text)
|
|
94
94
|
exit(8)
|
|
95
95
|
|
|
96
|
-
def get_body(self, **kwargs) ->
|
|
96
|
+
def get_body(self, **kwargs) -> ca.Section:
|
|
97
97
|
if hasattr(self, '_generated_section') and self._generated_section:
|
|
98
98
|
return self._generated_section
|
|
99
99
|
return super().get_body()
|
|
100
100
|
|
|
101
101
|
|
|
102
102
|
class TrueFalse(FromText):
|
|
103
|
-
pass
|
|
103
|
+
pass
|
|
@@ -8,7 +8,7 @@ from typing import List, Dict, Optional, Tuple, Any
|
|
|
8
8
|
|
|
9
9
|
from QuizGenerator.question import QuestionRegistry, Question
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
import QuizGenerator.contentast as ca
|
|
12
12
|
|
|
13
13
|
import logging
|
|
14
14
|
log = logging.getLogger(__name__)
|
|
@@ -266,26 +266,26 @@ class ValidStringsInLanguageQuestion(LanguageQuestion):
|
|
|
266
266
|
|
|
267
267
|
self.answers = {}
|
|
268
268
|
|
|
269
|
-
# Create answers with proper
|
|
269
|
+
# Create answers with proper ca.Answer signature
|
|
270
270
|
# value is the generated string, correct indicates if it's a valid answer
|
|
271
271
|
good_string = self.grammar_good.generate(self.include_spaces)
|
|
272
|
-
self.answers["answer_good"] =
|
|
272
|
+
self.answers["answer_good"] = ca.Answer(
|
|
273
273
|
value=good_string,
|
|
274
|
-
kind=
|
|
274
|
+
kind=ca.Answer.CanvasAnswerKind.MULTIPLE_ANSWER,
|
|
275
275
|
correct=True
|
|
276
276
|
)
|
|
277
277
|
|
|
278
278
|
bad_string = self.grammar_bad.generate(self.include_spaces)
|
|
279
|
-
self.answers["answer_bad"] =
|
|
279
|
+
self.answers["answer_bad"] = ca.Answer(
|
|
280
280
|
value=bad_string,
|
|
281
|
-
kind=
|
|
281
|
+
kind=ca.Answer.CanvasAnswerKind.MULTIPLE_ANSWER,
|
|
282
282
|
correct=False
|
|
283
283
|
)
|
|
284
284
|
|
|
285
285
|
bad_early_string = self.grammar_bad.generate(self.include_spaces, early_exit=True)
|
|
286
|
-
self.answers["answer_bad_early"] =
|
|
286
|
+
self.answers["answer_bad_early"] = ca.Answer(
|
|
287
287
|
value=bad_early_string,
|
|
288
|
-
kind=
|
|
288
|
+
kind=ca.Answer.CanvasAnswerKind.MULTIPLE_ANSWER,
|
|
289
289
|
correct=False
|
|
290
290
|
)
|
|
291
291
|
|
|
@@ -308,9 +308,9 @@ class ValidStringsInLanguageQuestion(LanguageQuestion):
|
|
|
308
308
|
is_correct = correct and not early_exit
|
|
309
309
|
|
|
310
310
|
if len(generated_string) < self.MAX_LENGTH and generated_string not in answer_text_set:
|
|
311
|
-
self.answers[f"answer_{num_tries}"] =
|
|
311
|
+
self.answers[f"answer_{num_tries}"] = ca.Answer(
|
|
312
312
|
value=generated_string,
|
|
313
|
-
kind=
|
|
313
|
+
kind=ca.Answer.CanvasAnswerKind.MULTIPLE_ANSWER,
|
|
314
314
|
correct=is_correct
|
|
315
315
|
)
|
|
316
316
|
answer_text_set.add(generated_string)
|
|
@@ -335,18 +335,18 @@ class ValidStringsInLanguageQuestion(LanguageQuestion):
|
|
|
335
335
|
"""Build question body and collect answers."""
|
|
336
336
|
answers = list(self.answers.values())
|
|
337
337
|
|
|
338
|
-
body =
|
|
338
|
+
body = ca.Section()
|
|
339
339
|
|
|
340
340
|
body.add_element(
|
|
341
|
-
|
|
342
|
-
|
|
341
|
+
ca.OnlyHtml([
|
|
342
|
+
ca.Paragraph([
|
|
343
343
|
"Given the following grammar, which of the below strings are part of the language?"
|
|
344
344
|
])
|
|
345
345
|
])
|
|
346
346
|
)
|
|
347
347
|
body.add_element(
|
|
348
|
-
|
|
349
|
-
|
|
348
|
+
ca.OnlyLatex([
|
|
349
|
+
ca.Paragraph([
|
|
350
350
|
"Given the following grammar "
|
|
351
351
|
"please circle any provided strings that are part of the language (or indicate clearly if there are none), "
|
|
352
352
|
"and on each blank line provide generate a new, unique string that is part of the language."
|
|
@@ -355,45 +355,45 @@ class ValidStringsInLanguageQuestion(LanguageQuestion):
|
|
|
355
355
|
)
|
|
356
356
|
|
|
357
357
|
body.add_element(
|
|
358
|
-
|
|
358
|
+
ca.Code(self.grammar_good.get_grammar_string())
|
|
359
359
|
)
|
|
360
360
|
|
|
361
361
|
# Add in some answers as latex-only options to be circled
|
|
362
|
-
latex_list =
|
|
362
|
+
latex_list = ca.OnlyLatex([])
|
|
363
363
|
for answer in self.featured_answers:
|
|
364
|
-
latex_list.add_element(
|
|
364
|
+
latex_list.add_element(ca.Paragraph([f"- `{str(answer)}`"]))
|
|
365
365
|
body.add_element(latex_list)
|
|
366
366
|
|
|
367
367
|
# For Latex-only, ask students to generate some more.
|
|
368
368
|
body.add_element(
|
|
369
|
-
|
|
370
|
-
|
|
369
|
+
ca.OnlyLatex([
|
|
370
|
+
ca.AnswerBlock([ca.AnswerTypes.String("", label="") for i in range(self.num_answer_blanks)])
|
|
371
371
|
])
|
|
372
372
|
)
|
|
373
373
|
|
|
374
374
|
return body, answers
|
|
375
375
|
|
|
376
|
-
def get_body(self, *args, **kwargs) ->
|
|
376
|
+
def get_body(self, *args, **kwargs) -> ca.Section:
|
|
377
377
|
"""Build question body (backward compatible interface)."""
|
|
378
378
|
body, _ = self._get_body(*args, **kwargs)
|
|
379
379
|
return body
|
|
380
380
|
|
|
381
381
|
def _get_explanation(self, *args, **kwargs):
|
|
382
382
|
"""Build question explanation."""
|
|
383
|
-
explanation =
|
|
383
|
+
explanation = ca.Section()
|
|
384
384
|
explanation.add_element(
|
|
385
|
-
|
|
385
|
+
ca.Paragraph([
|
|
386
386
|
"Remember, for a string to be part of our language, we need to be able to derive it from our grammar.",
|
|
387
387
|
"Unfortunately, there isn't space here to demonstrate the derivation so please work through them on your own!"
|
|
388
388
|
])
|
|
389
389
|
)
|
|
390
390
|
return explanation, []
|
|
391
391
|
|
|
392
|
-
def get_explanation(self, *args, **kwargs) ->
|
|
392
|
+
def get_explanation(self, *args, **kwargs) -> ca.Section:
|
|
393
393
|
"""Build question explanation (backward compatible interface)."""
|
|
394
394
|
explanation, _ = self._get_explanation(*args, **kwargs)
|
|
395
395
|
return explanation
|
|
396
396
|
|
|
397
|
-
def get_answers(self, *args, **kwargs) -> Tuple[
|
|
397
|
+
def get_answers(self, *args, **kwargs) -> Tuple[ca.Answer.CanvasAnswerKind, List[Dict[str,Any]]]:
|
|
398
398
|
|
|
399
|
-
return
|
|
399
|
+
return ca.Answer.CanvasAnswerKind.MULTIPLE_ANSWER, list(itertools.chain(*[a.get_for_canvas() for a in self.answers.values()]))
|