[Checkins] SVN: zope.schema/trunk/ - fix validation of schema with Object Field that specify Interface schema.

Godefroid Chapelle gotcha at bubblenet.be
Tue May 18 08:12:26 EDT 2010


Log message for revision 112445:
  - fix validation of schema with Object Field that specify Interface schema.
  
  - improve tests for validation of cycles
  

Changed:
  U   zope.schema/trunk/CHANGES.txt
  U   zope.schema/trunk/src/zope/schema/_field.py
  U   zope.schema/trunk/src/zope/schema/tests/test_objectfield.py

-=-
Modified: zope.schema/trunk/CHANGES.txt
===================================================================
--- zope.schema/trunk/CHANGES.txt	2010-05-18 12:08:13 UTC (rev 112444)
+++ zope.schema/trunk/CHANGES.txt	2010-05-18 12:12:25 UTC (rev 112445)
@@ -5,6 +5,7 @@
 3.6.4 (unreleased)
 ------------------
 
+- fix validation of schema with Object Field that specify Interface schema.
 
 3.6.3 (2010-04-30)
 ------------------

Modified: zope.schema/trunk/src/zope/schema/_field.py
===================================================================
--- zope.schema/trunk/src/zope/schema/_field.py	2010-05-18 12:08:13 UTC (rev 112444)
+++ zope.schema/trunk/src/zope/schema/_field.py	2010-05-18 12:12:25 UTC (rev 112445)
@@ -23,7 +23,7 @@
 from datetime import datetime, date, timedelta, time
 from zope.event import notify
 
-from zope.interface import classImplements, implements
+from zope.interface import classImplements, implements, Interface
 from zope.interface.interfaces import IInterface, IMethod
 
 from zope.schema.interfaces import IField
@@ -457,22 +457,37 @@
 def _validate_fields(schema, value, errors=None):
     if errors is None:
         errors = []
-    if hasattr(value, '__validating_schema'):
+    # Interface can be used as schema property for Object fields that plan to
+    # hold values of any type.
+    # Because Interface does not include any Attribute, it is obviously not
+    # worth looping on its methods and filter them all out.
+    if schema is Interface:
         return errors
-    value.__validating_schema = True
-    for name in schema.names(all=True):
-        if not IMethod.providedBy(schema[name]):
-            try:
-                attribute = schema[name]
-                if IField.providedBy(attribute):
-                    # validate attributes that are fields
-                    attribute.validate(getattr(value, name))
-            except ValidationError, error:
-                errors.append(error)
-            except AttributeError, error:
-                # property for the given name is not implemented
-                errors.append(SchemaNotFullyImplemented(error))
-    delattr(value, '__validating_schema')
+    # if `value` is part of a cyclic graph, we need to break the cycle to avoid
+    # infinite recursion.
+    if hasattr(value, '__schema_being_validated'):
+        return errors
+    # Mark the value as being validated.
+    value.__schema_being_validated = True
+    # (If we have gotten here, we know that `value` provides an interface
+    # other than zope.interface.Interface;
+    # iow, we can rely on the fact that it is an instance
+    # that supports attribute assignment.)
+    try:
+        for name in schema.names(all=True):
+            if not IMethod.providedBy(schema[name]):
+                try:
+                    attribute = schema[name]
+                    if IField.providedBy(attribute):
+                        # validate attributes that are fields
+                        attribute.validate(getattr(value, name))
+                except ValidationError, error:
+                    errors.append(error)
+                except AttributeError, error:
+                    # property for the given name is not implemented
+                    errors.append(SchemaNotFullyImplemented(error))
+    finally:
+        delattr(value, '__schema_being_validated')
     return errors
 
 

Modified: zope.schema/trunk/src/zope/schema/tests/test_objectfield.py
===================================================================
--- zope.schema/trunk/src/zope/schema/tests/test_objectfield.py	2010-05-18 12:08:13 UTC (rev 112444)
+++ zope.schema/trunk/src/zope/schema/tests/test_objectfield.py	2010-05-18 12:12:25 UTC (rev 112445)
@@ -50,55 +50,6 @@
     attribute = Attribute("Test attribute, an attribute can't be validated.")
 
 
-class ICyclic(Interface):
-    """A test schema"""
-
-    baz = Object(
-        schema=Interface,
-        title=_(u"Baz"),
-        description=_(u"Baz description"),
-        required=False,
-        )
-
-    baz_list = List(
-        value_type=Object(schema=Interface),
-        title=_(u"Baz List"),
-        description=_(u"Baz description"),
-        required=False,
-        )
-
-
-class IBaz(Interface):
-    """A test schema"""
-
-    cyclic = Object(
-        schema=ICyclic,
-        title=_(u"Cyclic"),
-        description=_(u"Cyclic description"),
-        required=False,
-        )
-
-ICyclic['baz'].schema = IBaz
-ICyclic['baz_list'].value_type.schema = IBaz
-
-
-class Cyclic(object):
-
-    implements(ICyclic)
-
-    def __init__(self, baz, baz_list):
-        self.baz = baz
-        self.baz_list = baz_list
-
-
-class Baz(object):
-
-    implements(IBaz)
-
-    def __init__(self, cyclic):
-        self.cyclic = cyclic
-
-
 class TestClass(object):
 
     implements(ITestSchema)
