Commit fe0d075161039a8d68e1c68ed5ced9184378619a
1 parent
df573134
Exists in
master
and in
1 other branch
- add_from_files does not immediately abort on error
- pretty errors and warnings on startup - removed option 'save_answers' from test configuration. It is now automatically defined depending on the existence of a 'answers_dir' option. - several fixes in managing paths.
Showing
4 changed files
with
82 additions
and
58 deletions
Show diff stats
config/server.conf
... | ... | @@ -23,8 +23,9 @@ server.socket_port = 8080 |
23 | 23 | log.screen = False |
24 | 24 | |
25 | 25 | # add path to the log files here. empty strings disable logging |
26 | -log.error_file = '/Users/mjsb/Library/Logs/Perguntations/errors.log' | |
27 | -log.access_file = '/Users/mjsb/Library/Logs/Perguntations/access.log' | |
26 | +log.error_file = '/Users/mjsb/Work/Projects/perguntations/logs/errors.log' | |
27 | +log.access_file = '' | |
28 | +; log.access_file = '/Users/mjsb/Library/Logs/Perguntations/access.log' | |
28 | 29 | |
29 | 30 | # DO NOT DISABLE SESSIONS! |
30 | 31 | tools.sessions.on = True | ... | ... |
questions.py
... | ... | @@ -9,7 +9,6 @@ import os.path |
9 | 9 | # Example usage: |
10 | 10 | # |
11 | 11 | # pool = QuestionPool() |
12 | -# pool.add_from_file('filename.yaml') | |
13 | 12 | # pool.add_from_files(['file1.yaml', 'file1.yaml']) |
14 | 13 | # |
15 | 14 | # test = [] |
... | ... | @@ -22,29 +21,18 @@ import os.path |
22 | 21 | # =========================================================================== |
23 | 22 | class QuestionsPool(dict): |
24 | 23 | '''This class contains base questions read from files, but which are |
25 | - not ready yet. They have to be instantiated separatly for each student.''' | |
24 | + not ready yet. They have to be instantiated for each student.''' | |
26 | 25 | |
27 | 26 | #------------------------------------------------------------------------ |
28 | - def add_from_file(self, filename, path='.'): | |
29 | - path_file = os.path.normpath(os.path.join(path, filename)) | |
30 | - try: | |
31 | - with open(path_file, 'r') as f: | |
32 | - questions = yaml.load(f) | |
33 | - except(FileNotFoundError): | |
34 | - print(' * Questions file "{0}" not found. Aborting...'.format(path_file)) | |
35 | - sys.exit(1) | |
36 | - except(yaml.parser.ParserError): | |
37 | - print(' * Error in questions file "{0}". Aborting...'.format(filename)) | |
38 | - sys.exit(1) | |
39 | - | |
40 | - # add defaults if missing from sources | |
27 | + def add(self, questions, filename, path): | |
28 | + # add some defaults if missing from sources | |
41 | 29 | for i, q in enumerate(questions): |
42 | 30 | if not isinstance(q, dict): |
43 | - print(' * question with index {0} in file {1} is not a dict. Ignoring...'.format(i, filename)) | |
31 | + print('[ WARNG ] Question with index {0} in file {1} is not a dict. Ignoring...'.format(i, filename)) | |
44 | 32 | continue |
45 | 33 | |
46 | 34 | if q['ref'] in self: |
47 | - print(' * question "{0}" in file "{1}" already exists in file "{2}".'.format(q['ref'], filename, self[q['ref']]['filename'])) | |
35 | + print('[ ERROR ] Duplicate question "{0}" in files "{1}" and "{2}".'.format(q['ref'], filename, self[q['ref']]['filename'])) | |
48 | 36 | sys.exit(1) |
49 | 37 | |
50 | 38 | # filename and index (number in the file, 0 based) |
... | ... | @@ -67,7 +55,16 @@ class QuestionsPool(dict): |
67 | 55 | #------------------------------------------------------------------------ |
68 | 56 | def add_from_files(self, files, path='.'): |
69 | 57 | for filename in files: |
70 | - self.add_from_file(filename, path) | |
58 | + try: | |
59 | + with open(os.path.normpath(os.path.join(path, filename)), 'r') as f: | |
60 | + questions = yaml.load(f) | |
61 | + except(FileNotFoundError): | |
62 | + print('[ ERROR ] Questions file "{0}" not found.'.format(filename)) | |
63 | + continue | |
64 | + except(yaml.parser.ParserError): | |
65 | + print('[ ERROR ] Error loading questions in YAML file "{0}".'.format(filename)) | |
66 | + continue | |
67 | + self.add(questions, filename, path) | |
71 | 68 | |
72 | 69 | |
73 | 70 | #============================================================================ |
... | ... | @@ -103,14 +100,14 @@ def create_question(q): |
103 | 100 | try: |
104 | 101 | questiontype = types[q['type']] |
105 | 102 | except KeyError: |
106 | - print(' * unsupported question type "{0}" in "{1}:{2}".'.format(q['type'], q['filename'], q['ref'])) | |
103 | + print('[ ERROR ] Unsupported question type "{0}" in "{1}:{2}".'.format(q['type'], q['filename'], q['ref'])) | |
107 | 104 | questiontype = Question |
108 | 105 | |
109 | 106 | # create question instance and return |
110 | 107 | try: |
111 | 108 | qinstance = questiontype(q) |
112 | 109 | except: |
113 | - print(' * Error creating question "{0}" from file "{1}".'.format(q['ref'], q['filename'])) | |
110 | + print('[ ERROR ] Creating question "{0}" from file "{1}".'.format(q['ref'], q['filename'])) | |
114 | 111 | |
115 | 112 | return qinstance |
116 | 113 | |
... | ... | @@ -126,7 +123,7 @@ def question_generator(q): |
126 | 123 | try: |
127 | 124 | p = subprocess.Popen([script], stdout=subprocess.PIPE, stdin=subprocess.PIPE, stderr=subprocess.STDOUT) |
128 | 125 | except FileNotFoundError: |
129 | - print(' * Script "{0}" of question "{1}" in file "{2}" could not be found'.format(script, q['ref'], q['filename'])) | |
126 | + print('[ ERROR ] Script "{0}" of question "{2}:{1}" not found'.format(script, q['ref'], q['filename'])) | |
130 | 127 | sys.exit(1) |
131 | 128 | |
132 | 129 | try: | ... | ... |
serve.py
... | ... | @@ -11,7 +11,7 @@ from os import path |
11 | 11 | |
12 | 12 | # path where this file is located |
13 | 13 | SERVER_PATH = path.dirname(path.realpath(__file__)) |
14 | -TEMPLATES_DIR = SERVER_PATH + '/templates' | |
14 | +TEMPLATES_DIR = path.join(SERVER_PATH, 'templates') | |
15 | 15 | |
16 | 16 | # my code |
17 | 17 | from myauth import AuthController, require |
... | ... | @@ -187,7 +187,7 @@ if __name__ == '__main__': |
187 | 187 | # --- controller |
188 | 188 | root = Root(testconf) |
189 | 189 | |
190 | - # --- Mount and run server. | |
190 | + # --- Mount and run server | |
191 | 191 | cherrypy.config.update({'tools.staticdir.root': SERVER_PATH}) |
192 | 192 | cherrypy.quickstart(root, '/', config=arg.server) |
193 | 193 | cherrypy.log('Terminated OK ------------------------', 'APPLICATION') | ... | ... |
test.py
1 | 1 | |
2 | -import os, sys | |
2 | +import os, sys, fnmatch | |
3 | 3 | import random |
4 | -import yaml | |
5 | -import json | |
4 | +import yaml, json | |
6 | 5 | import sqlite3 |
7 | 6 | from datetime import datetime |
8 | 7 | |
... | ... | @@ -13,16 +12,23 @@ import database |
13 | 12 | # =========================================================================== |
14 | 13 | def read_configuration(filename, debug=False, show_points=False, show_hints=False, practice=False, save_answers=False, show_ref=False): |
15 | 14 | # FIXME validar se ficheiros e directorios existem??? |
16 | - if not os.path.isfile(filename): | |
17 | - print(' * Cannot find file "%s"' % filename) | |
18 | - sys.exit(1) | |
19 | 15 | |
20 | 16 | try: |
21 | - with open(filename, 'r') as f: | |
22 | - test = yaml.load(f) | |
23 | - except: | |
24 | - print(' * Error reading yaml file "{0}".'.format(filename)) | |
25 | - raise | |
17 | + f = open(filename, 'r') | |
18 | + except IOError: | |
19 | + print('[ ERROR ] Cannot open YAML file "%s"' % filename) | |
20 | + sys.exit(1) | |
21 | + else: | |
22 | + with f: | |
23 | + try: | |
24 | + test = yaml.load(f) | |
25 | + except yaml.YAMLError as exc: | |
26 | + mark = exc.problem_mark | |
27 | + print('[ ERROR ] In YAML file "{0}" near line {1}, column {2}.'.format(filename,mark.line,mark.column+1)) | |
28 | + sys.exit(1) | |
29 | + # -- test yaml was loaded ok | |
30 | + | |
31 | + errors = 0 | |
26 | 32 | |
27 | 33 | # defaults: |
28 | 34 | test['ref'] = str(test.get('ref', filename)) |
... | ... | @@ -32,34 +38,51 @@ def read_configuration(filename, debug=False, show_points=False, show_hints=Fals |
32 | 38 | test['practice'] = bool(test.get('practice', practice)) |
33 | 39 | test['debug'] = bool(test.get('debug', debug)) |
34 | 40 | test['show_ref'] = bool(test.get('show_ref', show_ref)) |
35 | - test['path'] = str(test.get('path', '.')) # questions dir FIXME use os.path | |
36 | - # FIXME check that the directory exists | |
37 | - if not os.path.exists(test['path']): | |
38 | - print(' * Questions path "{0}" does not exist. Fix it in "{1}".'.format(test['path'], filename)) | |
39 | - sys.exit(1) | |
40 | - test['save_answers'] = bool(test.get('save_answers', save_answers)) | |
41 | - if test['save_answers']: | |
42 | - if 'answers_dir' not in test: | |
43 | - raise exception('Missing "answers_dir" in the test configuration.') | |
41 | + | |
42 | + # this is the base directory where questions are stored | |
43 | + test['questions_dir'] = os.path.normpath(os.path.expanduser(str(test.get('questions_dir', os.path.curdir)))) | |
44 | + if not os.path.exists(test['questions_dir']): | |
45 | + print('[ ERROR ] Questions directory "{0}" does not exist.\n Fix the "questions_dir" key in the configuration file "{1}".'.format(test['questions_dir'], filename)) | |
46 | + errors += 1 | |
47 | + | |
48 | + # where to put the students answers (optional) | |
49 | + if 'answers_dir' not in test: | |
50 | + print('[ WARNG ] Missing "answers_dir" in the test configuration file "{0}".\n Saving answers is disabled'.format(filename)) | |
51 | + test['save_answers'] = False | |
52 | + else: | |
53 | + test['answers_dir'] = os.path.normpath(os.path.expanduser(str(test['answers_dir']))) | |
44 | 54 | if not os.path.isdir(test['answers_dir']): |
45 | - print(' * Directory "%s" does not exist. Creating...' % test['answers_dir']) | |
46 | - os.mkdir(test['answers_dir']) | |
55 | + print('[ ERROR ] Directory "{0}" does not exist.'.format(test['answers_dir'])) | |
56 | + errors += 1 | |
57 | + test['save_answers'] = True | |
47 | 58 | |
59 | + # database with login credentials and grades | |
48 | 60 | if 'database' not in test: |
49 | - print(' * Missing database in the test configuration.') | |
61 | + print('[ ERROR ] Missing "database" key in the test configuration "{0}".'.format(filename)) | |
62 | + errors += 1 | |
63 | + else: | |
64 | + test['database'] = os.path.normpath(os.path.expanduser(str(test['database']))) | |
65 | + if not os.path.exists(test['database']): | |
66 | + print('[ ERROR ] Database "{0}" not found.'.format(test['database'])) | |
67 | + errors += 1 | |
68 | + | |
69 | + if errors > 0: | |
70 | + print('{0} error(s) found. Aborting!'.format(errors)) | |
50 | 71 | sys.exit(1) |
51 | - if not os.path.exists(test['database']): | |
52 | - print(' * Database "{0}" not found.'.format(test['database'])) | |
53 | - sys.exit(1) | |
54 | - | |
55 | 72 | |
56 | - if isinstance(test['files'], str): | |
57 | - test['files'] = [test['files']] | |
73 | + # deal with questions files | |
74 | + if 'files' not in test: | |
75 | + # no files were defined = load all from questions_dir | |
76 | + test['files'] = fnmatch.filter(os.listdir(test['questions_dir']), '*.yaml') | |
77 | + print('[ WARNG ] All YAML files from directory were loaded. Might not be such a good idea...') | |
78 | + else: | |
79 | + # only one file | |
80 | + if isinstance(test['files'], str): | |
81 | + test['files'] = [test['files']] | |
58 | 82 | |
59 | 83 | # replace ref,points by actual questions from pool |
60 | 84 | pool = questions.QuestionsPool() |
61 | - pool.add_from_files(files= | |
62 | - test['files'], path=test['path']) | |
85 | + pool.add_from_files(files=test['files'], path=test['questions_dir']) | |
63 | 86 | |
64 | 87 | for i, q in enumerate(test['questions']): |
65 | 88 | # each question is a list of alternative versions, even if the list |
... | ... | @@ -93,7 +116,7 @@ def read_configuration(filename, debug=False, show_points=False, show_hints=Fals |
93 | 116 | try: |
94 | 117 | qq = pool[r] |
95 | 118 | except KeyError: |
96 | - print(' * question "{0}" in test "{1}" not found in the pool. Ignoring...'.format(r, test['ref'])) | |
119 | + print('[ WARNG ] Question reference "{0}" of test "{1}" not found. Skipping...'.format(r, test['ref'])) | |
97 | 120 | continue |
98 | 121 | qq['points'] = p |
99 | 122 | l.append(qq) |
... | ... | @@ -111,7 +134,10 @@ class Test(dict): |
111 | 134 | |
112 | 135 | qlist = [] |
113 | 136 | for i, qq in enumerate(self['questions']): |
114 | - q = random.choice(qq) # select from alternative versions | |
137 | + try: | |
138 | + q = random.choice(qq) # select from alternative versions | |
139 | + except IndexError: | |
140 | + print(qq) # FIXME | |
115 | 141 | qlist.append(questions.create_question(q)) # create instance |
116 | 142 | self['questions'] = qlist |
117 | 143 | self['start_time'] = datetime.now() | ... | ... |