Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is there a way to transparently perform validation on SQLAlchemy objects?

Is there a way to perform validation on an object after (or as) the properties are set but before the session is committed?

For instance, I have a domain model Device that has a mac property. I would like to ensure that the mac property contains a valid and sanitized mac value before it is added to or updated in the database.

It looks like the Pythonic approach is to do most things as properties (including SQLAlchemy). If I had coded this in PHP or Java, I would probably have opted to create getter/setter methods to protect the data and give me the flexibility to handle this in the domain model itself.

public function mac() { return $this->mac; }
public function setMac($mac) {
    return $this->mac = $this->sanitizeAndValidateMac($mac);
}
public function sanitizeAndValidateMac($mac) {
    if ( ! preg_match(self::$VALID_MAC_REGEX) ) {
        throw new InvalidMacException($mac);
    }
    return strtolower($mac);
}

What is a Pythonic way to handle this type of situation using SQLAlchemy?

(While I'm aware that validation and should be handled elsewhere (i.e., web framework) I would like to figure out how to handle some of these domain specific validation rules as they are bound to come up frequently.)

UPDATE

I know that I could use property to do this under normal circumstances. The key part is that I am using SQLAlchemy with these classes. I do not understand exactly how SQLAlchemy is performing its magic but I suspect that creating and overriding these properties on my own could lead to unstable and/or unpredictable results.

like image 280
Beau Simensen Avatar asked Mar 05 '10 23:03

Beau Simensen


3 Answers

You can add data validation inside your SQLAlchemy classes using the @validates() decorator.

From the docs - Simple Validators:

An attribute validator can raise an exception, halting the process of mutating the attribute’s value, or can change the given value into something different.

from sqlalchemy.orm import validates

class EmailAddress(Base):
    __tablename__ = 'address'

    id = Column(Integer, primary_key=True)
    email = Column(String)

    @validates('email')
    def validate_email(self, key, address):
        # you can use assertions, such as
        # assert '@' in address
        # or raise an exception:
        if '@' not in address:
            raise ValueError('Email address must contain an @ sign.')
        return address
like image 143
culix Avatar answered Nov 04 '22 15:11

culix


Yes. This can be done nicely using a MapperExtension.

# uses sqlalchemy hooks to data model class specific validators before update and insert
class ValidationExtension( sqlalchemy.orm.interfaces.MapperExtension ):
    def before_update(self, mapper, connection, instance):
        """not every instance here is actually updated to the db, see http://www.sqlalchemy.org/docs/reference/orm/interfaces.html?highlight=mapperextension#sqlalchemy.orm.interfaces.MapperExtension.before_update"""
        instance.validate()
        return sqlalchemy.orm.interfaces.MapperExtension.before_update(self, mapper, connection, instance)
    def before_insert(self, mapper, connection, instance):
        instance.validate()
        return sqlalchemy.orm.interfaces.MapperExtension.before_insert(self, mapper, connection, instance)


sqlalchemy.orm.mapper( model, table, extension = ValidationExtension(), **mapper_args )

You may want to check before_update reference because not every instance here is actually updated to the db.

like image 21
cvogt Avatar answered Nov 04 '22 15:11

cvogt


"It looks like the Pythonic approach is to do most things as properties"

It varies, but that's close.

"If I had coded this in PHP or Java, I would probably have opted to create getter/setter methods..."

Good. That's Pythonic enough. Your getter and setter functions are bound up in a property; that's pretty good.

What's the question?

Are you asking how to spell property?

However, "transparent validation" -- if I read your example code correctly -- may not really be all that good an idea.

Your model and your validation should probably be kept separate. It's common to have multiple validations for a single model. For some users, fields are optional, fixed or not used; this leads to multiple validations.

You'll be happier following the Django design pattern of using a Form for validation, separate form the model.

like image 2
S.Lott Avatar answered Nov 04 '22 16:11

S.Lott