3

I'm using flask-sqlalchemy and trying to validate input to my database. For single fields, the validate decorator works well. However, I'm having issues with preventing values from being added to a collection/relationship based the value of on another field. For example, consider the following data model:

class MyRelation(db.Model):
    __tablename__ = "my_relation"
    id = db.Column(db.Integer, primary_key=True)
    my_data_id = db.Column(db.Integer, db.ForeignKey("my_data.id"))
    some_value = db.Column(db.String)

class MyData(db.Model):
    __tablename__ = "my_data"
    id = db.Column(db.Integer, primary_key=True)
    some_relation = db.relationship("MyRelation", backref="my_data")
    some_other_value = db.Column(db.Boolean)

The problem I'm running into is I want to ensure some_relation is empty if some_other_value == True. I have looked into the following options. I'm not sure if any of these could be tuned to provide the intended functionality:

  1. Use @validates with multiple fields as specified here. The problem with this approach is it seems to validate the collection one by one, and returning None in validation for poorly formed input yields sqlalchemy.orm.exc.FlushError: Can't flush None value found in collection Job.alternate_attributes. Returning an empty list also doesn't work.
  2. Use a before_insert event listener, as outlined here. The challenge I see with this is this seems like this would not be compatible with relationship events per the official docs.
  3. Use a before_flush event listener, which sounds like it might be compatible with this use case, but I'll be honest I'm having a hard time finding a good example of exactly how to use this given my goal. It's pretty abstract, and I don't see any examples in the docs (both of the examples mentioned at the end of the before_flush section don't actually contain any reference to the before_flush method they're supposedly demonstrating).

Any guidance would be much appreciated - thanks!

1 Answer 1

2

Answering my own question - the before_flush event listener is the way to go here:

@db.event.listens_for(db.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 = []

Works as expected, the only thing I wouldn't mind is if it could be an method on the MyData class with the rest of my @validates methods, but this is otherwise fairly clear.

Sign up to request clarification or add additional context in comments.

1 Comment

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.