Commit 4c5146e66cc5c1a6f3584e5a60f98c6aa043ff84

Authored by Miguel Barão
1 parent 7ce13d39
Exists in master and in 1 other branch dev

serving questions now working (except review if not in cache)

perguntations/app.py
... ... @@ -71,12 +71,15 @@ class App():
71 71  
72 72 # ------------------------------------------------------------------------
73 73 def _db_setup(self) -> None:
74   - logger.debug('Checking database...')
75   -
76   - # connect to database and check registered students
  74 + '''
  75 + Create database engine and checks for admin and students
  76 + '''
77 77 dbfile = os.path.expanduser(self._testfactory['database'])
  78 + logger.info('Checking database "%s"...', dbfile)
78 79 if not os.path.exists(dbfile):
79 80 raise AppException('No database. Use "initdb" to create.')
  81 +
  82 + # connect to database and check for admin & registered students
80 83 self._engine = create_engine(f'sqlite:///{dbfile}', future=True)
81 84 try:
82 85 with Session(self._engine, future=True) as session:
... ... @@ -92,20 +95,11 @@ class App():
92 95 msg = f'Database "{dbfile}" unusable.'
93 96 logger.error(msg)
94 97 raise AppException(msg) from None
95   -
96   - logger.info('Database "%s" has %d students.', dbfile, len(dbstudents))
  98 + logger.info('Database has %d students.', len(dbstudents))
97 99  
98 100 self._students = {uid: {'name': name, 'state': 'offline', 'test': None}
99 101 for uid, name in dbstudents}
100 102  
101   - # self._students = {}
102   - # for uid, name in dbstudents:
103   - # self._students[uid] = {
104   - # 'name': name,
105   - # 'state': 'offline', # offline, allowed, waiting, online
106   - # 'test': None
107   - # }
108   -
109 103 # ------------------------------------------------------------------------
110 104 async def login(self, uid: str, password: str, headers: dict) -> Optional[str]:
111 105 '''
... ... @@ -172,7 +166,7 @@ class App():
172 166 try:
173 167 testconf = load_yaml(filename)
174 168 testconf['testfile'] = filename
175   - except (IOError, yaml.YAMLError) as exc:
  169 + except (OSError, yaml.YAMLError) as exc:
176 170 msg = f'Cannot read test configuration "{filename}"'
177 171 logger.error(msg)
178 172 raise AppException(msg) from exc
... ... @@ -534,7 +528,7 @@ class App():
534 528 with open(filename, 'r', encoding='utf-8') as file:
535 529 allowed = {line.strip() for line in file}
536 530 allowed.discard('')
537   - except IOError as exc:
  531 + except OSError as exc:
538 532 error_msg = f'Cannot read file {filename}'
539 533 logger.critical(error_msg)
540 534 raise AppException(error_msg) from exc
... ...
perguntations/main.py
... ... @@ -71,7 +71,7 @@ def get_logger_config(debug=False) -> dict:
71 71 path = os.path.expanduser(os.environ.get('XDG_CONFIG_HOME', '~/.config/'))
72 72 try:
73 73 return load_yaml(os.path.join(path, APP_NAME, file))
74   - except IOError:
  74 + except OSError:
75 75 print('Using default logger configuration...')
76 76  
77 77 if debug:
... ...
perguntations/serve.py
... ... @@ -360,6 +360,7 @@ class FileHandler(BaseHandler):
360 360 Handles static files from questions like images, etc.
361 361 '''
362 362  
  363 + _filecache = {}
363 364  
364 365 @tornado.web.authenticated
365 366 async def get(self):
... ... @@ -371,35 +372,42 @@ class FileHandler(BaseHandler):
371 372 ref = self.get_query_argument('ref', None)
372 373 image = self.get_query_argument('image', None)
373 374 logger.debug('GET /file (ref=%s, image=%s)', ref, image)
  375 +
  376 + if ref is None or image is None:
  377 + return
  378 +
374 379 content_type = mimetypes.guess_type(image)[0]
375 380  
376   - if uid != '0':
377   - test = self.testapp.get_student_test(uid)
378   - else:
379   - logger.error('FIXME Cannot serve images for review.')
380   - raise tornado.web.HTTPError(404) # FIXME admin
  381 + if (ref, image) in self._filecache:
  382 + logger.debug('using cached file')
  383 + self.write(self._filecache[(ref, image)])
  384 + if content_type is not None:
  385 + self.set_header("Content-Type", content_type)
  386 + await self.flush()
  387 + return
381 388  
382   - if test is None:
  389 + try:
  390 + test = self.testapp.get_test(uid)
  391 + except KeyError:
  392 + logger.warning('Could not get test to serve image file')
383 393 raise tornado.web.HTTPError(404) # Not Found
384 394  
385 395 for question in test['questions']:
386 396 # search for the question that contains the image
387 397 if question['ref'] == ref:
388   - filepath = path.join(question['path'], b'public', image)
  398 + filepath = path.join(question['path'], 'public', image)
  399 +
389 400 try:
390   - file = open(filepath, 'rb')
391   - except FileNotFoundError:
392   - logger.error('File not found: %s', filepath)
393   - except PermissionError:
394   - logger.error('No permission: %s', filepath)
  401 + with open(filepath, 'rb') as file:
  402 + data = file.read()
395 403 except OSError:
396   - logger.error('Error opening file: %s', filepath)
397   - else:
398   - data = file.read()
399   - file.close()
  404 + logger.error('Error reading file "%s"', filepath)
  405 + break
  406 + self._filecache[(ref, image)] = data
  407 + self.write(data)
  408 + if content_type is not None:
400 409 self.set_header("Content-Type", content_type)
401   - self.write(data)
402   - await self.flush()
  410 + await self.flush()
403 411 break
404 412  
405 413  
... ...
perguntations/testfactory.py
... ... @@ -99,13 +99,6 @@ class TestFactory(dict):
99 99 if question['ref'] in qrefs:
100 100 question.update(zip(('path', 'filename', 'index'),
101 101 path.split(fullpath) + (i,)))
102   - # if question['type'] == 'code' and 'server' not in question:
103   - # try:
104   - # question['server'] = self['jobe_server']
105   - # except KeyError as exc:
106   - # msg = f'Missing JOBE server in "{question["ref"]}"'
107   - # raise TestFactoryException(msg) from exc
108   -
109 102 self['question_factory'][question['ref']] = QFactory(QDict(question))
110 103  
111 104 qmissing = qrefs.difference(set(self['question_factory'].keys()))
... ... @@ -338,4 +331,4 @@ class TestFactory(dict):
338 331 # ------------------------------------------------------------------------
339 332 def __repr__(self):
340 333 testsettings = '\n'.join(f' {k:14s}: {v}' for k, v in self.items())
341   - return '{\n' + testsettings + '\n}'
  334 + return 'TestFactory({\n' + testsettings + '\n})'
... ...