Commit 13c5be34b19bd4627453b4f075c2c1eb59009270

Authored by Miguel Barão
1 parent 28573779
Exists in master and in 1 other branch dev

more checks in questions.py

Showing 1 changed file with 58 additions and 19 deletions   Show diff stats
aprendizations/questions.py
@@ -42,7 +42,6 @@ class Question(dict): @@ -42,7 +42,6 @@ class Question(dict):
42 'comments': '', 42 'comments': '',
43 'solution': '', 43 'solution': '',
44 'files': {}, 44 'files': {},
45 - # 'max_tries': 3,  
46 })) 45 }))
47 46
48 def correct(self) -> None: 47 def correct(self) -> None:
@@ -220,13 +219,19 @@ class QuestionCheckbox(Question): @@ -220,13 +219,19 @@ class QuestionCheckbox(Question):
220 # check grade boundaries 219 # check grade boundaries
221 if self['discount'] and not all(0.0 <= x <= 1.0 220 if self['discount'] and not all(0.0 <= x <= 1.0
222 for x in self['correct']): 221 for x in self['correct']):
223 - logger.warning(' * * * BEHAVIOR CHANGE NOTICE * * *')  
224 - msg = (f'Correct values must be in the interval [0.0, 1.0] in '  
225 - f'"{self["ref"]}"')  
226 - logger.warning(msg)  
227 - logger.warning('I will convert "correct" to the new behavior, but '  
228 - 'you should change it in the questions')  
229 - logger.warning(' * * * END OF NOTICE * * *') 222 +
  223 + msg0 = ('+-------------- BEHAVIOR CHANGE NOTICE --------------+')
  224 + msg1 = ('| Correct values must be in the interval [0.0, 1.0]. |')
  225 + msg2 = ('| I will convert to the new behavior, but you should |')
  226 + msg3 = ('| fix it in the question. |')
  227 + msg4 = ('+----------------------------------------------------+')
  228 + logger.warning(msg0)
  229 + logger.warning(msg1)
  230 + logger.warning(msg2)
  231 + logger.warning(msg3)
  232 + logger.warning(msg4)
  233 + logger.warning(f'please fix "{self["ref"]}"')
  234 +
230 # normalize to [0,1] 235 # normalize to [0,1]
231 self['correct'] = [(x+1)/2 for x in self['correct']] 236 self['correct'] = [(x+1)/2 for x in self['correct']]
232 237
@@ -296,15 +301,21 @@ class QuestionText(Question): @@ -296,15 +301,21 @@ class QuestionText(Question):
296 301
297 # make sure its always a list of possible correct answers 302 # make sure its always a list of possible correct answers
298 if not isinstance(self['correct'], list): 303 if not isinstance(self['correct'], list):
299 - self['correct'] = [self['correct']] 304 + self['correct'] = [str(self['correct'])]
  305 + else:
  306 + # make sure all elements of the list are strings
  307 + self['correct'] = [str(a) for a in self['correct']]
300 308
301 - # make sure all elements of the list are strings  
302 - self['correct'] = [str(a) for a in self['correct']] 309 + for f in self['transform']:
  310 + if f not in ('remove_space', 'trim', 'normalize_space', 'lower',
  311 + 'upper'):
  312 + msg = (f'Unknown transform "{f}" in "{self["ref"]}"')
  313 + raise QuestionException(msg)
