[Checkins] SVN: zope.schema/trunk/ After many years of aggrevation, I finally had enough! :-)

Stephan Richter srichter at gmail.com
Fri Mar 18 14:30:33 EDT 2011


Log message for revision 121042:
  After many years of aggrevation, I finally had enough! :-)
  
  - Implemented a ``defaultFactory`` attribute for all fields. It is a callable
    that can be used to compute default values. The simplest case is::
  
      Date(defaultFactory=datetime.date.today)
  
    If the factory needs a context to compute a sensible default value, then it
    must provide ``IContextAwareDefaultFactory``, which can be used as follows::
  
      @provider(IContextAwareDefaultFactory)
      def today(context):
          return context.today()
  
      Date(defaultFactory=today)
  
  
  

Changed:
  U   zope.schema/trunk/CHANGES.txt
  U   zope.schema/trunk/setup.py
  U   zope.schema/trunk/src/zope/schema/README.txt
  U   zope.schema/trunk/src/zope/schema/_bootstrapfields.py
  U   zope.schema/trunk/src/zope/schema/_bootstrapinterfaces.py
  U   zope.schema/trunk/src/zope/schema/interfaces.py
  U   zope.schema/trunk/src/zope/schema/tests/test_field.py

-=-
Modified: zope.schema/trunk/CHANGES.txt
===================================================================
--- zope.schema/trunk/CHANGES.txt	2011-03-18 16:17:10 UTC (rev 121041)
+++ zope.schema/trunk/CHANGES.txt	2011-03-18 18:30:33 UTC (rev 121042)
@@ -2,12 +2,23 @@
 CHANGES
 =======
 
-3.7.2 (unreleased)
+3.8.0 (2011-03-18)
 ------------------
 
-- Nothing changed yet.
+- Implemented a ``defaultFactory`` attribute for all fields. It is a callable
+  that can be used to compute default values. The simplest case is::
 
+    Date(defaultFactory=datetime.date.today)
 
+  If the factory needs a context to compute a sensible default value, then it
+  must provide ``IContextAwareDefaultFactory``, which can be used as follows::
+
+    @provider(IContextAwareDefaultFactory)
+    def today(context):
+        return context.today()
+
+    Date(defaultFactory=today)
+
 3.7.1 (2010-12-25)
 ------------------
 

Modified: zope.schema/trunk/setup.py
===================================================================
--- zope.schema/trunk/setup.py	2011-03-18 16:17:10 UTC (rev 121041)
+++ zope.schema/trunk/setup.py	2011-03-18 18:30:33 UTC (rev 121042)
@@ -37,7 +37,7 @@
 
     class NullHandler(logging.Handler):
         level = 50
-        
+
         def emit(self, record):
             pass
 
