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 |