import os import yaml class QuestionException(Exception): """ Exception to be thrown when there is something wrong with the question file. """ pass class Questions(object): """ Object holding all the questions """ QUESTION_TYPES = ["Text", "Image", "Music", "Code"] QUESTION_KEYS = ["Name", "Question", "Answer", "Type", "Double-Jeopardy"] def __init__(self, qfile): self.qfile = qfile self._questions = None self._read_questions() def get_sections(self): return [s["Section"] for s in self._questions] def get_questions(self, section): sec = filter(lambda s: s["Section"] == section, self._questions) if len(sec) < 1: raise ValueError("Section %s does not exist" % (section,)) return sec[0]["Questions"] def get_question(self, section, question): if type(question) != int or question < 1 or question > 5: raise ValueError("question parameter needs to be an integer between 1 and 5") return self.get_questions(section)[question-1] def get_number_from_section(self, section): for i,s in enumerate(self._questions, 1): if s["Section"] == section: return i raise ValueError("Section '%s' does not exist" % section) def _read_questions(self): f = None # open file try: f = open(self.qfile) except OSError as e: raise QuestionException("Could not read question file: %s" % e) # load yaml ysrc = None try: ysrc = yaml.load(f.read()) except Exception as e: raise QuestionException("Error parsing question file %s: %s" % (self.qfile, e)) # now to check the integrity of the question file if type(ysrc) is not list: raise QuestionException("The questionfile has to be a list of question") for i, sec in enumerate(ysrc, 1): if not "Section" in sec.keys() or not "Questions" in sec.keys(): raise QuestionException("Section %d needs to have the keys 'Section' and 'Question' (case-sensitive)" % i) for j, q in enumerate(sec["Questions"], 1): # check for keys we need in each question if any([x not in q.keys() for x in ["Question", "Answer", "Type"]]): raise QuestionException("Question %d from section %d (%s) is missing one of the keywords Question, Answer or Type" % (j, i, sec["Section"])) # check for keys we do not know for key in q.keys(): if key not in self.QUESTION_KEYS: raise QuestionException("Qestion %d from section %d (%s) has invalid keyword '%s'" % (j, i, sec["Section"], key)) # check Double-Jeopardy is a bool and is set to false it non-existant if "Double-Jeopardy" not in q.keys(): q["Double-Jeopardy"] = False elif type(q["Double-Jeopardy"]) != bool: raise QuestionException("The Double-Jeopardy key from question %d from section %d (%s) must be either true or false" % (j, i, sec["Section"])) # check for broken question types if q["Type"] not in self.QUESTION_TYPES: raise QuestionException("Question %d from Section %d (%s) has an invalid type '%s' (valid types are %s)" % (j, i, sec["Section"], q["Type"], ", ".join(self.VALID_TYPES))) # check if file for music/image questions exist if q["Type"] in ("Music", "Image"): if not os.path.isfile(q["Question"]): raise QuestionException("File for question %d, section %d (%s) not found" % (j, i, sec["Section"])) # check if this section has enough questions if j != 5: raise QuestionException("Section %d (%s) needs to have exactly %d questions (has %d)" % (i, sec["Section"], 5, j)) # check for only having unique section names sections = [s["Section"] for s in ysrc] if len(sections) != len(set(sections)): raise QuestionException("All section names must be unique") # done, save yaml src to _questions self._questions = ysrc return True if __name__ == '__main__': q = Questions("questions/template.q") print(q.get_sections()) print(q.get_questions("A")) print(q.get_question("A", 1))