import validators from interfaces import * import declarative import protocols class SchemaMeta(declarative.DeclarativeMeta): """ Takes a class definition, and puts all the validators it finds into a class variable (fields). Also makes sure that this class variable is unique to this class (i.e., is not shared by subclasses), as it also does for chainedValidators and preValidators. """ def __new__(meta, className, bases, d): cls = declarative.DeclarativeMeta.__new__(meta, className, bases, d) # Don't bother doing anything if this is the most parent # Schema class (which is the only class with just # FancyValidator as a superclass): if bases == (validators.FancyValidator,): return cls # Make sure we have out own copy of fields, # not shared by parent classes (and indirectly not by # children either). cls.fields = cls.fields.copy() cls.chainedValidators = cls.chainedValidators[:] cls.preValidators = cls.preValidators[:] # Scan through the class variables we've defined *just* # for this subclass, looking for validators (both classes # and instances): for key, value in d.items(): if key in ['preValidators', 'chainedValidators', 'view']: continue validator = validators.adaptValidator(value) if validator: cls.fields[key] = value delattr(cls, key) # This last case means we're overwriting a validator # from a superclass: elif cls.fields.has_key(key): del cls.fields[key] for name, value in cls.fields.items(): cls.addField(name, value) return cls class Schema(validators.FancyValidator): """ A schema validates a dictionary of values, applying different validators (be key) to the different values. If allowExtraFields=True, keys without validators will be allowed; otherwise they will raise Invalid. Validators are associated with keys either with a class syntax, or as keyword arguments (class syntax is usually easier). Something like:: class MySchema(Schema): name = Validators.PlainText() phone = Validators.PhoneNumber() These will not be available as actual instance variables, but will be collected in a dictionary. To remove a validator in a subclass that is present in a superclass, set it to None, like:: class MySubSchema(MySchema): name = None """ __metaclass__ = SchemaMeta protocols.advise( instancesProvide=[ISchema]) chainedValidators = [] preValidators = [] allowExtraFields = False compound = True fields = {} order = [] messages = { 'notExpected': 'The input field %(name)s was not expected.', 'missingValue': "Missing value", } def _toPython(self, valueDict, state): if not valueDict and self.ifEmpty is not validators.NoDefault: return self.ifEmpty for validator in self.preValidators: valueDict = validators.toPython(validator, valueDict, state) new = {} errors = {} unused = self.fields.keys() if state is not None: previousKey = getattr(state, 'key', None) previousFullDict = getattr(state, 'fullDict', None) state.fullDict = valueDict try: for name, value in valueDict.items(): try: unused.remove(name) except ValueError: if not self.allowExtraFields: raise validators.Invalid( self.message('notExpected', state, name=repr(name)), valueDict, state) else: new[name] = value continue validator = validators.adaptValidator(self.fields[name], state, expandView=True) try: new[name] = validator.toPython(value, state) except validators.Invalid, e: errors[name] = e for name in unused: validator = validators.adaptValidator(self.fields[name], state, expandView=True) try: ifMissing = validator.ifMissing except AttributeError: ifMissing = validators.NoDefault if ifMissing is validators.NoDefault: errors[name] = validators.Invalid( self.message('missingValue', state), None, state) else: new[name] = validator.ifMissing if errors: raise validators.Invalid( formatCompoundError(errors), valueDict, state, errorDict=errors) for validator in self.chainedValidators: new = validators.toPython(validator, new, state) return new finally: if state is not None: state.key = previousKey state.fullDict = previousFullDict def addChainedValidator(self, cls, validator): if self is not None: if self.chainedValidators is cls.chainedValidators: self.chainedValidators = cls.chainedValidators[:] self.chainedValidators.append(validator) else: cls.chainedValidators.append(validator) addChainedValidator = declarative.classinstancemethod( addChainedValidator) def addField(self, cls, name, validator): if self is not None: if self.fields is cls.fields: self.fields = cls.fields.copy() self.fields[name] = validator else: cls.fields[name] = validator addField = declarative.classinstancemethod(addField) def addPreValidator(self, cls, validator): if self is not None: if self.preValidators is cls.preValidators: self.preValidators = cls.preValidators[:] self.preValidators.append(validator) else: cls.preValidators.append(validator) addPreValidator = declarative.classinstancemethod(addPreValidator) def formatCompoundError(v, indent=0): if isinstance(v, Exception): return str(v) elif isinstance(v, dict): l = v.items() l.sort() return ('%s\n' % (' '*indent)).join( ["%s: %s" % (k, formatCompoundError(value, indent=len(k)+2)) for k, value in l if value is not None]) elif isinstance(v, list): return ('%s\n' % (' '*indent)).join( ['%s' % (formatCompoundError(value, indent=indent)) for value in v if value is not None]) elif isinstance(v, str): return v else: assert 0, "I didn't expect something like %s" % repr(v)