Marshmallow - Using validation decorators @validates_schema and @validates in same schema not working?

Viewed 1037

I am new to marshmallow, and am working on validation. I have been able to use custom validators either by using @ validates or @validates_schema, but they don't seem to work at the same time. Is the following possible? Not sure what I'm missing. If one or the other is commented out, the opposite one works. Any suggestions?

Thank you!!

class TestSchema(Schema):

    field_1 = fields.String(required=True)
    field_2 = fields.String(required=True)
    field_3 = fields.String(required=True)

    @validates_schema()
    def validate_field1_less_field_2(self, data, **kwargs):
        errors = dict()
        if int(data["field_1"]) > int(data["field_2"]):
            errors["field_1"] = ["field_1 must be less than field_2"]
        if errors:
            raise ValidationError(errors)
    
    @validates('field_3')
    def validate_field_3(self, field_3):
        if any(field_3.upper() not in ['A', 'B', 'C'] for value in field_3):
            raise ValidationError(f'Incorrect characters in field_3.')


try:
    schema = TestSchema()
    schema.load(json.loads(json_string))
except ValidationError as err:
    print({err.messages})
1 Answers

I believe I have uncovered the issue. I was not using the option 'skip_on_field_errors=False' for the @validates_schema decorator. Once adding this, I got the following output:

Code:

import json

from marshmallow import fields, ValidationError, validates, validate, validates_schema, Schema


class TestSchema(Schema):
    # field_1 = fields.String(required=True)
    # field_2 = fields.String(required=True)
    field_1 = fields.Integer(required=True)
    field_2 = fields.Integer(required=True)
    field_3 = fields.String(required=True)

    @validates_schema(skip_on_field_errors=False)
    def validate_field1_less_field_2(self, data, **kwargs):
        errors = dict()
        if data["field_1"] > data["field_2"]:
        # if int(data["field_1"]) > int(data["field_2"]):
            errors["field_1"] = ["field_1 must be less than field_2"]
        if errors:
            raise ValidationError(errors)

    @validates('field_3')
    def validate_field_3(self, field_3):
        if any(value.upper() not in ['A', 'B', 'C'] for value in field_3):
            raise ValidationError(f'Incorrect characters in field_3.')



row =  {'field_1': '12', 'field_2': '6', 'field_3': 'aBx'}

json_string = json.dumps(row, indent=4)
print(f'json_string: {json_string}')

try:
    schema = TestSchema()
    schema.load(json.loads(json_string))
except ValidationError as err:
    print({err})

Output:

json_string: {
    "field_1": "12",
    "field_2": "6",
    "field_3": "aBx"
}
{ValidationError({'field_3': ['Incorrect characters in field_3.'], 'field_1': ['field_1 must be less than field_2']})}

Process finished with exit code 0

I also realized, that if a field is not valid, such as providing a string where an int is expected, you would probably not want to do any schema level validation, since that will cause errors - ex: if field_1 = 512 and field_2 = 5x2, you'd get:

    if data['field_1'] > data['field_2']:
KeyError: 'field_2'

Going forward, I am going to use the default skip_on_field_errors=True to make sure all the fields are valid before doing any comparisons between fields using the @validates_schema. Thank you for your patience, and I hope this helps someone else in the future.

Related