def __init__(cls, *args, **kwargs): def new_validator(validator_name): def inner(self, key, val): meth = getattr(db_validator, validator_name) try: return meth(val) except Exception as exc: # add an attribute identifying invalid field setattr(exc, 'invalid_field', key) raise return inner columns_to_validate = getattr(cls, '_columns_to_validate', None) if columns_to_validate: for name in columns_to_validate: table, col = cls._split_name_to_table_column(name) if table: validator_name = db_validator.adjuster_prefix + table + '_' + col else: validator_name = db_validator.adjuster_prefix + col validates_with_args = validates(col) validator_func = new_validator(validator_name) setattr(cls, validator_name, validates_with_args(validator_func)) super(CustomDeclarativeMeta, cls).__init__(*args, **kwargs)
def receive_class_instrument(cls): for field in cls.iter_columns(relationships=False, synonyms=False, use_inspection=False): if not isinstance(field, Field) or not field.can_validate: continue method_name = 'validate_%s' % field.name if not hasattr(cls, method_name): def validator(self, key, value): return self.get_column(key).validate(value) setattr(cls, method_name, validates(field.name)(validator))
class Layer3Domain(db.Model, TrackChanges): VRF = 'vrf' TYPES = {VRF: ('rd', )} id = Column(BigInteger, primary_key=True, nullable=False) name = Column(String(128), nullable=False, unique=True) type = Column(String(20), nullable=False) comment = Column(String(255), nullable=True, index=False) # Vrf rd = Column(BigInteger, nullable=True, unique=True) validate_uint32 = validates('rd')(validate_uint32) @property def display_name(self): return self.name @property def display_rd(self): return '%d:%d' % (self.rd >> 16, self.rd & 0xFFFF) def set_comment(self, comment): self.comment = comment self.update_modified() def set_rd(self, rd_str): if self.type != Layer3Domain.VRF: raise InvalidParameterError('Layer3domain %s is not of type vrf' % self.name) rd = Layer3Domain.parse_rd(rd_str) if Layer3Domain.query.filter_by(rd=rd).count() > 0: raise InvalidParameterError( 'Layer3domain with type vrf rd %s already exists' % rd_str) self.rd = rd @staticmethod def parse_rd(rd): m = re.match(r'(\d{1,5}):(\d{1,5})', rd) if m is None: raise InvalidParameterError("Invalid rd '%s'" % rd) def validate_uint16(s): value = int(s) if value < 0 or value > 2**16 - 1: raise InvalidParameterError("Invalid rd '%s'" % rd) return value a = validate_uint16(m.group(1)) b = validate_uint16(m.group(2)) return (a << 16) + b
def complex_validates(validate_rule): """Quickly setup attributes validation by one-time, based on `sqlalchemy.orm.validates`. Don't like `sqlalchemy.orm.validates`, you don't need create many model method, as long as pass formatted validate rule. (Cause of SQLAlchemy's validate mechanism, you need assignment this funciton's return value to a model property.) For simplicity, complex_validates don't support `include_removes` and `include_backrefs` parameters that in `sqlalchemy.orm.validates`. And we don't recommend you use this function multiple times in one model. Because this will bring many problems, like: 1. Multiple complex_validates's execute order was decide by it's model property name, and by reversed order. eg. predicates in `validator1 = complex_validates(...)` will be executed **AFTER** predicates in `validator2 = complex_validates(...)` 2. If you try to validate the same attribute in two (or more) complex_validates, only one of complex_validates will be execute. (May be this is a bug of SQLAlchemy?) `complex_validates` was currently based on `sqlalchemy.orm.validates`, so it is difficult to solve these problems. May be we can try to use `AttributeEvents` directly in further, to provide more reliable function. Rule Format ----------- { column_name: predicate # basic format (column_name2, column_name3): predicate # you can specify multiple column_names to given predicates column_name4: (predicate, predicate2) # you can specify multiple predicates to given column_names column_name5: [(predicate, arg1, ... argN)] # and you can specify what arguments should pass to predicate # when it doing validate (column_name6, column_name7): [(predicate, arg1, ... argN), predicate2] # another example } Notice: If you want pass arguments to predicate, you must wrap whole command by another list or tuple. Otherwise, we will determine the argument as another predicate. So, this is wrong: { column_name: (predicate, arg) } this is right: { column_name: [(predicate, arg)] } Predicate --------- There's some `predefined_predicates`, you can just reference its name in validate rule. {column_name: ['trans_upper']} Or you can pass your own predicate function to the rule, like this: def custom_predicate(value): return value_is_legal # return True or False for valid or invalid value {column_name: [custom_predicate]} If you want change the value when doing validate, return an `dict(value=new_value)` instead of boolean {column_name: lambda value: dict(value = value * 2)} # And you see, we can use lambda as a predicate. And the predicate can receive extra arguments, that passes in rule: def multiple(value, target_multiple): return dict(value= value * target_multiple) {column_name: (multiple, 10)} Complete Example ---------------- class People(db.Model): name = Column(String(100)) age = Column(Integer) IQ = Column(Integer) has_lover = Column(Boolean) validator = complex_validates({ 'name': [('min_length', 1), ('max_length', 100)], ('age', 'IQ'): [('min', 0)], 'has_lover': lambda value: return !value # hate you! })""" ref_dict = { # column_name: ( # (predicate, arg1, ... argN), # ... # ) } for column_names, predicate_refs in validate_rule.iteritems(): for column_name in _to_tuple(column_names): ref_dict[column_name] = \ ref_dict.get(column_name, tuple()) + _normalize_predicate_refs(predicate_refs) return validates(*ref_dict.keys())( lambda self, name, value: _validate_handler(name, value, ref_dict[name]))
def json_freezing_mapper(*args, **kwargs): m = mapper(*args, **kwargs) names = [c.name for c in m.columns if isinstance(c.type, JSONEncoded)] m.class_._json_freezer = validates(*names)( lambda self, name, val: freeze(val)) return m
class ZoneView(db.Model, TrackChanges): __table_constraints__ = (UniqueConstraint('name', 'zone_id'), ) soa_attrs = ['ttl', 'primary', 'mail', 'serial', 'refresh', 'retry', 'expire', 'minimum'] id = Column(BigInteger, primary_key=True, nullable=False) name = Column(String(255), nullable=False) zone_id = Column(BigInteger, ForeignKey('zone.id'), nullable=False) # SOA fields ttl = Column(Integer, nullable=False) primary = Column(String(255), nullable=False) mail = Column(String(255), nullable=False) serial = Column(BigInteger, nullable=False) refresh = Column(Integer, nullable=False) retry = Column(Integer, nullable=False) expire = Column(Integer, nullable=False) minimum = Column(BigInteger, nullable=False) zone = relationship(Zone, backref='views') @staticmethod def create(zone, name, from_profile=None, soa_attributes=None, copy_rrs=True): if from_profile: assert len(from_profile.views) == 1 from_view = from_profile.views[0] fields = dict( ttl=from_view.ttl, primary=from_view.primary, mail=from_view.mail, refresh=from_view.refresh, retry=from_view.retry, expire=from_view.expire, minimum=from_view.minimum) else: fields = dict( primary='localhost.', mail='hostmaster.' + (zone.name or 'root') + '.', refresh=app.config['DNS_DEFAULT_REFRESH'], retry=app.config['DNS_DEFAULT_RETRY'], expire=app.config['DNS_DEFAULT_EXPIRE'], minimum=app.config['DNS_DEFAULT_MINIMUM'], ttl=app.config['DNS_DEFAULT_ZONE_TTL']) if soa_attributes: fields.update(soa_attributes) if not fields.get('serial', None): fields['serial'] = int(datetime.date.today().strftime("%Y%m%d01")) zoneview = ZoneView(name=name, zone=zone, **fields) if from_profile and copy_rrs: for rr in RR.query.filter_by(view=from_view): db.session.add(RR(name=make_fqdn(RR.record_name(rr.name, rr.view.zone.name), zone.name), view=zoneview, type=rr.type, ttl=rr.ttl, ipblock=rr.ipblock, target=rr.target, value=rr.value)) db.session.add(zoneview) zone.update_modified() return zoneview validate_fqdn = validates('primary')(validate_fqdn) @validates('expire', 'ttl', 'minimum', 'refresh', 'retry') def validate_int32(self, key, value): value = int(value) if value < 0 or value > 2 ** 31 - 1: raise InvalidParameterError("Invalid %s: %d" % (key, value)) return value validate_uint32 = validates('serial')(validate_uint32) def set_soa_attrs(self, attributes): # TODO what happens to outputs if we set the serial here? ttl_change = 'ttl' in attributes and int(attributes['ttl']) != self.ttl if ttl_change: rrs = RR.query.filter_by(view=self, ttl=None).all() for rr in rrs: OutputUpdate.send_rr_action(rr, OutputUpdate.DELETE_RR) for name, value in attributes.iteritems(): setattr(self, name, value) if 'serial' not in attributes: self.update_serial() OutputUpdate.send_update_soa(self) if ttl_change: for rr in rrs: OutputUpdate.send_rr_action(rr, OutputUpdate.CREATE_RR) def update_serial(self): ''' Increment the serial and call update_modified(). Only update the serial by calling this function, otherwise outputs will not be correctly updated. ''' self.serial += 1 self.update_modified() @property def nsec3param(self): return self.zone.nsec3param def soa_value(self): return '{primary} {email} {serial} {refresh} {retry} {expire} {minimum}'.format( primary=self.primary, email=self.mail, serial=self.serial, refresh=self.refresh, retry=self.retry, expire=self.expire, minimum=self.minimum) @staticmethod def pdns_soa_value(value): primary, email, serial, refresh, retry, expire, minimum = value.split() return '{primary} {email} {serial} {refresh} {retry} {expire} {minimum}'.format( primary=strip_dot(primary), email=strip_dot(email), serial=serial, refresh=refresh, retry=retry, expire=expire, minimum=minimum) @property def outputs(self): return Output.query.join(Output.groups).join(ZoneGroup.views).filter(ZoneView.id == self.id) def __str__(self): zone_object = 'zone profile' if self.zone.profile else 'zone' if len(self.zone.views) == 1: return '{0} {1}'.format(zone_object, self.zone.display_name) else: return '{0} {1} view {2}'.format(zone_object, self.zone.display_name, self.name) @property def display_name(self): return self.name
def complex_validates(validate_rule): """Quickly setup attributes validation by one-time, based on `sqlalchemy.orm.validates`. Don't like `sqlalchemy.orm.validates`, you don't need create many model method, as long as pass formatted validate rule. (Cause of SQLAlchemy's validate mechanism, you need assignment this funciton's return value to a model property.) For simplicity, complex_validates don't support `include_removes` and `include_backrefs` parameters that in `sqlalchemy.orm.validates`. And we don't recommend you use this function multiple times in one model. Because this will bring many problems, like: 1. Multiple complex_validates's execute order was decide by it's model property name, and by reversed order. eg. predicates in `validator1 = complex_validates(...)` will be executed **AFTER** predicates in `validator2 = complex_validates(...)` 2. If you try to validate the same attribute in two (or more) complex_validates, only one of complex_validates will be execute. (May be this is a bug of SQLAlchemy?) `complex_validates` was currently based on `sqlalchemy.orm.validates`, so it is difficult to solve these problems. May be we can try to use `AttributeEvents` directly in further, to provide more reliable function. Rule Format ----------- { column_name: predicate # basic format (column_name2, column_name3): predicate # you can specify multiple column_names to given predicates column_name4: (predicate, predicate2) # you can specify multiple predicates to given column_names column_name5: [(predicate, arg1, ... argN)] # and you can specify what arguments should pass to predicate # when it doing validate (column_name6, column_name7): [(predicate, arg1, ... argN), predicate2] # another example } Notice: If you want pass arguments to predicate, you must wrap whole command by another list or tuple. Otherwise, we will determine the argument as another predicate. So, this is wrong: { column_name: (predicate, arg) } this is right: { column_name: [(predicate, arg)] } Predicate --------- There's some `predefined_predicates`, you can just reference its name in validate rule. {column_name: ['trans_upper']} Or you can pass your own predicate function to the rule, like this: def custom_predicate(value): return value_is_legal # return True or False for valid or invalid value {column_name: [custom_predicate]} If you want change the value when doing validate, return an `dict(value=new_value)` instead of boolean {column_name: lambda value: dict(value = value * 2)} # And you see, we can use lambda as a predicate. And the predicate can receive extra arguments, that passes in rule: def multiple(value, target_multiple): return dict(value= value * target_multiple) {column_name: (multiple, 10)} Complete Example ---------------- class People(db.Model): name = Column(String(100)) age = Column(Integer) IQ = Column(Integer) has_lover = Column(Boolean) validator = complex_validates({ 'name': [('min_length', 1), ('max_length', 100)], ('age', 'IQ'): [('min', 0)], 'has_lover': lambda value: return !value # hate you! })""" ref_dict = { # column_name: ( # (predicate, arg1, ... argN), # ... # ) } for column_names, predicate_refs in validate_rule.items(): for column_name in _to_tuple(column_names): ref_dict[column_name] = \ ref_dict.get(column_name, tuple()) + _normalize_predicate_refs(predicate_refs) return validates(*ref_dict.keys())( lambda self, name, value: _validate_handler(name, value, ref_dict[name]))
class User(fixtures.ComparableEntity): sv = validates("name")(SomeValidator())