[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