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']) | ... | ... |