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.
Files changed (30) hide show
  1. QuizGenerator/contentast.py +2198 -2213
  2. QuizGenerator/misc.py +1 -1
  3. QuizGenerator/mixins.py +64 -64
  4. QuizGenerator/premade_questions/basic.py +16 -16
  5. QuizGenerator/premade_questions/cst334/languages.py +26 -26
  6. QuizGenerator/premade_questions/cst334/math_questions.py +42 -42
  7. QuizGenerator/premade_questions/cst334/memory_questions.py +124 -124
  8. QuizGenerator/premade_questions/cst334/persistence_questions.py +48 -48
  9. QuizGenerator/premade_questions/cst334/process.py +38 -38
  10. QuizGenerator/premade_questions/cst463/gradient_descent/gradient_calculation.py +45 -45
  11. QuizGenerator/premade_questions/cst463/gradient_descent/gradient_descent_questions.py +34 -34
  12. QuizGenerator/premade_questions/cst463/gradient_descent/loss_calculations.py +53 -53
  13. QuizGenerator/premade_questions/cst463/gradient_descent/misc.py +2 -2
  14. QuizGenerator/premade_questions/cst463/math_and_data/matrix_questions.py +65 -65
  15. QuizGenerator/premade_questions/cst463/math_and_data/vector_questions.py +39 -39
  16. QuizGenerator/premade_questions/cst463/models/attention.py +36 -36
  17. QuizGenerator/premade_questions/cst463/models/cnns.py +26 -26
  18. QuizGenerator/premade_questions/cst463/models/rnns.py +36 -36
  19. QuizGenerator/premade_questions/cst463/models/text.py +32 -32
  20. QuizGenerator/premade_questions/cst463/models/weight_counting.py +15 -15
  21. QuizGenerator/premade_questions/cst463/neural-network-basics/neural_network_questions.py +124 -124
  22. QuizGenerator/premade_questions/cst463/tensorflow-intro/tensorflow_questions.py +161 -161
  23. QuizGenerator/question.py +41 -41
  24. QuizGenerator/quiz.py +7 -7
  25. QuizGenerator/typst_utils.py +2 -2
  26. {quizgenerator-0.6.2.dist-info → quizgenerator-0.7.0.dist-info}/METADATA +1 -1
  27. {quizgenerator-0.6.2.dist-info → quizgenerator-0.7.0.dist-info}/RECORD +30 -30
  28. {quizgenerator-0.6.2.dist-info → quizgenerator-0.7.0.dist-info}/WHEEL +0 -0
  29. {quizgenerator-0.6.2.dist-info → quizgenerator-0.7.0.dist-info}/entry_points.txt +0 -0
  30. {quizgenerator-0.6.2.dist-info → quizgenerator-0.7.0.dist-info}/licenses/LICENSE +0 -0
QuizGenerator/misc.py CHANGED
@@ -11,7 +11,7 @@ from typing import List, Dict, Tuple, Any
11
11
 
12
12
  import fractions
13
13
 
14
- from QuizGenerator.contentast import ContentAST
14
+ import QuizGenerator.contentast as ca
15
15
 
16
16
  log = logging.getLogger(__name__)
17
17
 
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
- from QuizGenerator.contentast import ContentAST
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 ContentAST.Table creation code.
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) -> ContentAST.Table:
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
- ContentAST.Table with the information formatted
32
+ ca.Table with the information formatted
33
33
  """
34
- # Don't convert ContentAST elements to strings - let them render properly
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 ContentAST elements as-is, convert others to strings
38
- if isinstance(value, ContentAST.Element):
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 ContentAST.Table(
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
- ) -> ContentAST.Table:
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
- ContentAST.Table with answers embedded in appropriate cells
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, ContentAST.Answer]:
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 ContentAST.Leaf, so it can be used directly
76
- if column in answer_columns and isinstance(value, ContentAST.Answer):
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 ContentAST.Table(
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
- ) -> ContentAST.Table:
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
- ContentAST.Table with parameters and answer
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 ContentAST.Leaf so it can be used directly
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 ContentAST.Table(
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
- ) -> ContentAST.Table:
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
- ContentAST.Table with multiple answer blanks
149
+ ca.Table with multiple answer blanks
150
150
  """
151
151
 
152
- def process_cell_value(value: Any) -> Union[str, ContentAST.Answer]:
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 ContentAST.Leaf so it can be used in the AST
156
- if isinstance(value, ContentAST.Answer):
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 ContentAST.Table(
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 ContentAST.Section objects following
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: ContentAST.Table,
189
- answer_block: ContentAST.AnswerBlock
190
- ) -> ContentAST.Section:
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 = ContentAST.Section()
197
- body.add_element(ContentAST.Paragraph([intro_text]))
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: ContentAST.Table
207
- ) -> ContentAST.Section:
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 = ContentAST.Section()
213
+ body = ca.Section()
214
214
  if intro_text:
215
- body.add_element(ContentAST.Paragraph([intro_text]))
215
+ body.add_element(ca.Paragraph([intro_text]))
216
216
  if instructions:
217
- body.add_element(ContentAST.Paragraph([instructions]))
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: ContentAST.Table,
225
- answer_table: ContentAST.Table = None,
224
+ parameter_table: ca.Table,
225
+ answer_table: ca.Table = None,
226
226
  additional_instructions: str = None
