Example #1
0
 def test_get_raises_invalid_validator_id(self):
     """Tests if class method 'get' in _Validator raises exception
     for invalid validator id.
     """
     with self.assertRaisesRegexp(
             Exception,
             'Invalid validator id: some invalid validator method name'):
         schema_utils.get_validator('some invalid validator method name')
Example #2
0
 def test_get_raises_invalid_validator_id(self):
     # type: () -> None
     """Tests if class method 'get' in _Validator raises exception
     for invalid validator id.
     """
     with self.assertRaisesRegexp(  # type: ignore[no-untyped-call]
             Exception,
             'Invalid validator id: some invalid validator method name'):
         schema_utils.get_validator('some invalid validator method name')
 def test_has_length_at_least_validator(self):
     """Tests if static method has_length_at_least returns true iff
     given list has length of at least the given value.
     """
     has_len_at_least = schema_utils.get_validator('has_length_at_least')
     self.assertTrue(has_len_at_least(['elem'], 0))
     self.assertTrue(has_len_at_least(['elem'], 1))  # boundary
     self.assertFalse(has_len_at_least(['elem'], 2))
 def test_is_at_most_validator(self):
     """Tests if static method is_at_most returns true iff obj
     is at most a value.
     """
     is_at_most = schema_utils.get_validator('is_at_most')
     self.assertTrue(is_at_most(2, 3))
     self.assertTrue(is_at_most(2, 2))  # boundary
     self.assertFalse(is_at_most(2, 1))
    def test_is_valid_numeric_expression_validator(self):
        """Tests for the is_valid_math_expression static method with
        numeric type.
        """
        is_valid_math_expression = schema_utils.get_validator(
            'is_valid_math_expression')

        self.assertFalse(is_valid_math_expression('a+b*2', False))
        self.assertTrue(is_valid_math_expression('3+4/2', False))
 def test_is_nonempty_validator(self):
     """Tests if static method is_nonempty returns true iff obj
     is not an empty str.
     """
     is_nonempty = schema_utils.get_validator('is_nonempty')
     self.assertTrue(is_nonempty('non-empty string'))
     self.assertTrue(is_nonempty(' '))
     self.assertTrue(is_nonempty('    '))
     self.assertFalse(is_nonempty(''))
    def test_is_url_fragment(self):
        validate_url_fragment = schema_utils.get_validator('is_url_fragment')

        self.assertTrue(validate_url_fragment('math'))
        self.assertTrue(validate_url_fragment('computer-science'))
        self.assertTrue(validate_url_fragment('bio-tech'))

        self.assertFalse(validate_url_fragment(''))
        self.assertFalse(validate_url_fragment('Abc'))
        self.assertFalse(validate_url_fragment('!@#$%^&*()_+='))
Example #8
0
    def test_is_valid_algebraic_expression_validator(self):
        # type: () -> None
        """Tests for the is_valid_algebraic_expression static method with
        algebraic type.
        """
        is_valid_algebraic_expression = schema_utils.get_validator(
            'is_valid_algebraic_expression')

        self.assertTrue(is_valid_algebraic_expression('a+b*2'))
        self.assertFalse(is_valid_algebraic_expression('3+4/2'))
    def test_is_supported_audio_language_code(self):
        is_supported_audio_language_code = schema_utils.get_validator(
            'is_supported_audio_language_code')

        self.assertTrue(is_supported_audio_language_code('en'))
        self.assertTrue(is_supported_audio_language_code('fr'))
        self.assertTrue(is_supported_audio_language_code('de'))

        self.assertFalse(is_supported_audio_language_code(''))
        self.assertFalse(is_supported_audio_language_code('zz'))
        self.assertFalse(is_supported_audio_language_code('test'))
Example #10
0
    def test_is_regex_matched(self):
        # type: () -> None
        is_regex_matched = schema_utils.get_validator('is_regex_matched')

        self.assertTrue(is_regex_matched(
            'exploration.EXP_ID_1.WzEuNjI2NTgxNDQwOTVlKzEyXQ==WzE3NThd',
            r'(exploration|collection)\.\w+\.\w+'))

        self.assertFalse(is_regex_matched(
            'WzEuNjI2NTgxNDQwOTVlKzEyXQ==WzE3NThd',
            r'(exploration|collection)\.\w+\.\w+'))
