[Zope3-checkins] CVS: Zope3/src/zope/interface/tests - test_interface.py:1.9

Gary Poster gary at zope.com
Tue Jan 20 16:17:43 EST 2004


Update of /cvs-repository/Zope3/src/zope/interface/tests
In directory cvs.zope.org:/tmp/cvs-serv28571/tests

Modified Files:
	test_interface.py 
Log Message:
Add a few useful bits.

1) You can now set an invariant in your interface, where an invariant is a
validation constraint on the interface as a whole (as opposed to the individual
field constraints in schema).  The invariant should simply be a callable that
accepts an object claiming to implement the interface, and raises Invalid or a
subclass thereof if the constraint is not met (the eventual intent is to fit
into the "error view" framework, but other systems can work for now).  It is
expected that objects validated by such invariants will work primarily or
exclusively with Attributes (schema fields) rather than method output; as such,
a pattern of validating data without an actual object may simply be to create a
dummy object, update the object's __dict__ with the data values, and have it
validated.

2) A convenience method is now attached to interfaces to aid in the use of
invariants: validateInvariants.  It validates object with all defined
invariants.  If the errors argument is None (the default), the method raises
the first Invalid error; if errors is a list, it appends all errors to the
list, and then raises Invalid with the errors as the first element of the
"args" tuple.

3) Any other tagged data for the interface can now be added by functions within
an interface--see how the invariant function works, and how the __init__ of the
InterfaceClass snarfs up the information.

An obvious goal for this infrastructure will be to integrate it into the schema/forms machinery.



=== Zope3/src/zope/interface/tests/test_interface.py 1.8 => 1.9 ===
--- Zope3/src/zope/interface/tests/test_interface.py:1.8	Fri Nov 21 12:11:44 2003
+++ Zope3/src/zope/interface/tests/test_interface.py	Tue Jan 20 16:17:42 2004
@@ -15,11 +15,9 @@
 import unittest
 from zope.testing.doctestunit import DocTestSuite
 from zope.interface.tests.unitfixtures import *  # hehehe
-from zope.interface.exceptions import BrokenImplementation
-from zope.interface import implementedBy
-from zope.interface import providedBy
-from zope.interface import Interface
-from zope.interface.interface import Attribute
+from zope.interface.exceptions import BrokenImplementation, Invalid
+from zope.interface import implementedBy, providedBy, invariant
+from zope.interface import Interface, directlyProvides, Attribute
 
 class InterfaceTests(unittest.TestCase):
 
@@ -139,11 +137,84 @@
         self.assertEqual(description.__name__, 'a1')
         self.assertEqual(description.__doc__, 'This is an attribute')
 
-
     def testFunctionAttributes(self):
         # Make sure function attributes become tagged values.
         meth = _I1['f12']
         self.assertEqual(meth.getTaggedValue('optional'), 1)
+    
+    def testInvariant(self):
+        # set up
+        def ifFooThenBar(obj):
+            if getattr(obj, 'foo', None) and not getattr(obj, 'bar', None):
+                raise Invalid('If Foo, then Bar!')
+        class IInvariant(Interface):
+            foo = Attribute('foo')
+            bar = Attribute('bar; must eval to Boolean True if foo does')
+            invariant(ifFooThenBar)
+        self.assertEquals(IInvariant.getTaggedValue('invariants'), 
+                          [ifFooThenBar])
+        class InvariantC(object):
+            pass
+        o = InvariantC()
+        directlyProvides(o, IInvariant)
+        # a helper
+        def errorsEqual(self, o, error_len, error_msgs):
+            self.assertRaises(Invalid, IInvariant.validateInvariants, o)
+            e = []
+            try:
+                IInvariant.validateInvariants(o, e)
+            except Invalid, error:
+                self.assertEquals(error.args[0], e)
+            else:
+                self._assert(0) # validateInvariants should always raise 
+                # Invalid
+            self.assertEquals(len(e), error_len)
+            msgs = [error.args[0] for error in e]
+            msgs.sort()
+            for msg in msgs:
+                self.assertEquals(msg, error_msgs.pop(0))
+        # the tests
+        self.assertEquals(IInvariant.validateInvariants(o), None)
+        o.bar = 27
+        self.assertEquals(IInvariant.validateInvariants(o), None)
+        o.foo = 42
+        self.assertEquals(IInvariant.validateInvariants(o), None)
+        del o.bar
+        errorsEqual(self, o, 1, ['If Foo, then Bar!'])
+        # now we'll do two invariants just to make sure that at least a minimal
+        # multi-invariant interface is tested.
+        def BarGreaterThanFoo(obj):
+            foo = getattr(obj, 'foo', None)
+            bar = getattr(obj, 'bar', None)
+            if foo is not None and isinstance(foo, type(bar)):
+                # type checking should be handled elsewhere (like, say, 
+                # schema); these invariants should be intra-interface 
+                # constraints.  This is a hacky way to do it, maybe, but you
+                # get the idea
+                if not bar > foo:
+                    raise Invalid('Please, Boo MUST be greater than Foo!')
+        invariants = IInvariant.getTaggedValue('invariants')
+        invariants.append(BarGreaterThanFoo) # if you really need to mutate,
+        # then this would be the way to do it.
+        # even though the interface has changed, we should still only have one 
+        # error so far, the same one as before.
+        errorsEqual(self, o, 1, ['If Foo, then Bar!'])
+        # however, if we set foo to 0 (Boolean False) and bar to a negative 
+        # number then we'll get the new error
+        o.foo = 2
+        o.bar = 1
+        errorsEqual(self, o, 1, ['Please, Boo MUST be greater than Foo!'])
+        # and if we set foo to a positive number and boo to 0, we'll
+        # get both errors!
+        o.foo = 1
+        o.bar = 0
+        errorsEqual(self, o, 2, ['If Foo, then Bar!',
+                                 'Please, Boo MUST be greater than Foo!'])
+        # for a happy ending, we'll make the invariants happy
+        o.foo = 1
+        o.bar = 2
+        self.assertEquals(IInvariant.validateInvariants(o), None) # bliss
+        
 
     def test___doc___element(self):
         class I(Interface):




More information about the Zope3-Checkins mailing list