[Zope3-checkins] CVS: Zope3/src/zope/schema - __init__.py:1.2 _bootstrapfields.py:1.2 _field.py:1.2 _schema.py:1.2 errornames.py:1.2 fieldproperty.py:1.2 interfaces.py:1.2 readme.stx:1.2
Jim Fulton
jim@zope.com
Wed, 25 Dec 2002 09:15:51 -0500
Update of /cvs-repository/Zope3/src/zope/schema
In directory cvs.zope.org:/tmp/cvs-serv20790/src/zope/schema
Added Files:
__init__.py _bootstrapfields.py _field.py _schema.py
errornames.py fieldproperty.py interfaces.py readme.stx
Log Message:
Grand renaming:
- Renamed most files (especially python modules) to lower case.
- Moved views and interfaces into separate hierarchies within each
project, where each top-level directory under the zope package
is a separate project.
- Moved everything to src from lib/python.
lib/python will eventually go away. I need access to the cvs
repository to make this happen, however.
There are probably some bits that are broken. All tests pass
and zope runs, but I haven't tried everything. There are a number
of cleanups I'll work on tomorrow.
=== Zope3/src/zope/schema/__init__.py 1.1 => 1.2 ===
--- /dev/null Wed Dec 25 09:15:51 2002
+++ Zope3/src/zope/schema/__init__.py Wed Dec 25 09:15:20 2002
@@ -0,0 +1,24 @@
+##############################################################################
+#
+# Copyright (c) 2002 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""Schema package constructor
+
+$Id$
+"""
+
+from zope.schema._field import Field, Container, Iterable, Orderable, MinMaxLen, ValueSet
+from zope.schema._field import Sequence
+from zope.schema._field import Bytes, BytesLine, Text, TextLine, Bool, Int, Float
+from zope.schema._field import Tuple, List, Dict, Datetime
+from zope.schema._schema import validateMapping, validateMappingAll,\
+ getFields, getFieldsInOrder
=== Zope3/src/zope/schema/_bootstrapfields.py 1.1 => 1.2 ===
--- /dev/null Wed Dec 25 09:15:51 2002
+++ Zope3/src/zope/schema/_bootstrapfields.py Wed Dec 25 09:15:20 2002
@@ -0,0 +1,274 @@
+##############################################################################
+#
+# Copyright (c) 2002 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""
+$Id$
+"""
+__metaclass__ = type
+
+from zope.interface import Attribute
+from zope.interface.implements import visitImplements
+
+from zope.schema.interfaces import StopValidation, ValidationError
+from zope.schema import errornames
+from zope.schema._schema import getFields
+
+class ValidatedProperty:
+
+ def __init__(self, name, check = None):
+ self.__info = name, check
+
+ def __set__(self, inst, value):
+ name, check = self.__info
+ if value is not None:
+ if check is not None:
+ check(inst, value)
+ else:
+ inst.validate(value)
+ inst.__dict__[name] = value
+
+class Field(Attribute):
+
+ # Type restrictions, if any
+ _type = None
+ order = 0
+ context = None
+
+ constraint = None
+ default = ValidatedProperty('default')
+
+ def __init__(self, __name__='', __doc__='',
+ title=u'', description=u'',
+ required=True, readonly=False, constraint=None, default=None,
+ ):
+ """Pass in field values as keyword parameters."""
+
+
+ if not __doc__:
+ if title:
+ if description:
+ __doc__ = "%s\n\n%s" % (title, description)
+ else:
+ __doc__ = title
+ elif description:
+ __doc__ = description
+
+ super(Field, self).__init__(__name__, __doc__)
+ self.title = title
+ self.description = description
+ self.required = required
+ self.readonly = readonly
+ if constraint is not None:
+ self.constraint = constraint
+ self.default = default
+
+ # Keep track of the order of field definition
+ Field.order += 1
+ self.order = Field.order
+
+ def bind(self, object):
+ clone = self.__class__.__new__(self.__class__)
+ clone.__dict__.update(self.__dict__)
+ clone.context = object
+ return clone
+
+ def validate(self, value):
+ if value is None:
+ if self.required:
+ raise ValidationError(errornames.RequiredMissing)
+ else:
+ try:
+ self._validate(value)
+ except StopValidation:
+ pass
+
+ def __eq__(self, other):
+ # should be the same type
+ if type(self) != type(other):
+ return False
+ # should have the same properties
+ names = {} # used as set of property names, ignoring values
+ visitImplements(self.__implements__, self,
+ lambda interface: names.update(getFields(interface)))
+ # order will be different always, don't compare it
+ if 'order' in names:
+ del names['order']
+ for name in names:
+ if getattr(self, name) != getattr(other, name):
+ return False
+ return True
+
+ def __ne__(self, other):
+ return not self.__eq__(other)
+
+ def _validate(self, value):
+
+ if self._type is not None and not isinstance(value, self._type):
+ raise ValidationError(errornames.WrongType, value, self._type)
+
+ if self.constraint is not None and not self.constraint(value):
+ raise ValidationError(errornames.ConstraintNotSatisfied, value)
+
+
+class Container(Field):
+
+ def _validate(self, value):
+ super(Container, self)._validate(value)
+
+ if not hasattr(value, '__contains__'):
+ try:
+ iter(value)
+ except:
+ raise ValidationError(errornames.NotAContainer, value)
+
+
+class Iterable(Container):
+
+ def _validate(self, value):
+ super(Iterable, self)._validate(value)
+
+ # See if we can get an iterator for it
+ try:
+ iter(value)
+ except:
+ raise ValidationError(errornames.NotAnIterator, value)
+
+
+class Orderable(Field):
+ """Values of ordered fields can be sorted
+
+ They can be restricted to a range of values.
+ """
+
+ min = ValidatedProperty('min')
+ max = ValidatedProperty('max')
+
+ def __init__(self, min=None, max=None, default=None, **kw):
+
+ # Set min and max to None so that we can validate if
+ # one of the super methods invoke validation.
+ self.min = None
+ self.max = None
+
+ super(Orderable, self).__init__(**kw)
+
+ # Now really set min and max
+ self.min = min
+ self.max = max
+
+ # We've taken over setting default so it can be limited by min
+ # and max.
+ self.default = default
+
+
+ def _validate(self, value):
+ super(Orderable, self)._validate(value)
+
+ if self.min is not None and value < self.min:
+ raise ValidationError(errornames.TooSmall, value, self.min)
+
+ if self.max is not None and value > self.max:
+ raise ValidationError(errornames.TooBig, value, self.max)
+
+_int_types = int, long
+class MinMaxLen(Field):
+ min_length = 0
+ max_length = None
+
+ def __init__(self, min_length=0, max_length=None, **kw):
+ self.min_length = min_length
+ self.max_length = max_length
+ super(MinMaxLen, self).__init__(**kw)
+
+ def _validate(self, value):
+ super(MinMaxLen, self)._validate(value)
+
+ if self.min_length is not None and len(value) < self.min_length:
+ raise ValidationError(errornames.TooShort, value, self.min_length)
+
+ if self.max_length is not None and len(value) > self.max_length:
+ raise ValidationError(errornames.TooLong, value, self.max_length)
+
+
+class ValueSet(Field):
+
+ def allowed_values(self, values):
+ # Reset current value so it doesn't hose validation
+ if not values:
+ return
+
+ old_allowed = getattr(self, 'allowed_values', None)
+ self.allowed_values = None
+
+ for value in values:
+
+ try:
+ self.validate(value)
+ except:
+ # restore the old value
+ self.allowed_values = old_allowed
+ raise
+
+ allowed_values = ValidatedProperty('allowed_values', allowed_values)
+
+ def __init__(self, allowed_values=None, default=None, **kw):
+
+ # Set allowed_values to None so that we can validate if
+ # one of the super methods invoke validation.
+ self.__dict__['allowed_values'] = None
+ super(ValueSet, self).__init__(**kw)
+ if allowed_values is not None:
+ self.allowed_values = allowed_values
+
+ # We've taken over setting default so it can be limited by min
+ # and max.
+ self.default = default
+
+ def _validate(self, value):
+ super(ValueSet, self)._validate(value)
+
+ if self.allowed_values:
+ if not value in self.allowed_values:
+ raise ValidationError(errornames.InvalidValue, value,
+ self.allowed_values)
+
+
+class Text(MinMaxLen, ValueSet):
+ """A field containing text used for human discourse."""
+ _type = unicode
+
+
+class TextLine(Text):
+ """A Text field with no newlines."""
+
+ def constraint(self, value):
+ # XXX we should probably use a more general definition of newlines
+ return '\n' not in value
+
+
+class Bool(Field):
+ """A field representing a Bool."""
+
+ try:
+ if type(True) is int:
+ # If we have True and it's an int, then pretend we're 2.2.0.
+ raise NameError("True")
+ except NameError:
+ # Pre booleans
+ _type = int
+ else:
+ _type = bool
+
+class Int(ValueSet, Orderable):
+ """A field representing a Integer."""
+ _type = int, long
=== Zope3/src/zope/schema/_field.py 1.1 => 1.2 ===
--- /dev/null Wed Dec 25 09:15:51 2002
+++ Zope3/src/zope/schema/_field.py Wed Dec 25 09:15:20 2002
@@ -0,0 +1,162 @@
+##############################################################################
+#
+# Copyright (c) 2002 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""
+$Id$
+"""
+__metaclass__ = type
+
+from zope.interface.implements import implements
+
+from zope.schema.interfaces import ValidationError
+from zope.schema.errornames import WrongContainedType
+
+from zope.schema.interfaces import IField, IContainer, IIterable, IOrderable
+from zope.schema.interfaces import IMinMaxLen, IValueSet, IText, ITextLine
+from zope.schema.interfaces import IBool, IInt, IBytes, IBytesLine, IFloat
+from zope.schema.interfaces import IDatetime, ISequence, ITuple, IList, IDict
+
+from zope.schema._bootstrapfields import Field, Container, Iterable, Orderable, MinMaxLen
+from zope.schema._bootstrapfields import ValueSet, Text, TextLine, Bool, Int
+from zope.schema.fieldproperty import FieldProperty
+from datetime import datetime
+
+# Fix up bootstrap field types
+Field.title = FieldProperty(IField['title'])
+Field.description = FieldProperty(IField['description'])
+Field.required = FieldProperty(IField['required'])
+Field.readonly = FieldProperty(IField['readonly'])
+# Default is already taken care of
+implements(Field, IField)
+
+implements(Container, IContainer)
+implements(Iterable, IIterable)
+implements(Orderable, IOrderable)
+
+MinMaxLen.min_length = FieldProperty(IMinMaxLen['min_length'])
+MinMaxLen.max_length = FieldProperty(IMinMaxLen['max_length'])
+implements(MinMaxLen, IMinMaxLen)
+
+implements(ValueSet, IValueSet)
+
+implements(Text, IText)
+implements(TextLine, ITextLine)
+implements(Bool, IBool)
+implements(Int, IInt)
+
+class Bytes(MinMaxLen, ValueSet):
+ __doc__ = IBytes.__doc__
+ __implements__ = IBytes
+
+ _type = str
+
+class BytesLine(Bytes):
+ """A Text field with no newlines."""
+
+ __implements__ = IBytesLine
+
+ def constraint(self, value):
+ # XXX we should probably use a more general definition of newlines
+ return '\n' not in value
+
+
+class Float(ValueSet, Orderable):
+ __doc__ = IFloat.__doc__
+ __implements__ = IFloat
+ _type = float
+
+class Datetime(ValueSet, Orderable):
+ __doc__ = IDatetime.__doc__
+ __implements__ = IDatetime
+ _type = datetime
+
+def _validate_sequence(value_types, value, errors=None):
+ if errors is None:
+ errors = []
+
+ if value_types is None:
+ return errors
+
+ for item in value:
+ error = None
+ for t in value_types:
+ try:
+ t.validate(item)
+ except ValidationError, error:
+ pass
+ else:
+ # We validated, so clear error (if any) and done with
+ # this value
+ error = None
+ break
+
+ if error is not None:
+ errors.append(error)
+
+ return errors
+
+
+class Sequence(MinMaxLen, Iterable):
+ __doc__ = ISequence.__doc__
+ value_types = FieldProperty(ISequence['value_types'])
+
+ def __init__(self, value_types=None, **kw):
+ super(Sequence, self).__init__(**kw)
+ self.value_types = value_types
+
+ def _validate(self, value):
+ super(Sequence, self)._validate(value)
+ try:
+ errors = _validate_sequence(self.value_types, value)
+ if errors:
+ raise ValidationError(WrongContainedType, errors)
+
+ finally:
+ errors = None
+
+class Tuple(Sequence):
+ """A field representing a Tuple."""
+ __implements__ = ITuple
+ _type = tuple
+
+class List(Sequence):
+ """A field representing a List."""
+ __implements__ = IList
+ _type = list
+
+class Dict(MinMaxLen, Iterable):
+ """A field representing a Dict."""
+ __implements__ = IDict
+ _type = dict
+ key_types = FieldProperty(IDict['key_types'])
+ value_types = FieldProperty(IDict['value_types'])
+
+ def __init__(self, key_types=None, value_types=None, **kw):
+ super(Dict, self).__init__(**kw)
+ self.key_types = key_types
+ self.value_types = value_types
+
+ def _validate(self, value):
+ super(Dict, self)._validate(value)
+ errors = []
+ try:
+ if self.value_types:
+ errors = _validate_sequence(self.value_types, value.values(),
+ errors)
+ errors = _validate_sequence(self.key_types, value, errors)
+
+ if errors:
+ raise ValidationError(WrongContainedType, errors)
+
+ finally:
+ errors = None
=== Zope3/src/zope/schema/_schema.py 1.1 => 1.2 ===
--- /dev/null Wed Dec 25 09:15:51 2002
+++ Zope3/src/zope/schema/_schema.py Wed Dec 25 09:15:20 2002
@@ -0,0 +1,67 @@
+##############################################################################
+#
+# Copyright (c) 2002 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""
+$Id$
+"""
+from zope.interface import Interface
+from zope.schema.interfaces import ValidationError, ValidationErrorsAll
+
+def getFields(schema):
+ """Get all fields on a schema.
+ """
+ from zope.schema.interfaces import IField
+ fields = {}
+ for name in schema.names(1):
+ attr = schema.getDescriptionFor(name)
+ if IField.isImplementedBy(attr):
+ fields[name] = attr
+ return fields
+
+def getFieldsInOrder(schema,
+ _fieldsorter=lambda x, y: cmp(x[1].order, y[1].order)):
+ """Get a list of (name, value) tuples in native schema order.
+ """
+ fields = getFields(schema).items()
+ fields.sort(_fieldsorter)
+ return fields
+
+# validate functions either return silently, or raise a ValidationError
+# or in case of the validate*All functions, a ValidationErrosAll exception.
+# this should somehow be stated in an interface.
+
+def validateMapping(schema, values):
+ """Pass in field values in mapping and validate whether they
+ conform to schema. Stop at first error.
+ """
+ from zope.schema.interfaces import IField
+ for name in schema.names(1):
+ attr = schema.getDescriptionFor(name)
+ if IField.isImplementedBy(attr):
+ attr.validate(values.get(name))
+
+def validateMappingAll(schema, values):
+ """Pass in field values in mapping and validate whether they
+ conform to schema.
+ """
+ errors = []
+ from zope.schema.interfaces import IField
+ for name in schema.names(1):
+ attr = schema.getDescriptionFor(name)
+ if IField.isImplementedBy(attr):
+ try:
+ attr.validate(values.get(name))
+ except ValidationError, e:
+ errors.append((name, e))
+ if errors:
+ raise ValidationErrorsAll, errors
=== Zope3/src/zope/schema/errornames.py 1.1 => 1.2 ===
--- /dev/null Wed Dec 25 09:15:51 2002
+++ Zope3/src/zope/schema/errornames.py Wed Dec 25 09:15:20 2002
@@ -0,0 +1,42 @@
+##############################################################################
+#
+# Copyright (c) 2002 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""Common Schema Error Names
+
+$Id$
+"""
+WrongType = u"WrongType"
+
+RequiredMissing = u'RequiredMissing'
+
+RequiredEmptyStr = u'RequiredEmptyString'
+
+TooBig = u'TooBig'
+
+TooSmall = u'TooSmall'
+
+TooLong = u'TooLong'
+
+TooShort = u'TooShort'
+
+InvalidValue = u'InvalidValue'
+
+TooManyDecimals = u'TooManyDecimals'
+
+WrongContainedType = u"WrongContainedType"
+
+ConstraintNotSatisfied = u'ConstraintNotSatisfied'
+
+NotAContainer = u'NotAContainer'
+
+NotAnIterator = u'NotAnIterator'
=== Zope3/src/zope/schema/fieldproperty.py 1.1 => 1.2 ===
--- /dev/null Wed Dec 25 09:15:51 2002
+++ Zope3/src/zope/schema/fieldproperty.py Wed Dec 25 09:15:20 2002
@@ -0,0 +1,61 @@
+##############################################################################
+#
+# Copyright (c) 2002 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""
+$Id$
+"""
+
+__metaclass__ = type
+
+_marker = object()
+
+class FieldProperty:
+ """Computed attributes based on schema fields
+
+ Field properties provide default values, data validation and error messages
+ based on data found in field meta-data.
+
+ Note that FieldProperties cannot be used with slots. They can only
+ be used for attributes stored in instance dictionaries.
+
+ """
+
+ def __init__(self, field, name=None):
+ if name is None:
+ name = field.__name__
+
+ self.__field = field
+ self.__name = name
+
+ def __get__(self, inst, klass):
+ if inst is None:
+ return self
+
+ value = inst.__dict__.get(self.__name, _marker)
+ if value is _marker:
+ field = self.__field.bind(inst)
+ value = getattr(field, 'default', _marker)
+ if value is _marker:
+ raise AttributeError, self.__name
+
+ return value
+
+ def __set__(self, inst, value):
+ field = self.__field.bind(inst)
+ field.validate(value)
+ inst.__dict__[self.__name] = value
+
+ def __getattr__(self, name):
+ return getattr(self.__field, name)
+
+__doc__ = FieldProperty.__doc__ + __doc__
=== Zope3/src/zope/schema/interfaces.py 1.1 => 1.2 ===
--- /dev/null Wed Dec 25 09:15:51 2002
+++ Zope3/src/zope/schema/interfaces.py Wed Dec 25 09:15:20 2002
@@ -0,0 +1,363 @@
+##############################################################################
+#
+# Copyright (c) 2002 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""Validation Exceptions
+
+$Id$
+"""
+
+class StopValidation(Exception):
+ """This exception is raised, if the validation is done for sure early.
+ Note that this exception should be always caught, since it is just a
+ way for the validator to save time."""
+ pass
+
+
+class ValidationError(Exception):
+ """If some check during the Validation process fails, this exception is
+ raised."""
+
+ def __cmp__(self, other):
+ return cmp(self.args, other.args)
+
+ def __repr__(self):
+ return ' '.join(map(str, self.args))
+
+# XXX YAGNI, this is doomed. ;)
+
+class ErrorContainer(Exception):
+ """ """
+
+ def __init__(self, errors):
+ Exception.__init__(self)
+ self.errors = errors
+
+ def __len__(self):
+ return len(self.errors)
+
+ def __getitem__(self, key):
+ return self.errors[key]
+
+ def __iter__(self):
+ return iter(self.errors)
+
+
+class ValidationErrorsAll(ErrorContainer):
+ """This is a collection error that contains all exceptions that occured
+ during the validation process."""
+
+
+class ConversionErrorsAll(ErrorContainer):
+ """This is a collection error that contains all exceptions that occured
+ during the conversion process."""
+
+
+
+"""These are the interfaces for the common fields.
+
+$Id$
+"""
+from zope.interface import Interface
+
+from zope.schema._bootstrapfields \
+ import Field, Text, TextLine, Bool, Int, Container, Iterable
+
+class IField(Interface):
+ """Basic Schema Field Interface.
+
+ Fields are used for Interface specifications. They at least provide
+ a title, description and a default value. You can also
+ specify if they are required and/or readonly.
+
+ The Field Interface is also used for validation and specifying
+ constraints.
+
+ We want to make it possible for a IField to not only work
+ on its value but also on the object this value is bound to.
+ This enables a Field implementation to perform validation
+ against an object which also marks a certain place.
+
+ Note that many fields need information about the object
+ containing a field. For example, when validating a value to be
+ set as an object attribute, it may be necessary for the field to
+ introspect the object's state. This means that the field needs to
+ have access to the object when performing validation.
+
+ We haven't really decided on the best way to approach providing
+ access to objects in field methods and properties. We've thought
+ of three approaches:
+
+ 1. Always pass the object:
+
+ field.validate(value, object)
+
+ 2. Bind the field to the object with a context wrapper:
+
+ field = ContextWrapper(field, object)
+ field.validate(value)
+
+ 3. Provide a specialized binding protocol:
+
+ bound = field.bind(object)
+ bound.validate(value)
+
+ Options 2 and 3 allow us to use properties, but require an extra
+ binding step.
+
+ Option 1 and 3 will require a significant refactoring.
+
+ Option 2 requires us to make field methods, or at least the
+ validate method into ContextMethods, which is a bit intrusive.
+
+ For now, we will use option 3.
+
+ """
+
+ def bind(object):
+ """return a copy of this field which is bound to an object.
+
+ The copy of the Field will have the 'context' attribute set
+ to 'object'. This way a Field can implement more complex
+ checks involving the object and its location.
+
+ Many fields don't need to be bound. Only fields that condition
+ validation or properties on an object containing the field
+ need to be bound.
+ """
+
+ title = TextLine(
+ title=u"Title",
+ description=u"A short summary or label",
+ default=u"",
+ required=False,
+ )
+
+ description = Text(
+ title=u"Description",
+ description=u"A description of the field",
+ default=u"",
+ required=False,
+ )
+
+ required = Bool(
+ title=u"Required",
+ description=(
+ u"tells whether a field requires its value to exist."),
+ default=True)
+
+ readonly = Bool(
+ title=u"Read Only",
+ description=u"Read-only.", # XXX what is this?
+ required=False,
+ default=False)
+
+ default = Field(
+ title=u"default field value if no value is present",
+ description=u"""The field default value may be None or a legal
+ field value"""
+ )
+
+ def constraint(value):
+ u"""check a customized contraint on the value.
+
+ You can implement this method with your Field to
+ require a certain constraint. This relaxes the need
+ to inherit/subclass a Field you to add a simple contraint.
+ Returns true if the given value is within the Field's contraint.
+ """
+
+ def validate(value):
+ u"""Validate that the given value is a valid field value.
+
+ Returns nothing but raises an error if the value is invalid.
+ It checks everything specific to a Field and also checks
+ with the additional constraint.
+ """
+
+ order = Int(
+ title=u"Field Order",
+ description=u"""\
+ The order attribute can be used to determine the order in
+ which fields in a schema were defined. If one field is created
+ after another (in the same thread), its order will be
+ greater.
+
+ (Fields in separate threads could have the same order.)
+ """,
+ required=True,
+ readonly=True,
+ )
+
+
+class IIterable(IField):
+ u"""Fields with a value that can be iterated over.
+
+ The value needs to follow the python __iter__ protocol.
+ """
+
+class IContainer(IField):
+ u"""Fields whose value allows an 'x in value' check.
+
+ The Value needs to have a conventional __contains__ method.
+ """
+
+class IOrderable(IField):
+ u"""a Field requiring its value to be orderable.
+
+ The value needs to have a conventional __cmp__ method.
+ """
+
+class ILen(IField):
+ u"""a Field requiring its value to have a length.
+
+ The value needs to have a conventional __len__ method.
+ """
+
+class IMinMax(IOrderable):
+ u"""a Field requiring its value to be between min and max.
+
+ This also means that the value needs to support the IOrderable interface.
+ """
+
+ min = Field(
+ title=u"Start of the range",
+ required=False,
+ default=None
+ )
+
+ max = Field(
+ title=u"End of the range (excluding the value itself)",
+ required=False,
+ default=None
+ )
+
+
+class IMinMaxLen(ILen):
+ u"""a Field requiring the length of its value to be within a range"""
+
+ min_length = Int(
+ title=u"Minimum length",
+ description=u"""\
+ Value after whitespace processing cannot have less than
+ min_length characters. If min_length is None, there is
+ no minimum.
+ """,
+ required=False,
+ min=0, # needs to be a positive number
+ default=0)
+
+ max_length = Int(
+ title=u"Maximum length",
+ description=u"""\
+ Value after whitespace processing cannot have greater
+ or equal than max_length characters. If max_length is
+ None, there is no maximum.""",
+ required=False,
+ min=0, # needs to be a positive number
+ default=None)
+
+class IValueSet(IField):
+ u"""Field whose value is contained in a predefined set"""
+
+ allowed_values = Container(
+ title=u"Allowed Values",
+ description=u"""\
+ Only values specified here can be values of this field.
+ If the list is empty, then there are no further
+ restictions.""",
+ required=False)
+
+class IBool(IField):
+ u"""a Boolean Field."""
+
+class IBytes(IMinMaxLen, IValueSet, IIterable):
+ u"""a Field containing a byte string (like the python str).
+
+ The value might be contrained to be with length limits, or
+ be within a set of values.
+ """
+
+class IBytesLine(IBytes):
+ u"""a Field containing a byte string without newlines."""
+
+class IText(IMinMaxLen, IValueSet, IIterable):
+ u"""a Field containing a unicode string."""
+
+class ITextLine(IText):
+ u"""a Field containing a unicode string without newlines."""
+
+class IInt(IMinMax, IValueSet):
+ u"""a Field containing an Integer Value."""
+
+class IFloat(IMinMax, IValueSet):
+ u"""a Field containing a Float."""
+
+class IDatetime(IMinMax, IValueSet):
+ u"""a Field containing a DateTime."""
+
+def _fields(values):
+ for value in values:
+ if not IField.isImplementedBy(value):
+ return False
+ return True
+
+class ISequence(IMinMaxLen, IIterable):
+ u"""a Field containing a Sequence value.
+
+ The Value must be iterable and may have a min_length/max_length.
+ """
+
+ value_types = Iterable(
+ title=u"Value Types",
+ description=(
+ u"""\
+ If set to a non-empty value, field value items must conform to one
+ of the given types, which are expressed via fields.
+ """),
+ required=False,
+ constraint=_fields,
+ )
+
+class ITuple(ISequence):
+ u"""a Field containing a conventional tuple."""
+
+class IList(ISequence):
+ u"""a Field containing a conventional list."""
+
+class IDict(IMinMaxLen, IIterable):
+ u"""a Field containing a conventional dict.
+
+ the key_types and value_types field allow specification
+ of restrictions for the dict.
+ """
+
+ key_types = Iterable(
+ title=u"Value Types",
+ description=u"""\
+ If set to a non-empty value, field value keys must conform to one
+ of the given types, which are expressed via fields.
+ """,
+ constraint=_fields,
+ required=False,
+ )
+
+ value_types = Iterable(
+ title=u"Value Types",
+ description=(
+ u"""\
+ If set to a non-empty value, field value values must conform to one
+ of the given types, which are expressed via fields.
+ """),
+ constraint=_fields,
+ required=False,
+ )
=== Zope3/src/zope/schema/readme.stx 1.1 => 1.2 ===
--- /dev/null Wed Dec 25 09:15:51 2002
+++ Zope3/src/zope/schema/readme.stx Wed Dec 25 09:15:20 2002
@@ -0,0 +1,164 @@
+Zope 3 Schemas
+
+Table of contents
+
+ Short introduction
+
+ Long introduction
+
+ What is a schema, how does it compare to an interface and why
+ should you care?
+
+ What does a Schema look like
+
+ Where Schemas make sense: Providing views from Schemas
+
+ Issues to be solved
+
+
+Introduction
+
+ Schemas extend the notion of interfaces to descriptions of
+ Attributes rather than methods. Every Schema is an interface
+ and specifies the public fields of an object. A "Field" roughly
+ corresponds to an attribute of a python object. But a Field
+ provides space for a title and a description. It can also
+ constrain its value and provide a validation method. Besides
+ you can optionally specify characteristics such as its value
+ beeing readonly or not required.
+
+ Zope 3 schemas were born when Jim Fulton and Martijn Faassen
+ thought about Formulator for Zope3 and PropertySets while at
+ the Zope3 sprint at the Zope BBQ in Berlin. They realized that
+ if you strip all view logic from forms than you have something
+ to interfaces. And thus schemas were born.
+
+Dependencies
+
+ The Schema package only depends on the Interface package.
+
+Simple Usage
+
+ $ python
+ >>> class Bookmark:
+ ... def __init__(self, url):
+ ... self.url = url
+
+ >>> from Zope.Schema import TextLine, validateMapping
+ >>> from Interface import Interface
+ >>> class IBookmark(Interface):
+ ... url = TextLine(title=u'url of the bookmark')
+ ...
+
+ >>> obj = Bookmark(u'zope website', u'http://www.zope.org',
+ ... keywords=('web', 'python'))
+ >>> validateMapping(IBookmark, obj.__dict__)
+
+ the last statement validates that our object conforms
+ to the IBookmark Schema.
+
+What is a schema, how does it compare to an interface?
+
+ A schema is an extended interface which defines Fields.
+ You can validate that attributes of an object conform to their Fields
+ defined on the schema. With plain interfaces you can only validate
+ that Methods conform to their interface specification.
+
+ So interfaces and schemas refer to different aspects of an object
+ (resp. its code and state).
+
+ A Schema starts out like an interface but defines certain
+ Fields to which an object's attributes must conform. Let's look at
+ a stripped down example from the programmer's tutorial (chapter two)::
+
+ from Interface import Interface
+ from Zope.Schema import Interface, Text, TextLine
+
+ class IContact(Interface):
+ "Provides access to basic contact information."
+
+ first = TextLine(title=u"First name")
+ last = TextLine(title=u"Last name")
+ email = TextLine(title=u"Electronic mail address")
+ address = Text(title=u"Postal address")
+ postalCode = TextLine(title=u"Postal code",
+ constraint = re.compile("\d{5,5}(-\d{4,4})?$").match)
+
+ 'TextLine' is a field and expresses that an attribute is a single line
+ of unicode text. 'Text' expresses an arbitrary unicode ("text") object.
+ The most interesting part is the last attribute specification. It constrains
+ the 'postalCode' attribute to only have values that are US postal codes.
+
+ Now we want a class that aheres to the IContact Schema:
+
+ class Contact(Persistence.Persistent):
+ __implements__ = IContact
+
+ def __init__(self, first, last, email, address, pc):
+ self.first = first
+ self.last = last
+ self.email = email
+ self.address = address
+ self.postalCode = pc
+
+ Now you can see if an instance of 'Contact' actually implements
+ the schema:
+
+ from Zope.App.Schema import validateMapping
+ someone = contact('Tim','Roberts', 'tim@roberts', '','')
+ validateMapping(IContact, someone.__dict__)
+
+
+Issues to be solved
+
+ These issues were written up at Rotterdam Sprint (12/4/2002).
+
+ I18n
+
+ How i18n interferes with Schemas is not thought out. In an non-english
+ context we probably want to have titles and descriptions easily
+ translatable. The best idea so far is to use an attribute name
+ together with its surrounding namespace (Interface-name etc.)
+ as the canonical unique id used for looking up translations.
+
+ Example:
+
+ class book(Interface):
+ author = ITextLine()
+
+ To get to the And in view, while the widget or widget's messages
+ are constructed:
+
+ TranslatorService.getMessage('book.author.title','DE_DE')
+
+ Integration with Interfaces
+
+ How closely are Interfaces and Schema related? Should they be refactored
+ into one package? Currently the Interface package is outside the Zope
+ namespace and Schema is at Zope.Schema. Should this stay this way? Are
+ Schemas Zope-Specific?
+
+ Shouldn't the 'implements' function on the interface package be
+ named setImplements as opposed to getImplements? This way if you write
+
+ setImplements(klass, interface)
+
+ it is obvious what you mean (as opposed to implements(klass, interface)
+ which could mean *asking* if the klass implements the interface.
+
+ Clarify and clean up use cases
+
+ Some use cases are not easy to understand. A lot of them look like
+ features rather than use cases. The list of schema use cases
+ needs to be cleaned up and be (sometimes) more detailed.
+
+References
+
+ Use case list,
+ http://dev.zope.org/Wikis/DevSite/Projects/ComponentArchitecture/Zope3SchemasUseCases
+
+ documented interfaces, Zope/Schema/IField.py
+ Jim Fulton's Programmers Tutorial,
+ in CVS Docs/ZopeComponentArchitecture/PythonProgrammerTutorial/Chapter2
+
+