diff --git a/BUGS.md b/BUGS.md index 092e358..e236520 100644 --- a/BUGS.md +++ b/BUGS.md @@ -4,11 +4,12 @@ - usar thread.Lock para aceder a variaveis de estado. - permitir adicionar imagens nas perguntas. - debug mode: log levels not working +- replace sys.exit calls +- if does not find questions, aborts silently # TODO - implementar practice mode. -- enviar logs para web? - SQLAlchemy em vez da classe database. - single page web no teste/correcçao. Página construída em javascript, obter perguntas com ajax (para practice?). - aviso na pagina principal para quem usa browser da treta @@ -19,6 +20,7 @@ - fazer uma calculadora javascript e por no menu. surge como modal - GeoIP? - alunos online têm acesso a /correct e servidor rebenta. (não é fácil impedir...) +- enviar logs para web? # FIXED diff --git a/questions.py b/questions.py index fa19dd1..b86d033 100644 --- a/questions.py +++ b/questions.py @@ -46,36 +46,8 @@ else: # correct: !regex '[aA]zul' yaml.add_constructor('!regex', lambda l, n: re.compile(l.construct_scalar(n))) -# --------------------------------------------------------------------------- -# Runs a script and returns its stdout parsed as yaml, or None on error. -# Note: requires python 3.5+ -# --------------------------------------------------------------------------- -def run_script(script, stdin='', timeout=5): - try: - p = subprocess.run([script], - input=stdin, - stdout=subprocess.PIPE, - stderr=subprocess.STDOUT, - universal_newlines=True, - timeout=timeout, - ) - except FileNotFoundError: - logger.error('Script not found: "{0}".'.format(script)) - # return qerror - except PermissionError: - logger.error('Script "{0}" not executable (wrong permissions?).'.format(script)) - except subprocess.TimeoutExpired: - logger.error('Timeout {0}s exceeded while running script "{1}"'.format(timeout, script)) - else: - if p.returncode != 0: - logger.warning('Script "{0}" returned error code {1}.'.format(script, p.returncode)) - else: - try: - output = yaml.load(p.stdout) - except: - logger.error('Error parsing yaml output of script "{0}"'.format(script)) - else: - return output +from tools import load_yaml, run_script + # =========================================================================== # This class contains a pool of questions generators from which particular @@ -111,15 +83,8 @@ class QuestionFactory(dict): # load single YAML questions file # ----------------------------------------------------------------------- def load_file(self, filename, questions_dir=''): - try: - with open(path.normpath(path.join(questions_dir, filename)), 'r', encoding='utf-8') as f: - questions = yaml.load(f) - except EnvironmentError: - logger.error('Couldn''t open "{0}". Skipped!'.format(file)) - questions = [] - except yaml.parser.ParserError: - logger.error('While loading questions from "{0}". Skipped!'.format(file)) - questions = [] + f = path.normpath(path.join(questions_dir, filename)) + questions = load_yaml(f, default=[]) n = 0 for i, q in enumerate(questions): @@ -174,7 +139,15 @@ class QuestionFactory(dict): logger.debug('Running script to generate question "{0}".'.format(q['ref'])) q.setdefault('arg', '') # optional arguments will be sent to stdin script = path.normpath(path.join(q['path'], q['script'])) - q.update(run_script(script=script, stdin=q['arg'])) + out = run_script(script=script, stdin=q['arg']) + try: + q.update(out) + except: + q.update({ + 'type': 'alert', + 'title': 'Erro interno', + 'text': 'Ocorreu um erro a gerar esta pergunta.' + }) # The generator was replaced by a question but not yet instantiated # Finally we create an instance of Question() diff --git a/serve.py b/serve.py index 7ecc197..cc07670 100755 --- a/serve.py +++ b/serve.py @@ -8,7 +8,6 @@ import argparse import logging.config import html import json -import yaml try: import cherrypy @@ -18,6 +17,7 @@ except ImportError: print('Some python packages are missing. See README.md for instructions.') sys.exit(1) +from tools import load_yaml # ============================================================================ # Authentication @@ -239,15 +239,19 @@ if __name__ == '__main__': filename = path.abspath(path.expanduser(arg.testfile[0])) # --- Setup logging - with open(LOGGER_CONF,'r') as f: - logging.config.dictConfig(yaml.load(f)) + logging.config.dictConfig(load_yaml(LOGGER_CONF)) + + # with open(LOGGER_CONF,'r') as f: + # logging.config.dictConfig(yaml.load(f)) # --- start application from app import App try: app = App(filename, vars(arg)) - except: + except Exception as e: + logging.critical('Cannot start application.') + raise e # FIXME just for testing sys.exit(1) # --- create webserver diff --git a/templates/admin.html b/templates/admin.html index 5adfbfe..3c787b8 100644 --- a/templates/admin.html +++ b/templates/admin.html @@ -59,8 +59,8 @@
diff --git a/templates/grade.html b/templates/grade.html index 05e3172..4ef5759 100644 --- a/templates/grade.html +++ b/templates/grade.html @@ -46,20 +46,12 @@ UÉvora diff --git a/test.py b/test.py index 6e0bc19..236f44c 100644 --- a/test.py +++ b/test.py @@ -19,32 +19,7 @@ except ImportError: # my code import questions import database - -# =========================================================================== - - - -# FIXME replace sys.exit calls by exceptions - -# ----------------------------------------------------------------------- -# load dictionary from yaml file -# ----------------------------------------------------------------------- -def load_yaml(filename): - try: - f = open(filename, 'r', encoding='utf-8') - except IOError: - logger.critical('Cannot open YAML file "{}"'.format(filename)) - sys.exit(1) # FIXME - else: - with f: - try: - d = yaml.load(f) - except yaml.YAMLError as e: - mark = e.problem_mark - logger.critical('In YAML file "{0}" near line {1}, column {2}.'.format(filename, mark.line, mark.column+1)) - sys.exit(1) # FIXME - return d - +from tools import load_yaml # =========================================================================== class TestFactoryException(Exception): # FIXME unused @@ -81,7 +56,7 @@ class TestFactory(dict): for q in self['questions']: for r in q['ref']: if r not in self.question_factory: - logger.error('Can''t find question "{}".'.format(r)) + logger.error('Can\'t find question "{}".'.format(r)) logger.info('Test factory ready for "{}".'.format(self['ref'])) @@ -96,8 +71,8 @@ class TestFactory(dict): # check for important missing keys in the test configuration file if 'database' not in self: - logger.critical('Missing "database"!') - sys.exit(1) # FIXME + logger.critical('Missing "database" key in configuration.') + raise TestFactoryException() if 'ref' not in self: logger.warning('Missing "ref". Will use current date/time.') @@ -126,11 +101,11 @@ class TestFactory(dict): if not path.isfile(self['database']): logger.critical('Cannot find database "{}"'.format(self['database'])) - sys.exit(1) + raise TestFactoryException() if not path.isdir(self['questions_dir']): logger.critical('Cannot find questions directory "{}"'.format(self['questions_dir'])) - sys.exit(1) + raise TestFactoryException() # make sure we have a list of question files. # no files were defined ==> load all YAML files from questions_dir @@ -138,8 +113,8 @@ class TestFactory(dict): try: self['files'] = fnmatch.filter(listdir(self['questions_dir']), '*.yaml') except EnvironmentError: - logger.critical('Could not get list of YAML question files.') - sys.exit(1) + logger.critical('Couldn\'t get list of YAML question files.') + raise TestFactoryException() if isinstance(self['files'], str): self['files'] = [self['files']] @@ -147,11 +122,11 @@ class TestFactory(dict): # FIXME if 'questions' not in self: load all of them - try: # FIXME write logs to answers_dir? + try: f = open(path.join(self['answers_dir'],'REMOVE-ME'), 'w') except EnvironmentError: logger.critical('Cannot write answers to "{0}".'.format(self['answers_dir'])) - sys.exit(1) + raise TestFactoryException() else: with f: f.write('You can safely remove this file.') diff --git a/tools.py b/tools.py new file mode 100644 index 0000000..d5dd4f4 --- /dev/null +++ b/tools.py @@ -0,0 +1,58 @@ + + +import subprocess +import logging +import yaml + +# setup logger for this module +logger = logging.getLogger(__name__) + +# --------------------------------------------------------------------------- +# load data from yaml file +# --------------------------------------------------------------------------- +def load_yaml(filename, default=None): + try: + f = open(filename, 'r', encoding='utf-8') + except IOError: + logger.error('Can\'t open file "{}"'.format(filename)) + return default + else: + with f: + try: + return yaml.load(f) + except yaml.YAMLError as e: + # except yaml.parser.ParserError: + mark = e.problem_mark + logger.error('In YAML file "{0}" near line {1}, column {2}.'.format(filename, mark.line, mark.column+1)) + return default + +# --------------------------------------------------------------------------- +# Runs a script and returns its stdout parsed as yaml, or None on error. +# Note: requires python 3.5+ +# --------------------------------------------------------------------------- +def run_script(script, stdin='', timeout=5): + try: + p = subprocess.run([script], + input=stdin, + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + universal_newlines=True, + timeout=timeout, + ) + except FileNotFoundError: + logger.error('Script not found: "{0}".'.format(script)) + except PermissionError: + logger.error('Script "{0}" not executable (wrong permissions?).'.format(script)) + except subprocess.TimeoutExpired: + logger.error('Timeout {0}s exceeded while running script "{1}"'.format(timeout, script)) + else: + if p.returncode != 0: + logger.warning('Script "{0}" returned error code {1}.'.format(script, p.returncode)) + else: + try: + output = yaml.load(p.stdout) + except: + logger.error('Error parsing yaml output of script "{0}"'.format(script)) + else: + return output + -- libgit2 0.21.2