From a0ab1672c29cd98e668f8d8be829bbaa224861d1 Mon Sep 17 00:00:00 2001 From: Miguel BarĂ£o Date: Sat, 15 Jan 2022 13:07:34 +0000 Subject: [PATCH] Code cleanup Remove generate() method from qfactory. Question generation is async. --- perguntations/app.py | 74 +++++++++++++++++--------------------------------------------------------- perguntations/questions.py | 12 +++--------- perguntations/testfactory.py | 7 +++++-- 3 files changed, 25 insertions(+), 68 deletions(-) diff --git a/perguntations/app.py b/perguntations/app.py index 69c7ecf..943151a 100644 --- a/perguntations/app.py +++ b/perguntations/app.py @@ -30,7 +30,6 @@ from .questions import question_from # setup logger for this module logger = logging.getLogger(__name__) - async def check_password(password: str, hashed: bytes) -> bool: '''check password in executor''' loop = asyncio.get_running_loop() @@ -47,7 +46,6 @@ async def hash_password(password: str) -> bytes: class AppException(Exception): '''Exception raised in this module''' - # ============================================================================ # main application # ============================================================================ @@ -62,6 +60,7 @@ class App(): self._make_test_factory(config['testfile']) self._db_setup() # setup engine and load all students + # FIXME get_event_loop will be deprecated in python3.10 asyncio.get_event_loop().run_until_complete(self._assign_tests()) # command line options: --allow-all, --allow-list filename @@ -70,7 +69,7 @@ class App(): elif config['allow_list'] is not None: self.allow_from_list(config['allow_list']) else: - logger.info('Students login not yet allowed') + logger.info('Students not allowed to login') if config['correct']: self._correct_tests() @@ -81,9 +80,9 @@ class App(): Create database engine and checks for admin and students ''' dbfile = os.path.expanduser(self._testfactory['database']) - logger.info('Checking database "%s"...', dbfile) + logger.debug('Checking database "%s"...', dbfile) if not os.path.exists(dbfile): - raise AppException('No database. Use "initdb" to create.') + raise AppException('No database, use "initdb" to create') # connect to database and check for admin & registered students self._engine = create_engine(f'sqlite:///{dbfile}', future=True) @@ -98,10 +97,10 @@ class App(): logger.error(msg) raise AppException(msg) from None except OperationalError: - msg = f'Database "{dbfile}" unusable.' + msg = f'Database "{dbfile}" unusable' logger.error(msg) raise AppException(msg) from None - logger.info('Database has %d students.', len(dbstudents)) + logger.info('Database has %d students', len(dbstudents)) self._students = {uid: { 'name': name, @@ -145,15 +144,14 @@ class App(): # success if uid == '0': logger.info('Admin login from %s', headers['remote_ip']) - await self._assign_tests() else: student = self._students[uid] student['test'].start(uid) student['state'] = 'online' student['headers'] = headers student['unfocus'] = False - student['area'] = 1.0 - logger.info('"%s" login from %s.', uid, headers['remote_ip']) + student['area'] = 0.0 + logger.info('"%s" login from %s', uid, headers['remote_ip']) return None # ------------------------------------------------------------------------ @@ -176,7 +174,7 @@ class App(): student.pop('headers', None) student.pop('unfocus', None) student.pop('area', None) - logger.info('"%s" logged out.', uid) + logger.info('"%s" logged out', uid) # ------------------------------------------------------------------------ def _make_test_factory(self, filename: str) -> None: @@ -214,19 +212,19 @@ class App(): return # --- submit answers and correct test - logger.info('"%s" submitted %d answers.', uid, len(ans)) + logger.info('"%s" submitted %d answers', uid, len(ans)) test = self._students[uid]['test'] test.submit(ans) if test['autocorrect']: await test.correct_async() - logger.info('"%s" grade = %g points.', uid, test['grade']) + logger.info('"%s" grade = %g points', uid, test['grade']) # --- save test in JSON format fname = f'{uid}--{test["ref"]}--{test["finish_time"]}.json' fpath = os.path.join(test['answers_dir'], fname) test.save_json(fpath) - logger.info('"%s" saved JSON.', uid) + logger.info('"%s" saved JSON', uid) # --- insert test and questions into the database # only corrected questions are added @@ -258,7 +256,7 @@ class App(): with Session(self._engine, future=True) as session: session.add(test_row) session.commit() - logger.info('"%s" database updated.', uid) + logger.info('"%s" database updated', uid) # ------------------------------------------------------------------------ def _correct_tests(self) -> None: @@ -293,7 +291,7 @@ class App(): # save JSON file (overwriting the old one) uid = test['student'] test.save_json(dbtest.filename) - logger.debug('%s saved JSON file.', uid) + logger.debug('%s saved JSON file', uid) # update database dbtest.grade = test['grade'] @@ -337,7 +335,7 @@ class App(): # state=test['state'], # comment='')) - # logger.info('"%s" gave up.', uid) + # logger.info('"%s" gave up', uid) # return test # ------------------------------------------------------------------------ @@ -453,51 +451,13 @@ class App(): 'grades': self.get_grades(uid, self._testfactory['ref']) } for uid, student in self._students.items()] - # ------------------------------------------------------------------------ - # def get_student_grades_from_all_tests(self, uid): - # '''get grades of student from all tests''' - # with self._db_session() as sess: - # return sess.query(Test.title, Test.grade, Test.finishtime)\ - # .filter_by(student_id=uid)\ - # .order_by(Test.finishtime) - - # --- private methods ---------------------------------------------------- - # def _get_all_students(self): - # '''get all students from database''' - # with Session(self._engine, future=True) as session: - # query = select(Student.id, Student.name, Student.password)\ - # .where(Student.id != '0') - # students - # questions = session.execute( - # select(Test.id, Test.student_id, Test.starttime, - # Question.number, Question.grade). - # join(Question). - # where(Test.ref == test_ref) - # ).all() - - - # return session.query(Student.id, Student.name, Student.password)\ - # .filter(Student.id != '0')\ - # .order_by(Student.id) - - # def get_allowed_students(self): - # # set of 'uid' allowed to login - # return self.allowed - - # def get_file(self, uid, ref, key): - # # get filename of (uid, ref, name) if declared in the question - # t = self.get_student_test(uid) - # for q in t['questions']: - # if q['ref'] == ref and key in q['files']: - # return os.path.abspath(os.path.join(q['path'], q['files'][key])) - # ======================================================================== # SETTERS # ======================================================================== def allow_student(self, uid: str) -> None: '''allow a single student to login''' self._students[uid]['state'] = 'allowed' - logger.info('"%s" allowed to login.', uid) + logger.info('"%s" allowed to login', uid) # ------------------------------------------------------------------------ def deny_student(self, uid: str) -> None: @@ -512,7 +472,7 @@ class App(): '''allow all students to login''' for student in self._students.values(): student['state'] = 'allowed' - logger.info('Allowed %d students.', len(self._students)) + logger.info('Allowed %d students', len(self._students)) # ------------------------------------------------------------------------ def deny_all_students(self) -> None: diff --git a/perguntations/questions.py b/perguntations/questions.py index a78ab83..d93271e 100644 --- a/perguntations/questions.py +++ b/perguntations/questions.py @@ -5,10 +5,9 @@ Description: Classes the implement several types of questions. # python standard library -import asyncio from datetime import datetime import logging -from os import path +import os import random import re from typing import Any, Dict, NewType @@ -499,7 +498,7 @@ class QuestionTextArea(Question): 'args': [] })) - self['correct'] = path.join(self['path'], self['correct']) + self['correct'] = os.path.join(self['path'], self['correct']) # ------------------------------------------------------------------------ def correct(self) -> None: @@ -676,7 +675,7 @@ class QFactory(): logger.debug(' \\_ Running "%s"', qdict['script']) qdict.setdefault('args', []) qdict.setdefault('stdin', '') - script = path.join(qdict['path'], qdict['script']) + script = os.path.join(qdict['path'], qdict['script']) out = await run_script_async(script=script, args=qdict['args'], stdin=qdict['stdin']) @@ -685,8 +684,3 @@ class QFactory(): question = question_from(qdict) # returns a Question instance question.gen() return question - - # ------------------------------------------------------------------------ - def generate(self) -> Question: - '''generate question (synchronous version)''' - return asyncio.get_event_loop().run_until_complete(self.gen_async()) diff --git a/perguntations/testfactory.py b/perguntations/testfactory.py index a8a1d86..297b2c3 100644 --- a/perguntations/testfactory.py +++ b/perguntations/testfactory.py @@ -3,6 +3,7 @@ TestFactory - generates tests for students ''' # python standard library +import asyncio from os import path import random import logging @@ -255,10 +256,12 @@ class TestFactory(dict): ''' checks if questions can be correctly generated and corrected ''' - logger.info('Checking if questions can be generated and corrected...') + logger.info('Checking questions...') + # FIXME get_event_loop will be deprecated in python3.10 + loop = asyncio.get_event_loop() for i, (qref, qfact) in enumerate(self['question_factory'].items()): try: - question = qfact.generate() + question = loop.run_until_complete(qfact.gen_async()) except Exception as exc: msg = f'Failed to generate "{qref}"' raise TestFactoryException(msg) from exc -- libgit2 0.21.2