# -*- coding: utf-8 -*-
##############################################################################
# Copyright (c) 2002 Zope Foundation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (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 Fields
"""
__docformat__ = 'restructuredtext'
try:
from collections import abc
except ImportError: # pragma: no cover
# Python 2
import collections as abc
from datetime import datetime
from datetime import date
from datetime import timedelta
from datetime import time
import decimal
import re
from zope.interface import classImplements
from zope.interface import implementer
from zope.interface.interfaces import IInterface
from zope.schema.interfaces import IASCII
from zope.schema.interfaces import IASCIILine
from zope.schema.interfaces import IBaseVocabulary
from zope.schema.interfaces import IBool
from zope.schema.interfaces import IBytes
from zope.schema.interfaces import IBytesLine
from zope.schema.interfaces import IChoice
from zope.schema.interfaces import ICollection
from zope.schema.interfaces import IComplex
from zope.schema.interfaces import IContainer
from zope.schema.interfaces import IContextSourceBinder
from zope.schema.interfaces import IDate
from zope.schema.interfaces import IDatetime
from zope.schema.interfaces import IDecimal
from zope.schema.interfaces import IDict
from zope.schema.interfaces import IDottedName
from zope.schema.interfaces import IField
from zope.schema.interfaces import IFloat
from zope.schema.interfaces import IFromBytes
from zope.schema.interfaces import IFromUnicode
from zope.schema.interfaces import IFrozenSet
from zope.schema.interfaces import IId
from zope.schema.interfaces import IIterable
from zope.schema.interfaces import IInt
from zope.schema.interfaces import IIntegral
from zope.schema.interfaces import IInterfaceField
from zope.schema.interfaces import IList
from zope.schema.interfaces import IMinMaxLen
from zope.schema.interfaces import IMapping
from zope.schema.interfaces import IMutableMapping
from zope.schema.interfaces import IMutableSequence
from zope.schema.interfaces import INativeString
from zope.schema.interfaces import INativeStringLine
from zope.schema.interfaces import IObject
from zope.schema.interfaces import INumber
from zope.schema.interfaces import IPassword
from zope.schema.interfaces import IPythonIdentifier
from zope.schema.interfaces import IReal
from zope.schema.interfaces import IRational
from zope.schema.interfaces import ISet
from zope.schema.interfaces import ISequence
from zope.schema.interfaces import ISource
from zope.schema.interfaces import ISourceText
from zope.schema.interfaces import IText
from zope.schema.interfaces import ITextLine
from zope.schema.interfaces import ITime
from zope.schema.interfaces import ITimedelta
from zope.schema.interfaces import ITuple
from zope.schema.interfaces import IURI
from zope.schema.interfaces import ValidationError
from zope.schema.interfaces import InvalidValue
from zope.schema.interfaces import WrongType
from zope.schema.interfaces import WrongContainedType
from zope.schema.interfaces import NotUnique
from zope.schema.interfaces import NotAnInterface
from zope.schema.interfaces import InvalidURI
from zope.schema.interfaces import InvalidId
from zope.schema.interfaces import InvalidDottedName
from zope.schema.interfaces import ConstraintNotSatisfied
from zope.schema._bootstrapfields import Field
from zope.schema._bootstrapfields import Complex
from zope.schema._bootstrapfields import Container # API import for __init__
from zope.schema._bootstrapfields import Iterable
from zope.schema._bootstrapfields import Orderable
from zope.schema._bootstrapfields import Text
from zope.schema._bootstrapfields import TextLine
from zope.schema._bootstrapfields import Bool
from zope.schema._bootstrapfields import Int
from zope.schema._bootstrapfields import Integral
from zope.schema._bootstrapfields import Number
from zope.schema._bootstrapfields import Password
from zope.schema._bootstrapfields import Rational
from zope.schema._bootstrapfields import Real
from zope.schema._bootstrapfields import MinMaxLen
from zope.schema._bootstrapfields import _NotGiven
from zope.schema._bootstrapfields import Object
from zope.schema.fieldproperty import FieldProperty
from zope.schema.vocabulary import getVocabularyRegistry
from zope.schema.vocabulary import SimpleVocabulary
from zope.schema._compat import text_type
from zope.schema._compat import string_types
from zope.schema._compat import binary_type
from zope.schema._compat import PY3
from zope.schema._compat import make_binary
# 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
classImplements(Field, IField)
MinMaxLen.min_length = FieldProperty(IMinMaxLen['min_length'])
MinMaxLen.max_length = FieldProperty(IMinMaxLen['max_length'])
classImplements(Text, IText)
classImplements(TextLine, ITextLine)
classImplements(Password, IPassword)
classImplements(Bool, IBool)
classImplements(Iterable, IIterable)
classImplements(Container, IContainer)
classImplements(Number, INumber)
classImplements(Complex, IComplex)
classImplements(Real, IReal)
classImplements(Rational, IRational)
classImplements(Integral, IIntegral)
classImplements(Int, IInt)
classImplements(Object, IObject)
[docs]@implementer(ISourceText)
class SourceText(Text):
__doc__ = ISourceText.__doc__
_type = text_type
[docs]@implementer(IBytes, IFromUnicode, IFromBytes)
class Bytes(MinMaxLen, Field):
__doc__ = IBytes.__doc__
_type = binary_type
[docs] def fromUnicode(self, value):
""" See IFromUnicode.
"""
return self.fromBytes(make_binary(value))
def fromBytes(self, value):
self.validate(value)
return value
[docs]@implementer(INativeString, IFromUnicode, IFromBytes)
class NativeString(Text if PY3 else Bytes):
"""
A native string is always the type `str`.
In addition to :class:`~zope.schema.interfaces.INativeString`,
this implements :class:`~zope.schema.interfaces.IFromUnicode` and
:class:`~zope.schema.interfaces.IFromBytes`.
.. versionchanged:: 4.9.0
This is now a distinct type instead of an alias for either `Text` or `Bytes`,
depending on the platform.
"""
_type = str
if PY3:
def fromBytes(self, value):
value = value.decode('utf-8')
self.validate(value)
return value
[docs]@implementer(IASCII)
class ASCII(NativeString):
__doc__ = IASCII.__doc__
def _validate(self, value):
super(ASCII, self)._validate(value)
if not value:
return
if not max(map(ord, value)) < 128:
raise InvalidValue().with_field_and_value(self, value)
[docs]@implementer(IBytesLine)
class BytesLine(Bytes):
"""A `Bytes` field with no newlines."""
def constraint(self, value):
# TODO: we should probably use a more general definition of newlines
return b'\n' not in value
[docs]@implementer(INativeStringLine, IFromUnicode, IFromBytes)
class NativeStringLine(TextLine if PY3 else BytesLine):
"""
A native string is always the type `str`; this field excludes
newlines.
In addition to :class:`~zope.schema.interfaces.INativeStringLine`,
this implements :class:`~zope.schema.interfaces.IFromUnicode` and
:class:`~zope.schema.interfaces.IFromBytes`.
.. versionchanged:: 4.9.0
This is now a distinct type instead of an alias for either `TextLine`
or `BytesLine`, depending on the platform.
"""
_type = str
if PY3:
def fromBytes(self, value):
value = value.decode('utf-8')
self.validate(value)
return value
[docs]@implementer(IASCIILine)
class ASCIILine(ASCII):
__doc__ = IASCIILine.__doc__
def constraint(self, value):
# TODO: we should probably use a more general definition of newlines
return '\n' not in value
class InvalidFloatLiteral(ValueError, ValidationError):
"""Raised by Float fields."""
[docs]@implementer(IFloat)
class Float(Real):
"""
A field representing a native :class:`float` and implementing
:class:`zope.schema.interfaces.IFloat`.
The class :class:`zope.schema.Real` is a more general version,
accepting floats, integers, and fractions.
The :meth:`fromUnicode` method only accepts values that can be parsed
by the ``float`` constructor::
>>> from zope.schema._field import Float
>>> f = Float()
>>> f.fromUnicode("1")
1.0
>>> f.fromUnicode("125.6")
125.6
>>> f.fromUnicode("1+0j") # doctest: +IGNORE_EXCEPTION_DETAIL
Traceback (most recent call last):
...
InvalidFloatLiteral: Invalid literal for float(): 1+0j
>>> f.fromUnicode("1/2") # doctest: +IGNORE_EXCEPTION_DETAIL
Traceback (most recent call last):
...
InvalidFloatLiteral: invalid literal for float(): 1/2
>>> f.fromUnicode(str(2**31234) + '.' + str(2**256)) # doctest: +ELLIPSIS
inf
>>> f.fromUnicode("not a number") # doctest: +IGNORE_EXCEPTION_DETAIL
Traceback (most recent call last):
...
InvalidFloatLiteral: could not convert string to float: not a number
Likewise for :meth:`fromBytes`::
>>> from zope.schema._field import Float
>>> f = Float()
>>> f.fromBytes(b"1")
1.0
>>> f.fromBytes(b"125.6")
125.6
>>> f.fromBytes(b"1+0j") # doctest: +IGNORE_EXCEPTION_DETAIL
Traceback (most recent call last):
...
InvalidFloatLiteral: Invalid literal for float(): 1+0j
>>> f.fromBytes(b"1/2") # doctest: +IGNORE_EXCEPTION_DETAIL
Traceback (most recent call last):
...
InvalidFloatLiteral: invalid literal for float(): 1/2
>>> f.fromBytes((str(2**31234) + '.' + str(2**256)).encode('ascii')) # doctest: +ELLIPSIS
inf
>>> f.fromBytes(b"not a number") # doctest: +IGNORE_EXCEPTION_DETAIL
Traceback (most recent call last):
...
InvalidFloatLiteral: could not convert string to float: not a number
"""
_type = float
_unicode_converters = (float,)
_validation_error = InvalidFloatLiteral
class InvalidDecimalLiteral(ValueError, ValidationError):
"Raised by decimal fields"
[docs]@implementer(IDecimal)
class Decimal(Number):
"""
A field representing a native :class:`decimal.Decimal` and implementing
:class:`zope.schema.interfaces.IDecimal`.
The :meth:`fromUnicode` method only accepts values that can be parsed
by the ``Decimal`` constructor::
>>> from zope.schema._field import Decimal
>>> f = Decimal()
>>> f.fromUnicode("1")
Decimal('1')
>>> f.fromUnicode("125.6")
Decimal('125.6')
>>> f.fromUnicode("1+0j") # doctest: +IGNORE_EXCEPTION_DETAIL
Traceback (most recent call last):
...
InvalidDecimalLiteral: Invalid literal for Decimal(): 1+0j
>>> f.fromUnicode("1/2") # doctest: +IGNORE_EXCEPTION_DETAIL
Traceback (most recent call last):
...
InvalidDecimalLiteral: Invalid literal for Decimal(): 1/2
>>> f.fromUnicode(str(2**31234) + '.' + str(2**256)) # doctest: +ELLIPSIS
Decimal('2349...936')
>>> f.fromUnicode("not a number") # doctest: +IGNORE_EXCEPTION_DETAIL
Traceback (most recent call last):
...
InvalidDecimalLiteral: could not convert string to float: not a number
Likewise for :meth:`fromBytes`::
>>> from zope.schema._field import Decimal
>>> f = Decimal()
>>> f.fromBytes(b"1")
Decimal('1')
>>> f.fromBytes(b"125.6")
Decimal('125.6')
>>> f.fromBytes(b"1+0j") # doctest: +IGNORE_EXCEPTION_DETAIL
Traceback (most recent call last):
...
InvalidDecimalLiteral: Invalid literal for Decimal(): 1+0j
>>> f.fromBytes(b"1/2") # doctest: +IGNORE_EXCEPTION_DETAIL
Traceback (most recent call last):
...
InvalidDecimalLiteral: Invalid literal for Decimal(): 1/2
>>> f.fromBytes((str(2**31234) + '.' + str(2**256)).encode("ascii")) # doctest: +ELLIPSIS
Decimal('2349...936')
>>> f.fromBytes(b"not a number") # doctest: +IGNORE_EXCEPTION_DETAIL
Traceback (most recent call last):
...
InvalidDecimalLiteral: could not convert string to float: not a number
"""
_type = decimal.Decimal
_unicode_converters = (decimal.Decimal,)
_validation_error = InvalidDecimalLiteral
[docs]@implementer(IDatetime)
class Datetime(Orderable, Field):
__doc__ = IDatetime.__doc__
_type = datetime
def __init__(self, *args, **kw):
super(Datetime, self).__init__(*args, **kw)
[docs]@implementer(IDate)
class Date(Orderable, Field):
__doc__ = IDate.__doc__
_type = date
def _validate(self, value):
super(Date, self)._validate(value)
if isinstance(value, datetime):
raise WrongType(value, self._type, self.__name__).with_field_and_value(self, value)
[docs]@implementer(ITimedelta)
class Timedelta(Orderable, Field):
__doc__ = ITimedelta.__doc__
_type = timedelta
[docs]@implementer(ITime)
class Time(Orderable, Field):
__doc__ = ITime.__doc__
_type = time
class MissingVocabularyError(ValidationError,
ValueError,
LookupError):
"""Raised when a named vocabulary cannot be found."""
# Subclasses ValueError and LookupError for backwards compatibility
class InvalidVocabularyError(ValidationError,
ValueError,
TypeError):
"""Raised when the vocabulary is not an ISource."""
# Subclasses TypeError and ValueError for backwards compatibility
def __init__(self, vocabulary):
super(InvalidVocabularyError, self).__init__("Invalid vocabulary %r" % (vocabulary,))
[docs]@implementer(IChoice, IFromUnicode)
class Choice(Field):
"""Choice fields can have a value found in a constant or dynamic set of
values given by the field definition.
"""
def __init__(self, values=None, vocabulary=None, source=None, **kw):
"""Initialize object."""
if vocabulary is not None:
if (not isinstance(vocabulary, string_types)
and not IBaseVocabulary.providedBy(vocabulary)):
raise ValueError('vocabulary must be a string or implement '
'IBaseVocabulary')
if source is not None:
raise ValueError(
"You cannot specify both source and vocabulary.")
elif source is not None:
vocabulary = source
if (values is None and vocabulary is None):
raise ValueError(
"You must specify either values or vocabulary."
)
if values is not None and vocabulary is not None:
raise ValueError(
"You cannot specify both values and vocabulary."
)
self.vocabulary = None
self.vocabularyName = None
if values is not None:
self.vocabulary = SimpleVocabulary.fromValues(values)
elif isinstance(vocabulary, string_types):
self.vocabularyName = vocabulary
else:
if (not ISource.providedBy(vocabulary)
and not IContextSourceBinder.providedBy(vocabulary)):
raise InvalidVocabularyError(vocabulary)
self.vocabulary = vocabulary
# Before a default value is checked, it is validated. However, a
# named vocabulary is usually not complete when these fields are
# initialized. Therefore signal the validation method to ignore
# default value checks during initialization of a Choice tied to a
# registered vocabulary.
self._init_field = (bool(self.vocabularyName) or
IContextSourceBinder.providedBy(self.vocabulary))
super(Choice, self).__init__(**kw)
self._init_field = False
source = property(lambda self: self.vocabulary)
def _resolve_vocabulary(self, value):
# Find the vocabulary we should use, raising
# an exception if this isn't possible, and returning
# an ISource otherwise.
vocabulary = self.vocabulary
if IContextSourceBinder.providedBy(vocabulary) and self.context is not None:
vocabulary = vocabulary(self.context)
elif vocabulary is None and self.vocabularyName is not None:
vr = getVocabularyRegistry()
try:
vocabulary = vr.get(self.context, self.vocabularyName)
except LookupError:
raise MissingVocabularyError(
"Can't validate value without vocabulary named %r" % (self.vocabularyName,)
).with_field_and_value(self, value)
if not ISource.providedBy(vocabulary):
raise InvalidVocabularyError(vocabulary).with_field_and_value(self, value)
return vocabulary
[docs] def bind(self, context):
"""See zope.schema._bootstrapinterfaces.IField."""
clone = super(Choice, self).bind(context)
# Eagerly get registered vocabulary if needed;
# once that's done, just return it
vocabulary = clone.vocabulary = clone._resolve_vocabulary(None)
clone._resolve_vocabulary = lambda value: vocabulary
return clone
[docs] def fromUnicode(self, value):
""" See IFromUnicode.
"""
self.validate(value)
return value
def _validate(self, value):
# Pass all validations during initialization
if self._init_field:
return
super(Choice, self)._validate(value)
vocabulary = self._resolve_vocabulary(value)
if value not in vocabulary:
raise ConstraintNotSatisfied(value, self.__name__).with_field_and_value(self, value)
@implementer(IFromUnicode, IFromBytes)
class _StrippedNativeStringLine(NativeStringLine):
_invalid_exc_type = None
def fromUnicode(self, value):
v = value.strip()
# On Python 2, self._type is bytes, so we need to encode
# unicode down to ASCII bytes. On Python 3, self._type is
# unicode, but we don't want to allow non-ASCII values, to match
# Python 2 (our regexs would reject that anyway.)
try:
v = v.encode('ascii') # bytes
except UnicodeEncodeError:
raise self._invalid_exc_type(value).with_field_and_value(self, value)
if not isinstance(v, self._type):
v = v.decode('ascii')
self.validate(v)
return v
def fromBytes(self, value):
return self.fromUnicode(value.decode('ascii'))
_isuri = r"[a-zA-z0-9+.-]+:" # scheme
_isuri += r"\S*$" # non space (should be pickier)
_isuri = re.compile(_isuri).match
[docs]@implementer(IURI)
class URI(_StrippedNativeStringLine):
"""
URI schema field.
URIs can be validated from both unicode values and bytes values,
producing a native text string in both cases::
>>> from zope.schema import URI
>>> field = URI()
>>> field.fromUnicode(u' https://example.com ')
'https://example.com'
>>> field.fromBytes(b' https://example.com ')
'https://example.com'
.. versionchanged:: 4.8.0
Implement :class:`zope.schema.interfaces.IFromBytes`
"""
def _validate(self, value):
super(URI, self)._validate(value)
if _isuri(value):
return
raise InvalidURI(value).with_field_and_value(self, value)
# An identifier is a letter or underscore, followed by
# any number of letters, underscores, and digits.
_identifier_pattern = r'[a-zA-Z_]+\w*'
# The whole string must match to be an identifier
_is_identifier = re.compile('^' + _identifier_pattern + '$').match
_isdotted = re.compile(
# The start of the line, followed by an identifier,
'^' + _identifier_pattern
# optionally followed by .identifier any number of times
+ r"([.]" + _identifier_pattern + r")*"
# followed by the end of the line.
+ r"$").match
[docs]@implementer(IPythonIdentifier)
class PythonIdentifier(_StrippedNativeStringLine):
"""
This field describes a python identifier, i.e. a variable name.
Empty strings are allowed.
Identifiers can be validated from both unicode values and bytes values,
producing a native text string in both cases::
>>> from zope.schema import PythonIdentifier
>>> field = PythonIdentifier()
>>> field.fromUnicode(u'zope')
'zope'
>>> field.fromBytes(b'_zope')
'_zope'
>>> field.fromUnicode(u' ')
''
.. versionadded:: 4.9.0
"""
def _validate(self, value):
super(PythonIdentifier, self)._validate(value)
if value and not _is_identifier(value):
raise InvalidValue(value).with_field_and_value(self, value)
[docs]@implementer(IDottedName)
class DottedName(_StrippedNativeStringLine):
"""Dotted name field.
Values of DottedName fields must be Python-style dotted names.
Dotted names can be validated from both unicode values and bytes values,
producing a native text string in both cases::
>>> from zope.schema import DottedName
>>> field = DottedName()
>>> field.fromUnicode(u'zope.schema')
'zope.schema'
>>> field.fromBytes(b'zope.schema')
'zope.schema'
>>> field.fromUnicode(u'zope._schema')
'zope._schema'
.. versionchanged:: 4.8.0
Implement :class:`zope.schema.interfaces.IFromBytes`
.. versionchanged:: 4.9.0
Allow leading underscores in each component.
"""
_invalid_exc_type = InvalidDottedName
def __init__(self, *args, **kw):
self.min_dots = int(kw.pop("min_dots", 0))
if self.min_dots < 0:
raise ValueError("min_dots cannot be less than zero")
self.max_dots = kw.pop("max_dots", None)
if self.max_dots is not None:
self.max_dots = int(self.max_dots)
if self.max_dots < self.min_dots:
raise ValueError("max_dots cannot be less than min_dots")
super(DottedName, self).__init__(*args, **kw)
def _validate(self, value):
"""
"""
super(DottedName, self)._validate(value)
if not _isdotted(value):
raise InvalidDottedName(value).with_field_and_value(self, value)
dots = value.count(".")
if dots < self.min_dots:
raise InvalidDottedName(
"too few dots; %d required" % self.min_dots, value
).with_field_and_value(self, value)
if self.max_dots is not None and dots > self.max_dots:
raise InvalidDottedName("too many dots; no more than %d allowed" %
self.max_dots, value).with_field_and_value(self, value)
[docs]@implementer(IId)
class Id(_StrippedNativeStringLine):
"""Id field
Values of id fields must be either uris or dotted names.
.. versionchanged:: 4.8.0
Implement :class:`zope.schema.interfaces.IFromBytes`
"""
_invalid_exc_type = InvalidId
def _validate(self, value):
super(Id, self)._validate(value)
if _isuri(value):
return
if _isdotted(value) and "." in value:
return
raise InvalidId(value).with_field_and_value(self, value)
[docs]@implementer(IInterfaceField)
class InterfaceField(Field):
__doc__ = IInterfaceField.__doc__
def _validate(self, value):
super(InterfaceField, self)._validate(value)
if not IInterface.providedBy(value):
raise NotAnInterface(
value,
self.__name__
).with_field_and_value(self, value)
def _validate_sequence(value_type, value, errors=None):
"""Validates a sequence value.
Returns a list of validation errors generated during the validation. If
no errors are generated, returns an empty list.
value_type is a field. value is the sequence being validated. errors is
an optional list of errors that will be prepended to the return value.
To illustrate, we'll use a text value type. All values must be unicode.
>>> field = TextLine(required=True)
To validate a sequence of various values:
>>> from zope.schema._compat import text_type
>>> errors = _validate_sequence(field, (bytearray(b'foo'), u'bar', 1))
>>> errors
[WrongType(bytearray(b'foo'), <...>, ''), WrongType(1, <...>, '')]
The only valid value in the sequence is the second item. The others
generated errors.
We can use the optional errors argument to collect additional errors
for a new sequence:
>>> errors = _validate_sequence(field, (2, u'baz'), errors)
>>> errors
[WrongType(bytearray(b'foo'), <...>, ''), WrongType(1, <...>, ''), WrongType(2, <...>, '')]
"""
if errors is None:
errors = []
if value_type is None:
return errors
for item in value:
try:
value_type.validate(item)
except ValidationError as error:
errors.append(error)
return errors
def _validate_uniqueness(self, value):
temp_values = []
for item in value:
if item in temp_values:
raise NotUnique(item).with_field_and_value(self, value)
temp_values.append(item)
[docs]@implementer(ICollection)
class Collection(MinMaxLen, Iterable):
"""
A generic collection implementing :class:`zope.schema.interfaces.ICollection`.
Subclasses can define the attribute ``value_type`` to be a field
such as an :class:`Object` that will be checked for each member of
the collection. This can then be omitted from the constructor call.
They can also define the attribute ``_type`` to be a concrete
class (or tuple of classes) that the collection itself will
be checked to be an instance of. This cannot be set in the constructor.
.. versionchanged:: 4.6.0
Add the ability for subclasses to specify ``value_type``
and ``unique``, and allow eliding them from the constructor.
"""
value_type = None
unique = False
def __init__(self, value_type=_NotGiven, unique=_NotGiven, **kw):
super(Collection, self).__init__(**kw)
# whine if value_type is not a field
if value_type is not _NotGiven:
self.value_type = value_type
if self.value_type is not None and not IField.providedBy(self.value_type):
raise ValueError("'value_type' must be field instance.")
if unique is not _NotGiven:
self.unique = unique
[docs] def bind(self, context):
"""See zope.schema._bootstrapinterfaces.IField."""
clone = super(Collection, self).bind(context)
# binding value_type is necessary for choices with named vocabularies,
# and possibly also for other fields.
if clone.value_type is not None:
clone.value_type = clone.value_type.bind(context)
return clone
def _validate(self, value):
super(Collection, self)._validate(value)
errors = _validate_sequence(self.value_type, value)
if errors:
try:
raise WrongContainedType(errors, self.__name__).with_field_and_value(self, value)
finally:
# Break cycles
del errors
if self.unique:
_validate_uniqueness(self, value)
#: An alternate name for :class:`.Collection`.
#:
#: .. deprecated:: 4.6.0
#: Use :class:`.Collection` instead.
AbstractCollection = Collection
[docs]@implementer(ISequence)
class Sequence(Collection):
"""
A field representing an ordered sequence.
.. versionadded:: 4.6.0
"""
_type = abc.Sequence
[docs]@implementer(ITuple)
class Tuple(Sequence):
"""A field representing a Tuple."""
_type = tuple
[docs]@implementer(IMutableSequence)
class MutableSequence(Sequence):
"""
A field representing a mutable sequence.
.. versionadded:: 4.6.0
"""
_type = abc.MutableSequence
[docs]@implementer(IList)
class List(MutableSequence):
"""A field representing a List."""
_type = list
class _AbstractSet(Collection):
unique = True
def __init__(self, *args, **kwargs):
super(_AbstractSet, self).__init__(*args, **kwargs)
if not self.unique: # set members are always unique
raise TypeError(
"__init__() got an unexpected keyword argument 'unique'")
[docs]@implementer(ISet)
class Set(_AbstractSet):
"""A field representing a set."""
_type = set
[docs]@implementer(IFrozenSet)
class FrozenSet(_AbstractSet):
_type = frozenset
[docs]@implementer(IMapping)
class Mapping(MinMaxLen, Iterable):
"""
A field representing a mapping.
.. versionadded:: 4.6.0
"""
_type = abc.Mapping
key_type = None
value_type = None
def __init__(self, key_type=None, value_type=None, **kw):
super(Mapping, self).__init__(**kw)
# whine if key_type or value_type is not a field
if key_type is not None and not IField.providedBy(key_type):
raise ValueError("'key_type' must be field instance.")
if value_type is not None and not IField.providedBy(value_type):
raise ValueError("'value_type' must be field instance.")
self.key_type = key_type
self.value_type = value_type
def _validate(self, value):
super(Mapping, self)._validate(value)
errors = []
if self.value_type:
errors = _validate_sequence(self.value_type, value.values(),
errors)
errors = _validate_sequence(self.key_type, value, errors)
if errors:
try:
raise WrongContainedType(errors, self.__name__).with_field_and_value(self, value)
finally:
# Break cycles
del errors
[docs] def bind(self, object):
"""See zope.schema._bootstrapinterfaces.IField."""
clone = super(Mapping, self).bind(object)
# binding value_type is necessary for choices with named vocabularies,
# and possibly also for other fields.
if clone.key_type is not None:
clone.key_type = clone.key_type.bind(object)
if clone.value_type is not None:
clone.value_type = clone.value_type.bind(object)
return clone
[docs]@implementer(IMutableMapping)
class MutableMapping(Mapping):
"""
A field representing a mutable mapping.
.. versionadded:: 4.6.0
"""
_type = abc.MutableMapping
[docs]@implementer(IDict)
class Dict(MutableMapping):
"""A field representing a Dict."""
_type = dict