Robin  van Leeuwen
Robin van Leeuwen

Reputation: 2711

SQLAlchemy validation and extra parameters

I know i can setup a validator on a Model's column using, according to the SQLAlchemy documenation:

def validate_phone(target, value, oldvalue, initiator):
    "Strip non-numeric characters from a phone number"

    return re.sub(r'(?![0-9])', '', value)  

# setup listener on UserContact.phone attribute, instructing
# it to use the return value 
listen(UserContact.phone, 'set', validate_phone, retval=True)

But i want to pass extra arguments to the validator for example:

def check_range(target, value, oldvalue, initiator, begin, end)
    if value in range(begin, end):
        return value
    else:
        raise ValidationError()

How must i configure this validator in the listener so that it will accept the extra arguments begin and end?

Upvotes: 0

Views: 536

Answers (1)

Robin  van Leeuwen
Robin van Leeuwen

Reputation: 2711

OK, i solved it by adding a parameter to my Column definition (i had already defined an RsColumn class which inherited from Column to add extra arguments) which contains a dict with validator-function-names and optional arguments as kwargs.

When my application starts it parses all the Models and checks if there are custom validators defined on a column in the

validators=[
     {
         "function": "myvalidatorname", 
         "kwargs": {"arg1": 1, "args": "someotherargument"}
     },
     { ... }
]

-argument in the column. It sets a listener on "set" which executes the function "execute_field_validations" which again parses all the "validators" in the field and executes them optionally with arguments if they are given.

def check_positive(value, field):
    # do stuff
    pass


def check_range(value, field, begin, end):
    # do range checking
    pass


def execute_field_validations(model_instance, value, old_value, initiator):
    """
    When a "set" event is given on a models field/column then execute
    all the fields validators optionally with arguments as described 
    in the model's definition in db.models

    for example VehicleType.loading_meters:

        loading_meters = RsColumn(..., validators=[
            {"function": "check_positive"},
            {"function": "check_range", "kwargs": {"begin": 0, "end": 1000}}
        ], ...)
    """
    field = model_instance.__mapper__.columns[initiator.key]

    for validator in field.validators:

        try:
            assert validator["function"] in globals(), \
                "Invalid validator '{0}' in column ".format(
                    validator["function"]
                )

            validator_function = globals()[validator["function"]]

            if "kwargs" in validator:
                validator_function(value, field, **validator["kwargs"])
            else:
                validator_function(value, field)

        except Exception as e:
             raise ValueError()         

def configure_validators_on_individual_fields(base):
    """
    Parse through all models and all defined columns/fields
    and see if there are individual validators set on them.
    If so add a listser for them on the "set"-event
    """
    for table in base.metadata.tables:
        for field in base.metadata.tables[table].columns:
            if field.validators:
                listen(
                    getattr(
                        get_class_by_tablename(table),
                        field.name
                    ), 
                    "set", 
                    execute_field_validations
                )

Upvotes: 1

Related Questions