@@ -151,6 +102,79 @@
     # attribute
 
 
+class ISchemaWithObjectFieldAsInterface(Interface):
+
+    obj = Object(
+        schema=Interface,
+        title=_(u"Object"),
+        description=_(u"object description"),
+        required=False)
+
+
+class ClassWithObjectFieldAsInterface(object):
+
+    implements(ISchemaWithObjectFieldAsInterface)
+
+    _obj = None
+
+    def getobj(self):
+        return self._obj
+
+    def setobj(self, value):
+        self._obj = value
+
+    obj = property(getobj, setobj, None, u'obj')
+
+
+class IUnit(Interface):
+    """A schema that participate to a cycle"""
+
+    boss = Object(
+        schema=Interface,
+        title=_(u"Boss"),
+        description=_(u"Boss description"),
+        required=False,
+        )
+
+    members = List(
+        value_type=Object(schema=Interface),
+        title=_(u"Member List"),
+        description=_(u"Member list description"),
+        required=False,
+        )
+
+
+class IPerson(Interface):
+    """A schema that participate to a cycle"""
+
+    unit = Object(
+        schema=IUnit,
+        title=_(u"Unit"),
+        description=_(u"Unit description"),
+        required=False,
+        )
+
+IUnit['boss'].schema = IPerson
+IUnit['members'].value_type.schema = IPerson
+
+
+class Unit(object):
+
+    implements(IUnit)
+
+    def __init__(self, person, person_list):
+        self.boss = person
+        self.members = person_list
+
+
+class Person(object):
+
+    implements(IPerson)
+
+    def __init__(self, unit):
+        self.unit = unit
+
+
 class ObjectTest(CleanUp, FieldTestBase):
     """Test the Object Field."""
 
@@ -235,6 +259,14 @@
         errors = self.getErrors(field.validate, data)
         self.assert_(isinstance(errors[0], SchemaNotFullyImplemented))
 
+    def test_validate_with_non_object_value(self):
+        field = self.makeTestObject(
+            schema=ISchemaWithObjectFieldAsInterface,
+            required=False)
+        instance = ClassWithObjectFieldAsInterface()
+        instance.obj = (1, 1)
+        field.validate(instance)
+
     def test_beforeAssignEvent(self):
         field = self.makeTestObject(schema=ITestSchema, required=False,
                                     __name__='object_field')
@@ -259,35 +291,35 @@
     # cycles
 
     def test_with_cycles_validate(self):
-        field = self.makeTestObject(schema=ICyclic)
-        baz1 = Baz(None)
-        baz2 = Baz(None)
-        cyclic = Cyclic(baz1, [baz1, baz2])
-        baz1.cyclic = cyclic
-        baz2.cyclic = cyclic
-        field.validate(cyclic)
+        field = self.makeTestObject(schema=IUnit)
+        person1 = Person(None)
+        person2 = Person(None)
+        unit = Unit(person1, [person1, person2])
+        person1.unit = unit
+        person2.unit = unit
+        field.validate(unit)
 
     def test_with_cycles_object_not_valid(self):
-        field = self.makeTestObject(schema=ICyclic)
+        field = self.makeTestObject(schema=IUnit)
         data = self.makeTestData()
-        baz1 = Baz(None)
-        baz2 = Baz(None)
-        baz3 = Baz(data)
-        cyclic = Cyclic(baz3, [baz1, baz2])
-        baz1.cyclic = cyclic
-        baz2.cyclic = cyclic
-        self.assertRaises(WrongContainedType, field.validate, cyclic)
+        person1 = Person(None)
+        person2 = Person(None)
+        person3 = Person(data)
+        unit = Unit(person3, [person1, person2])
+        person1.unit = unit
+        person2.unit = unit
+        self.assertRaises(WrongContainedType, field.validate, unit)
 
     def test_with_cycles_collection_not_valid(self):
-        field = self.makeTestObject(schema=ICyclic)
+        field = self.makeTestObject(schema=IUnit)
         data = self.makeTestData()
-        baz1 = Baz(None)
-        baz2 = Baz(None)
-        baz3 = Baz(data)
-        cyclic = Cyclic(baz1, [baz2, baz3])
-        baz1.cyclic = cyclic
-        baz2.cyclic = cyclic
-        self.assertRaises(WrongContainedType, field.validate, cyclic)
+        person1 = Person(None)
+        person2 = Person(None)
+        person3 = Person(data)
+        unit = Unit(person1, [person2, person3])
+        person1.unit = unit
+        person2.unit = unit
+        self.assertRaises(WrongContainedType, field.validate, unit)
 
 
 def test_suite():



More information about the checkins mailing list