Example #11
0
def _validate_validator(obj_type, validator):
    # type: (Text, Dict[Text, Any]) -> None
    """Validates the value of a 'validator' field.

    Args:
        obj_type: str. The type of the object.
        validator: dict. The Specs that needs to be validated.

    Raises:
        AssertionError. The object fails to validate against the schema.
    """
    reference_dict = VALIDATOR_SPECS[obj_type]
    assert 'id' in validator, 'id is not present in validator'
    assert validator['id'] in reference_dict, (
        '%s is not present in reference_dict' % validator['id'])

    customization_keys = list(validator.keys())
    customization_keys.remove('id')
    assert (
        set(customization_keys) ==
        set(reference_dict[validator['id']].keys())), (
            'Missing keys: %s, Extra keys: %s' % (
                list(
                    set(reference_dict[validator['id']].keys()) -
                    set(customization_keys)),
                list(
                    set(customization_keys) -
                    set(reference_dict[validator['id']].keys()))))
    for key in customization_keys:
        value = validator[key]
        schema = reference_dict[validator['id']][key]
        try:
            schema_utils.normalize_against_schema(value, schema)
        except Exception as e:
            raise AssertionError(e)

    # Check that the id corresponds to a valid normalizer function.
    validator_fn = schema_utils.get_validator(validator['id'])
    assert set(inspect.getargspec(validator_fn).args) == set(
        customization_keys + ['obj']), (
            'Missing keys: %s, Extra keys: %s' % (
                list(
                    set(customization_keys + ['obj']) -
                    set(inspect.getargspec(validator_fn).args)),
                list(
                    set(inspect.getargspec(validator_fn).args) -
                    set(customization_keys + ['obj']))))
Example #12
0
def _validate_validator(obj_type, validator):
    """Validates the value of a 'validator' field."""
    reference_dict = VALIDATOR_SPECS[obj_type]
    assert 'id' in validator and validator['id'] in reference_dict

    customization_keys = list(validator.keys())
    customization_keys.remove('id')
    assert (set(customization_keys) ==
            set(reference_dict[validator['id']].keys()))
    for key in customization_keys:
        value = validator[key]
        schema = reference_dict[validator['id']][key]
        try:
            schema_utils.normalize_against_schema(value, schema)
        except Exception as e:
            raise AssertionError(e)

    # Check that the id corresponds to a valid normalizer function.
    validator_fn = schema_utils.get_validator(validator['id'])
    assert set(inspect.getargspec(validator_fn).args) == set(
        customization_keys + ['obj'])
Example #13
0
    def test_is_valid_math_equation_validator(self):
        # type: () -> None
        """Tests for the is_valid_math_equation static method."""
        is_valid_math_equation = schema_utils.get_validator(
            'is_valid_math_equation')

        self.assertTrue(is_valid_math_equation('a+b=c'))
        self.assertTrue(is_valid_math_equation('x^2+y^2=z^2'))
        self.assertTrue(is_valid_math_equation('y = m*x + b'))
        self.assertTrue(
            is_valid_math_equation('alpha^a + beta^b = gamma^(-c)'))
        self.assertTrue(is_valid_math_equation('a+b=0'))
        self.assertTrue(is_valid_math_equation('0=a+b'))
        self.assertTrue(is_valid_math_equation('(a/b)+c=(4^3)*a'))
        self.assertTrue(is_valid_math_equation('2^alpha-(-3) = 3'))
        self.assertTrue(is_valid_math_equation('(a+b)^2 = a^2 + b^2 + 2*a*b'))
        self.assertTrue(is_valid_math_equation('(a+b)^2 = a^2 + b^2 + 2ab'))
        self.assertTrue(is_valid_math_equation('x/a + y/b = 1'))
        self.assertTrue(is_valid_math_equation('3 = -5 + pi^x'))
        self.assertTrue(is_valid_math_equation('0.4 + 0.5 = alpha * 4'))
        self.assertTrue(is_valid_math_equation('sqrt(a+b)=c - gamma/2.4'))
        self.assertTrue(is_valid_math_equation('abs(35 - x) = 22.3'))

        self.assertFalse(is_valid_math_equation('3 -= 2/a'))
        self.assertFalse(is_valid_math_equation('3 == 2/a'))
        self.assertFalse(is_valid_math_equation('x + y = '))
        self.assertFalse(is_valid_math_equation('(a+b = 0)'))
        self.assertFalse(is_valid_math_equation('pi = 3.1415'))
        self.assertFalse(is_valid_math_equation('a+b=0=a-b'))
        self.assertFalse(is_valid_math_equation('alpha - beta/c'))
        self.assertFalse(is_valid_math_equation('2^alpha-(-3*) = 3'))
        self.assertFalse(is_valid_math_equation('a~b = 0'))
        self.assertFalse(is_valid_math_equation('a+b<=0'))
        self.assertFalse(is_valid_math_equation('a+b>=0'))
        self.assertFalse(is_valid_math_equation('a+b<0'))
        self.assertFalse(is_valid_math_equation('a+b>0'))
        self.assertFalse(is_valid_math_equation('5+3=8'))
        self.assertFalse(is_valid_math_equation('(a+(b)=0'))
        self.assertFalse(is_valid_math_equation('a+b=c:)'))
