Search code examples
google-app-engineapp-engine-ndb

How can I validate across properties using Google appengine ndb?


Say I have a class with two properties like so:

class Banana(ndb.Model):
    is_delicious = ndb.BooleanProperty(default=True)
    is_rotten = ndb.BooleanProperty(default=False)

A rotten Banana entry cannot be delicious. How can I prevent saving a delicous, rotten banana to the datastore?

I can override the __init__ method as in this answer, but this does not prevent someone updating the banana to an impossible state.

The docs show a validator option but this doesn't work across fields.

How can I validate two fields of my model against one another to prevent saving an object in an incorrect state?


Solution

  • this does not prevent someone updating the banana to an impossible state.

    Datastore provides almost zero schema enforcement on its own.

    You could open the web console for your Datastore (https://console.cloud.google.com/datastore/entities) select an entity and start deleting properties off of it, even if you ndb code has required=True when defining the property

    enter image description here

    In the picture I could edit the field completed to be a boolean instead of a date-time and then appengine would throw an exception everytime this entity is fetched via ndb.

    So I don't know where that leaves you. You could go the __init__ route

    You could put the check in _pre_put_hook:

    class Banana(ndb.Model):
        is_delicious = ndb.BooleanProperty(default=True)
        is_rotten = ndb.BooleanProperty(default=False)
        def _pre_put_hook(self):
            if self.is_delicious and self.is_rotten:
                raise Exception("A rotten Banana entry cannot be delicious")
    

    You could have a ComputedProperty do the check:

    class Banana(ndb.Model):
        is_delicious = ndb.BooleanProperty(default=True)
        is_rotten = ndb.BooleanProperty(default=False)
    
        def _is_valid(self):
            if self.is_delicious and self.is_rotten:
                raise Exception("A rotten Banana entry cannot be delicious")
            return True
    
        is_valid = ndb.ComputedProperty(lambda self: self._is_valid())
    

    But all of these will only work when the db is being access by your ndb code