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 | 42 | 'comments': '', |
43 | 43 | 'solution': '', |
44 | 44 | 'files': {}, |
45 | - # 'max_tries': 3, | |
46 | 45 | })) |
47 | 46 | |
48 | 47 | def correct(self) -> None: |
... | ... | @@ -220,13 +219,19 @@ class QuestionCheckbox(Question): |
220 | 219 | # check grade boundaries |
221 | 220 | if self['discount'] and not all(0.0 <= x <= 1.0 |
222 | 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 | 235 | # normalize to [0,1] |
231 | 236 | self['correct'] = [(x+1)/2 for x in self['correct']] |
232 | 237 | |
... | ... | @@ -296,15 +301,21 @@ class QuestionText(Question): |
296 | 301 | |
297 | 302 | # make sure its always a list of possible correct answers |
298 | 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 | 316 | if any(c != self.transform(c) for c in self['correct']): |
306 | 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 | 321 | # apply optional filters to the answer |
... | ... | @@ -358,8 +369,12 @@ class QuestionTextRegex(Question): |
358 | 369 | if not isinstance(self['correct'], list): |
359 | 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 | 380 | def correct(self) -> None: |
... | ... | @@ -368,12 +383,12 @@ class QuestionTextRegex(Question): |
368 | 383 | self['grade'] = 0.0 |
369 | 384 | for r in self['correct']: |
370 | 385 | try: |
371 | - if re.match(r, self['answer']): | |
386 | + if r.match(self['answer']): | |
372 | 387 | self['grade'] = 1.0 |
373 | 388 | return |
374 | 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 | 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 | 438 | def correct(self) -> None: |
400 | 439 | super().correct() |
... | ... | @@ -576,7 +615,7 @@ class QFactory(object): |
576 | 615 | if q['type'] == 'generator': |
577 | 616 | logger.debug(f' \\_ Running "{q["script"]}".') |
578 | 617 | q.setdefault('args', []) |
579 | - q.setdefault('stdin', '') # FIXME is it really necessary? | |
618 | + q.setdefault('stdin', '') | |
580 | 619 | script = path.join(q['path'], q['script']) |
581 | 620 | out = await run_script_async(script=script, args=q['args'], |
582 | 621 | stdin=q['stdin']) | ... | ... |