[Zope3-checkins] CVS: Zope3/src/zope/schema - _bootstrapfields.py:1.17.2.1 _field.py:1.14.4.1 interfaces.py:1.17.2.1 vocabulary.py:1.2.2.1

Grégoire Weber zope@i-con.ch
Sun, 22 Jun 2003 10:24:27 -0400


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

Modified Files:
      Tag: cw-mail-branch
	_bootstrapfields.py _field.py interfaces.py vocabulary.py 
Log Message:
Synced up with HEAD

=== Zope3/src/zope/schema/_bootstrapfields.py 1.17 => 1.17.2.1 ===
--- Zope3/src/zope/schema/_bootstrapfields.py:1.17	Mon May 19 16:24:09 2003
+++ Zope3/src/zope/schema/_bootstrapfields.py	Sun Jun 22 10:23:51 2003
@@ -27,10 +27,10 @@
 class ValidatedProperty:
 
     def __init__(self, name, check=None):
-        self.__info = name, check
+        self._info = name, check
 
     def __set__(self, inst, value):
-        name, check = self.__info
+        name, check = self._info
         if value is not None:
             if check is not None:
                 check(inst, value)


=== Zope3/src/zope/schema/_field.py 1.14 => 1.14.4.1 ===
--- Zope3/src/zope/schema/_field.py:1.14	Mon May 12 06:02:41 2003
+++ Zope3/src/zope/schema/_field.py	Sun Jun 22 10:23:51 2003
@@ -18,7 +18,7 @@
 
 import warnings
 
-from zope.interface import classImplements
+from zope.interface import classImplements, implements
 from zope.interface.interfaces import IInterface
 
 from zope.schema.interfaces import ValidationError
@@ -62,19 +62,19 @@
 
 class SourceText(Text):
     __doc__ = ISourceText.__doc__
-    __implements__ = ISourceText
+    implements(ISourceText)
     _type = unicode
 
 class Bytes(Enumerated, MinMaxLen, Field):
     __doc__ = IBytes.__doc__
-    __implements__ = IBytes
+    implements(IBytes)
 
     _type = str
 
 class BytesLine(Bytes):
     """A Text field with no newlines."""
 
-    __implements__ = IBytesLine
+    implements(IBytesLine)
 
     def constraint(self, value):
         # XXX we should probably use a more general definition of newlines
@@ -83,7 +83,7 @@
 
 class Float(Enumerated, Orderable, Field):
     __doc__ = IFloat.__doc__
-    __implements__ = IFloat
+    implements(IFloat)
     _type = float
 
     def __init__(self, *args, **kw):
@@ -97,11 +97,11 @@
 
 class EnumeratedFloat(Float):
     __doc__ = IEnumeratedFloat.__doc__
-    __implements__ = IEnumeratedFloat
+    implements(IEnumeratedFloat)
 
 class Datetime(Enumerated, Orderable, Field):
     __doc__ = IDatetime.__doc__
-    __implements__ = IDatetime
+    implements(IDatetime)
     _type = datetime
 
     def __init__(self, *args, **kw):
@@ -115,11 +115,11 @@
 
 class EnumeratedDatetime(Datetime):
     __doc__ = IEnumeratedDatetime.__doc__
-    __implements__ = IEnumeratedDatetime
+    implements(IEnumeratedDatetime)
 
 class InterfaceField(Field):
     __doc__ = IInterfaceField.__doc__
-    __implements__ = IInterfaceField
+    implements(IInterfaceField)
 
     def _validate(self, value):
         super(InterfaceField, self)._validate(value)
@@ -154,7 +154,7 @@
 
 class Sequence(MinMaxLen, Iterable, Field):
     __doc__ = ISequence.__doc__
-    __implements__ = ISequence
+    implements(ISequence)
     value_types = FieldProperty(ISequence['value_types'])
 
     def __init__(self, value_types=None, **kw):
