Hi folks, I have written a generic schema validation base class for an external project of mine (not a Zope 3 instance actually, just use Zope 3 packages).
The basic idea is that it automatically validates changes to an interface implementation derived from this class and makes a good way of generic data validation. It provides validation when a variable is set directly or using the update function (for several variables). Now I have a problem with a function that should update several variables at the same time. Because I can only set one at a time, if an invariant error occurs (condition of one variable is depending on another one) the class returns with a half messy state (half of the variables is set, the other half not). So is there a way to easily validate the values before I set them (send them as a list to the validation routine) or make a weird copy of the class? Anyway, to show you what I mean, here is the code and a doctest which shows what should not happen and still does: Class: from zope.schema import getValidationErrors as get_validation_errors from zope.schema import getFieldNames as get_field_names class schema_base(object): """ Base class for interface based schema implementation. Provides generic schema validation for schema implementations""" field_names = [] def __init__(self, class_interface): """ Set up schema validation. Note: to reduce iteration cycles we assume that once a class is initialized based on an interface, the interface won't change anymore. Parameters class_interface We need the interface class we are validating against.""" self.class_interface = class_interface self.field_names = get_field_names(self.class_interface) def __setattr__(self, item, value): """ The overwriting of this function is required to call the schema validation routine when setting attributes directly. If the value validation against the interface fails, the old value is reset. """ if item not in self.field_names and \ item is not 'class_interface' and \ item is not 'field_names': raise ValueError, "invalid field to set: %s" % item item_initialized = False if hasattr(self, item): old_value = self.__getattribute__(item) item_initialized = True dict.__setattr__(self, item, value) try: get_validation_errors(self.class_interface, self) except ValueError: if item_initialized is True: dict.__setattr__(self, item, old_value) raise except: # TODO: repetition, ugly.. if item_initialized is True: dict.__setattr__(self, item, old_value) raise ValueError, "validation of %s failed for value %s" % ( item, value) # FIXME: validation is done one by one. if invariant (schema field relative) # errors occur, we get a messy half state.IMPORTANT!!! def update(self, **key_value): """ With this function we can assing several attributes of the class with a single function call. """ for key, value in key_value.items(): setattr(self, key, value) And the doctest: ================= Schema Base Class ================= Define an interface to base the schema on. >>> from zope.interface import Interface, invariant >>> from zope.schema import TextLine, Int >>> >>> class IData(Interface): ... ... Name = TextLine( ... title = u'Name of the data.', ... readonly = True, ... required = True) ... ... Num = Int( ... title = u'Number.', ... default = 0, ... min = 0, max = 999) ... ... MaxNum = Int( ... title = u'Maximal number.', ... description = u'-1 means the number is unknown', ... default = -1, ... min = -1, max = 999) ... ... @invariant ... def Num_greater_MaxNum(obj): ... if obj.MaxNum is not -1 and obj.MaxNum < obj.Num: ... raise ValueError, "%s < %s" % (obj.MaxNum, obj.Num) ... Connect the interface with an implementation based on the schema base. >>> from zope.interface import Interface, implements >>> from zope.schema.fieldproperty import FieldProperty >>> >>> from generic.schema_base import schema_base >>> >>> class DataBase(schema_base): ... def __init__(self): ... schema_base.__init__(self, IData) ... >>> >>> class Data(DataBase): ... implements(IData) ... ... Name = FieldProperty(IData['Name']) ... Num = FieldProperty(IData['Num']) ... MaxNum = FieldProperty(IData['MaxNum']) ... ... def __init__(self, Name): ... DataBase.__init__(self) ... self.Name = Name[:255] ... Create an instance of the data class. >>> test_class = Data(u'Testclass') Check supposed values. >>> print test_class.Name Testclass >>> print test_class.Num 0 >>> print test_class.MaxNum -1 Set valid values with assigment. >>> test_class.Num = 5 >>> test_class.MaxNum = 10 Set valid values with function call. >>> test_class.update(Num = 10, MaxNum = -1) Try to set an invalid value with assigment. Should rase exception. >>> test_class.Num = -5 Traceback (most recent call last): ... TooSmall: (-5, 0) The value shold stayed like it was before failed setting. >>> print test_class.Num 10 Do the same with function assigment. >>> test_class.update(Num = -5) Traceback (most recent call last): ... TooSmall: (-5, 0) Check value again. >>> print test_class.Num 10 Try to set values violating invariant constraints. The error from the invariant in the interface declaration should be raised. >>> test_class.update(Num = 20, MaxNum = 10) Traceback (most recent call last): ... ValueError: 10 < 20 Values of the test class variables should be unchanged. !!! Currently this one fails, serious issue! See source! >>> print test_class.Num 10 >>> print test_class.MaxNum -1 This one should fail. >>> test_class.Name = u'Can not rename readonly' Traceback (most recent call last): ... ValueError: ('Name', 'field is readonly') (the doctest fails at the point with the !!! marking.) Now a function like getValidationError(<interface>, [fieldnames,fieldvalues]) would solve the problem. But I did not find such a thing. Any ideas? Additional comments about the general idea of such a class or other implementational comments are also welcome. -- Sebastian Bartos, <seth.kriti...@googlemail.com> keyserevr: pgp.mit.edu
signature.asc
Description: This is a digitally signed message part
_______________________________________________ Zope3-users mailing list Zope3-users@zope.org http://mail.zope.org/mailman/listinfo/zope3-users