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 | 30 | # setup logger for this module |
| 31 | 31 | logger = logging.getLogger(__name__) |
| 32 | 32 | |
| 33 | - | |
| 34 | 33 | async def check_password(password: str, hashed: bytes) -> bool: |
| 35 | 34 | '''check password in executor''' |
| 36 | 35 | loop = asyncio.get_running_loop() |
| ... | ... | @@ -47,7 +46,6 @@ async def hash_password(password: str) -> bytes: |
| 47 | 46 | class AppException(Exception): |
| 48 | 47 | '''Exception raised in this module''' |
| 49 | 48 | |
| 50 | - | |
| 51 | 49 | # ============================================================================ |
| 52 | 50 | # main application |
| 53 | 51 | # ============================================================================ |
| ... | ... | @@ -62,6 +60,7 @@ class App(): |
| 62 | 60 | self._make_test_factory(config['testfile']) |
| 63 | 61 | self._db_setup() # setup engine and load all students |
| 64 | 62 | |
| 63 | + # FIXME get_event_loop will be deprecated in python3.10 | |
| 65 | 64 | asyncio.get_event_loop().run_until_complete(self._assign_tests()) |
| 66 | 65 | |
| 67 | 66 | # command line options: --allow-all, --allow-list filename |
| ... | ... | @@ -70,7 +69,7 @@ class App(): |
| 70 | 69 | elif config['allow_list'] is not None: |
| 71 | 70 | self.allow_from_list(config['allow_list']) |
| 72 | 71 | else: |
| 73 | - logger.info('Students login not yet allowed') | |
| 72 | + logger.info('Students not allowed to login') | |
| 74 | 73 | |
| 75 | 74 | if config['correct']: |
| 76 | 75 | self._correct_tests() |
| ... | ... | @@ -81,9 +80,9 @@ class App(): |
| 81 | 80 | Create database engine and checks for admin and students |
| 82 | 81 | ''' |
| 83 | 82 | dbfile = os.path.expanduser(self._testfactory['database']) |
| 84 | - logger.info('Checking database "%s"...', dbfile) | |
| 83 | + logger.debug('Checking database "%s"...', dbfile) | |
| 85 | 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 | 87 | # connect to database and check for admin & registered students |
| 89 | 88 | self._engine = create_engine(f'sqlite:///{dbfile}', future=True) |
| ... | ... | @@ -98,10 +97,10 @@ class App(): |
| 98 | 97 | logger.error(msg) |
| 99 | 98 | raise AppException(msg) from None |
| 100 | 99 | except OperationalError: |
| 101 | - msg = f'Database "{dbfile}" unusable.' | |
| 100 | + msg = f'Database "{dbfile}" unusable' | |
| 102 | 101 | logger.error(msg) |
| 103 | 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 | 105 | self._students = {uid: { |
| 107 | 106 | 'name': name, |
| ... | ... | @@ -145,15 +144,14 @@ class App(): |
| 145 | 144 | # success |
| 146 | 145 | if uid == '0': |
| 147 | 146 | logger.info('Admin login from %s', headers['remote_ip']) |
| 148 | - await self._assign_tests() | |
| 149 | 147 | else: |
| 150 | 148 | student = self._students[uid] |
| 151 | 149 | student['test'].start(uid) |
| 152 | 150 | student['state'] = 'online' |
| 153 | 151 | student['headers'] = headers |
| 154 | 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 | 155 | return None |
| 158 | 156 | |
| 159 | 157 | # ------------------------------------------------------------------------ |
| ... | ... | @@ -176,7 +174,7 @@ class App(): |
| 176 | 174 | student.pop('headers', None) |
| 177 | 175 | student.pop('unfocus', None) |
| 178 | 176 | student.pop('area', None) |
| 179 | - logger.info('"%s" logged out.', uid) | |
| 177 | + logger.info('"%s" logged out', uid) | |
| 180 | 178 | |
| 181 | 179 | # ------------------------------------------------------------------------ |
| 182 | 180 | def _make_test_factory(self, filename: str) -> None: |
| ... | ... | @@ -214,19 +212,19 @@ class App(): |
| 214 | 212 | return |
| 215 | 213 | |
| 216 | 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 | 216 | test = self._students[uid]['test'] |
| 219 | 217 | test.submit(ans) |
| 220 | 218 | |
| 221 | 219 | if test['autocorrect']: |
| 222 | 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 | 223 | # --- save test in JSON format |
| 226 | 224 | fname = f'{uid}--{test["ref"]}--{test["finish_time"]}.json' |
| 227 | 225 | fpath = os.path.join(test['answers_dir'], fname) |
| 228 | 226 | test.save_json(fpath) |
| 229 | - logger.info('"%s" saved JSON.', uid) | |
| 227 | + logger.info('"%s" saved JSON', uid) | |
| 230 | 228 | |
| 231 | 229 | # --- insert test and questions into the database |
| 232 | 230 | # only corrected questions are added |
| ... | ... | @@ -258,7 +256,7 @@ class App(): |
| 258 | 256 | with Session(self._engine, future=True) as session: |
| 259 | 257 | session.add(test_row) |
| 260 | 258 | session.commit() |
| 261 | - logger.info('"%s" database updated.', uid) | |
| 259 | + logger.info('"%s" database updated', uid) | |
| 262 | 260 | |
| 263 | 261 | # ------------------------------------------------------------------------ |
| 264 | 262 | def _correct_tests(self) -> None: |
| ... | ... | @@ -293,7 +291,7 @@ class App(): |
| 293 | 291 | # save JSON file (overwriting the old one) |
| 294 | 292 | uid = test['student'] |
| 295 | 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 | 296 | # update database |
| 299 | 297 | dbtest.grade = test['grade'] |
| ... | ... | @@ -337,7 +335,7 @@ class App(): |
| 337 | 335 | # state=test['state'], |
| 338 | 336 | # comment='')) |
| 339 | 337 | |
| 340 | - # logger.info('"%s" gave up.', uid) | |
| 338 | + # logger.info('"%s" gave up', uid) | |
| 341 | 339 | # return test |
| 342 | 340 | |
| 343 | 341 | # ------------------------------------------------------------------------ |
| ... | ... | @@ -453,51 +451,13 @@ class App(): |
| 453 | 451 | 'grades': self.get_grades(uid, self._testfactory['ref']) } |
| 454 | 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 | 455 | # SETTERS |
| 496 | 456 | # ======================================================================== |
| 497 | 457 | def allow_student(self, uid: str) -> None: |
| 498 | 458 | '''allow a single student to login''' |
| 499 | 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 | 463 | def deny_student(self, uid: str) -> None: |
| ... | ... | @@ -512,7 +472,7 @@ class App(): |
| 512 | 472 | '''allow all students to login''' |
| 513 | 473 | for student in self._students.values(): |
| 514 | 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 | 478 | def deny_all_students(self) -> None: | ... | ... |
perguntations/questions.py
| ... | ... | @@ -5,10 +5,9 @@ Description: Classes the implement several types of questions. |
| 5 | 5 | |
| 6 | 6 | |
| 7 | 7 | # python standard library |
| 8 | -import asyncio | |
| 9 | 8 | from datetime import datetime |
| 10 | 9 | import logging |
| 11 | -from os import path | |
| 10 | +import os | |
| 12 | 11 | import random |
| 13 | 12 | import re |
| 14 | 13 | from typing import Any, Dict, NewType |
| ... | ... | @@ -499,7 +498,7 @@ class QuestionTextArea(Question): |
| 499 | 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 | 504 | def correct(self) -> None: |
| ... | ... | @@ -676,7 +675,7 @@ class QFactory(): |
| 676 | 675 | logger.debug(' \\_ Running "%s"', qdict['script']) |
| 677 | 676 | qdict.setdefault('args', []) |
| 678 | 677 | qdict.setdefault('stdin', '') |
| 679 | - script = path.join(qdict['path'], qdict['script']) | |
| 678 | + script = os.path.join(qdict['path'], qdict['script']) | |
| 680 | 679 | out = await run_script_async(script=script, |
| 681 | 680 | args=qdict['args'], |
| 682 | 681 | stdin=qdict['stdin']) |
| ... | ... | @@ -685,8 +684,3 @@ class QFactory(): |
| 685 | 684 | question = question_from(qdict) # returns a Question instance |
| 686 | 685 | question.gen() |
| 687 | 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 | 3 | ''' |
| 4 | 4 | |
| 5 | 5 | # python standard library |
| 6 | +import asyncio | |
| 6 | 7 | from os import path |
| 7 | 8 | import random |
| 8 | 9 | import logging |
| ... | ... | @@ -255,10 +256,12 @@ class TestFactory(dict): |
| 255 | 256 | ''' |
| 256 | 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 | 262 | for i, (qref, qfact) in enumerate(self['question_factory'].items()): |
| 260 | 263 | try: |
| 261 | - question = qfact.generate() | |
| 264 | + question = loop.run_until_complete(qfact.gen_async()) | |
| 262 | 265 | except Exception as exc: |
| 263 | 266 | msg = f'Failed to generate "{qref}"' |
| 264 | 267 | raise TestFactoryException(msg) from exc | ... | ... |