[Zope3-checkins] CVS: Zope3/src/zope/schema - __init__.py:1.1.2.1 _bootstrapfields.py:1.1.2.1 _field.py:1.1.2.1 _schema.py:1.1.2.1 errornames.py:1.1.2.1 fieldproperty.py:1.1.2.1 interfaces.py:1.1.2.1 readme.stx:1.1.2.1

Jim Fulton jim@zope.com
Mon, 23 Dec 2002 14:33:15 -0500


Update of /cvs-repository/Zope3/src/zope/schema
In directory cvs.zope.org:/tmp/cvs-serv19908/zope/schema

Added Files:
      Tag: NameGeddon-branch
	__init__.py _bootstrapfields.py _field.py _schema.py 
	errornames.py fieldproperty.py interfaces.py readme.stx 
Log Message:
Initial renaming before debugging

=== Added File Zope3/src/zope/schema/__init__.py ===
##############################################################################
#
# 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: __init__.py,v 1.1.2.1 2002/12/23 19:33:12 jim Exp $
"""

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


=== Added File Zope3/src/zope/schema/_bootstrapfields.py ===
##############################################################################
#
# 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: _bootstrapfields.py,v 1.1.2.1 2002/12/23 19:33:12 jim Exp $
"""
__metaclass__ = type

from zope.interface.element import Attribute
from zope.interface.implements import visitImplements

from zope.schema.interfaces import StopValidation, ValidationError
import zope.schema.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


=== Added File Zope3/src/zope/schema/_field.py ===
##############################################################################
#
# 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: _field.py,v 1.1.2.1 2002/12/23 19:33:12 jim Exp $
"""
__metaclass__ = type

from zope.interface.implements import implements

from zope.schema.interfaces import ValidationError
import zope.schema.errornames

import zope.schema.interfaces
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.IField['title'])
Field.description = FieldProperty(IField.IField['description'])
Field.required    = FieldProperty(IField.IField['required'])
Field.readonly    = FieldProperty(IField.IField['readonly'])
# Default is already taken care of
implements(Field, IField.IField)

implements(Container, IField.IContainer)
implements(Iterable, IField.IIterable)
implements(Orderable, IField.IOrderable)

MinMaxLen.min_length = FieldProperty(IField.IMinMaxLen['min_length'])
MinMaxLen.max_length = FieldProperty(IField.IMinMaxLen['max_length'])
implements(MinMaxLen, IField.IMinMaxLen)

implements(ValueSet, IField.IValueSet)

implements(Text, IField.IText)
implements(TextLine, IField.ITextLine)
implements(Bool, IField.IBool)
implements(Int, IField.IInt)
            
class Bytes(MinMaxLen, ValueSet):
    __doc__ = IField.IBytes.__doc__
    __implements__ = IField.IBytes
    
    _type = str

class BytesLine(Bytes):
    """A Text field with no newlines."""

    __implements__ = IField.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__ = IField.IFloat.__doc__
    __implements__ = IField.IFloat
    _type = float

class Datetime(ValueSet, Orderable):
    __doc__ = IField.IDatetime.__doc__
    __implements__ = IField.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__ = IField.ISequence.__doc__
    value_types = FieldProperty(IField.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(ErrorNames.WrongContainedType, errors)
                
        finally:
            errors = None

class Tuple(Sequence):
    """A field representing a Tuple."""
    __implements__ = IField.ITuple
    _type = tuple

class List(Sequence):
    """A field representing a List."""
    __implements__ = IField.IList
    _type = list

class Dict(MinMaxLen, Iterable):
    """A field representing a Dict."""
    __implements__ = IField.IDict
    _type = dict
    key_types   = FieldProperty(IField.IDict['key_types'])
    value_types = FieldProperty(IField.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(ErrorNames.WrongContainedType, errors)
                
        finally:
            errors = None



=== Added File Zope3/src/zope/schema/_schema.py ===
##############################################################################
#
# 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: _schema.py,v 1.1.2.1 2002/12/23 19:33:13 jim Exp $
"""
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



=== Added File Zope3/src/zope/schema/errornames.py ===
##############################################################################
#
# 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: errornames.py,v 1.1.2.1 2002/12/23 19:33:13 jim Exp $
"""
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'


=== Added File Zope3/src/zope/schema/fieldproperty.py ===
##############################################################################
#
# 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: fieldproperty.py,v 1.1.2.1 2002/12/23 19:33:13 jim Exp $
"""

__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__



=== Added File Zope3/src/zope/schema/interfaces.py ===
##############################################################################
#
# 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: interfaces.py,v 1.1.2.1 2002/12/23 19:33:13 jim Exp $
"""

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: interfaces.py,v 1.1.2.1 2002/12/23 19:33:13 jim Exp $
"""
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,
        )


=== Added File Zope3/src/zope/schema/readme.stx ===
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