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 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
... ...