@@ -61,7 +61,7 @@
     return suite
 
 setup(name='zope.schema',
-      version = '3.7.2dev',
+      version = '3.8.0',
       url='http://pypi.python.org/pypi/zope.schema',
       license='ZPL 2.1',
       description='zope.interface extension for defining data schemas',

Modified: zope.schema/trunk/src/zope/schema/README.txt
===================================================================
--- zope.schema/trunk/src/zope/schema/README.txt	2011-03-18 16:17:10 UTC (rev 121041)
+++ zope.schema/trunk/src/zope/schema/README.txt	2011-03-18 18:30:33 UTC (rev 121042)
@@ -233,7 +233,8 @@
 
 - A default value
 
-  Default field values are assigned to objects when they are first created.
+  Default field values are assigned to objects when they are first created. A
+  default factory can be specified to dynamically compute default values.
 
 
 Fields and Widgets

Modified: zope.schema/trunk/src/zope/schema/_bootstrapfields.py
===================================================================
--- zope.schema/trunk/src/zope/schema/_bootstrapfields.py	2011-03-18 16:17:10 UTC (rev 121041)
+++ zope.schema/trunk/src/zope/schema/_bootstrapfields.py	2011-03-18 18:30:33 UTC (rev 121042)
@@ -26,6 +26,7 @@
 from zope.schema._bootstrapinterfaces import TooSmall, TooBig
 from zope.schema._bootstrapinterfaces import TooShort, TooLong
 from zope.schema._bootstrapinterfaces import InvalidValue
+from zope.schema._bootstrapinterfaces import IContextAwareDefaultFactory
 
 from zope.schema._schema import getFields
 
@@ -44,11 +45,30 @@
                 inst.validate(value)
         inst.__dict__[name] = value
 
-    if sys.platform.startswith('java'):
-        # apparently descriptors work differently on Jython
-        def __get__(self, inst, owner):
-            name, check = self._info
+    def __get__(self, inst, owner):
+        name, check = self._info
+        return inst.__dict__[name]
+
+class DefaultProperty(ValidatedProperty):
+
+    def __get__(self, inst, owner):
+        name, check = self._info
+        defaultFactory = inst.__dict__['defaultFactory']
+        # If there is no default factory, simply return the default.
+        if defaultFactory is None:
             return inst.__dict__[name]
+        # Get the default value by calling the factory. Some factories might
+        # require a context to produce a value.
+        if IContextAwareDefaultFactory.providedBy(defaultFactory):
+            value = defaultFactory(inst.context)
+        else:
+            value = defaultFactory()
+        # Check that the created value is valid.
+        if check is not None:
+            check(inst, value)
+        else:
+            inst.validate(value)
+        return value
 
 
 class Field(Attribute):
@@ -74,7 +94,7 @@
     #    of Field (including Field subclass) instances.
     order = 0
 
-    default = ValidatedProperty('default')
+    default = DefaultProperty('default')
 
     # These were declared as slots in zope.interface, we override them here to
     # get rid of the dedcriptors so they don't break .bind()
@@ -84,7 +104,7 @@
 
     def __init__(self, title=u'', description=u'', __name__='',
                  required=True, readonly=False, constraint=None, default=None,
-                 missing_value=__missing_value_marker):
+                 defaultFactory=None, missing_value=__missing_value_marker):
         """Pass in field values as keyword parameters.
 
 
@@ -127,6 +147,7 @@
         if constraint is not None:
             self.constraint = constraint
         self.default = default
+        self.defaultFactory = defaultFactory
 
         # Keep track of the order of field definitions
         Field.order += 1

Modified: zope.schema/trunk/src/zope/schema/_bootstrapinterfaces.py
===================================================================
--- zope.schema/trunk/src/zope/schema/_bootstrapinterfaces.py	2011-03-18 16:17:10 UTC (rev 121041)
+++ zope.schema/trunk/src/zope/schema/_bootstrapinterfaces.py	2011-03-18 18:30:33 UTC (rev 121042)
@@ -82,3 +82,13 @@
     def fromUnicode(str):
         """Convert a unicode string to a value.
         """
+
+class IContextAwareDefaultFactory(zope.interface.Interface):
+    """A default factory that requires a context.
+
+    The context is the field context. If the field is not bound, context may
+    be ``None``.
+    """
+
+    def __call__(context):
+        """Returns a default value for the field."""

Modified: zope.schema/trunk/src/zope/schema/interfaces.py
===================================================================
--- zope.schema/trunk/src/zope/schema/interfaces.py	2011-03-18 16:17:10 UTC (rev 121041)
+++ zope.schema/trunk/src/zope/schema/interfaces.py	2011-03-18 18:30:33 UTC (rev 121042)
@@ -42,6 +42,7 @@
 from zope.schema._bootstrapinterfaces import TooLong
 from zope.schema._bootstrapinterfaces import TooShort
 from zope.schema._bootstrapinterfaces import InvalidValue
+from zope.schema._bootstrapinterfaces import IContextAwareDefaultFactory
 
 class WrongContainedType(ValidationError):
     __doc__ = _("""Wrong contained type""")

Modified: zope.schema/trunk/src/zope/schema/tests/test_field.py
===================================================================
--- zope.schema/trunk/src/zope/schema/tests/test_field.py	2011-03-18 16:17:10 UTC (rev 121041)
+++ zope.schema/trunk/src/zope/schema/tests/test_field.py	2011-03-18 18:30:33 UTC (rev 121042)
@@ -19,10 +19,11 @@
 from doctest import DocTestSuite
 from unittest import TestCase, TestSuite, makeSuite
 
-from zope.interface import Interface
+from zope.interface import Interface, provider
 from zope.schema import Field, Text, Int
+from zope.schema.interfaces import IContextAwareDefaultFactory
 from zope.schema.interfaces import ValidationError, RequiredMissing
-from zope.schema.interfaces import ConstraintNotSatisfied
+from zope.schema.interfaces import ConstraintNotSatisfied, WrongType
 from zope.testing import renormalizing
 
 class FieldTestBase(TestCase):
@@ -135,7 +136,34 @@
         i.validate(11)
         self.assertRaises(ConstraintNotSatisfied, i.validate, 10)
 
+    def testSimpleDefaultFactory(self):
+        field = Int(defaultFactory=lambda: 42)
+        self.assertEqual(field.default, 42)
 
+        # The default factory always wins against a default value.
+        field = Int(default=41, defaultFactory=lambda: 42)
+        self.assertEqual(field.default, 42)
+
+    def testContextAwareDefaultFactory(self):
+        @provider(IContextAwareDefaultFactory)
+        def getAnswerToUniverse(context):
+            if context is None:
+                return 0
+            return context.answer
+
+        field = Int(defaultFactory=getAnswerToUniverse)
+        self.assertEqual(field.default, 0)
+
+        class Context(object):
+            answer = 42
+
+        bound = field.bind(Context())
+        self.assertEqual(bound.default, 42)
+
+    def testBadValueDefaultFactory(self):
+        field = Int(defaultFactory=lambda: '42')
+        self.assertRaises(WrongType, lambda: field.default)
+
 class FieldDefaultBehaviour(TestCase):
     def test_required_defaults_to_true(self):
         class MyField(Field):



More information about the checkins mailing list