# QFactory is a class that can generate question instances, e.g. by shuffling # options, running a script to generate the question, etc. # # To generate an instance of a question we use the method generate() where # the argument is the reference of the question we wish to produce. # # Example: # # # read question from file # qdict = tools.load_yaml(filename) # qfactory = QFactory(qdict) # question = qfactory.generate() # # # experiment answering one question and correct it # question.updateAnswer('42') # insert answer # grade = question.correct() # correct answer # An instance of an actual question is an object that inherits from Question() # # Question - base class inherited by other classes # QuestionInformation - not a question, just a box with content # QuestionRadio - single choice from a list of options # QuestionCheckbox - multiple choice, equivalent to multiple true/false # QuestionText - line of text compared to a list of acceptable answers # QuestionTextRegex - line of text matched against a regular expression # QuestionTextArea - corrected by an external program # QuestionNumericInterval - line of text parsed as a float # base from os import path import logging # project from tools import run_script from questions import QuestionInformation, QuestionRadio, QuestionCheckbox, QuestionText, QuestionTextRegex, QuestionTextArea, QuestionNumericInterval # setup logger for this module logger = logging.getLogger(__name__) # =========================================================================== # Question Factory # =========================================================================== class QFactory(object): # Depending on the type of question, a different question class will be # instantiated. All these classes derive from the base class `Question`. _types = { 'radio' : QuestionRadio, 'checkbox' : QuestionCheckbox, 'text' : QuestionText, 'text-regex': QuestionTextRegex, 'numeric-interval': QuestionNumericInterval, 'textarea' : QuestionTextArea, # -- informative panels -- 'information': QuestionInformation, 'info': QuestionInformation, 'warning' : QuestionInformation, 'warn': QuestionInformation, 'alert' : QuestionInformation, 'success' : QuestionInformation, } def __init__(self, question_dict): self.question = question_dict # ----------------------------------------------------------------------- # Given a ref returns an instance of a descendent of Question(), # i.e. a question object (radio, checkbox, ...). # ----------------------------------------------------------------------- def generate(self): logger.debug(f'Generating "{self.question["ref"]}"') # Shallow copy so that script generated questions will not replace # the original generators q = self.question.copy() # If question is of generator type, an external program will be run # which will print a valid question in yaml format to stdout. This # output is then yaml parsed into a dictionary `q`. if q['type'] == 'generator': logger.debug(f' \_ Running script "{q["script"]}"...') q.setdefault('arg', '') # optional arguments will be sent to stdin script = path.join(q['path'], q['script']) out = run_script(script=script, stdin=q['arg']) q.update(out) # Finally we create an instance of Question() try: qinstance = self._types[q['type']](q) # instance with correct class except KeyError as e: logger.error(f'Failed to generate question "{q["ref"]}"') raise e else: return qinstance