Commit 4c5146e66cc5c1a6f3584e5a60f98c6aa043ff84
1 parent
7ce13d39
Exists in
master
and in
1 other branch
serving questions now working (except review if not in cache)
Showing
4 changed files
with
37 additions
and
42 deletions
Show diff stats
perguntations/app.py
@@ -71,12 +71,15 @@ class App(): | @@ -71,12 +71,15 @@ class App(): | ||
71 | 71 | ||
72 | # ------------------------------------------------------------------------ | 72 | # ------------------------------------------------------------------------ |
73 | def _db_setup(self) -> None: | 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 | dbfile = os.path.expanduser(self._testfactory['database']) | 77 | dbfile = os.path.expanduser(self._testfactory['database']) |
78 | + logger.info('Checking database "%s"...', dbfile) | ||
78 | if not os.path.exists(dbfile): | 79 | if not os.path.exists(dbfile): |
79 | raise AppException('No database. Use "initdb" to create.') | 80 | raise AppException('No database. Use "initdb" to create.') |
81 | + | ||
82 | + # connect to database and check for admin & registered students | ||
80 | self._engine = create_engine(f'sqlite:///{dbfile}', future=True) | 83 | self._engine = create_engine(f'sqlite:///{dbfile}', future=True) |
81 | try: | 84 | try: |
82 | with Session(self._engine, future=True) as session: | 85 | with Session(self._engine, future=True) as session: |
@@ -92,20 +95,11 @@ class App(): | @@ -92,20 +95,11 @@ class App(): | ||
92 | msg = f'Database "{dbfile}" unusable.' | 95 | msg = f'Database "{dbfile}" unusable.' |
93 | logger.error(msg) | 96 | logger.error(msg) |
94 | raise AppException(msg) from None | 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 | self._students = {uid: {'name': name, 'state': 'offline', 'test': None} | 100 | self._students = {uid: {'name': name, 'state': 'offline', 'test': None} |
99 | for uid, name in dbstudents} | 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 | async def login(self, uid: str, password: str, headers: dict) -> Optional[str]: | 104 | async def login(self, uid: str, password: str, headers: dict) -> Optional[str]: |
111 | ''' | 105 | ''' |
@@ -172,7 +166,7 @@ class App(): | @@ -172,7 +166,7 @@ class App(): | ||
172 | try: | 166 | try: |
173 | testconf = load_yaml(filename) | 167 | testconf = load_yaml(filename) |
174 | testconf['testfile'] = filename | 168 | testconf['testfile'] = filename |
175 | - except (IOError, yaml.YAMLError) as exc: | 169 | + except (OSError, yaml.YAMLError) as exc: |
176 | msg = f'Cannot read test configuration "{filename}"' | 170 | msg = f'Cannot read test configuration "{filename}"' |
177 | logger.error(msg) | 171 | logger.error(msg) |
178 | raise AppException(msg) from exc | 172 | raise AppException(msg) from exc |
@@ -534,7 +528,7 @@ class App(): | @@ -534,7 +528,7 @@ class App(): | ||
534 | with open(filename, 'r', encoding='utf-8') as file: | 528 | with open(filename, 'r', encoding='utf-8') as file: |
535 | allowed = {line.strip() for line in file} | 529 | allowed = {line.strip() for line in file} |
536 | allowed.discard('') | 530 | allowed.discard('') |
537 | - except IOError as exc: | 531 | + except OSError as exc: |
538 | error_msg = f'Cannot read file {filename}' | 532 | error_msg = f'Cannot read file {filename}' |
539 | logger.critical(error_msg) | 533 | logger.critical(error_msg) |
540 | raise AppException(error_msg) from exc | 534 | raise AppException(error_msg) from exc |
perguntations/main.py
@@ -71,7 +71,7 @@ def get_logger_config(debug=False) -> dict: | @@ -71,7 +71,7 @@ def get_logger_config(debug=False) -> dict: | ||
71 | path = os.path.expanduser(os.environ.get('XDG_CONFIG_HOME', '~/.config/')) | 71 | path = os.path.expanduser(os.environ.get('XDG_CONFIG_HOME', '~/.config/')) |
72 | try: | 72 | try: |
73 | return load_yaml(os.path.join(path, APP_NAME, file)) | 73 | return load_yaml(os.path.join(path, APP_NAME, file)) |
74 | - except IOError: | 74 | + except OSError: |
75 | print('Using default logger configuration...') | 75 | print('Using default logger configuration...') |
76 | 76 | ||
77 | if debug: | 77 | if debug: |
perguntations/serve.py
@@ -360,6 +360,7 @@ class FileHandler(BaseHandler): | @@ -360,6 +360,7 @@ class FileHandler(BaseHandler): | ||
360 | Handles static files from questions like images, etc. | 360 | Handles static files from questions like images, etc. |
361 | ''' | 361 | ''' |
362 | 362 | ||
363 | + _filecache = {} | ||
363 | 364 | ||
364 | @tornado.web.authenticated | 365 | @tornado.web.authenticated |
365 | async def get(self): | 366 | async def get(self): |
@@ -371,35 +372,42 @@ class FileHandler(BaseHandler): | @@ -371,35 +372,42 @@ class FileHandler(BaseHandler): | ||
371 | ref = self.get_query_argument('ref', None) | 372 | ref = self.get_query_argument('ref', None) |
372 | image = self.get_query_argument('image', None) | 373 | image = self.get_query_argument('image', None) |
373 | logger.debug('GET /file (ref=%s, image=%s)', ref, image) | 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 | content_type = mimetypes.guess_type(image)[0] | 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 | raise tornado.web.HTTPError(404) # Not Found | 393 | raise tornado.web.HTTPError(404) # Not Found |
384 | 394 | ||
385 | for question in test['questions']: | 395 | for question in test['questions']: |
386 | # search for the question that contains the image | 396 | # search for the question that contains the image |
387 | if question['ref'] == ref: | 397 | if question['ref'] == ref: |
388 | - filepath = path.join(question['path'], b'public', image) | 398 | + filepath = path.join(question['path'], 'public', image) |
399 | + | ||
389 | try: | 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 | except OSError: | 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 | self.set_header("Content-Type", content_type) | 409 | self.set_header("Content-Type", content_type) |
401 | - self.write(data) | ||
402 | - await self.flush() | 410 | + await self.flush() |
403 | break | 411 | break |
404 | 412 | ||
405 | 413 |
perguntations/testfactory.py
@@ -99,13 +99,6 @@ class TestFactory(dict): | @@ -99,13 +99,6 @@ class TestFactory(dict): | ||
99 | if question['ref'] in qrefs: | 99 | if question['ref'] in qrefs: |
100 | question.update(zip(('path', 'filename', 'index'), | 100 | question.update(zip(('path', 'filename', 'index'), |
101 | path.split(fullpath) + (i,))) | 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 | self['question_factory'][question['ref']] = QFactory(QDict(question)) | 102 | self['question_factory'][question['ref']] = QFactory(QDict(question)) |
110 | 103 | ||
111 | qmissing = qrefs.difference(set(self['question_factory'].keys())) | 104 | qmissing = qrefs.difference(set(self['question_factory'].keys())) |
@@ -338,4 +331,4 @@ class TestFactory(dict): | @@ -338,4 +331,4 @@ class TestFactory(dict): | ||
338 | # ------------------------------------------------------------------------ | 331 | # ------------------------------------------------------------------------ |
339 | def __repr__(self): | 332 | def __repr__(self): |
340 | testsettings = '\n'.join(f' {k:14s}: {v}' for k, v in self.items()) | 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})' |