@@ -170,19 +170,19 @@
 
 class Tuple(Sequence):
     """A field representing a Tuple."""
-    __implements__ = ITuple
+    implements(ITuple)
     _type = tuple
 
 
 class List(Sequence):
     """A field representing a List."""
-    __implements__ = IList
+    implements(IList)
     _type = list
 
 
 class Dict(MinMaxLen, Iterable, Field):
     """A field representing a Dict."""
-    __implements__ = IDict
+    implements(IDict)
     _type = dict
     key_types   = FieldProperty(IDict['key_types'])
     value_types = FieldProperty(IDict['value_types'])


=== Zope3/src/zope/schema/interfaces.py 1.17 => 1.17.2.1 ===
--- Zope3/src/zope/schema/interfaces.py:1.17	Wed May 21 17:11:23 2003
+++ Zope3/src/zope/schema/interfaces.py	Sun Jun 22 10:23:51 2003
@@ -62,44 +62,19 @@
     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:
+    have access to the object when performing validation::
 
          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.
+        """Return a copy of this field which is bound to context.
 
         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.
+        checks involving the object's location/environment.
 
         Many fields don't need to be bound. Only fields that condition
         validation or properties on an object containing the field
@@ -398,27 +373,6 @@
         )
 
 
-class IAbstractVocabulary(Interface):
-    """Representation of a vocabulary.
-
-    At this most basic level, a vocabulary only need to support a test
-    for containment.  This can be implemented either by __contains__()
-    or by sequence __getitem__() (the later only being useful for
-    vocabularies which are intrinsically ordered).
-    """
-
-    def getQuery():
-        """Return an IVocabularyQuery object for this vocabulary.
-
-        Vocabularies which do not support query must return None.
-        """
-
-    def getTerm(value):
-        """Return the ITerm object for the term 'value'.
-
-        If 'value' is not a valid term, this method raises LookupError.
-        """
-
 class IVocabularyQuery(Interface):
     """Query object for a vocabulary.
 
@@ -447,6 +401,40 @@
         "value", "The value used to represent vocabulary term in a field.")
 
 
+class ITokenizedTerm(ITerm):
+    """Object representing a single value in a tokenized vocabulary.
+    """
+
+    token = Attribute(
+        "token",
+        """Token which can be used to represent the value on a stream.
+
+        The value of this attribute must be a non-empty 7-bit string.
+        Control characters are not allowed.
+        """)
+
+
+class IBaseVocabulary(Interface):
+    """Representation of a vocabulary.
+
+    At this most basic level, a vocabulary only need to support a test
+    for containment.  This can be implemented either by __contains__()
+    or by sequence __getitem__() (the later only being useful for
+    vocabularies which are intrinsically ordered).
+    """
+
+    def getQuery():
+        """Return an IVocabularyQuery object for this vocabulary.
+
+        Vocabularies which do not support query must return None.
+        """
+
+    def getTerm(value):
+        """Return the ITerm object for the term 'value'.
+
+        If 'value' is not a valid term, this method raises LookupError.
+        """
+
 class IIterableVocabulary(Interface):
     """Vocabulary which supports iteration over allowed values.
 
@@ -461,15 +449,21 @@
         """Return the number of valid terms, or sys.maxint."""
 
 
-class ISubsetVocabulary(Interface):
-    """Vocabulary which represents a subset of another vocabulary."""
+class IVocabulary(IIterableVocabulary, IBaseVocabulary):
+    """Vocabulary which is iterable."""
 
-    def getMasterVocabulary():
-        """Returns the vocabulary that this is a subset of."""
 
+class IVocabularyTokenized(Interface):
+    """Vocabulary that provides support for tokenized representation.
 