227
- ) -> ContentAST.Section:
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 = ContentAST.Section()
234
- body.add_element(ContentAST.Paragraph([intro_text]))
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(ContentAST.Paragraph([additional_instructions]))
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 ContentAST.RepeatedProblemPart element
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 ContentAST.RepeatedProblemPart element from subpart data.
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 ContentAST.Element
284
+ - A ca.Element
285
285
  - A tuple/list of elements to be joined
286
286
 
287
287
  Returns:
288
- ContentAST.RepeatedProblemPart: The formatted multi-part element
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
- from QuizGenerator.contentast import ContentAST
299
- return ContentAST.RepeatedProblemPart(subpart_data_list)
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
- ContentAST.RepeatedProblemPart.
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 = ContentAST.Matrix.to_latex(
319
+ matrix_a = ca.Matrix.to_latex(
320
320
  [[v] for v in vector_a], "b"
321
321
  )
322
- matrix_b = ContentAST.Matrix.to_latex(
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
- ContentAST.Section: Complete question body with intro and subparts
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
- from QuizGenerator.contentast import ContentAST
350
- body = ContentAST.Section()
351
- body.add_element(ContentAST.Paragraph([intro_text]))
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': ContentAST.Answer.integer('a', 5),
372
- 'b': ContentAST.Answer.integer('b', 12),
373
- 'c': ContentAST.Answer.integer('c', -3)
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 = ContentAST.Section()
499
+ body = ca.Section()
500
500
  answers = []
501
501
 
502
- body.add_element(ContentAST.Paragraph([self.get_intro_text()]))
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(ContentAST.Equation(f"{equation_latex} = ", inline=False))
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 = ContentAST.Section()
539
+ explanation = ca.Section()
540
540
 
541
- explanation.add_element(ContentAST.Paragraph([self.get_explanation_intro()]))
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 ContentAST.Paragraph([f"Part ({letter}): Calculation details would go here."])
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 ContentAST.Paragraph(["Single question explanation would go here."])
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
- from QuizGenerator.contentast import *
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) -> ContentAST.Section:
24
+ def get_body(self, **kwargs) -> ca.Section:
25
25
 
26
- return ContentAST.Section([ContentAST.Text(self.text)])
26
+ return ca.Section([ca.Text(self.text)])
27
27
 
28
- def get_answers(self, *args, **kwargs) -> Tuple[ContentAST.Answer.CanvasAnswerKind, List[Dict[str,Any]]]:
29
- return ContentAST.Answer.CanvasAnswerKind.ESSAY, []
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 ContentAST available
48
+ # Create a local namespace for exec with content AST helpers available
49
49
  local_namespace = {
50
- 'ContentAST': ContentAST,
51
- 'Section': ContentAST.Section,
52
- 'Text': ContentAST.Text,
53
- 'Table': ContentAST.Table,
54
- 'Paragraph': ContentAST.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) -> ContentAST.Section:
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 ContentAST.Section or convert string to Section
81
- if isinstance(generated_content, ContentAST.Section):
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) -> ContentAST.Section:
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
- from QuizGenerator.contentast import ContentAST, AnswerTypes
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 ContentAST.Answer signature
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"] = ContentAST.Answer(
272
+ self.answers["answer_good"] = ca.Answer(
273
273
  value=good_string,
274
- kind=ContentAST.Answer.CanvasAnswerKind.MULTIPLE_ANSWER,
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"] = ContentAST.Answer(
279
+ self.answers["answer_bad"] = ca.Answer(
280
280
  value=bad_string,
281
- kind=ContentAST.Answer.CanvasAnswerKind.MULTIPLE_ANSWER,
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"] = ContentAST.Answer(
286
+ self.answers["answer_bad_early"] = ca.Answer(
287
287
  value=bad_early_string,
288
- kind=ContentAST.Answer.CanvasAnswerKind.MULTIPLE_ANSWER,
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}"] = ContentAST.Answer(
311
+ self.answers[f"answer_{num_tries}"] = ca.Answer(
312
312
  value=generated_string,
313
- kind=ContentAST.Answer.CanvasAnswerKind.MULTIPLE_ANSWER,
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 = ContentAST.Section()
338
+ body = ca.Section()
339
339
 
340
340
  body.add_element(
341
- ContentAST.OnlyHtml([
342
- ContentAST.Paragraph([
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
- ContentAST.OnlyLatex([
349
- ContentAST.Paragraph([
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
- ContentAST.Code(self.grammar_good.get_grammar_string())
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 = ContentAST.OnlyLatex([])
362
+ latex_list = ca.OnlyLatex([])
363
363
  for answer in self.featured_answers:
364
- latex_list.add_element(ContentAST.Paragraph([f"- `{str(answer)}`"]))
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
- ContentAST.OnlyLatex([
370
- ContentAST.AnswerBlock([AnswerTypes.String("", label="") for i in range(self.num_answer_blanks)])
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) -> ContentAST.Section:
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 = ContentAST.Section()
383
+ explanation = ca.Section()
384
384
  explanation.add_element(
385
- ContentAST.Paragraph([
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) -> ContentAST.Section:
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[ContentAST.Answer.CanvasAnswerKind, List[Dict[str,Any]]]:
397
+ def get_answers(self, *args, **kwargs) -> Tuple[ca.Answer.CanvasAnswerKind, List[Dict[str,Any]]]:
398
398
 
399
- return ContentAST.Answer.CanvasAnswerKind.MULTIPLE_ANSWER, list(itertools.chain(*[a.get_for_canvas() for a in self.answers.values()]))
399
+ return ca.Answer.CanvasAnswerKind.MULTIPLE_ANSWER, list(itertools.chain(*[a.get_for_canvas() for a in self.answers.values()]))