Commit 13c5be34b19bd4627453b4f075c2c1eb59009270
1 parent
28573779
Exists in
master
and in
1 other branch
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']) |