-class IVocabulary(IIterableVocabulary, IAbstractVocabulary):
-    """Vocabulary which is iterable."""
+    This interface must be used as a mix-in with IBaseVocabulary.
+
+    Terms returned from getTerm() and provided by iteration must
+    conform to ITokenizedTerm.
+    """
+
+    def getTermByToken(token):
+        """Return an ITokenizedTerm for the passed-in token."""
 
 
 class IVocabularyFieldMixin(Interface):
@@ -486,11 +480,11 @@
 
     vocabulary = Attribute(
         "vocabulary",
-        ("IAbstractVocabulary to be used, or None.\n"
+        ("IBaseVocabulary to be used, or None.\n"
          "\n"
          "If None, the vocabularyName should be used by an\n"
          "IVocabularyRegistry should be used to locate an appropriate\n"
-         "IAbstractVocabulary object."))
+         "IBaseVocabulary object."))
 
 
 class IVocabularyField(IVocabularyFieldMixin, IField):
@@ -501,16 +495,50 @@
     """
 
 
-class IVocabularyMultiField(IVocabularyFieldMixin, IField):
+class IVocabularyMultiField(IVocabularyFieldMixin, IMinMaxLen, IField):
+    # XXX This is really a base class used in the more specific
+    # IVocabulary*Field interfaces.
     """Field with a value containing selections from a vocabulary..
 
     The value for fields of this type need to support at least
     containment checks using 'in' and iteration.
+
+    The length constraint provided by IMinMaxLen constrains the number
+    of elements in the value.
+    """
+
+
+class IVocabularyBagField(IVocabularyMultiField):
+    """Field representing an unordered collection of values from a
+    vocabulary.
+
+    Specific values may be represented more than once.
+    """
+
+class IVocabularyListField(IVocabularyMultiField):
+    """Field representing an ordered collection of values from a
+    vocabulary.
+
+    Specific values may be represented more than once.
+    """
+
+class IVocabularySetField(IVocabularyMultiField):
+    """Field representing an unordered collection of values from a
+    vocabulary.
+
+    Specific values may be represented at most once.
+    """
+
+class IVocabularyUniqueListField(IVocabularyMultiField):
+    """Field representing an ordered collection of values from a
+    vocabulary.
+
+    Specific values may be represented at most once.
     """
 
 
 class IVocabularyRegistry(Interface):
-    """Registry that provides IAbstractVocabulary objects for specific fields.
+    """Registry that provides IBaseVocabulary objects for specific fields.
     """
 
     def get(object, name):


=== Zope3/src/zope/schema/vocabulary.py 1.2 => 1.2.2.1 ===
--- Zope3/src/zope/schema/vocabulary.py:1.2	Tue May 20 12:10:30 2003
+++ Zope3/src/zope/schema/vocabulary.py	Sun Jun 22 10:23:51 2003
@@ -14,11 +14,21 @@
 
 """Vocabulary support for schema."""
 
-from zope.schema import Field
+import copy
+
+from zope.interface.declarations import directlyProvides, implements
 from zope.schema import errornames
+from zope.schema import Field
+from zope.schema import MinMaxLen
+from zope.schema._bootstrapfields import ValidatedProperty
 from zope.schema.interfaces import ValidationError
-from zope.schema.interfaces import IAbstractVocabulary, IVocabularyRegistry
-from zope.schema.interfaces import IVocabularyField, IVocabularyMultiField
+from zope.schema.interfaces import IVocabularyRegistry
+from zope.schema.interfaces import IVocabularyField
+from zope.schema.interfaces import IVocabularyBagField, IVocabularyListField
+from zope.schema.interfaces import IVocabularySetField
+from zope.schema.interfaces import IVocabularyUniqueListField
+from zope.schema.interfaces import IVocabulary, IVocabularyTokenized
+from zope.schema.interfaces import ITokenizedTerm
 
 try:
     basestring  # new in Python 2.3
@@ -26,12 +36,21 @@
     from types import StringTypes as basestring
 
 
-class VocabularyField(Field):
-    """Field that adds support for use of an external vocabulary.
+class ContainerValidatedProperty(ValidatedProperty):
+
+    def __get__(self, inst, type=None):
+        name, check = self._info
+        try:
+            value = inst.__dict__[name]
+        except KeyError:
+            raise AttributeError, name
+        if value is not None:
+            value = copy.copy(value)
+        return value
+
+
+class BaseVocabularyField(Field):
 
