[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