Commit a333dc72030ffe2603e289666d469e8cc5e10caa
1 parent
3259fc7c
Exists in
master
and in
1 other branch
Add type annotations mostly in questions.py
Showing
6 changed files
with
86 additions
and
62 deletions
Show diff stats
.gitignore
| @@ -2,4 +2,5 @@ | @@ -2,4 +2,5 @@ | ||
| 2 | /aprendizations.egg-info/ | 2 | /aprendizations.egg-info/ |
| 3 | /aprendizations/__pycache__/ | 3 | /aprendizations/__pycache__/ |
| 4 | /demo/students.db | 4 | /demo/students.db |
| 5 | -/node_modules/ | ||
| 6 | \ No newline at end of file | 5 | \ No newline at end of file |
| 6 | +/node_modules/ | ||
| 7 | +/.mypy_cache/ | ||
| 7 | \ No newline at end of file | 8 | \ No newline at end of file |
aprendizations/learnapp.py
| @@ -6,6 +6,7 @@ from contextlib import contextmanager # `with` statement in db sessions | @@ -6,6 +6,7 @@ from contextlib import contextmanager # `with` statement in db sessions | ||
| 6 | import asyncio | 6 | import asyncio |
| 7 | from datetime import datetime | 7 | from datetime import datetime |
| 8 | from random import random | 8 | from random import random |
| 9 | +from typing import Dict | ||
| 9 | 10 | ||
| 10 | # third party libraries | 11 | # third party libraries |
| 11 | import bcrypt | 12 | import bcrypt |
| @@ -75,13 +76,12 @@ class LearnApp(object): | @@ -75,13 +76,12 @@ class LearnApp(object): | ||
| 75 | errors = 0 | 76 | errors = 0 |
| 76 | for qref in self.factory: | 77 | for qref in self.factory: |
| 77 | logger.debug(f'Checking "{qref}"...') | 78 | logger.debug(f'Checking "{qref}"...') |
| 78 | - q = self.factory[qref].generate() | ||
| 79 | try: | 79 | try: |
| 80 | q = self.factory[qref].generate() | 80 | q = self.factory[qref].generate() |
| 81 | except Exception: | 81 | except Exception: |
| 82 | logger.error(f'Failed to generate "{qref}".') | 82 | logger.error(f'Failed to generate "{qref}".') |
| 83 | errors += 1 | 83 | errors += 1 |
| 84 | - raise | 84 | + raise LearnException('Sanity checks') |
| 85 | continue | 85 | continue |
| 86 | 86 | ||
| 87 | if 'tests_right' in q: | 87 | if 'tests_right' in q: |
| @@ -89,8 +89,7 @@ class LearnApp(object): | @@ -89,8 +89,7 @@ class LearnApp(object): | ||
| 89 | q['answer'] = t | 89 | q['answer'] = t |
| 90 | q.correct() | 90 | q.correct() |
| 91 | if q['grade'] < 1.0: | 91 | if q['grade'] < 1.0: |
| 92 | - logger.error(f'Failed to correct right answer in ' | ||
| 93 | - f'"{qref}".') | 92 | + logger.error(f'Failed right answer in "{qref}".') |
| 94 | errors += 1 | 93 | errors += 1 |
| 95 | continue # to next right test | 94 | continue # to next right test |
| 96 | 95 | ||
| @@ -99,14 +98,13 @@ class LearnApp(object): | @@ -99,14 +98,13 @@ class LearnApp(object): | ||
| 99 | q['answer'] = t | 98 | q['answer'] = t |
| 100 | q.correct() | 99 | q.correct() |
| 101 | if q['grade'] >= 1.0: | 100 | if q['grade'] >= 1.0: |
| 102 | - logger.error(f'Failed to correct right answer in ' | ||
| 103 | - f'"{qref}".') | 101 | + logger.error(f'Failed wrong answer in "{qref}".') |
| 104 | errors += 1 | 102 | errors += 1 |
| 105 | continue # to next wrong test | 103 | continue # to next wrong test |
| 106 | 104 | ||
| 107 | if errors > 0: | 105 | if errors > 0: |
| 108 | logger.info(f'{errors:>6} errors found.') | 106 | logger.info(f'{errors:>6} errors found.') |
| 109 | - raise | 107 | + raise LearnException('Sanity checks') |
| 110 | else: | 108 | else: |
| 111 | logger.info('No errors found.') | 109 | logger.info('No errors found.') |
| 112 | 110 | ||
| @@ -315,8 +313,8 @@ class LearnApp(object): | @@ -315,8 +313,8 @@ class LearnApp(object): | ||
| 315 | for tref, attr in topics.items(): | 313 | for tref, attr in topics.items(): |
| 316 | for d in attr.get('deps', []): | 314 | for d in attr.get('deps', []): |
| 317 | if d not in g.nodes(): | 315 | if d not in g.nodes(): |
| 318 | - logger.error(f'Topic "{tref}" depends on "{d}" but the ' | ||
| 319 | - f'latter does not exist') | 316 | + logger.error(f'Topic "{tref}" depends on "{d}" but it ' |
| 317 | + f'does not exist') | ||
| 320 | raise LearnException() | 318 | raise LearnException() |
| 321 | else: | 319 | else: |
| 322 | g.add_edge(d, tref) | 320 | g.add_edge(d, tref) |
| @@ -345,7 +343,7 @@ class LearnApp(object): | @@ -345,7 +343,7 @@ class LearnApp(object): | ||
| 345 | # ------------------------------------------------------------------------ | 343 | # ------------------------------------------------------------------------ |
| 346 | # Buils dictionary of question factories | 344 | # Buils dictionary of question factories |
| 347 | # ------------------------------------------------------------------------ | 345 | # ------------------------------------------------------------------------ |
| 348 | - def make_factory(self): | 346 | + def make_factory(self) -> Dict[str, QFactory]: |
| 349 | logger.info('Building questions factory...') | 347 | logger.info('Building questions factory...') |
| 350 | factory = {} # {'qref': QFactory()} | 348 | factory = {} # {'qref': QFactory()} |
| 351 | g = self.deps | 349 | g = self.deps |
| @@ -382,39 +380,39 @@ class LearnApp(object): | @@ -382,39 +380,39 @@ class LearnApp(object): | ||
| 382 | return factory | 380 | return factory |
| 383 | 381 | ||
| 384 | # ------------------------------------------------------------------------ | 382 | # ------------------------------------------------------------------------ |
| 385 | - def get_login_counter(self, uid): | 383 | + def get_login_counter(self, uid: str) -> int: |
| 386 | return self.online[uid]['counter'] | 384 | return self.online[uid]['counter'] |
| 387 | 385 | ||
| 388 | # ------------------------------------------------------------------------ | 386 | # ------------------------------------------------------------------------ |
| 389 | - def get_student_name(self, uid): | 387 | + def get_student_name(self, uid: str) -> str: |
| 390 | return self.online[uid].get('name', '') | 388 | return self.online[uid].get('name', '') |
| 391 | 389 | ||
| 392 | # ------------------------------------------------------------------------ | 390 | # ------------------------------------------------------------------------ |
| 393 | - def get_student_state(self, uid): | 391 | + def get_student_state(self, uid: str): |
| 394 | return self.online[uid]['state'].get_knowledge_state() | 392 | return self.online[uid]['state'].get_knowledge_state() |
| 395 | 393 | ||
| 396 | # ------------------------------------------------------------------------ | 394 | # ------------------------------------------------------------------------ |
| 397 | - def get_student_progress(self, uid): | 395 | + def get_student_progress(self, uid: str): |
| 398 | return self.online[uid]['state'].get_topic_progress() | 396 | return self.online[uid]['state'].get_topic_progress() |
| 399 | 397 | ||
| 400 | # ------------------------------------------------------------------------ | 398 | # ------------------------------------------------------------------------ |
| 401 | - def get_current_question(self, uid): | 399 | + def get_current_question(self, uid: str): |
| 402 | return self.online[uid]['state'].get_current_question() # dict | 400 | return self.online[uid]['state'].get_current_question() # dict |
| 403 | 401 | ||
| 404 | # ------------------------------------------------------------------------ | 402 | # ------------------------------------------------------------------------ |
| 405 | - def get_student_question_type(self, uid): | 403 | + def get_student_question_type(self, uid: str) -> str: |
| 406 | return self.online[uid]['state'].get_current_question()['type'] | 404 | return self.online[uid]['state'].get_current_question()['type'] |
| 407 | 405 | ||
| 408 | # ------------------------------------------------------------------------ | 406 | # ------------------------------------------------------------------------ |
| 409 | - def get_student_topic(self, uid): | 407 | + def get_student_topic(self, uid: str) -> str: |
| 410 | return self.online[uid]['state'].get_current_topic() # str | 408 | return self.online[uid]['state'].get_current_topic() # str |
| 411 | 409 | ||
| 412 | # ------------------------------------------------------------------------ | 410 | # ------------------------------------------------------------------------ |
| 413 | - def get_title(self): | 411 | + def get_title(self) -> str: |
| 414 | return self.deps.graph.get('title', '') # FIXME | 412 | return self.deps.graph.get('title', '') # FIXME |
| 415 | 413 | ||
| 416 | # ------------------------------------------------------------------------ | 414 | # ------------------------------------------------------------------------ |
| 417 | - def get_topic_name(self, ref): | 415 | + def get_topic_name(self, ref: str) -> str: |
| 418 | return self.deps.node[ref]['name'] | 416 | return self.deps.node[ref]['name'] |
| 419 | 417 | ||
| 420 | # ------------------------------------------------------------------------ | 418 | # ------------------------------------------------------------------------ |
aprendizations/questions.py
| @@ -5,6 +5,7 @@ import re | @@ -5,6 +5,7 @@ import re | ||
| 5 | from os import path | 5 | from os import path |
| 6 | import logging | 6 | import logging |
| 7 | import asyncio | 7 | import asyncio |
| 8 | +from typing import Any, Dict, NewType | ||
| 8 | 9 | ||
| 9 | # this project | 10 | # this project |
| 10 | from .tools import run_script | 11 | from .tools import run_script |
| @@ -13,6 +14,9 @@ from .tools import run_script | @@ -13,6 +14,9 @@ from .tools import run_script | ||
| 13 | logger = logging.getLogger(__name__) | 14 | logger = logging.getLogger(__name__) |
| 14 | 15 | ||
| 15 | 16 | ||
| 17 | +QDict = NewType('QDict', Dict[str, Any]) | ||
| 18 | + | ||
| 19 | + | ||
| 16 | class QuestionException(Exception): | 20 | class QuestionException(Exception): |
| 17 | pass | 21 | pass |
| 18 | 22 | ||
| @@ -27,18 +31,18 @@ class Question(dict): | @@ -27,18 +31,18 @@ class Question(dict): | ||
| 27 | to a student. | 31 | to a student. |
| 28 | Instances can shuffle options, or automatically generate questions. | 32 | Instances can shuffle options, or automatically generate questions. |
| 29 | ''' | 33 | ''' |
| 30 | - def __init__(self, q): | 34 | + def __init__(self, q: QDict) -> None: |
| 31 | super().__init__(q) | 35 | super().__init__(q) |
| 32 | 36 | ||
| 33 | # add required keys if missing | 37 | # add required keys if missing |
| 34 | - self.set_defaults({ | 38 | + self.set_defaults(QDict({ |
| 35 | 'title': '', | 39 | 'title': '', |
| 36 | 'answer': None, | 40 | 'answer': None, |
| 37 | 'comments': '', | 41 | 'comments': '', |
| 38 | 'solution': '', | 42 | 'solution': '', |
| 39 | 'files': {}, | 43 | 'files': {}, |
| 40 | 'max_tries': 3, | 44 | 'max_tries': 3, |
| 41 | - }) | 45 | + })) |
| 42 | 46 | ||
| 43 | def correct(self) -> None: | 47 | def correct(self) -> None: |
| 44 | self['comments'] = '' | 48 | self['comments'] = '' |
| @@ -48,7 +52,7 @@ class Question(dict): | @@ -48,7 +52,7 @@ class Question(dict): | ||
| 48 | loop = asyncio.get_running_loop() | 52 | loop = asyncio.get_running_loop() |
| 49 | await loop.run_in_executor(None, self.correct) | 53 | await loop.run_in_executor(None, self.correct) |
| 50 | 54 | ||
| 51 | - def set_defaults(self, d) -> None: | 55 | + def set_defaults(self, d: QDict) -> None: |
| 52 | 'Add k:v pairs from default dict d for nonexistent keys' | 56 | 'Add k:v pairs from default dict d for nonexistent keys' |
| 53 | for k, v in d.items(): | 57 | for k, v in d.items(): |
| 54 | self.setdefault(k, v) | 58 | self.setdefault(k, v) |
| @@ -69,18 +73,18 @@ class QuestionRadio(Question): | @@ -69,18 +73,18 @@ class QuestionRadio(Question): | ||
| 69 | 73 | ||
| 70 | # ------------------------------------------------------------------------ | 74 | # ------------------------------------------------------------------------ |
| 71 | # FIXME marking all options right breaks | 75 | # FIXME marking all options right breaks |
| 72 | - def __init__(self, q): | 76 | + def __init__(self, q: QDict) -> None: |
| 73 | super().__init__(q) | 77 | super().__init__(q) |
| 74 | 78 | ||
| 75 | n = len(self['options']) | 79 | n = len(self['options']) |
| 76 | 80 | ||
| 77 | - self.set_defaults({ | 81 | + self.set_defaults(QDict({ |
| 78 | 'text': '', | 82 | 'text': '', |
| 79 | 'correct': 0, | 83 | 'correct': 0, |
| 80 | 'shuffle': True, | 84 | 'shuffle': True, |
| 81 | 'discount': True, | 85 | 'discount': True, |
| 82 | 'max_tries': (n + 3) // 4 # 1 try for each 4 options | 86 | 'max_tries': (n + 3) // 4 # 1 try for each 4 options |
| 83 | - }) | 87 | + })) |
| 84 | 88 | ||
| 85 | # convert int to list, e.g. correct: 2 --> correct: [0,0,1,0,0] | 89 | # convert int to list, e.g. correct: 2 --> correct: [0,0,1,0,0] |
| 86 | # correctness levels from 0.0 to 1.0 (no discount here!) | 90 | # correctness levels from 0.0 to 1.0 (no discount here!) |
| @@ -97,7 +101,7 @@ class QuestionRadio(Question): | @@ -97,7 +101,7 @@ class QuestionRadio(Question): | ||
| 97 | right = [i for i in range(n) if self['correct'][i] >= 1] | 101 | right = [i for i in range(n) if self['correct'][i] >= 1] |
| 98 | wrong = [i for i in range(n) if self['correct'][i] < 1] | 102 | wrong = [i for i in range(n) if self['correct'][i] < 1] |
| 99 | 103 | ||
| 100 | - self.set_defaults({'choose': 1+len(wrong)}) | 104 | + self.set_defaults(QDict({'choose': 1+len(wrong)})) |
| 101 | 105 | ||
| 102 | # try to choose 1 correct option | 106 | # try to choose 1 correct option |
| 103 | if right: | 107 | if right: |
| @@ -147,20 +151,20 @@ class QuestionCheckbox(Question): | @@ -147,20 +151,20 @@ class QuestionCheckbox(Question): | ||
| 147 | ''' | 151 | ''' |
| 148 | 152 | ||
| 149 | # ------------------------------------------------------------------------ | 153 | # ------------------------------------------------------------------------ |
| 150 | - def __init__(self, q): | 154 | + def __init__(self, q: QDict) -> None: |
| 151 | super().__init__(q) | 155 | super().__init__(q) |
| 152 | 156 | ||
| 153 | n = len(self['options']) | 157 | n = len(self['options']) |
| 154 | 158 | ||
| 155 | # set defaults if missing | 159 | # set defaults if missing |
| 156 | - self.set_defaults({ | 160 | + self.set_defaults(QDict({ |
| 157 | 'text': '', | 161 | 'text': '', |
| 158 | 'correct': [1.0] * n, # Using 0.0 breaks (right, wrong) options | 162 | 'correct': [1.0] * n, # Using 0.0 breaks (right, wrong) options |
| 159 | 'shuffle': True, | 163 | 'shuffle': True, |
| 160 | 'discount': True, | 164 | 'discount': True, |
| 161 | 'choose': n, # number of options | 165 | 'choose': n, # number of options |
| 162 | 'max_tries': max(1, min(n - 1, 3)) | 166 | 'max_tries': max(1, min(n - 1, 3)) |
| 163 | - }) | 167 | + })) |
| 164 | 168 | ||
| 165 | if len(self['correct']) != n: | 169 | if len(self['correct']) != n: |
| 166 | msg = f'Options and correct mismatch in "{self["ref"]}"' | 170 | msg = f'Options and correct mismatch in "{self["ref"]}"' |
| @@ -218,13 +222,13 @@ class QuestionText(Question): | @@ -218,13 +222,13 @@ class QuestionText(Question): | ||
| 218 | ''' | 222 | ''' |
| 219 | 223 | ||
| 220 | # ------------------------------------------------------------------------ | 224 | # ------------------------------------------------------------------------ |
| 221 | - def __init__(self, q): | 225 | + def __init__(self, q: QDict) -> None: |
| 222 | super().__init__(q) | 226 | super().__init__(q) |
| 223 | 227 | ||
| 224 | - self.set_defaults({ | 228 | + self.set_defaults(QDict({ |
| 225 | 'text': '', | 229 | 'text': '', |
| 226 | 'correct': [], | 230 | 'correct': [], |
| 227 | - }) | 231 | + })) |
| 228 | 232 | ||
| 229 | # make sure its always a list of possible correct answers | 233 | # make sure its always a list of possible correct answers |
| 230 | if not isinstance(self['correct'], list): | 234 | if not isinstance(self['correct'], list): |
| @@ -251,13 +255,13 @@ class QuestionTextRegex(Question): | @@ -251,13 +255,13 @@ class QuestionTextRegex(Question): | ||
| 251 | ''' | 255 | ''' |
| 252 | 256 | ||
| 253 | # ------------------------------------------------------------------------ | 257 | # ------------------------------------------------------------------------ |
| 254 | - def __init__(self, q): | 258 | + def __init__(self, q: QDict) -> None: |
| 255 | super().__init__(q) | 259 | super().__init__(q) |
| 256 | 260 | ||
| 257 | - self.set_defaults({ | 261 | + self.set_defaults(QDict({ |
| 258 | 'text': '', | 262 | 'text': '', |
| 259 | 'correct': '$.^', # will always return false | 263 | 'correct': '$.^', # will always return false |
| 260 | - }) | 264 | + })) |
| 261 | 265 | ||
| 262 | # ------------------------------------------------------------------------ | 266 | # ------------------------------------------------------------------------ |
| 263 | def correct(self) -> None: | 267 | def correct(self) -> None: |
| @@ -282,13 +286,13 @@ class QuestionNumericInterval(Question): | @@ -282,13 +286,13 @@ class QuestionNumericInterval(Question): | ||
| 282 | ''' | 286 | ''' |
| 283 | 287 | ||
| 284 | # ------------------------------------------------------------------------ | 288 | # ------------------------------------------------------------------------ |
| 285 | - def __init__(self, q): | 289 | + def __init__(self, q: QDict) -> None: |
| 286 | super().__init__(q) | 290 | super().__init__(q) |
| 287 | 291 | ||
| 288 | - self.set_defaults({ | 292 | + self.set_defaults(QDict({ |
| 289 | 'text': '', | 293 | 'text': '', |
| 290 | 'correct': [1.0, -1.0], # will always return false | 294 | 'correct': [1.0, -1.0], # will always return false |
| 291 | - }) | 295 | + })) |
| 292 | 296 | ||
| 293 | # ------------------------------------------------------------------------ | 297 | # ------------------------------------------------------------------------ |
| 294 | def correct(self) -> None: | 298 | def correct(self) -> None: |
| @@ -317,16 +321,16 @@ class QuestionTextArea(Question): | @@ -317,16 +321,16 @@ class QuestionTextArea(Question): | ||
| 317 | ''' | 321 | ''' |
| 318 | 322 | ||
| 319 | # ------------------------------------------------------------------------ | 323 | # ------------------------------------------------------------------------ |
| 320 | - def __init__(self, q): | 324 | + def __init__(self, q: QDict) -> None: |
| 321 | super().__init__(q) | 325 | super().__init__(q) |
| 322 | 326 | ||
| 323 | - self.set_defaults({ | 327 | + self.set_defaults(QDict({ |
| 324 | 'text': '', | 328 | 'text': '', |
| 325 | 'lines': 8, | 329 | 'lines': 8, |
| 326 | 'timeout': 5, # seconds | 330 | 'timeout': 5, # seconds |
| 327 | 'correct': '', # trying to execute this will fail => grade 0.0 | 331 | 'correct': '', # trying to execute this will fail => grade 0.0 |
| 328 | 'args': [] | 332 | 'args': [] |
| 329 | - }) | 333 | + })) |
| 330 | 334 | ||
| 331 | self['correct'] = path.join(self['path'], self['correct']) # FIXME | 335 | self['correct'] = path.join(self['path'], self['correct']) # FIXME |
| 332 | 336 | ||
| @@ -360,11 +364,11 @@ class QuestionTextArea(Question): | @@ -360,11 +364,11 @@ class QuestionTextArea(Question): | ||
| 360 | # =========================================================================== | 364 | # =========================================================================== |
| 361 | class QuestionInformation(Question): | 365 | class QuestionInformation(Question): |
| 362 | # ------------------------------------------------------------------------ | 366 | # ------------------------------------------------------------------------ |
| 363 | - def __init__(self, q): | 367 | + def __init__(self, q: QDict) -> None: |
| 364 | super().__init__(q) | 368 | super().__init__(q) |
| 365 | - self.set_defaults({ | 369 | + self.set_defaults(QDict({ |
| 366 | 'text': '', | 370 | 'text': '', |
| 367 | - }) | 371 | + })) |
| 368 | 372 | ||
| 369 | # ------------------------------------------------------------------------ | 373 | # ------------------------------------------------------------------------ |
| 370 | # can return negative values for wrong answers | 374 | # can return negative values for wrong answers |
| @@ -414,14 +418,14 @@ class QFactory(object): | @@ -414,14 +418,14 @@ class QFactory(object): | ||
| 414 | 'success': QuestionInformation, | 418 | 'success': QuestionInformation, |
| 415 | } | 419 | } |
| 416 | 420 | ||
| 417 | - def __init__(self, question_dict={}): | 421 | + def __init__(self, question_dict: QDict = QDict({})) -> None: |
| 418 | self.question = question_dict | 422 | self.question = question_dict |
| 419 | 423 | ||
| 420 | # ----------------------------------------------------------------------- | 424 | # ----------------------------------------------------------------------- |
| 421 | # Given a ref returns an instance of a descendent of Question(), | 425 | # Given a ref returns an instance of a descendent of Question(), |
| 422 | # i.e. a question object (radio, checkbox, ...). | 426 | # i.e. a question object (radio, checkbox, ...). |
| 423 | # ----------------------------------------------------------------------- | 427 | # ----------------------------------------------------------------------- |
| 424 | - def generate(self): | 428 | + def generate(self) -> Question: |
| 425 | logger.debug(f'Generating "{self.question["ref"]}"...') | 429 | logger.debug(f'Generating "{self.question["ref"]}"...') |
| 426 | # Shallow copy so that script generated questions will not replace | 430 | # Shallow copy so that script generated questions will not replace |
| 427 | # the original generators | 431 | # the original generators |
| @@ -433,14 +437,14 @@ class QFactory(object): | @@ -433,14 +437,14 @@ class QFactory(object): | ||
| 433 | if q['type'] == 'generator': | 437 | if q['type'] == 'generator': |
| 434 | logger.debug(f' \\_ Running "{q["script"]}".') | 438 | logger.debug(f' \\_ Running "{q["script"]}".') |
| 435 | q.setdefault('args', []) | 439 | q.setdefault('args', []) |
| 436 | - q.setdefault('stdin', '') | 440 | + q.setdefault('stdin', '') # FIXME does not exist anymore? |
| 437 | script = path.join(q['path'], q['script']) | 441 | script = path.join(q['path'], q['script']) |
| 438 | out = run_script(script=script, args=q['args'], stdin=q['stdin']) | 442 | out = run_script(script=script, args=q['args'], stdin=q['stdin']) |
| 439 | q.update(out) | 443 | q.update(out) |
| 440 | 444 | ||
| 441 | # Finally we create an instance of Question() | 445 | # Finally we create an instance of Question() |
| 442 | try: | 446 | try: |
| 443 | - qinstance = self._types[q['type']](q) # instance matching class | 447 | + qinstance = self._types[q['type']](QDict(q)) # of matching class |
| 444 | except QuestionException as e: | 448 | except QuestionException as e: |
| 445 | logger.error(e) | 449 | logger.error(e) |
| 446 | raise e | 450 | raise e |
aprendizations/serve.py
| @@ -13,6 +13,7 @@ import signal | @@ -13,6 +13,7 @@ import signal | ||
| 13 | import functools | 13 | import functools |
| 14 | import ssl | 14 | import ssl |
| 15 | import asyncio | 15 | import asyncio |
| 16 | +# from typing import NoReturn | ||
| 16 | 17 | ||
| 17 | # third party libraries | 18 | # third party libraries |
| 18 | import tornado.ioloop | 19 | import tornado.ioloop |
| @@ -187,8 +188,6 @@ class RootHandler(BaseHandler): | @@ -187,8 +188,6 @@ class RootHandler(BaseHandler): | ||
| 187 | # FIXME should not change state... | 188 | # FIXME should not change state... |
| 188 | # ---------------------------------------------------------------------------- | 189 | # ---------------------------------------------------------------------------- |
| 189 | class TopicHandler(BaseHandler): | 190 | class TopicHandler(BaseHandler): |
| 190 | - SUPPORTED_METHODS = ['GET'] | ||
| 191 | - | ||
| 192 | @tornado.web.authenticated | 191 | @tornado.web.authenticated |
| 193 | async def get(self, topic): | 192 | async def get(self, topic): |
| 194 | uid = self.current_user | 193 | uid = self.current_user |
| @@ -209,8 +208,6 @@ class TopicHandler(BaseHandler): | @@ -209,8 +208,6 @@ class TopicHandler(BaseHandler): | ||
| 209 | # Serves files from the /public subdir of the topics. | 208 | # Serves files from the /public subdir of the topics. |
| 210 | # ---------------------------------------------------------------------------- | 209 | # ---------------------------------------------------------------------------- |
| 211 | class FileHandler(BaseHandler): | 210 | class FileHandler(BaseHandler): |
| 212 | - SUPPORTED_METHODS = ['GET'] | ||
| 213 | - | ||
| 214 | @tornado.web.authenticated | 211 | @tornado.web.authenticated |
| 215 | async def get(self, filename): | 212 | async def get(self, filename): |
| 216 | uid = self.current_user | 213 | uid = self.current_user |
| @@ -238,8 +235,6 @@ class FileHandler(BaseHandler): | @@ -238,8 +235,6 @@ class FileHandler(BaseHandler): | ||
| 238 | # respond to AJAX to get a JSON question | 235 | # respond to AJAX to get a JSON question |
| 239 | # ---------------------------------------------------------------------------- | 236 | # ---------------------------------------------------------------------------- |
| 240 | class QuestionHandler(BaseHandler): | 237 | class QuestionHandler(BaseHandler): |
| 241 | - SUPPORTED_METHODS = ['GET', 'POST'] | ||
| 242 | - | ||
| 243 | templates = { | 238 | templates = { |
| 244 | 'checkbox': 'question-checkbox.html', | 239 | 'checkbox': 'question-checkbox.html', |
| 245 | 'radio': 'question-radio.html', | 240 | 'radio': 'question-radio.html', |
aprendizations/tools.py
| @@ -4,6 +4,7 @@ from os import path | @@ -4,6 +4,7 @@ from os import path | ||
| 4 | import subprocess | 4 | import subprocess |
| 5 | import logging | 5 | import logging |
| 6 | import re | 6 | import re |
| 7 | +from typing import Any, List | ||
| 7 | 8 | ||
| 8 | # third party libraries | 9 | # third party libraries |
| 9 | import yaml | 10 | import yaml |
| @@ -123,7 +124,7 @@ class HighlightRenderer(mistune.Renderer): | @@ -123,7 +124,7 @@ class HighlightRenderer(mistune.Renderer): | ||
| 123 | markdown = MarkdownWithMath(HighlightRenderer(escape=True)) | 124 | markdown = MarkdownWithMath(HighlightRenderer(escape=True)) |
| 124 | 125 | ||
| 125 | 126 | ||
| 126 | -def md_to_html(text, strip_p_tag=False, q=None): | 127 | +def md_to_html(text: str, strip_p_tag: bool = False) -> str: |
| 127 | md = markdown(text) | 128 | md = markdown(text) |
| 128 | if strip_p_tag and md.startswith('<p>') and md.endswith('</p>'): | 129 | if strip_p_tag and md.startswith('<p>') and md.endswith('</p>'): |
| 129 | return md[3:-5] | 130 | return md[3:-5] |
| @@ -134,7 +135,7 @@ def md_to_html(text, strip_p_tag=False, q=None): | @@ -134,7 +135,7 @@ def md_to_html(text, strip_p_tag=False, q=None): | ||
| 134 | # --------------------------------------------------------------------------- | 135 | # --------------------------------------------------------------------------- |
| 135 | # load data from yaml file | 136 | # load data from yaml file |
| 136 | # --------------------------------------------------------------------------- | 137 | # --------------------------------------------------------------------------- |
| 137 | -def load_yaml(filename, default=None): | 138 | +def load_yaml(filename: str, default: Any = None) -> Any: |
| 138 | filename = path.expanduser(filename) | 139 | filename = path.expanduser(filename) |
| 139 | try: | 140 | try: |
| 140 | f = open(filename, 'r', encoding='utf-8') | 141 | f = open(filename, 'r', encoding='utf-8') |
| @@ -149,9 +150,12 @@ def load_yaml(filename, default=None): | @@ -149,9 +150,12 @@ def load_yaml(filename, default=None): | ||
| 149 | try: | 150 | try: |
| 150 | default = yaml.safe_load(f) | 151 | default = yaml.safe_load(f) |
| 151 | except yaml.YAMLError as e: | 152 | except yaml.YAMLError as e: |
| 152 | - mark = e.problem_mark | ||
| 153 | - logger.error(f'In file "{filename}" near line {mark.line}, ' | ||
| 154 | - f'column {mark.column+1}') | 153 | + if hasattr(e, 'problem_mark'): |
| 154 | + mark = e.problem_mark | ||
| 155 | + logger.error(f'File "{filename}" near line {mark.line}, ' | ||
| 156 | + f'column {mark.column+1}') | ||
| 157 | + else: | ||
| 158 | + logger.error(f'File "{filename}"') | ||
| 155 | finally: | 159 | finally: |
| 156 | return default | 160 | return default |
| 157 | 161 | ||
| @@ -161,7 +165,11 @@ def load_yaml(filename, default=None): | @@ -161,7 +165,11 @@ def load_yaml(filename, default=None): | ||
| 161 | # The script is run in another process but this function blocks waiting | 165 | # The script is run in another process but this function blocks waiting |
| 162 | # for its termination. | 166 | # for its termination. |
| 163 | # --------------------------------------------------------------------------- | 167 | # --------------------------------------------------------------------------- |
| 164 | -def run_script(script, args=[], stdin='', timeout=5): | 168 | +def run_script(script: str, |
| 169 | + args: List[str] = [], | ||
| 170 | + stdin: str = '', | ||
| 171 | + timeout: int = 5) -> Any: | ||
| 172 | + | ||
| 165 | script = path.expanduser(script) | 173 | script = path.expanduser(script) |
| 166 | try: | 174 | try: |
| 167 | cmd = [script] + [str(a) for a in args] | 175 | cmd = [script] + [str(a) for a in args] |
| @@ -0,0 +1,18 @@ | @@ -0,0 +1,18 @@ | ||
| 1 | +[mypy-sqlalchemy.*] | ||
| 2 | +ignore_missing_imports = True | ||
| 3 | + | ||
| 4 | +[mypy-pygments.*] | ||
| 5 | +ignore_missing_imports = True | ||
| 6 | + | ||
| 7 | +[mypy-networkx.*] | ||
| 8 | +ignore_missing_imports = True | ||
| 9 | + | ||
| 10 | +[mypy-bcrypt.*] | ||
| 11 | +ignore_missing_imports = True | ||
| 12 | + | ||
| 13 | +[mypy-mistune.*] | ||
| 14 | +ignore_missing_imports = True | ||
| 15 | + | ||
| 16 | +[mypy-setuptools.*] | ||
| 17 | +ignore_missing_imports = True | ||
| 18 | + |