-    The value is a single value from the vocabulary.
-    """
-    __implements__ = IVocabularyField
 
     def __init__(self, vocabulary=None, **kw):
         # set up the vocabulary:
@@ -39,10 +58,27 @@
             self.vocabulary = None
             self.vocabularyName = vocabulary
         else:
+            assert vocabulary is not None
             self.vocabulary = vocabulary
             self.vocabularyName = None
         # call the base initializer
-        super(VocabularyField, self).__init__(**kw)
+        super(BaseVocabularyField, self).__init__(**kw)
+
+    def bind(self, object):
+        clone = super(BaseVocabularyField, self).bind(object)
+        # get registered vocabulary/presentation if needed:
+        if clone.vocabulary is None:
+            vr = getVocabularyRegistry()
+            clone.vocabulary = vr.get(object, self.vocabularyName)
+        return clone
+
+
+class VocabularyField(BaseVocabularyField):
+    """Field that adds support for use of an external vocabulary.
+
+    The value is a single value from the vocabulary.
+    """
+    implements(IVocabularyField)
 
     def _validate(self, value):
         if self.vocabulary is None:
@@ -55,30 +91,172 @@
             raise ValidationError(errornames.ConstraintNotSatisfied,
                                   value)
 
-    def bind(self, object):
-        clone = super(VocabularyField, self).bind(object)
-        # get registered vocabulary/presentation if needed:
-        if clone.vocabulary is None:
-            vr = getVocabularyRegistry()
-            clone.vocabulary = vr.get(object, self.vocabularyName)
-        return clone
-
 
-class VocabularyMultiField(VocabularyField):
+class VocabularyMultiField(MinMaxLen, BaseVocabularyField):
     """Field that adds support for use of an external vocabulary.
 
     The value is a collection of values from the vocabulary.
