def _load_questions(node): """Load a list of questions. Args: node (List[dict]): list of question information, the key of each dict should be the question type, the value should be the content. Returns: List[Question]: list of question objects, parsed from the provided information """ results = [] exceptions = [] for ind, question in enumerate(node): try: results.append(_load_question(question)) except YbeLoadingError as ex: ex.question_ind = ind ex.question_id = list(question.values())[0].get('id') exceptions.append(ex) continue question_ids = [question.id for question in results] if len(question_ids) != len(set(question_ids)): duplicates = [item for item, count in collections.Counter(question_ids).items() if count > 1] exceptions.append(YbeLoadingError(f'There were multiple questions with the same id "{duplicates}"')) if len(exceptions): str(YbeMultipleLoadingErrors(exceptions)) raise YbeMultipleLoadingErrors(exceptions) return results
def _load_open_question(node): """Load the information of an open question. Args: node (dict): the question information Returns: ybe.lib.ybe_contents.OpenQuestion: the loaded question object, parsed from the provided information """ exceptions = [] try: basic_info = _load_question_basics(node) except YbeLoadingError as ex: exceptions.append(ex) try: options = OpenQuestionOptions(**node.get('options', {})) except YbeLoadingError as ex: exceptions.append(ex) if len(exceptions): raise YbeMultipleLoadingErrors(exceptions) return OpenQuestion(options=options, **basic_info)
def _load_multiple_response(node): """Load the information of a multiple response question. Args: node (dict): the question information Returns: ybe.lib.ybe_contents.MultipleResponse: the loaded question object, parsed from the provided information """ exceptions = [] try: basic_info = _load_question_basics(node) except YbeLoadingError as ex: exceptions.append(ex) try: answers = _load_multiple_response_answers(node.get('answers', [])) except YbeLoadingError as ex: exceptions.append(ex) if len(exceptions): raise YbeMultipleLoadingErrors(exceptions) return MultipleResponse(answers=answers, **basic_info)
def _load_question_basics(node, points_must_be_set=True): """Load the basic information of an Ybe question. Args: node (dict): the question information points_must_be_set (boolean): if points must be set as a field for this question. Returns: dict: basic information for a Ybe question. """ exceptions = [] try: text = _load_text_from_node(node) except YbeLoadingError as ex: exceptions.append(ex) try: meta_data = _load_question_meta_data(node.get('meta_data', {})) except YbeLoadingError as ex: exceptions.append(ex) try: points = _load_points(node.get('points'), must_be_set=points_must_be_set) except YbeLoadingError as ex: exceptions.append(ex) if len(exceptions): raise YbeMultipleLoadingErrors(exceptions) return {'id': node.get('id'), 'text': text, 'meta_data': meta_data, 'points': points}
def _load_meta_data_classification(node): """Load the classification meta data of a question. Args: node (dict): the content of the classification meta data node Returns: ybe.lib.ybe_contents.ClassificationQuestionMetaData: the question classification meta data """ if not len(node): return ClassificationQuestionMetaData() exceptions = [] related_concepts = node.get('related_concepts') if not (isinstance(related_concepts, list) or related_concepts is None): exceptions.append(YbeLoadingError(f'The value for ``meta_data.classification.related_concepts`` ' f'should be a list, "{related_concepts}" given.')) skill_level = node.get('skill_level') skill_levels = ClassificationQuestionMetaData.available_skill_levels if skill_level not in skill_levels and skill_level is not None: exceptions.append(YbeLoadingError(f'The value for ``meta_data.classification.skill_level`` should be one of ' f'"{skill_levels}", while "{skill_level}" was given.')) chapter = node.get('chapter') if not isinstance(chapter, int) and chapter is not None: exceptions.append(YbeLoadingError(f'The value for ``meta_data.classification.chapter`` should be an integer, ' f'"{chapter}" was given.')) difficulty = node.get('difficulty') if (not isinstance(difficulty, int) or difficulty not in range(0, 11)) and difficulty is not None: exceptions.append(YbeLoadingError(f'The value for ``meta_data.classification.difficulty`` should be an ' f'integer between [1-10], "{difficulty}" was given.')) if len(exceptions): raise YbeMultipleLoadingErrors(exceptions) return ClassificationQuestionMetaData( skill_level=skill_level, related_concepts=related_concepts, module=node.get('module'), chapter=chapter, difficulty=difficulty )
def _load_text_only_question(node): """Load the information of text only question. Args: node (dict): the question information Returns: ybe.lib.ybe_contents.TextOnlyQuestion: the loaded question object, parsed from the provided information """ exceptions = [] try: basic_info = _load_question_basics(node, points_must_be_set=False) except YbeLoadingError as ex: exceptions.append(ex) if len(exceptions): raise YbeMultipleLoadingErrors(exceptions) return TextOnlyQuestion(**basic_info)
exceptions = [] answers = [] for ind, item in enumerate(node): content = item['answer'] answers.append(MultipleChoiceAnswer( text=_load_text_from_node(content), correct=content.get('correct', False) )) if not (s := sum(answer.correct for answer in answers)) == 1: exceptions.append(YbeLoadingError(f'A multiple choice question must have exactly ' f'1 answer marked as correct, {s} marked.')) if len(exceptions): raise YbeMultipleLoadingErrors(exceptions) return answers def _load_multiple_response_answers(node): """Load all the answers of a multiple response question. Args: node (List[dict]): the list of answer items Returns: List[ybe.lib.ybe_contents.MultipleResponseAnswer]: the multiple reponse answers """ exceptions = []