Commit a0ab1672c29cd98e668f8d8be829bbaa224861d1

Authored by Miguel Barão
1 parent 31b2eb9f
Exists in master and in 1 other branch dev

Code cleanup

Remove generate() method from qfactory. Question generation is async.
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