Commit a0ab1672c29cd98e668f8d8be829bbaa224861d1
1 parent
31b2eb9f
Exists in
master
and in
1 other branch
Code cleanup
Remove generate() method from qfactory. Question generation is async.
Showing
3 changed files
with
25 additions
and
68 deletions
Show diff stats
perguntations/app.py
| @@ -30,7 +30,6 @@ from .questions import question_from | @@ -30,7 +30,6 @@ from .questions import question_from | ||
| 30 | # setup logger for this module | 30 | # setup logger for this module |
| 31 | logger = logging.getLogger(__name__) | 31 | logger = logging.getLogger(__name__) |
| 32 | 32 | ||
| 33 | - | ||
| 34 | async def check_password(password: str, hashed: bytes) -> bool: | 33 | async def check_password(password: str, hashed: bytes) -> bool: |
| 35 | '''check password in executor''' | 34 | '''check password in executor''' |
| 36 | loop = asyncio.get_running_loop() | 35 | loop = asyncio.get_running_loop() |
| @@ -47,7 +46,6 @@ async def hash_password(password: str) -> bytes: | @@ -47,7 +46,6 @@ async def hash_password(password: str) -> bytes: | ||
| 47 | class AppException(Exception): | 46 | class AppException(Exception): |
| 48 | '''Exception raised in this module''' | 47 | '''Exception raised in this module''' |
| 49 | 48 | ||
| 50 | - | ||
| 51 | # ============================================================================ | 49 | # ============================================================================ |
| 52 | # main application | 50 | # main application |
| 53 | # ============================================================================ | 51 | # ============================================================================ |
| @@ -62,6 +60,7 @@ class App(): | @@ -62,6 +60,7 @@ class App(): | ||
| 62 | self._make_test_factory(config['testfile']) | 60 | self._make_test_factory(config['testfile']) |
| 63 | self._db_setup() # setup engine and load all students | 61 | self._db_setup() # setup engine and load all students |
| 64 | 62 | ||
| 63 | + # FIXME get_event_loop will be deprecated in python3.10 | ||
| 65 | asyncio.get_event_loop().run_until_complete(self._assign_tests()) | 64 | asyncio.get_event_loop().run_until_complete(self._assign_tests()) |
| 66 | 65 | ||
| 67 | # command line options: --allow-all, --allow-list filename | 66 | # command line options: --allow-all, --allow-list filename |
| @@ -70,7 +69,7 @@ class App(): | @@ -70,7 +69,7 @@ class App(): | ||
| 70 | elif config['allow_list'] is not None: | 69 | elif config['allow_list'] is not None: |
| 71 | self.allow_from_list(config['allow_list']) | 70 | self.allow_from_list(config['allow_list']) |
| 72 | else: | 71 | else: |
| 73 | - logger.info('Students login not yet allowed') | 72 | + logger.info('Students not allowed to login') |
| 74 | 73 | ||
| 75 | if config['correct']: | 74 | if config['correct']: |
| 76 | self._correct_tests() | 75 | self._correct_tests() |
| @@ -81,9 +80,9 @@ class App(): | @@ -81,9 +80,9 @@ class App(): | ||
| 81 | Create database engine and checks for admin and students | 80 | Create database engine and checks for admin and students |
| 82 | ''' | 81 | ''' |
| 83 | dbfile = os.path.expanduser(self._testfactory['database']) | 82 | dbfile = os.path.expanduser(self._testfactory['database']) |
| 84 | - logger.info('Checking database "%s"...', dbfile) | 83 | + logger.debug('Checking database "%s"...', dbfile) |
| 85 | if not os.path.exists(dbfile): | 84 | if not os.path.exists(dbfile): |
| 86 | - raise AppException('No database. Use "initdb" to create.') | 85 | + raise AppException('No database, use "initdb" to create') |
| 87 | 86 | ||
| 88 | # connect to database and check for admin & registered students | 87 | # connect to database and check for admin & registered students |
| 89 | self._engine = create_engine(f'sqlite:///{dbfile}', future=True) | 88 | self._engine = create_engine(f'sqlite:///{dbfile}', future=True) |
| @@ -98,10 +97,10 @@ class App(): | @@ -98,10 +97,10 @@ class App(): | ||
| 98 | logger.error(msg) | 97 | logger.error(msg) |
| 99 | raise AppException(msg) from None | 98 | raise AppException(msg) from None |
| 100 | except OperationalError: | 99 | except OperationalError: |
| 101 | - msg = f'Database "{dbfile}" unusable.' | 100 | + msg = f'Database "{dbfile}" unusable' |
| 102 | logger.error(msg) | 101 | logger.error(msg) |
| 103 | raise AppException(msg) from None | 102 | raise AppException(msg) from None |
| 104 | - logger.info('Database has %d students.', len(dbstudents)) | 103 | + logger.info('Database has %d students', len(dbstudents)) |
| 105 | 104 | ||
| 106 | self._students = {uid: { | 105 | self._students = {uid: { |
| 107 | 'name': name, | 106 | 'name': name, |
| @@ -145,15 +144,14 @@ class App(): | @@ -145,15 +144,14 @@ class App(): | ||
| 145 | # success | 144 | # success |
| 146 | if uid == '0': | 145 | if uid == '0': |
| 147 | logger.info('Admin login from %s', headers['remote_ip']) | 146 | logger.info('Admin login from %s', headers['remote_ip']) |
| 148 | - await self._assign_tests() | ||
| 149 | else: | 147 | else: |
| 150 | student = self._students[uid] | 148 | student = self._students[uid] |
| 151 | student['test'].start(uid) | 149 | student['test'].start(uid) |
| 152 | student['state'] = 'online' | 150 | student['state'] = 'online' |
| 153 | student['headers'] = headers | 151 | student['headers'] = headers |
| 154 | student['unfocus'] = False | 152 | student['unfocus'] = False |
| 155 | - student['area'] = 1.0 | ||
| 156 | - logger.info('"%s" login from %s.', uid, headers['remote_ip']) | 153 | + student['area'] = 0.0 |
| 154 | + logger.info('"%s" login from %s', uid, headers['remote_ip']) | ||
| 157 | return None | 155 | return None |
| 158 | 156 | ||
| 159 | # ------------------------------------------------------------------------ | 157 | # ------------------------------------------------------------------------ |
| @@ -176,7 +174,7 @@ class App(): | @@ -176,7 +174,7 @@ class App(): | ||
| 176 | student.pop('headers', None) | 174 | student.pop('headers', None) |
| 177 | student.pop('unfocus', None) | 175 | student.pop('unfocus', None) |
| 178 | student.pop('area', None) | 176 | student.pop('area', None) |
| 179 | - logger.info('"%s" logged out.', uid) | 177 | + logger.info('"%s" logged out', uid) |
| 180 | 178 | ||
| 181 | # ------------------------------------------------------------------------ | 179 | # ------------------------------------------------------------------------ |
| 182 | def _make_test_factory(self, filename: str) -> None: | 180 | def _make_test_factory(self, filename: str) -> None: |
| @@ -214,19 +212,19 @@ class App(): | @@ -214,19 +212,19 @@ class App(): | ||
| 214 | return | 212 | return |
| 215 | 213 | ||
| 216 | # --- submit answers and correct test | 214 | # --- submit answers and correct test |
| 217 | - logger.info('"%s" submitted %d answers.', uid, len(ans)) | 215 | + logger.info('"%s" submitted %d answers', uid, len(ans)) |
| 218 | test = self._students[uid]['test'] | 216 | test = self._students[uid]['test'] |
| 219 | test.submit(ans) | 217 | test.submit(ans) |
| 220 | 218 | ||
| 221 | if test['autocorrect']: | 219 | if test['autocorrect']: |
| 222 | await test.correct_async() | 220 | await test.correct_async() |
| 223 | - logger.info('"%s" grade = %g points.', uid, test['grade']) | 221 | + logger.info('"%s" grade = %g points', uid, test['grade']) |
| 224 | 222 | ||
| 225 | # --- save test in JSON format | 223 | # --- save test in JSON format |
| 226 | fname = f'{uid}--{test["ref"]}--{test["finish_time"]}.json' | 224 | fname = f'{uid}--{test["ref"]}--{test["finish_time"]}.json' |
| 227 | fpath = os.path.join(test['answers_dir'], fname) | 225 | fpath = os.path.join(test['answers_dir'], fname) |
| 228 | test.save_json(fpath) | 226 | test.save_json(fpath) |
| 229 | - logger.info('"%s" saved JSON.', uid) | 227 | + logger.info('"%s" saved JSON', uid) |
| 230 | 228 | ||
| 231 | # --- insert test and questions into the database | 229 | # --- insert test and questions into the database |
| 232 | # only corrected questions are added | 230 | # only corrected questions are added |
| @@ -258,7 +256,7 @@ class App(): | @@ -258,7 +256,7 @@ class App(): | ||
| 258 | with Session(self._engine, future=True) as session: | 256 | with Session(self._engine, future=True) as session: |
| 259 | session.add(test_row) | 257 | session.add(test_row) |
| 260 | session.commit() | 258 | session.commit() |
| 261 | - logger.info('"%s" database updated.', uid) | 259 | + logger.info('"%s" database updated', uid) |
| 262 | 260 | ||
| 263 | # ------------------------------------------------------------------------ | 261 | # ------------------------------------------------------------------------ |
| 264 | def _correct_tests(self) -> None: | 262 | def _correct_tests(self) -> None: |
| @@ -293,7 +291,7 @@ class App(): | @@ -293,7 +291,7 @@ class App(): | ||
| 293 | # save JSON file (overwriting the old one) | 291 | # save JSON file (overwriting the old one) |
| 294 | uid = test['student'] | 292 | uid = test['student'] |
| 295 | test.save_json(dbtest.filename) | 293 | test.save_json(dbtest.filename) |
| 296 | - logger.debug('%s saved JSON file.', uid) | 294 | + logger.debug('%s saved JSON file', uid) |
| 297 | 295 | ||
| 298 | # update database | 296 | # update database |
| 299 | dbtest.grade = test['grade'] | 297 | dbtest.grade = test['grade'] |
| @@ -337,7 +335,7 @@ class App(): | @@ -337,7 +335,7 @@ class App(): | ||
| 337 | # state=test['state'], | 335 | # state=test['state'], |
| 338 | # comment='')) | 336 | # comment='')) |
| 339 | 337 | ||
| 340 | - # logger.info('"%s" gave up.', uid) | 338 | + # logger.info('"%s" gave up', uid) |
| 341 | # return test | 339 | # return test |
| 342 | 340 | ||
| 343 | # ------------------------------------------------------------------------ | 341 | # ------------------------------------------------------------------------ |
| @@ -453,51 +451,13 @@ class App(): | @@ -453,51 +451,13 @@ class App(): | ||
| 453 | 'grades': self.get_grades(uid, self._testfactory['ref']) } | 451 | 'grades': self.get_grades(uid, self._testfactory['ref']) } |
| 454 | for uid, student in self._students.items()] | 452 | for uid, student in self._students.items()] |
| 455 | 453 | ||
| 456 | - # ------------------------------------------------------------------------ | ||
| 457 | - # def get_student_grades_from_all_tests(self, uid): | ||
| 458 | - # '''get grades of student from all tests''' | ||
| 459 | - # with self._db_session() as sess: | ||
| 460 | - # return sess.query(Test.title, Test.grade, Test.finishtime)\ | ||
| 461 | - # .filter_by(student_id=uid)\ | ||
| 462 | - # .order_by(Test.finishtime) | ||
| 463 | - | ||
| 464 | - # --- private methods ---------------------------------------------------- | ||
| 465 | - # def _get_all_students(self): | ||
| 466 | - # '''get all students from database''' | ||
| 467 | - # with Session(self._engine, future=True) as session: | ||
| 468 | - # query = select(Student.id, Student.name, Student.password)\ | ||
| 469 | - # .where(Student.id != '0') | ||
| 470 | - # students | ||
| 471 | - # questions = session.execute( | ||
| 472 | - # select(Test.id, Test.student_id, Test.starttime, | ||
| 473 | - # Question.number, Question.grade). | ||
| 474 | - # join(Question). | ||
| 475 | - # where(Test.ref == test_ref) | ||
| 476 | - # ).all() | ||
| 477 | - | ||
| 478 | - | ||
| 479 | - # return session.query(Student.id, Student.name, Student.password)\ | ||
| 480 | - # .filter(Student.id != '0')\ | ||
| 481 | - # .order_by(Student.id) | ||
| 482 | - | ||
| 483 | - # def get_allowed_students(self): | ||
| 484 | - # # set of 'uid' allowed to login | ||
| 485 | - # return self.allowed | ||
| 486 | - | ||
| 487 | - # def get_file(self, uid, ref, key): | ||
| 488 | - # # get filename of (uid, ref, name) if declared in the question | ||
| 489 | - # t = self.get_student_test(uid) | ||
| 490 | - # for q in t['questions']: | ||
| 491 | - # if q['ref'] == ref and key in q['files']: | ||
| 492 | - # return os.path.abspath(os.path.join(q['path'], q['files'][key])) | ||
| 493 | - | ||
| 494 | # ======================================================================== | 454 | # ======================================================================== |
| 495 | # SETTERS | 455 | # SETTERS |
| 496 | # ======================================================================== | 456 | # ======================================================================== |
| 497 | def allow_student(self, uid: str) -> None: | 457 | def allow_student(self, uid: str) -> None: |
| 498 | '''allow a single student to login''' | 458 | '''allow a single student to login''' |
| 499 | self._students[uid]['state'] = 'allowed' | 459 | self._students[uid]['state'] = 'allowed' |
| 500 | - logger.info('"%s" allowed to login.', uid) | 460 | + logger.info('"%s" allowed to login', uid) |
| 501 | 461 | ||
| 502 | # ------------------------------------------------------------------------ | 462 | # ------------------------------------------------------------------------ |
| 503 | def deny_student(self, uid: str) -> None: | 463 | def deny_student(self, uid: str) -> None: |
| @@ -512,7 +472,7 @@ class App(): | @@ -512,7 +472,7 @@ class App(): | ||
| 512 | '''allow all students to login''' | 472 | '''allow all students to login''' |
| 513 | for student in self._students.values(): | 473 | for student in self._students.values(): |
| 514 | student['state'] = 'allowed' | 474 | student['state'] = 'allowed' |
| 515 | - logger.info('Allowed %d students.', len(self._students)) | 475 | + logger.info('Allowed %d students', len(self._students)) |
| 516 | 476 | ||
| 517 | # ------------------------------------------------------------------------ | 477 | # ------------------------------------------------------------------------ |
| 518 | def deny_all_students(self) -> None: | 478 | def deny_all_students(self) -> None: |
perguntations/questions.py
| @@ -5,10 +5,9 @@ Description: Classes the implement several types of questions. | @@ -5,10 +5,9 @@ Description: Classes the implement several types of questions. | ||
| 5 | 5 | ||
| 6 | 6 | ||
| 7 | # python standard library | 7 | # python standard library |
| 8 | -import asyncio | ||
| 9 | from datetime import datetime | 8 | from datetime import datetime |
| 10 | import logging | 9 | import logging |
| 11 | -from os import path | 10 | +import os |
| 12 | import random | 11 | import random |
| 13 | import re | 12 | import re |
| 14 | from typing import Any, Dict, NewType | 13 | from typing import Any, Dict, NewType |
| @@ -499,7 +498,7 @@ class QuestionTextArea(Question): | @@ -499,7 +498,7 @@ class QuestionTextArea(Question): | ||
| 499 | 'args': [] | 498 | 'args': [] |
| 500 | })) | 499 | })) |
| 501 | 500 | ||
| 502 | - self['correct'] = path.join(self['path'], self['correct']) | 501 | + self['correct'] = os.path.join(self['path'], self['correct']) |
| 503 | 502 | ||
| 504 | # ------------------------------------------------------------------------ | 503 | # ------------------------------------------------------------------------ |
| 505 | def correct(self) -> None: | 504 | def correct(self) -> None: |
| @@ -676,7 +675,7 @@ class QFactory(): | @@ -676,7 +675,7 @@ class QFactory(): | ||
| 676 | logger.debug(' \\_ Running "%s"', qdict['script']) | 675 | logger.debug(' \\_ Running "%s"', qdict['script']) |
| 677 | qdict.setdefault('args', []) | 676 | qdict.setdefault('args', []) |
| 678 | qdict.setdefault('stdin', '') | 677 | qdict.setdefault('stdin', '') |
| 679 | - script = path.join(qdict['path'], qdict['script']) | 678 | + script = os.path.join(qdict['path'], qdict['script']) |
| 680 | out = await run_script_async(script=script, | 679 | out = await run_script_async(script=script, |
| 681 | args=qdict['args'], | 680 | args=qdict['args'], |
| 682 | stdin=qdict['stdin']) | 681 | stdin=qdict['stdin']) |
| @@ -685,8 +684,3 @@ class QFactory(): | @@ -685,8 +684,3 @@ class QFactory(): | ||
| 685 | question = question_from(qdict) # returns a Question instance | 684 | question = question_from(qdict) # returns a Question instance |
| 686 | question.gen() | 685 | question.gen() |
| 687 | return question | 686 | return question |
| 688 | - | ||
| 689 | - # ------------------------------------------------------------------------ | ||
| 690 | - def generate(self) -> Question: | ||
| 691 | - '''generate question (synchronous version)''' | ||
| 692 | - return asyncio.get_event_loop().run_until_complete(self.gen_async()) |
perguntations/testfactory.py
| @@ -3,6 +3,7 @@ TestFactory - generates tests for students | @@ -3,6 +3,7 @@ TestFactory - generates tests for students | ||
| 3 | ''' | 3 | ''' |
| 4 | 4 | ||
| 5 | # python standard library | 5 | # python standard library |
| 6 | +import asyncio | ||
| 6 | from os import path | 7 | from os import path |
| 7 | import random | 8 | import random |
| 8 | import logging | 9 | import logging |
| @@ -255,10 +256,12 @@ class TestFactory(dict): | @@ -255,10 +256,12 @@ class TestFactory(dict): | ||
| 255 | ''' | 256 | ''' |
| 256 | checks if questions can be correctly generated and corrected | 257 | checks if questions can be correctly generated and corrected |
| 257 | ''' | 258 | ''' |
| 258 | - logger.info('Checking if questions can be generated and corrected...') | 259 | + logger.info('Checking questions...') |
| 260 | + # FIXME get_event_loop will be deprecated in python3.10 | ||
| 261 | + loop = asyncio.get_event_loop() | ||
| 259 | for i, (qref, qfact) in enumerate(self['question_factory'].items()): | 262 | for i, (qref, qfact) in enumerate(self['question_factory'].items()): |
| 260 | try: | 263 | try: |
| 261 | - question = qfact.generate() | 264 | + question = loop.run_until_complete(qfact.gen_async()) |
| 262 | except Exception as exc: | 265 | except Exception as exc: |
| 263 | msg = f'Failed to generate "{qref}"' | 266 | msg = f'Failed to generate "{qref}"' |
| 264 | raise TestFactoryException(msg) from exc | 267 | raise TestFactoryException(msg) from exc |