Example #14
0
    def _convert_state_v34_dict_to_v35_dict(cls, question_state_dict):
        """Converts from version 34 to 35. Version 35 upgrades all explorations
        that use the MathExpressionInput interaction to use one of
        AlgebraicExpressionInput, NumericExpressionInput, or MathEquationInput
        interactions.

        Args:
            question_state_dict: dict. A dict where each key-value pair
                represents respectively, a state name and a dict used to
                initialize a State domain object.

        Returns:
            dict. The converted question_state_dict.
        """
        is_valid_algebraic_expression = schema_utils.get_validator(
            'is_valid_algebraic_expression')
        is_valid_numeric_expression = schema_utils.get_validator(
            'is_valid_numeric_expression')
        is_valid_math_equation = schema_utils.get_validator(
            'is_valid_math_equation')
        ltt = latex2text.LatexNodes2Text()

        if question_state_dict['interaction']['id'] == 'MathExpressionInput':
            new_answer_groups = []
            types_of_inputs = set()
            for group in question_state_dict['interaction']['answer_groups']:
                new_answer_group = copy.deepcopy(group)
                for rule_spec in new_answer_group['rule_specs']:
                    rule_input = ltt.latex_to_text(rule_spec['inputs']['x'])

                    rule_input = exp_domain.clean_math_expression(rule_input)

                    type_of_input = exp_domain.TYPE_INVALID_EXPRESSION
                    if is_valid_algebraic_expression(rule_input):
                        type_of_input = (
                            exp_domain.TYPE_VALID_ALGEBRAIC_EXPRESSION)
                    elif is_valid_numeric_expression(rule_input):
                        type_of_input = exp_domain.TYPE_VALID_NUMERIC_EXPRESSION
                    elif is_valid_math_equation(rule_input):
                        type_of_input = exp_domain.TYPE_VALID_MATH_EQUATION

                    types_of_inputs.add(type_of_input)

                    if type_of_input != exp_domain.TYPE_INVALID_EXPRESSION:
                        rule_spec['inputs']['x'] = rule_input
                        if type_of_input == exp_domain.TYPE_VALID_MATH_EQUATION:
                            rule_spec['inputs']['y'] = 'both'
                        rule_spec['rule_type'] = 'MatchesExactlyWith'

                new_answer_groups.append(new_answer_group)

            if exp_domain.TYPE_INVALID_EXPRESSION not in types_of_inputs:
                # If at least one rule input is an equation, we remove
                # all other rule inputs that are expressions.
                if exp_domain.TYPE_VALID_MATH_EQUATION in types_of_inputs:
                    new_interaction_id = exp_domain.TYPE_VALID_MATH_EQUATION
                    for group in new_answer_groups:
                        new_rule_specs = []
                        for rule_spec in group['rule_specs']:
                            if is_valid_math_equation(
                                    rule_spec['inputs']['x']):
                                new_rule_specs.append(rule_spec)
                        group['rule_specs'] = new_rule_specs
                # Otherwise, if at least one rule_input is an algebraic
                # expression, we remove all other rule inputs that are
                # numeric expressions.
                elif exp_domain.TYPE_VALID_ALGEBRAIC_EXPRESSION in (
                        types_of_inputs):
                    new_interaction_id = (
                        exp_domain.TYPE_VALID_ALGEBRAIC_EXPRESSION)
                    for group in new_answer_groups:
                        new_rule_specs = []
                        for rule_spec in group['rule_specs']:
                            if is_valid_algebraic_expression(
                                    rule_spec['inputs']['x']):
                                new_rule_specs.append(rule_spec)
                        group['rule_specs'] = new_rule_specs
                else:
                    new_interaction_id = (
                        exp_domain.TYPE_VALID_NUMERIC_EXPRESSION)

                # Removing answer groups that have no rule specs left after
                # the filtration done above.
                new_answer_groups = [
                    answer_group for answer_group in new_answer_groups
                    if (len(answer_group['rule_specs']) != 0)
                ]

                # Removing feedback keys, from voiceovers_mapping and
                # translations_mapping, that correspond to the rules that
                # got deleted.
                old_answer_groups_feedback_keys = [
                    answer_group['outcome']['feedback']['content_id']
                    for answer_group in (
                        question_state_dict['interaction']['answer_groups'])
                ]
                new_answer_groups_feedback_keys = [
                    answer_group['outcome']['feedback']['content_id']
                    for answer_group in (new_answer_groups)
                ]
                content_ids_to_delete = set(
                    old_answer_groups_feedback_keys) - set(
                        new_answer_groups_feedback_keys)
                for content_id in content_ids_to_delete:
                    if content_id in question_state_dict[
                            'recorded_voiceovers']['voiceovers_mapping']:
                        del question_state_dict['recorded_voiceovers'][
                            'voiceovers_mapping'][content_id]
                    if content_id in question_state_dict[
                            'written_translations']['translations_mapping']:
                        del question_state_dict['written_translations'][
                            'translations_mapping'][content_id]

                question_state_dict['interaction']['id'] = new_interaction_id
                question_state_dict['interaction']['answer_groups'] = (
                    new_answer_groups)
                if question_state_dict['interaction']['solution']:
                    correct_answer = question_state_dict['interaction'][
                        'solution']['correct_answer']['ascii']
                    correct_answer = exp_domain.clean_math_expression(
                        correct_answer)
                    question_state_dict['interaction']['solution'][
                        'correct_answer'] = correct_answer

        return question_state_dict
    def map(item):
        is_valid_math_expression = schema_utils.get_validator(
            'is_valid_math_expression')
        is_valid_math_equation = schema_utils.get_validator(
            'is_valid_math_equation')
        ltt = latex2text.LatexNodes2Text()
        unicode_to_text_mapping = (
            MathExpressionValidationOneOffJob.UNICODE_TO_TEXT)
        inverse_trig_fns_mapping = (
            MathExpressionValidationOneOffJob.INVERSE_TRIG_FNS_MAPPING)
        trig_fns = MathExpressionValidationOneOffJob.TRIG_FNS

        if not item.deleted:
            exploration = exp_fetchers.get_exploration_from_model(item)
            for state_name, state in exploration.states.items():
                if state.interaction.id == 'MathExpressionInput':
                    for group in state.interaction.answer_groups:
                        for rule_spec in group.rule_specs:
                            rule_input = ltt.latex_to_text(
                                rule_spec.inputs['x'])

                            # Shifting powers in trig functions to the end.
                            # For eg. 'sin^2(x)' -> 'sin(x)^2'.
                            for trig_fn in trig_fns:
                                rule_input = re.sub(
                                    r'%s(\^\d)\((.)\)' % trig_fn,
                                    r'%s(\2)\1' % trig_fn, rule_input)

                            # Adding parens to trig functions that don't have
                            # any. For eg. 'cosA' -> 'cos(A)'.
                            for trig_fn in trig_fns:
                                rule_input = re.sub(r'%s(?!\()(.)' % trig_fn,
                                                    r'%s(\1)' % trig_fn,
                                                    rule_input)

                            # The pylatexenc lib outputs the unicode values of
                            # special characters like sqrt and pi, which is why
                            # they need to be replaced with their corresponding
                            # text values before performing validation.
                            for unicode_char, text in (
                                    unicode_to_text_mapping.items()):
                                rule_input = rule_input.replace(
                                    unicode_char, text)

                            # Replacing trig functions that have format which is
                            # incompatible with the validations.
                            for invalid_trig_fn, valid_trig_fn in (
                                    inverse_trig_fns_mapping.items()):
                                rule_input = rule_input.replace(
                                    invalid_trig_fn, valid_trig_fn)

                            validity = 'Invalid'
                            if is_valid_math_expression(rule_input):
                                validity = 'Valid Expression'
                            elif is_valid_math_equation(rule_input):
                                validity = 'Valid Equation'

                            output_values = '%s %s: %s' % (item.id, state_name,
                                                           rule_input)

                            yield (validity, output_values.encode('utf-8'))