303 314
304 - # make sure that the answers are invariant with respect to the filters 315 + # check if answers are invariant with respect to the transforms
305 if any(c != self.transform(c) for c in self['correct']): 316 if any(c != self.transform(c) for c in self['correct']):
306 logger.warning(f'in "{self["ref"]}", correct answers are not ' 317 logger.warning(f'in "{self["ref"]}", correct answers are not '
307 - 'invariant wrt transformations') 318 + 'invariant wrt transformations => never correct')
308 319
309 # ------------------------------------------------------------------------ 320 # ------------------------------------------------------------------------
310 # apply optional filters to the answer 321 # apply optional filters to the answer
@@ -358,8 +369,12 @@ class QuestionTextRegex(Question): @@ -358,8 +369,12 @@ class QuestionTextRegex(Question):
358 if not isinstance(self['correct'], list): 369 if not isinstance(self['correct'], list):
359 self['correct'] = [self['correct']] 370 self['correct'] = [self['correct']]
360 371
361 - # make sure all elements of the list are strings  
362 - self['correct'] = [str(a) for a in self['correct']] 372 + # converts patterns to compiled versions
  373 + try:
  374 + self['correct'] = [re.compile(a) for a in self['correct']]
  375 + except Exception:
  376 + msg = f'Failed to compile regex in "{self["ref"]}"'
  377 + raise QuestionException(msg)
363 378
364 # ------------------------------------------------------------------------ 379 # ------------------------------------------------------------------------
365 def correct(self) -> None: 380 def correct(self) -> None:
@@ -368,12 +383,12 @@ class QuestionTextRegex(Question): @@ -368,12 +383,12 @@ class QuestionTextRegex(Question):
368 self['grade'] = 0.0 383 self['grade'] = 0.0
369 for r in self['correct']: 384 for r in self['correct']:
370 try: 385 try:
371 - if re.match(r, self['answer']): 386 + if r.match(self['answer']):
372 self['grade'] = 1.0 387 self['grade'] = 1.0
373 return 388 return
374 except TypeError: 389 except TypeError:
375 - logger.error(f'While matching regex {self["correct"]} with'  
376 - f' answer "{self["answer"]}".') 390 + logger.error(f'While matching regex {r.pattern} with '
  391 + f'answer "{self["answer"]}".')
377 392
378 393
379 # ============================================================================ 394 # ============================================================================
@@ -395,6 +410,30 @@ class QuestionNumericInterval(Question): @@ -395,6 +410,30 @@ class QuestionNumericInterval(Question):
395 'correct': [1.0, -1.0], # will always return false 410 'correct': [1.0, -1.0], # will always return false
396 })) 411 }))
397 412
  413 + # if only one number n is given, make an interval [n,n]
  414 + if isinstance(self['correct'], (int, float)):
  415 + self['correct'] = [float(self['correct']), float(self['correct'])]
  416 +
  417 + # make sure its a list of two numbers
  418 + elif isinstance(self['correct'], list):
  419 + if len(self['correct']) != 2:
  420 + msg = (f'Numeric interval must be a list with two numbers, in '
  421 + f'{self["ref"]}')
  422 + raise QuestionException(msg)
  423 +
  424 + try:
  425 + self['correct'] = [float(n) for n in self['correct']]
  426 + except Exception:
  427 + msg = (f'Numeric interval must be a list with two numbers, in '
  428 + f'{self["ref"]}')
  429 + raise QuestionException(msg)
  430 +
  431 + # invalid
  432 + else:
  433 + msg = (f'Numeric interval must be a list with two numbers, in '
  434 + f'{self["ref"]}')
  435 + raise QuestionException(msg)
  436 +
398 # ------------------------------------------------------------------------ 437 # ------------------------------------------------------------------------
399 def correct(self) -> None: 438 def correct(self) -> None:
400 super().correct() 439 super().correct()
@@ -576,7 +615,7 @@ class QFactory(object): @@ -576,7 +615,7 @@ class QFactory(object):
576 if q['type'] == 'generator': 615 if q['type'] == 'generator':
577 logger.debug(f' \\_ Running "{q["script"]}".') 616 logger.debug(f' \\_ Running "{q["script"]}".')
578 q.setdefault('args', []) 617 q.setdefault('args', [])
579 - q.setdefault('stdin', '') # FIXME is it really necessary? 618 + q.setdefault('stdin', '')
580 script = path.join(q['path'], q['script']) 619 script = path.join(q['path'], q['script'])
581 out = await run_script_async(script=script, args=q['args'], 620 out = await run_script_async(script=script, args=q['args'],
582 stdin=q['stdin']) 621 stdin=q['stdin'])