+
+    This class cannot be used directly; a subclass must be used to
+    specify concrete behavior.
     """
-    __implements__ = IVocabularyMultiField
+
+    default = ContainerValidatedProperty("default")
+
+    def __init__(self, **kw):
+        if self.__class__ is VocabularyMultiField:
+            raise NotImplementedError(
+                "The VocabularyMultiField class cannot be used directly.")
+        if "default" not in kw and not kw.get("min_length"):
+            kw["default"] = []
+        super(VocabularyMultiField, self).__init__(**kw)
 
     def _validate(self, value):
         vocab = self.vocabulary
-        if vocab is None:
-            raise ValueError("can't validate value without vocabulary")
+        if value:
+            if vocab is None:
+                raise ValueError("can't validate value without vocabulary")
+            for v in value:
+                if v not in vocab:
+                    raise ValidationError(errornames.ConstraintNotSatisfied, v)
+        super(VocabularyMultiField, self)._validate(value)
+
+class UniqueElements(object):
+    """Mix-in class that checks that each contained element is unique."""
+
+    def _validate(self, value):
+        d = {}
         for v in value:
-            if v not in vocab:
-                raise ValidationError(errornames.ConstraintNotSatisfied, v)
+            if v in d:
+                raise ValidationError()
+            d[v] = v
+        super(UniqueElements, self)._validate(value)
+
+class VocabularyBagField(VocabularyMultiField):
+    implements(IVocabularyBagField)
+    __doc__ = IVocabularyBagField.__doc__
+
+class VocabularyListField(VocabularyMultiField):
+    implements(IVocabularyListField)
+    __doc__ = IVocabularyListField.__doc__
+
+class VocabularySetField(UniqueElements, VocabularyMultiField):
+    implements(IVocabularySetField)
+    __doc__ = IVocabularySetField.__doc__
+
+class VocabularyUniqueListField(UniqueElements, VocabularyMultiField):
+    implements(IVocabularyUniqueListField)
+    __doc__ = IVocabularyUniqueListField.__doc__
+
+
+# simple vocabularies performing enumerated-like tasks
+
+class SimpleTerm:
+    """Simple tokenized term used by SimpleVocabulary."""
+
+    implements(ITokenizedTerm)
+
+    def __init__(self, value, token=None):
+        """Create a term for value and token. If token is omitted,
+        str(value) is used for the token
+        """
+        self.value = value
+        if token is None:
+            token = value
+        self.token = str(token)
+
+class SimpleVocabulary(object):
+    """Vocabulary that works from a sequence of terms."""
+
+    implements(IVocabulary, IVocabularyTokenized)
+
+    def __init__(self, terms, *interfaces):
+        """Initialize the vocabulary given a list of terms.
+
+        The vocabulary keeps a reference to the list of terms passed
+        in; it should never be modified while the vocabulary is used.
+
+        One or more interfaces may also be provided so that alternate
+        widgets may be bound without subclassing.
+        """
+        self.by_value = {}
+        self.by_token = {}
+        self._terms = terms
+        for term in self._terms:
+            self.by_value[term.value] = term
+            self.by_token[term.token] = term
+        assert len(self.by_value) == len(self.by_token) == len(terms), \
+               'Supplied vocabulary values resulted in duplicate term tokens'
+        if interfaces:
+            directlyProvides(self, *interfaces)
+
+    def fromItems(cls, items, *interfaces):
+        """Construct a vocabulary from a list of (token, value) pairs.
+
+        The order of the items is preserved as the order of the terms
+        in the vocabulary.  Terms are created by calling the class
+        method createTerm() with the pair (value, token).
+
+        One or more interfaces may also be provided so that alternate
+        widgets may be bound without subclassing.
+        """
+        terms = [cls.createTerm((value, token)) for (token, value) in items]
+        return cls(terms, *interfaces)
+    fromItems = classmethod(fromItems)
+
+    def fromValues(cls, values, *interfaces):
+        """Construct a vocabulary from a simple list.
+
+        Values of the list become both the tokens and values of the
+        terms in the vocabulary.  The order of the values is preserved
+        as the order of the terms in the vocabulary.  Tokens are
+        created by calling the class method createTerm() with the
+        value as the only parameter.
+
+        One or more interfaces may also be provided so that alternate
+        widgets may be bound without subclassing.
+        """
+        terms = [cls.createTerm(value) for value in values]
+        return cls(terms, *interfaces)
+    fromValues = classmethod(fromValues)
+
+    def createTerm(cls, data):
+        """Create a single term from data.
+
+        Subclasses may override this with a class method that creates
+        a term of the appropriate type from the single data argument.
+        """
+        if isinstance(data, tuple):
+            return SimpleTerm(*data)
+        else:
+            return SimpleTerm(data)
+    createTerm = classmethod(createTerm)
+
+    def __contains__(self, value):
+        return value in self.by_value
+
+    def getQuery(self):
+        return None
+
+    def getTerm(self, value):
+        try:
+            return self.by_value[value]
+        except KeyError:
+            raise LookupError(value)
 
+    def getTermByToken(self, token):
+        try:
+            return self.by_token[token]
+        except KeyError:
+            raise LookupError(token)
+
+    def __iter__(self):
+        return iter(self._terms)
+
+    def __len__(self):
+        return len(self.by_value)
+
+# registry code
 
 class VocabularyRegistryError(LookupError):
     def __init__(self, name):
@@ -91,7 +269,7 @@
 
 class VocabularyRegistry(object):
     __slots__ = '_map',
-    __implements__ = IVocabularyRegistry
+    implements(IVocabularyRegistry)
 
     def __init__(self):
         self._map = {}
@@ -105,7 +283,6 @@
 
     def register(self, name, factory):
         self._map[name] = factory
-
 
 _vocabularies = None