Flask validates decorator multiple fields simultaneously

Question:

I have been using the @validates decorator in sqlalchemy.orm from flask to validate fields, and all has gone well as long as all of the fields are independent of one another such as:

@validates('field_one')
def validates_field_one(self, key, value):
   #field one validation

@validates('field_two')
def validates_field_two(self, key, value):
   #field two validation

However, now I need to do some validation that will require access to field_one and field_two simultaneously. It looks like validates accepts multiple arguments to the validates decorator, however, it will simply run the validation function once for each argument, as such:

@validates('field_one', 'field_two')
def validates_fields(self, keys, values):
   #field validation

Results in a work flow of validate field_one and then validate field_two. However, I would like to validate both at the same time(a trivial example of which would be assert that the value of field_one is not the value of field_two, an example of which would be disallowing self-loops in a graph where field_one and field_two refer to nodes and it is performing validation on an edge). How would be the best way to go about doing that?

Asked By: Brent Hronik

||

Answers:

Order the fields in the order they were defined on the model. Then check if the last field is the one being validated. Otherwise just return the value unchecked. If the validator is validating one of the earlier fields, some of them will not be set yet.

@validates('field_one', 'field_two')
def validates_fields(self, key, value):
    if key == 'field_two':
        assert self.field_one != value
    return value

See this example.

Answered By: r-m-n

Adding another answer here, as the accepted one didn’t quite meet my use case for using another field to validate and modify relationship/collection fields, which are not really compatible with @validates. In this case you can use an event listener for the before_flush event to achieve what you’re looking for:

@event.listens_for(Session, 'before_flush')
def validate_and_modify_relationships(session, flush_context, instances):
    """
    Complex validation that cannot be performed with @valdiates
    """
    
    # new records only (for updates only, use session.dirty)
    for instance in session.new:
        if isinstance(instance, MyData):
            if instance.some_other_value:
                instance.some_relation = []

More details here: Flask-SQLAlchemy validation: prevent adding to relationship based on other field

Answered By: Brendan