mirror of
https://github.com/nottinghamtec/PyRIGS.git
synced 2026-04-19 00:21:47 +00:00
Added printing requirements
This commit is contained in:
77
zope/schema/__init__.py
Normal file
77
zope/schema/__init__.py
Normal file
@@ -0,0 +1,77 @@
|
||||
##############################################################################
|
||||
#
|
||||
# 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 package constructor
|
||||
"""
|
||||
# Field APIs
|
||||
from zope.schema._field import ASCII
|
||||
from zope.schema._field import ASCIILine
|
||||
from zope.schema._field import Bool
|
||||
from zope.schema._field import Bytes
|
||||
from zope.schema._field import BytesLine
|
||||
from zope.schema._field import Choice
|
||||
from zope.schema._field import Container
|
||||
from zope.schema._field import Date
|
||||
from zope.schema._field import Datetime
|
||||
from zope.schema._field import Decimal
|
||||
from zope.schema._field import Dict
|
||||
from zope.schema._field import DottedName
|
||||
from zope.schema._field import Field
|
||||
from zope.schema._field import Float
|
||||
from zope.schema._field import FrozenSet
|
||||
from zope.schema._field import Id
|
||||
from zope.schema._field import Int
|
||||
from zope.schema._field import InterfaceField
|
||||
from zope.schema._field import Iterable
|
||||
from zope.schema._field import List
|
||||
from zope.schema._field import MinMaxLen
|
||||
from zope.schema._field import NativeString
|
||||
from zope.schema._field import NativeStringLine
|
||||
from zope.schema._field import Object
|
||||
from zope.schema._field import Orderable
|
||||
from zope.schema._field import Password
|
||||
from zope.schema._field import Set
|
||||
from zope.schema._field import SourceText
|
||||
from zope.schema._field import Text
|
||||
from zope.schema._field import TextLine
|
||||
from zope.schema._field import Time
|
||||
from zope.schema._field import Timedelta
|
||||
from zope.schema._field import Tuple
|
||||
from zope.schema._field import URI
|
||||
|
||||
# Schema APIs
|
||||
from zope.schema._schema import getFields
|
||||
from zope.schema._schema import getFieldsInOrder
|
||||
from zope.schema._schema import getFieldNames
|
||||
from zope.schema._schema import getFieldNamesInOrder
|
||||
from zope.schema._schema import getValidationErrors
|
||||
from zope.schema._schema import getSchemaValidationErrors
|
||||
|
||||
# Acessor API
|
||||
from zope.schema.accessors import accessors
|
||||
|
||||
# Error API
|
||||
from zope.schema.interfaces import ValidationError
|
||||
from zope.schema._bootstrapinterfaces import NO_VALUE
|
||||
|
||||
|
||||
# pep 8 friendlyness
|
||||
ASCII, ASCIILine, Bool, Bytes, BytesLine, Choice, Container, Date, Datetime
|
||||
Decimal, Dict, DottedName, Field, Float, FrozenSet, Id, Int, InterfaceField
|
||||
Iterable, List, MinMaxLen, NativeString, NativeStringLine, Object, Orderable
|
||||
Password, Set, SourceText, Text, TextLine, Time, Timedelta, Tuple, URI
|
||||
getFields, getFieldsInOrder, getFieldNames, getFieldNamesInOrder,
|
||||
getValidationErrors, getSchemaValidationErrors
|
||||
accessors
|
||||
ValidationError
|
||||
NO_VALUE
|
||||
441
zope/schema/_bootstrapfields.py
Normal file
441
zope/schema/_bootstrapfields.py
Normal file
@@ -0,0 +1,441 @@
|
||||
##############################################################################
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
##############################################################################
|
||||
"""Bootstrapping fields
|
||||
"""
|
||||
__docformat__ = 'restructuredtext'
|
||||
|
||||
from zope.interface import Attribute
|
||||
from zope.interface import providedBy
|
||||
from zope.interface import implementer
|
||||
|
||||
from zope.schema._bootstrapinterfaces import ConstraintNotSatisfied
|
||||
from zope.schema._bootstrapinterfaces import IContextAwareDefaultFactory
|
||||
from zope.schema._bootstrapinterfaces import IFromUnicode
|
||||
from zope.schema._bootstrapinterfaces import NotAContainer
|
||||
from zope.schema._bootstrapinterfaces import NotAnIterator
|
||||
from zope.schema._bootstrapinterfaces import RequiredMissing
|
||||
from zope.schema._bootstrapinterfaces import StopValidation
|
||||
from zope.schema._bootstrapinterfaces import TooBig
|
||||
from zope.schema._bootstrapinterfaces import TooLong
|
||||
from zope.schema._bootstrapinterfaces import TooShort
|
||||
from zope.schema._bootstrapinterfaces import TooSmall
|
||||
from zope.schema._bootstrapinterfaces import WrongType
|
||||
from zope.schema._compat import u
|
||||
from zope.schema._compat import text_type
|
||||
from zope.schema._compat import integer_types
|
||||
|
||||
from zope.schema._schema import getFields
|
||||
|
||||
|
||||
class ValidatedProperty(object):
|
||||
|
||||
def __init__(self, name, check=None):
|
||||
self._info = name, check
|
||||
|
||||
def __set__(self, inst, value):
|
||||
name, check = self._info
|
||||
if value != inst.missing_value:
|
||||
if check is not None:
|
||||
check(inst, value)
|
||||
else:
|
||||
inst.validate(value)
|
||||
inst.__dict__[name] = value
|
||||
|
||||
def __get__(self, inst, owner):
|
||||
name, check = self._info
|
||||
return inst.__dict__[name]
|
||||
|
||||
|
||||
class DefaultProperty(ValidatedProperty):
|
||||
|
||||
def __get__(self, inst, owner):
|
||||
name, check = self._info
|
||||
defaultFactory = inst.__dict__.get('defaultFactory')
|
||||
# If there is no default factory, simply return the default.
|
||||
if defaultFactory is None:
|
||||
return inst.__dict__[name]
|
||||
# Get the default value by calling the factory. Some factories might
|
||||
# require a context to produce a value.
|
||||
if IContextAwareDefaultFactory.providedBy(defaultFactory):
|
||||
value = defaultFactory(inst.context)
|
||||
else:
|
||||
value = defaultFactory()
|
||||
# Check that the created value is valid.
|
||||
if check is not None:
|
||||
check(inst, value)
|
||||
elif value != inst.missing_value:
|
||||
inst.validate(value)
|
||||
return value
|
||||
|
||||
|
||||
class Field(Attribute):
|
||||
|
||||
# Type restrictions, if any
|
||||
_type = None
|
||||
context = None
|
||||
|
||||
# If a field has no assigned value, it will be set to missing_value.
|
||||
missing_value = None
|
||||
|
||||
# This is the default value for the missing_value argument to the
|
||||
# Field constructor. A marker is helpful since we don't want to
|
||||
# overwrite missing_value if it is set differently on a Field
|
||||
# subclass and isn't specified via the constructor.
|
||||
__missing_value_marker = object()
|
||||
|
||||
# Note that the "order" field has a dual existance:
|
||||
# 1. The class variable Field.order is used as a source for the
|
||||
# monotonically increasing values used to provide...
|
||||
# 2. The instance variable self.order which provides a
|
||||
# monotonically increasing value that tracks the creation order
|
||||
# of Field (including Field subclass) instances.
|
||||
order = 0
|
||||
|
||||
default = DefaultProperty('default')
|
||||
|
||||
# These were declared as slots in zope.interface, we override them here to
|
||||
# get rid of the dedcriptors so they don't break .bind()
|
||||
__name__ = None
|
||||
interface = None
|
||||
_Element__tagged_values = None
|
||||
|
||||
def __init__(self, title=u(''), description=u(''), __name__='',
|
||||
required=True, readonly=False, constraint=None, default=None,
|
||||
defaultFactory=None, missing_value=__missing_value_marker):
|
||||
"""Pass in field values as keyword parameters.
|
||||
|
||||
|
||||
Generally, you want to pass either a title and description, or
|
||||
a doc string. If you pass no doc string, it will be computed
|
||||
from the title and description. If you pass a doc string that
|
||||
follows the Python coding style (title line separated from the
|
||||
body by a blank line), the title and description will be
|
||||
computed from the doc string. Unfortunately, the doc string
|
||||
must be passed as a positional argument.
|
||||
|
||||
Here are some examples:
|
||||
|
||||
>>> from zope.schema._compat import u
|
||||
>>> f = Field()
|
||||
>>> f.__doc__, f.title, f.description
|
||||
('', u'', u'')
|
||||
|
||||
>>> f = Field(title=u('sample'))
|
||||
>>> f.__doc__, f.title, f.description
|
||||
(u'sample', u'sample', u'')
|
||||
|
||||
>>> f = Field(title=u('sample'), description=u('blah blah\\nblah'))
|
||||
>>> f.__doc__, f.title, f.description
|
||||
(u'sample\\n\\nblah blah\\nblah', u'sample', u'blah blah\\nblah')
|
||||
"""
|
||||
__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
|
||||
self.defaultFactory = defaultFactory
|
||||
|
||||
# Keep track of the order of field definitions
|
||||
Field.order += 1
|
||||
self.order = Field.order
|
||||
|
||||
if missing_value is not self.__missing_value_marker:
|
||||
self.missing_value = missing_value
|
||||
|
||||
def constraint(self, value):
|
||||
return True
|
||||
|
||||
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 == self.missing_value:
|
||||
if self.required:
|
||||
raise RequiredMissing(self.__name__)
|
||||
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
|
||||
for interface in providedBy(self):
|
||||
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 WrongType(value, self._type, self.__name__)
|
||||
|
||||
if not self.constraint(value):
|
||||
raise ConstraintNotSatisfied(value, self.__name__)
|
||||
|
||||
def get(self, object):
|
||||
return getattr(object, self.__name__)
|
||||
|
||||
def query(self, object, default=None):
|
||||
return getattr(object, self.__name__, default)
|
||||
|
||||
def set(self, object, value):
|
||||
if self.readonly:
|
||||
raise TypeError("Can't set values on read-only fields "
|
||||
"(name=%s, class=%s.%s)"
|
||||
% (self.__name__,
|
||||
object.__class__.__module__,
|
||||
object.__class__.__name__))
|
||||
setattr(object, self.__name__, value)
|
||||
|
||||
|
||||
class Container(Field):
|
||||
|
||||
def _validate(self, value):
|
||||
super(Container, self)._validate(value)
|
||||
|
||||
if not hasattr(value, '__contains__'):
|
||||
try:
|
||||
iter(value)
|
||||
except TypeError:
|
||||
raise NotAContainer(value)
|
||||
|
||||
|
||||
# XXX This class violates the Liskov Substituability Principle: it
|
||||
# is derived from Container, but cannot be used everywhere an instance
|
||||
# of Container could be, because it's '_validate' is more restrictive.
|
||||
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 TypeError:
|
||||
raise NotAnIterator(value)
|
||||
|
||||
|
||||
class Orderable(object):
|
||||
"""Values of ordered fields can be sorted.
|
||||
|
||||
They can be restricted to a range of values.
|
||||
|
||||
Orderable is a mixin used in combination with Field.
|
||||
"""
|
||||
|
||||
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 TooSmall(value, self.min)
|
||||
|
||||
if self.max is not None and value > self.max:
|
||||
raise TooBig(value, self.max)
|
||||
|
||||
|
||||
class MinMaxLen(object):
|
||||
"""Expresses constraints on the length of a field.
|
||||
|
||||
MinMaxLen is a mixin used in combination with 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 TooShort(value, self.min_length)
|
||||
|
||||
if self.max_length is not None and len(value) > self.max_length:
|
||||
raise TooLong(value, self.max_length)
|
||||
|
||||
|
||||
@implementer(IFromUnicode)
|
||||
class Text(MinMaxLen, Field):
|
||||
"""A field containing text used for human discourse."""
|
||||
_type = text_type
|
||||
|
||||
def __init__(self, *args, **kw):
|
||||
super(Text, self).__init__(*args, **kw)
|
||||
|
||||
def fromUnicode(self, str):
|
||||
"""
|
||||
>>> from zope.schema._compat import u
|
||||
>>> from zope.schema._compat import b
|
||||
>>> t = Text(constraint=lambda v: 'x' in v)
|
||||
>>> t.fromUnicode(b("foo x spam"))
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
WrongType: ('foo x spam', <type 'unicode'>, '')
|
||||
>>> t.fromUnicode(u("foo x spam"))
|
||||
u'foo x spam'
|
||||
>>> t.fromUnicode(u("foo spam"))
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ConstraintNotSatisfied: (u'foo spam', '')
|
||||
"""
|
||||
self.validate(str)
|
||||
return str
|
||||
|
||||
|
||||
class TextLine(Text):
|
||||
"""A text field with no newlines."""
|
||||
|
||||
def constraint(self, value):
|
||||
return '\n' not in value and '\r' not in value
|
||||
|
||||
|
||||
class Password(TextLine):
|
||||
"""A text field containing a text used as a password."""
|
||||
|
||||
UNCHANGED_PASSWORD = object()
|
||||
|
||||
def set(self, context, value):
|
||||
"""Update the password.
|
||||
|
||||
We use a special marker value that a widget can use
|
||||
to tell us that the password didn't change. This is
|
||||
needed to support edit forms that don't display the
|
||||
existing password and want to work together with
|
||||
encryption.
|
||||
|
||||
"""
|
||||
if value is self.UNCHANGED_PASSWORD:
|
||||
return
|
||||
super(Password, self).set(context, value)
|
||||
|
||||
def validate(self, value):
|
||||
try:
|
||||
existing = bool(self.get(self.context))
|
||||
except AttributeError:
|
||||
existing = False
|
||||
if value is self.UNCHANGED_PASSWORD and existing:
|
||||
# Allow the UNCHANGED_PASSWORD value, if a password is set already
|
||||
return
|
||||
return super(Password, self).validate(value)
|
||||
|
||||
|
||||
class Bool(Field):
|
||||
"""A field representing a Bool."""
|
||||
|
||||
_type = bool
|
||||
|
||||
def _validate(self, value):
|
||||
# Convert integers to bools to they don't get mis-flagged
|
||||
# by the type check later.
|
||||
if isinstance(value, int):
|
||||
value = bool(value)
|
||||
Field._validate(self, value)
|
||||
|
||||
def set(self, object, value):
|
||||
if isinstance(value, int):
|
||||
value = bool(value)
|
||||
Field.set(self, object, value)
|
||||
|
||||
def fromUnicode(self, str):
|
||||
"""
|
||||
>>> from zope.schema._compat import b
|
||||
>>> b = Bool()
|
||||
>>> IFromUnicode.providedBy(b)
|
||||
True
|
||||
>>> b.fromUnicode('True')
|
||||
True
|
||||
>>> b.fromUnicode('')
|
||||
False
|
||||
>>> b.fromUnicode('true')
|
||||
True
|
||||
>>> b.fromUnicode('false') or b.fromUnicode('False')
|
||||
False
|
||||
"""
|
||||
v = str == 'True' or str == 'true'
|
||||
self.validate(v)
|
||||
return v
|
||||
|
||||
|
||||
@implementer(IFromUnicode)
|
||||
class Int(Orderable, Field):
|
||||
"""A field representing an Integer."""
|
||||
_type = integer_types
|
||||
|
||||
def __init__(self, *args, **kw):
|
||||
super(Int, self).__init__(*args, **kw)
|
||||
|
||||
def fromUnicode(self, str):
|
||||
"""
|
||||
>>> f = Int()
|
||||
>>> f.fromUnicode("125")
|
||||
125
|
||||
>>> f.fromUnicode("125.6") #doctest: +IGNORE_EXCEPTION_DETAIL
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValueError: invalid literal for int(): 125.6
|
||||
"""
|
||||
v = int(str)
|
||||
self.validate(v)
|
||||
return v
|
||||
120
zope/schema/_bootstrapinterfaces.py
Normal file
120
zope/schema/_bootstrapinterfaces.py
Normal file
@@ -0,0 +1,120 @@
|
||||
##############################################################################
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
##############################################################################
|
||||
"""Bootstrap schema interfaces and exceptions
|
||||
"""
|
||||
import zope.interface
|
||||
|
||||
from zope.schema._messageid import _
|
||||
|
||||
|
||||
class StopValidation(Exception):
|
||||
"""Raised if the validation is completed early.
|
||||
|
||||
Note that this exception should be always caught, since it is just
|
||||
a way for the validator to save time.
|
||||
"""
|
||||
|
||||
|
||||
class ValidationError(zope.interface.Invalid):
|
||||
"""Raised if the Validation process fails."""
|
||||
|
||||
def doc(self):
|
||||
return self.__class__.__doc__
|
||||
|
||||
def __cmp__(self, other):
|
||||
if not hasattr(other, 'args'):
|
||||
return -1
|
||||
return cmp(self.args, other.args)
|
||||
|
||||
def __eq__(self, other):
|
||||
if not hasattr(other, 'args'):
|
||||
return False
|
||||
return self.args == other.args
|
||||
|
||||
__hash__ = zope.interface.Invalid.__hash__ # python3
|
||||
|
||||
def __repr__(self): # pragma: no cover
|
||||
return '%s(%s)' % (self.__class__.__name__,
|
||||
', '.join(repr(arg) for arg in self.args))
|
||||
|
||||
|
||||
class RequiredMissing(ValidationError):
|
||||
__doc__ = _("""Required input is missing.""")
|
||||
|
||||
|
||||
class WrongType(ValidationError):
|
||||
__doc__ = _("""Object is of wrong type.""")
|
||||
|
||||
|
||||
class TooBig(ValidationError):
|
||||
__doc__ = _("""Value is too big""")
|
||||
|
||||
|
||||
class TooSmall(ValidationError):
|
||||
__doc__ = _("""Value is too small""")
|
||||
|
||||
|
||||
class TooLong(ValidationError):
|
||||
__doc__ = _("""Value is too long""")
|
||||
|
||||
|
||||
class TooShort(ValidationError):
|
||||
__doc__ = _("""Value is too short""")
|
||||
|
||||
|
||||
class InvalidValue(ValidationError):
|
||||
__doc__ = _("""Invalid value""")
|
||||
|
||||
|
||||
class ConstraintNotSatisfied(ValidationError):
|
||||
__doc__ = _("""Constraint not satisfied""")
|
||||
|
||||
|
||||
class NotAContainer(ValidationError):
|
||||
__doc__ = _("""Not a container""")
|
||||
|
||||
|
||||
class NotAnIterator(ValidationError):
|
||||
__doc__ = _("""Not an iterator""")
|
||||
|
||||
|
||||
class IFromUnicode(zope.interface.Interface):
|
||||
"""Parse a unicode string to a value
|
||||
|
||||
We will often adapt fields to this interface to support views and
|
||||
other applications that need to conver raw data as unicode
|
||||
values.
|
||||
"""
|
||||
|
||||
def fromUnicode(str):
|
||||
"""Convert a unicode string to a value.
|
||||
"""
|
||||
|
||||
|
||||
class IContextAwareDefaultFactory(zope.interface.Interface):
|
||||
"""A default factory that requires a context.
|
||||
|
||||
The context is the field context. If the field is not bound, context may
|
||||
be ``None``.
|
||||
"""
|
||||
|
||||
def __call__(context):
|
||||
"""Returns a default value for the field."""
|
||||
|
||||
|
||||
class NO_VALUE(object):
|
||||
def __repr__(self):
|
||||
return '<NO_VALUE>'
|
||||
|
||||
NO_VALUE = NO_VALUE()
|
||||
58
zope/schema/_compat.py
Normal file
58
zope/schema/_compat.py
Normal file
@@ -0,0 +1,58 @@
|
||||
import sys
|
||||
|
||||
PY3 = sys.version_info[0] >= 3
|
||||
|
||||
try:
|
||||
from collections import OrderedDict
|
||||
except ImportError: # pragma: no cover
|
||||
from ordereddict import OrderedDict
|
||||
|
||||
# pep 8 friendlyness
|
||||
OrderedDict
|
||||
|
||||
|
||||
if PY3: # pragma: no cover
|
||||
|
||||
def b(s):
|
||||
return s.encode("latin-1")
|
||||
|
||||
def u(s):
|
||||
return s
|
||||
|
||||
string_types = str,
|
||||
text_type = str
|
||||
binary_type = bytes
|
||||
integer_types = int,
|
||||
|
||||
def non_native_string(x):
|
||||
if isinstance(x, bytes):
|
||||
return x
|
||||
return bytes(x, 'unicode_escape')
|
||||
|
||||
def make_binary(x):
|
||||
if isinstance(x, bytes):
|
||||
return x
|
||||
return x.encode('ascii')
|
||||
|
||||
else: # pragma: no cover
|
||||
|
||||
def b(s):
|
||||
return s
|
||||
|
||||
def u(s):
|
||||
return unicode(s, "unicode_escape")
|
||||
|
||||
string_types = basestring,
|
||||
text_type = unicode
|
||||
binary_type = str
|
||||
integer_types = (int, long)
|
||||
|
||||
def non_native_string(x):
|
||||
if isinstance(x, unicode):
|
||||
return x
|
||||
return unicode(x, 'unicode_escape')
|
||||
|
||||
def make_binary(x):
|
||||
if isinstance(x, str):
|
||||
return x
|
||||
return x.encode('ascii')
|
||||
701
zope/schema/_field.py
Normal file
701
zope/schema/_field.py
Normal file
@@ -0,0 +1,701 @@
|
||||
# -*- 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'
|
||||
|
||||
from datetime import datetime
|
||||
from datetime import date
|
||||
from datetime import timedelta
|
||||
from datetime import time
|
||||
import decimal
|
||||
import re
|
||||
import threading
|
||||
|
||||
from zope.event import notify
|
||||
from zope.interface import classImplements
|
||||
from zope.interface import implementer
|
||||
from zope.interface import Interface
|
||||
from zope.interface.interfaces import IInterface
|
||||
from zope.interface.interfaces import IMethod
|
||||
|
||||
from zope.schema.interfaces import IASCII
|
||||
from zope.schema.interfaces import IASCIILine
|
||||
from zope.schema.interfaces import IBaseVocabulary
|
||||
from zope.schema.interfaces import IBeforeObjectAssignedEvent
|
||||
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 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 IFromUnicode
|
||||
from zope.schema.interfaces import IFrozenSet
|
||||
from zope.schema.interfaces import IId
|
||||
from zope.schema.interfaces import IInt
|
||||
from zope.schema.interfaces import IInterfaceField
|
||||
from zope.schema.interfaces import IList
|
||||
from zope.schema.interfaces import IMinMaxLen
|
||||
from zope.schema.interfaces import IObject
|
||||
from zope.schema.interfaces import IPassword
|
||||
from zope.schema.interfaces import ISet
|
||||
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 SchemaNotProvided
|
||||
from zope.schema.interfaces import SchemaNotFullyImplemented
|
||||
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 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 Password
|
||||
from zope.schema._bootstrapfields import MinMaxLen
|
||||
from zope.schema.fieldproperty import FieldProperty
|
||||
from zope.schema.vocabulary import getVocabularyRegistry
|
||||
from zope.schema.vocabulary import VocabularyRegistryError
|
||||
from zope.schema.vocabulary import SimpleVocabulary
|
||||
|
||||
from zope.schema._compat import b
|
||||
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
|
||||
|
||||
# pep 8 friendlyness
|
||||
Container
|
||||
|
||||
# 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(Bool, IFromUnicode)
|
||||
classImplements(Int, IInt)
|
||||
|
||||
|
||||
@implementer(ISourceText)
|
||||
class SourceText(Text):
|
||||
__doc__ = ISourceText.__doc__
|
||||
_type = text_type
|
||||
|
||||
|
||||
@implementer(IBytes, IFromUnicode)
|
||||
class Bytes(MinMaxLen, Field):
|
||||
__doc__ = IBytes.__doc__
|
||||
|
||||
_type = binary_type
|
||||
|
||||
def fromUnicode(self, uc):
|
||||
""" See IFromUnicode.
|
||||
"""
|
||||
v = make_binary(uc)
|
||||
self.validate(v)
|
||||
return v
|
||||
|
||||
# for things which are of the str type on both Python 2 and 3
|
||||
if PY3: # pragma: no cover
|
||||
NativeString = Text
|
||||
else: # pragma: no cover
|
||||
NativeString = Bytes
|
||||
|
||||
|
||||
@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
|
||||
|
||||
|
||||
@implementer(IBytesLine)
|
||||
class BytesLine(Bytes):
|
||||
"""A Text 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
|
||||
|
||||
# for things which are of the str type on both Python 2 and 3
|
||||
if PY3: # pragma: no cover
|
||||
NativeStringLine = TextLine
|
||||
else: # pragma: no cover
|
||||
NativeStringLine = BytesLine
|
||||
|
||||
|
||||
@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
|
||||
|
||||
|
||||
@implementer(IFloat, IFromUnicode)
|
||||
class Float(Orderable, Field):
|
||||
__doc__ = IFloat.__doc__
|
||||
_type = float
|
||||
|
||||
def __init__(self, *args, **kw):
|
||||
super(Float, self).__init__(*args, **kw)
|
||||
|
||||
def fromUnicode(self, uc):
|
||||
""" See IFromUnicode.
|
||||
"""
|
||||
v = float(uc)
|
||||
self.validate(v)
|
||||
return v
|
||||
|
||||
|
||||
@implementer(IDecimal, IFromUnicode)
|
||||
class Decimal(Orderable, Field):
|
||||
__doc__ = IDecimal.__doc__
|
||||
_type = decimal.Decimal
|
||||
|
||||
def __init__(self, *args, **kw):
|
||||
super(Decimal, self).__init__(*args, **kw)
|
||||
|
||||
def fromUnicode(self, uc):
|
||||
""" See IFromUnicode.
|
||||
"""
|
||||
try:
|
||||
v = decimal.Decimal(uc)
|
||||
except decimal.InvalidOperation:
|
||||
raise ValueError('invalid literal for Decimal(): %s' % uc)
|
||||
self.validate(v)
|
||||
return v
|
||||
|
||||
|
||||
@implementer(IDatetime)
|
||||
class Datetime(Orderable, Field):
|
||||
__doc__ = IDatetime.__doc__
|
||||
_type = datetime
|
||||
|
||||
def __init__(self, *args, **kw):
|
||||
super(Datetime, self).__init__(*args, **kw)
|
||||
|
||||
|
||||
@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__)
|
||||
|
||||
|
||||
@implementer(ITimedelta)
|
||||
class Timedelta(Orderable, Field):
|
||||
__doc__ = ITimedelta.__doc__
|
||||
_type = timedelta
|
||||
|
||||
|
||||
@implementer(ITime)
|
||||
class Time(Orderable, Field):
|
||||
__doc__ = ITime.__doc__
|
||||
_type = time
|
||||
|
||||
|
||||
@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 ValueError('Invalid 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 bind(self, object):
|
||||
"""See zope.schema._bootstrapinterfaces.IField."""
|
||||
clone = super(Choice, self).bind(object)
|
||||
# get registered vocabulary if needed:
|
||||
if IContextSourceBinder.providedBy(self.vocabulary):
|
||||
clone.vocabulary = self.vocabulary(object)
|
||||
elif clone.vocabulary is None and self.vocabularyName is not None:
|
||||
vr = getVocabularyRegistry()
|
||||
clone.vocabulary = vr.get(object, self.vocabularyName)
|
||||
|
||||
if not ISource.providedBy(clone.vocabulary):
|
||||
raise ValueError('Invalid clone vocabulary')
|
||||
|
||||
return clone
|
||||
|
||||
def fromUnicode(self, str):
|
||||
""" See IFromUnicode.
|
||||
"""
|
||||
self.validate(str)
|
||||
return str
|
||||
|
||||
def _validate(self, value):
|
||||
# Pass all validations during initialization
|
||||
if self._init_field:
|
||||
return
|
||||
super(Choice, self)._validate(value)
|
||||
vocabulary = self.vocabulary
|
||||
if vocabulary is None:
|
||||
vr = getVocabularyRegistry()
|
||||
try:
|
||||
vocabulary = vr.get(None, self.vocabularyName)
|
||||
except VocabularyRegistryError:
|
||||
raise ValueError("Can't validate value without vocabulary")
|
||||
if value not in vocabulary:
|
||||
raise ConstraintNotSatisfied(value, self.__name__)
|
||||
|
||||
|
||||
_isuri = r"[a-zA-z0-9+.-]+:" # scheme
|
||||
_isuri += r"\S*$" # non space (should be pickier)
|
||||
_isuri = re.compile(_isuri).match
|
||||
|
||||
|
||||
@implementer(IURI, IFromUnicode)
|
||||
class URI(NativeStringLine):
|
||||
"""URI schema field
|
||||
"""
|
||||
|
||||
def _validate(self, value):
|
||||
super(URI, self)._validate(value)
|
||||
if _isuri(value):
|
||||
return
|
||||
|
||||
raise InvalidURI(value)
|
||||
|
||||
def fromUnicode(self, value):
|
||||
""" See IFromUnicode.
|
||||
"""
|
||||
v = str(value.strip())
|
||||
self.validate(v)
|
||||
return v
|
||||
|
||||
|
||||
_isdotted = re.compile(
|
||||
r"([a-zA-Z][a-zA-Z0-9_]*)"
|
||||
r"([.][a-zA-Z][a-zA-Z0-9_]*)*"
|
||||
# use the whole line
|
||||
r"$").match
|
||||
|
||||
|
||||
@implementer(IDottedName)
|
||||
class DottedName(NativeStringLine):
|
||||
"""Dotted name field.
|
||||
|
||||
Values of DottedName fields must be Python-style dotted names.
|
||||
"""
|
||||
|
||||
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)
|
||||
dots = value.count(".")
|
||||
if dots < self.min_dots:
|
||||
raise InvalidDottedName(
|
||||
"too few dots; %d required" % self.min_dots, 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)
|
||||
|
||||
def fromUnicode(self, value):
|
||||
v = value.strip()
|
||||
if not isinstance(v, self._type):
|
||||
v = v.encode('ascii')
|
||||
self.validate(v)
|
||||
return v
|
||||
|
||||
|
||||
@implementer(IId, IFromUnicode)
|
||||
class Id(NativeStringLine):
|
||||
"""Id field
|
||||
|
||||
Values of id fields must be either uris or dotted names.
|
||||
"""
|
||||
|
||||
def _validate(self, value):
|
||||
super(Id, self)._validate(value)
|
||||
if _isuri(value):
|
||||
return
|
||||
if _isdotted(value) and "." in value:
|
||||
return
|
||||
|
||||
raise InvalidId(value)
|
||||
|
||||
def fromUnicode(self, value):
|
||||
""" See IFromUnicode.
|
||||
"""
|
||||
v = value.strip()
|
||||
if not isinstance(v, self._type):
|
||||
v = v.encode('ascii')
|
||||
self.validate(v)
|
||||
return v
|
||||
|
||||
|
||||
@implementer(IInterfaceField)
|
||||
class InterfaceField(Field):
|
||||
__doc__ = IInterfaceField.__doc__
|
||||
|
||||
def _validate(self, value):
|
||||
super(InterfaceField, self)._validate(value)
|
||||
if not IInterface.providedBy(value):
|
||||
raise WrongType("An interface is required", value, self.__name__)
|
||||
|
||||
|
||||
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.
|
||||
|
||||
>>> from zope.schema._compat import u
|
||||
>>> from zope.schema._compat import b
|
||||
>>> field = TextLine(required=True)
|
||||
|
||||
To validate a sequence of various values:
|
||||
|
||||
>>> errors = _validate_sequence(field, (b('foo'), u('bar'), 1))
|
||||
>>> errors # XXX assumes Python2 reprs
|
||||
[WrongType('foo', <type 'unicode'>, ''), WrongType(1, <type 'unicode'>, '')]
|
||||
|
||||
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 # XXX assumes Python2 reprs
|
||||
[WrongType('foo', <type 'unicode'>, ''), WrongType(1, <type 'unicode'>, ''), WrongType(2, <type 'unicode'>, '')]
|
||||
|
||||
"""
|
||||
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(value):
|
||||
temp_values = []
|
||||
for item in value:
|
||||
if item in temp_values:
|
||||
raise NotUnique(item)
|
||||
|
||||
temp_values.append(item)
|
||||
|
||||
|
||||
class AbstractCollection(MinMaxLen, Iterable):
|
||||
value_type = None
|
||||
unique = False
|
||||
|
||||
def __init__(self, value_type=None, unique=False, **kw):
|
||||
super(AbstractCollection, self).__init__(**kw)
|
||||
# whine if value_type is not a field
|
||||
if value_type is not None and not IField.providedBy(value_type):
|
||||
raise ValueError("'value_type' must be field instance.")
|
||||
self.value_type = value_type
|
||||
self.unique = unique
|
||||
|
||||
def bind(self, object):
|
||||
"""See zope.schema._bootstrapinterfaces.IField."""
|
||||
clone = super(AbstractCollection, self).bind(object)
|
||||
# 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(object)
|
||||
return clone
|
||||
|
||||
def _validate(self, value):
|
||||
super(AbstractCollection, self)._validate(value)
|
||||
errors = _validate_sequence(self.value_type, value)
|
||||
if errors:
|
||||
raise WrongContainedType(errors, self.__name__)
|
||||
if self.unique:
|
||||
_validate_uniqueness(value)
|
||||
|
||||
|
||||
@implementer(ITuple)
|
||||
class Tuple(AbstractCollection):
|
||||
"""A field representing a Tuple."""
|
||||
_type = tuple
|
||||
|
||||
|
||||
@implementer(IList)
|
||||
class List(AbstractCollection):
|
||||
"""A field representing a List."""
|
||||
_type = list
|
||||
|
||||
|
||||
@implementer(ISet)
|
||||
class Set(AbstractCollection):
|
||||
"""A field representing a set."""
|
||||
_type = set
|
||||
|
||||
def __init__(self, **kw):
|
||||
if 'unique' in kw: # set members are always unique
|
||||
raise TypeError(
|
||||
"__init__() got an unexpected keyword argument 'unique'")
|
||||
super(Set, self).__init__(unique=True, **kw)
|
||||
|
||||
|
||||
@implementer(IFrozenSet)
|
||||
class FrozenSet(AbstractCollection):
|
||||
_type = frozenset
|
||||
|
||||
def __init__(self, **kw):
|
||||
if 'unique' in kw: # set members are always unique
|
||||
raise TypeError(
|
||||
"__init__() got an unexpected keyword argument 'unique'")
|
||||
super(FrozenSet, self).__init__(unique=True, **kw)
|
||||
|
||||
|
||||
VALIDATED_VALUES = threading.local()
|
||||
|
||||
|
||||
def _validate_fields(schema, value, errors=None):
|
||||
if errors is None:
|
||||
errors = []
|
||||
# Interface can be used as schema property for Object fields that plan to
|
||||
# hold values of any type.
|
||||
# Because Interface does not include any Attribute, it is obviously not
|
||||
# worth looping on its methods and filter them all out.
|
||||
if schema is Interface:
|
||||
return errors
|
||||
# if `value` is part of a cyclic graph, we need to break the cycle to avoid
|
||||
# infinite recursion. Collect validated objects in a thread local dict by
|
||||
# it's python represenation. A previous version was setting a volatile
|
||||
# attribute which didn't work with security proxy
|
||||
if id(value) in VALIDATED_VALUES.__dict__:
|
||||
return errors
|
||||
VALIDATED_VALUES.__dict__[id(value)] = True
|
||||
# (If we have gotten here, we know that `value` provides an interface
|
||||
# other than zope.interface.Interface;
|
||||
# iow, we can rely on the fact that it is an instance
|
||||
# that supports attribute assignment.)
|
||||
try:
|
||||
for name in schema.names(all=True):
|
||||
if not IMethod.providedBy(schema[name]):
|
||||
try:
|
||||
attribute = schema[name]
|
||||
if IChoice.providedBy(attribute):
|
||||
# Choice must be bound before validation otherwise
|
||||
# IContextSourceBinder is not iterable in validation
|
||||
bound = attribute.bind(value)
|
||||
bound.validate(getattr(value, name))
|
||||
elif IField.providedBy(attribute):
|
||||
# validate attributes that are fields
|
||||
attribute.validate(getattr(value, name))
|
||||
except ValidationError as error:
|
||||
errors.append(error)
|
||||
except AttributeError as error:
|
||||
# property for the given name is not implemented
|
||||
errors.append(SchemaNotFullyImplemented(error))
|
||||
finally:
|
||||
del VALIDATED_VALUES.__dict__[id(value)]
|
||||
return errors
|
||||
|
||||
|
||||
@implementer(IObject)
|
||||
class Object(Field):
|
||||
__doc__ = IObject.__doc__
|
||||
|
||||
def __init__(self, schema, **kw):
|
||||
if not IInterface.providedBy(schema):
|
||||
raise WrongType
|
||||
|
||||
self.schema = schema
|
||||
super(Object, self).__init__(**kw)
|
||||
|
||||
def _validate(self, value):
|
||||
super(Object, self)._validate(value)
|
||||
|
||||
# schema has to be provided by value
|
||||
if not self.schema.providedBy(value):
|
||||
raise SchemaNotProvided
|
||||
|
||||
# check the value against schema
|
||||
errors = _validate_fields(self.schema, value)
|
||||
if errors:
|
||||
raise WrongContainedType(errors, self.__name__)
|
||||
|
||||
def set(self, object, value):
|
||||
# Announce that we're going to assign the value to the object.
|
||||
# Motivation: Widgets typically like to take care of policy-specific
|
||||
# actions, like establishing location.
|
||||
event = BeforeObjectAssignedEvent(value, self.__name__, object)
|
||||
notify(event)
|
||||
# The event subscribers are allowed to replace the object, thus we need
|
||||
# to replace our previous value.
|
||||
value = event.object
|
||||
super(Object, self).set(object, value)
|
||||
|
||||
|
||||
@implementer(IBeforeObjectAssignedEvent)
|
||||
class BeforeObjectAssignedEvent(object):
|
||||
"""An object is going to be assigned to an attribute on another object."""
|
||||
|
||||
def __init__(self, object, name, context):
|
||||
self.object = object
|
||||
self.name = name
|
||||
self.context = context
|
||||
|
||||
|
||||
@implementer(IDict)
|
||||
class Dict(MinMaxLen, Iterable):
|
||||
"""A field representing a Dict."""
|
||||
_type = dict
|
||||
key_type = None
|
||||
value_type = None
|
||||
|
||||
def __init__(self, key_type=None, value_type=None, **kw):
|
||||
super(Dict, 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(Dict, self)._validate(value)
|
||||
errors = []
|
||||
try:
|
||||
if self.value_type:
|
||||
errors = _validate_sequence(self.value_type, value.values(),
|
||||
errors)
|
||||
errors = _validate_sequence(self.key_type, value, errors)
|
||||
|
||||
if errors:
|
||||
raise WrongContainedType(errors, self.__name__)
|
||||
|
||||
finally:
|
||||
errors = None
|
||||
|
||||
def bind(self, object):
|
||||
"""See zope.schema._bootstrapinterfaces.IField."""
|
||||
clone = super(Dict, 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
|
||||
20
zope/schema/_messageid.py
Normal file
20
zope/schema/_messageid.py
Normal file
@@ -0,0 +1,20 @@
|
||||
##############################################################################
|
||||
#
|
||||
# Copyright (c) 2000 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.
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
try:
|
||||
from zope.i18nmessageid import MessageFactory
|
||||
except ImportError: # pragma: no cover
|
||||
from zope.schema._compat import text_type as _
|
||||
else: # pragma: no cover
|
||||
_ = MessageFactory("zope")
|
||||
90
zope/schema/_schema.py
Normal file
90
zope/schema/_schema.py
Normal file
@@ -0,0 +1,90 @@
|
||||
##############################################################################
|
||||
#
|
||||
# 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 convenience functions
|
||||
"""
|
||||
|
||||
import zope.interface.verify
|
||||
|
||||
|
||||
def getFieldNames(schema):
|
||||
"""Return a list of all the Field names in a schema.
|
||||
"""
|
||||
from zope.schema.interfaces import IField
|
||||
return [name for name in schema if IField.providedBy(schema[name])]
|
||||
|
||||
|
||||
def getFields(schema):
|
||||
"""Return a dictionary containing all the Fields in a schema.
|
||||
"""
|
||||
from zope.schema.interfaces import IField
|
||||
fields = {}
|
||||
for name in schema:
|
||||
attr = schema[name]
|
||||
if IField.providedBy(attr):
|
||||
fields[name] = attr
|
||||
return fields
|
||||
|
||||
|
||||
def getFieldsInOrder(schema, _field_key=lambda x: x[1].order):
|
||||
"""Return a list of (name, value) tuples in native schema order.
|
||||
"""
|
||||
return sorted(getFields(schema).items(), key=_field_key)
|
||||
|
||||
|
||||
def getFieldNamesInOrder(schema):
|
||||
"""Return a list of all the Field names in a schema in schema order.
|
||||
"""
|
||||
return [name for name, field in getFieldsInOrder(schema)]
|
||||
|
||||
|
||||
def getValidationErrors(schema, object):
|
||||
"""Return a list of all validation errors.
|
||||
"""
|
||||
errors = getSchemaValidationErrors(schema, object)
|
||||
if errors:
|
||||
return errors
|
||||
|
||||
# Only validate invariants if there were no previous errors. Previous
|
||||
# errors could be missing attributes which would most likely make an
|
||||
# invariant raise an AttributeError.
|
||||
invariant_errors = []
|
||||
try:
|
||||
schema.validateInvariants(object, invariant_errors)
|
||||
except zope.interface.exceptions.Invalid:
|
||||
# Just collect errors
|
||||
pass
|
||||
errors = [(None, e) for e in invariant_errors]
|
||||
return errors
|
||||
|
||||
|
||||
def getSchemaValidationErrors(schema, object):
|
||||
errors = []
|
||||
for name in schema.names(all=True):
|
||||
if zope.interface.interfaces.IMethod.providedBy(schema[name]):
|
||||
continue
|
||||
attribute = schema[name]
|
||||
if not zope.schema.interfaces.IField.providedBy(attribute):
|
||||
continue
|
||||
try:
|
||||
value = getattr(object, name)
|
||||
except AttributeError as error:
|
||||
# property for the given name is not implemented
|
||||
errors.append((
|
||||
name, zope.schema.interfaces.SchemaNotFullyImplemented(error)))
|
||||
else:
|
||||
try:
|
||||
attribute.bind(object).validate(value)
|
||||
except zope.schema.ValidationError as e:
|
||||
errors.append((name, e))
|
||||
return errors
|
||||
120
zope/schema/accessors.py
Normal file
120
zope/schema/accessors.py
Normal file
@@ -0,0 +1,120 @@
|
||||
##############################################################################
|
||||
#
|
||||
# Copyright (c) 2003 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.
|
||||
#
|
||||
##############################################################################
|
||||
"""
|
||||
Field accessors
|
||||
===============
|
||||
|
||||
Accessors are used to model methods used to access data defined by fields.
|
||||
Accessors are fields that work by decorating existing fields.
|
||||
|
||||
To define accessors in an interface, use the accessors function::
|
||||
|
||||
class IMyInterface(Interface):
|
||||
|
||||
getFoo, setFoo = accessors(Text(title=u'Foo', ...))
|
||||
|
||||
getBar = accessors(TextLine(title=u'Foo', readonly=True, ...)
|
||||
|
||||
|
||||
Normally a read accessor and a write accessor are defined. Only a
|
||||
read accessor is defined for read-only fields.
|
||||
|
||||
Read accessors function as access method specifications and as field
|
||||
specifications. Write accessors are solely method specifications.
|
||||
"""
|
||||
|
||||
from zope.interface import providedBy, implementedBy
|
||||
from zope.interface.interface import Method
|
||||
|
||||
|
||||
class FieldReadAccessor(Method):
|
||||
"""Field read accessor
|
||||
"""
|
||||
|
||||
# A read field accessor is a method and a field.
|
||||
# A read accessor is a decorator of a field, using the given
|
||||
# fields properties to provide meta data.
|
||||
|
||||
def __provides__(self):
|
||||
return providedBy(self.field) + implementedBy(FieldReadAccessor)
|
||||
__provides__ = property(__provides__)
|
||||
|
||||
def __init__(self, field):
|
||||
self.field = field
|
||||
Method.__init__(self, '')
|
||||
self.__doc__ = 'get %s' % field.__doc__
|
||||
|
||||
def getSignatureString(self):
|
||||
return '()'
|
||||
|
||||
def getSignatureInfo(self):
|
||||
return {'positional': (),
|
||||
'required': (),
|
||||
'optional': (),
|
||||
'varargs': None,
|
||||
'kwargs': None,
|
||||
}
|
||||
|
||||
def get(self, object):
|
||||
return getattr(object, self.__name__)()
|
||||
|
||||
def query(self, object, default=None):
|
||||
try:
|
||||
f = getattr(object, self.__name__)
|
||||
except AttributeError:
|
||||
return default
|
||||
else:
|
||||
return f()
|
||||
|
||||
def set(self, object, value):
|
||||
if self.readonly:
|
||||
raise TypeError("Can't set values on read-only fields")
|
||||
getattr(object, self.writer.__name__)(value)
|
||||
|
||||
def __getattr__(self, name):
|
||||
return getattr(self.field, name)
|
||||
|
||||
def bind(self, object):
|
||||
clone = self.__class__.__new__(self.__class__)
|
||||
clone.__dict__.update(self.__dict__)
|
||||
clone.field = self.field.bind(object)
|
||||
return clone
|
||||
|
||||
|
||||
class FieldWriteAccessor(Method):
|
||||
|
||||
def __init__(self, field):
|
||||
Method.__init__(self, '')
|
||||
self.field = field
|
||||
self.__doc__ = 'set %s' % field.__doc__
|
||||
|
||||
def getSignatureString(self):
|
||||
return '(newvalue)'
|
||||
|
||||
def getSignatureInfo(self):
|
||||
return {'positional': ('newvalue',),
|
||||
'required': ('newvalue',),
|
||||
'optional': (),
|
||||
'varargs': None,
|
||||
'kwargs': None,
|
||||
}
|
||||
|
||||
|
||||
def accessors(field):
|
||||
reader = FieldReadAccessor(field)
|
||||
yield reader
|
||||
if not field.readonly:
|
||||
writer = FieldWriteAccessor(field)
|
||||
reader.writer = writer
|
||||
yield writer
|
||||
150
zope/schema/fieldproperty.py
Normal file
150
zope/schema/fieldproperty.py
Normal file
@@ -0,0 +1,150 @@
|
||||
##############################################################################
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
##############################################################################
|
||||
"""Computed attributes based on schema fields
|
||||
"""
|
||||
|
||||
from copy import copy
|
||||
import sys
|
||||
import zope.schema
|
||||
from zope import interface
|
||||
from zope import event
|
||||
from zope.schema import interfaces
|
||||
from zope.schema._bootstrapinterfaces import NO_VALUE
|
||||
|
||||
_marker = object()
|
||||
|
||||
|
||||
@interface.implementer(interfaces.IFieldUpdatedEvent)
|
||||
class FieldUpdatedEvent(object):
|
||||
|
||||
def __init__(self, inst, field, old_value, new_value):
|
||||
self.inst = inst
|
||||
self.field = field
|
||||
self.old_value = old_value
|
||||
self.new_value = new_value
|
||||
|
||||
|
||||
class FieldProperty(object):
|
||||
"""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 queryValue(self, inst, default):
|
||||
value = inst.__dict__.get(self.__name, default)
|
||||
if value is default:
|
||||
field = self.__field.bind(inst)
|
||||
value = getattr(field, 'default', default)
|
||||
return value
|
||||
|
||||
def __set__(self, inst, value):
|
||||
field = self.__field.bind(inst)
|
||||
field.validate(value)
|
||||
if field.readonly and self.__name in inst.__dict__:
|
||||
raise ValueError(self.__name, 'field is readonly')
|
||||
oldvalue = self.queryValue(inst, NO_VALUE)
|
||||
inst.__dict__[self.__name] = value
|
||||
event.notify(FieldUpdatedEvent(inst, field, oldvalue, value))
|
||||
|
||||
def __getattr__(self, name):
|
||||
return getattr(self.__field, name)
|
||||
|
||||
|
||||
def createFieldProperties(schema, omit=[]):
|
||||
"""Creates a FieldProperty fields in `schema` on the class it is called on.
|
||||
|
||||
schema ... interface those fields should be added to class
|
||||
omit ... list of field names to be omitted in creation
|
||||
|
||||
"""
|
||||
frame = sys._getframe(1)
|
||||
for name in zope.schema.getFieldNamesInOrder(schema):
|
||||
if name in omit:
|
||||
continue
|
||||
frame.f_locals[name] = FieldProperty(schema[name])
|
||||
|
||||
|
||||
class FieldPropertyStoredThroughField(object):
|
||||
|
||||
def __init__(self, field, name=None):
|
||||
if name is None:
|
||||
name = field.__name__
|
||||
|
||||
self.field = copy(field)
|
||||
self.field.__name__ = "__st_%s_st" % self.field.__name__
|
||||
self.__name = name
|
||||
|
||||
def setValue(self, inst, field, value):
|
||||
field.set(inst, value)
|
||||
|
||||
def getValue(self, inst, field):
|
||||
return field.query(inst, _marker)
|
||||
|
||||
def queryValue(self, inst, field, default):
|
||||
return field.query(inst, default)
|
||||
|
||||
def __getattr__(self, name):
|
||||
return getattr(self.field, name)
|
||||
|
||||
def __get__(self, inst, klass):
|
||||
if inst is None:
|
||||
return self
|
||||
|
||||
field = self.field.bind(inst)
|
||||
value = self.getValue(inst, field)
|
||||
if value is _marker:
|
||||
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)
|
||||
if field.readonly:
|
||||
if self.queryValue(inst, field, _marker) is _marker:
|
||||
field.readonly = False
|
||||
self.setValue(inst, field, value)
|
||||
field.readonly = True
|
||||
return
|
||||
else:
|
||||
raise ValueError(self.__name, 'field is readonly')
|
||||
oldvalue = self.queryValue(inst, field, NO_VALUE)
|
||||
self.setValue(inst, field, value)
|
||||
event.notify(FieldUpdatedEvent(inst, self.field, oldvalue, value))
|
||||
764
zope/schema/interfaces.py
Normal file
764
zope/schema/interfaces.py
Normal file
@@ -0,0 +1,764 @@
|
||||
##############################################################################
|
||||
#
|
||||
# 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 interfaces and exceptions
|
||||
"""
|
||||
__docformat__ = "reStructuredText"
|
||||
|
||||
from zope.interface import Interface, Attribute
|
||||
from zope.interface.common.mapping import IEnumerableMapping
|
||||
|
||||
|
||||
# Import from _bootstrapinterfaces only because other packages will expect
|
||||
# to find these interfaces here.
|
||||
from zope.schema._bootstrapfields import Field
|
||||
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._bootstrapinterfaces import StopValidation
|
||||
from zope.schema._bootstrapinterfaces import ValidationError
|
||||
from zope.schema._bootstrapinterfaces import IFromUnicode
|
||||
from zope.schema._bootstrapinterfaces import RequiredMissing
|
||||
from zope.schema._bootstrapinterfaces import WrongType
|
||||
from zope.schema._bootstrapinterfaces import ConstraintNotSatisfied
|
||||
from zope.schema._bootstrapinterfaces import NotAContainer
|
||||
from zope.schema._bootstrapinterfaces import NotAnIterator
|
||||
from zope.schema._bootstrapinterfaces import TooSmall
|
||||
from zope.schema._bootstrapinterfaces import TooBig
|
||||
from zope.schema._bootstrapinterfaces import TooLong
|
||||
from zope.schema._bootstrapinterfaces import TooShort
|
||||
from zope.schema._bootstrapinterfaces import InvalidValue
|
||||
from zope.schema._bootstrapinterfaces import IContextAwareDefaultFactory
|
||||
|
||||
from zope.schema._compat import PY3
|
||||
from zope.schema._compat import u
|
||||
from zope.schema._messageid import _
|
||||
|
||||
|
||||
# pep 8 friendlyness
|
||||
StopValidation, ValidationError, IFromUnicode, RequiredMissing, WrongType
|
||||
ConstraintNotSatisfied, NotAContainer, NotAnIterator
|
||||
TooSmall, TooBig, TooLong, TooShort, InvalidValue, IContextAwareDefaultFactory
|
||||
|
||||
|
||||
class WrongContainedType(ValidationError):
|
||||
__doc__ = _("""Wrong contained type""")
|
||||
|
||||
|
||||
class NotUnique(ValidationError):
|
||||
__doc__ = _("""One or more entries of sequence are not unique.""")
|
||||
|
||||
|
||||
class SchemaNotFullyImplemented(ValidationError):
|
||||
__doc__ = _("""Schema not fully implemented""")
|
||||
|
||||
|
||||
class SchemaNotProvided(ValidationError):
|
||||
__doc__ = _("""Schema not provided""")
|
||||
|
||||
|
||||
class InvalidURI(ValidationError):
|
||||
__doc__ = _("""The specified URI is not valid.""")
|
||||
|
||||
|
||||
class InvalidId(ValidationError):
|
||||
__doc__ = _("""The specified id is not valid.""")
|
||||
|
||||
|
||||
class InvalidDottedName(ValidationError):
|
||||
__doc__ = _("""The specified dotted name is not valid.""")
|
||||
|
||||
|
||||
class Unbound(Exception):
|
||||
__doc__ = _("""The field is not bound.""")
|
||||
|
||||
|
||||
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::
|
||||
|
||||
bound = field.bind(object)
|
||||
bound.validate(value)
|
||||
|
||||
"""
|
||||
|
||||
def bind(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's location/environment.
|
||||
|
||||
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=_("Title"),
|
||||
description=_("A short summary or label"),
|
||||
default=u(""),
|
||||
required=False,
|
||||
)
|
||||
|
||||
description = Text(
|
||||
title=_("Description"),
|
||||
description=_("A description of the field"),
|
||||
default=u(""),
|
||||
required=False,
|
||||
)
|
||||
|
||||
required = Bool(
|
||||
title=_("Required"),
|
||||
description=(_("Tells whether a field requires its value to exist.")),
|
||||
default=True)
|
||||
|
||||
readonly = Bool(
|
||||
title=_("Read Only"),
|
||||
description=_("If true, the field's value cannot be changed."),
|
||||
required=False,
|
||||
default=False)
|
||||
|
||||
default = Field(
|
||||
title=_("Default Value"),
|
||||
description=_("""The field default value may be None or a legal
|
||||
field value""")
|
||||
)
|
||||
|
||||
missing_value = Field(
|
||||
title=_("Missing Value"),
|
||||
description=_("""If input for this Field is missing, and that's ok,
|
||||
then this is the value to use""")
|
||||
)
|
||||
|
||||
order = Int(
|
||||
title=_("Field Order"),
|
||||
description=_("""
|
||||
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,
|
||||
)
|
||||
|
||||
def constraint(value):
|
||||
"""Check a customized constraint 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 constraint.
|
||||
Returns true if the given value is within the Field's constraint.
|
||||
"""
|
||||
|
||||
def validate(value):
|
||||
"""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.
|
||||
"""
|
||||
|
||||
def get(object):
|
||||
"""Get the value of the field for the given object."""
|
||||
|
||||
def query(object, default=None):
|
||||
"""Query the value of the field for the given object.
|
||||
|
||||
Return the default if the value hasn't been set.
|
||||
"""
|
||||
|
||||
def set(object, value):
|
||||
"""Set the value of the field for the object
|
||||
|
||||
Raises a type error if the field is a read-only field.
|
||||
"""
|
||||
|
||||
|
||||
class IIterable(IField):
|
||||
"""Fields with a value that can be iterated over.
|
||||
|
||||
The value needs to support iteration; the implementation mechanism
|
||||
is not constrained. (Either `__iter__()` or `__getitem__()` may be
|
||||
used.)
|
||||
"""
|
||||
|
||||
|
||||
class IContainer(IField):
|
||||
"""Fields whose value allows an ``x in value`` check.
|
||||
|
||||
The value needs to support the `in` operator, but is not
|
||||
constrained in how it does so (whether it defines `__contains__()`
|
||||
or `__getitem__()` is immaterial).
|
||||
"""
|
||||
|
||||
|
||||
class IOrderable(IField):
|
||||
"""Field requiring its value to be orderable.
|
||||
|
||||
The set of value needs support a complete ordering; the
|
||||
implementation mechanism is not constrained. Either `__cmp__()` or
|
||||
'rich comparison' methods may be used.
|
||||
"""
|
||||
|
||||
|
||||
class ILen(IField):
|
||||
"""A Field requiring its value to have a length.
|
||||
|
||||
The value needs to have a conventional __len__ method.
|
||||
"""
|
||||
|
||||
|
||||
class IMinMax(IOrderable):
|
||||
"""Field requiring its value to be between min and max.
|
||||
|
||||
This implies that the value needs to support the IOrderable interface.
|
||||
"""
|
||||
|
||||
min = Field(
|
||||
title=_("Start of the range"),
|
||||
required=False,
|
||||
default=None
|
||||
)
|
||||
|
||||
max = Field(
|
||||
title=_("End of the range (including the value itself)"),
|
||||
required=False,
|
||||
default=None
|
||||
)
|
||||
|
||||
|
||||
class IMinMaxLen(ILen):
|
||||
"""Field requiring the length of its value to be within a range"""
|
||||
|
||||
min_length = Int(
|
||||
title=_("Minimum length"),
|
||||
description=_("""
|
||||
Value after whitespace processing cannot have less than
|
||||
`min_length` characters (if a string type) or elements (if
|
||||
another sequence type). 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=_("Maximum length"),
|
||||
description=_("""
|
||||
Value after whitespace processing cannot have greater
|
||||
or equal than `max_length` characters (if a string type) or
|
||||
elements (if another sequence type). If `max_length` is
|
||||
``None``, there is no maximum."""),
|
||||
required=False,
|
||||
min=0, # needs to be a positive number
|
||||
default=None)
|
||||
|
||||
|
||||
class IInterfaceField(IField):
|
||||
"""Fields with a value that is an interface (implementing
|
||||
zope.interface.Interface)."""
|
||||
|
||||
|
||||
class IBool(IField):
|
||||
"""Boolean Field."""
|
||||
|
||||
default = Bool(
|
||||
title=_("Default Value"),
|
||||
description=_("""The field default value may be None or a legal
|
||||
field value""")
|
||||
)
|
||||
|
||||
|
||||
class IBytes(IMinMaxLen, IIterable, IField):
|
||||
"""Field containing a byte string (like the python str).
|
||||
|
||||
The value might be constrained to be with length limits.
|
||||
"""
|
||||
|
||||
|
||||
class IText(IMinMaxLen, IIterable, IField):
|
||||
"""Field containing a unicode string."""
|
||||
|
||||
|
||||
# for things which are of the str type on both Python 2 and 3
|
||||
if PY3: # pragma: no cover
|
||||
INativeString = IText
|
||||
else: # pragma: no cover
|
||||
INativeString = IBytes
|
||||
|
||||
|
||||
class IASCII(INativeString):
|
||||
"""Field containing a 7-bit ASCII string. No characters > DEL
|
||||
(chr(127)) are allowed
|
||||
|
||||
The value might be constrained to be with length limits.
|
||||
"""
|
||||
|
||||
|
||||
class IBytesLine(IBytes):
|
||||
"""Field containing a byte string without newlines."""
|
||||
|
||||
|
||||
class IASCIILine(IASCII):
|
||||
"""Field containing a 7-bit ASCII string without newlines."""
|
||||
|
||||
|
||||
class ISourceText(IText):
|
||||
"""Field for source text of object."""
|
||||
|
||||
|
||||
class ITextLine(IText):
|
||||
"""Field containing a unicode string without newlines."""
|
||||
|
||||
|
||||
if PY3: # pragma: no cover
|
||||
INativeStringLine = ITextLine
|
||||
else: # pragma: no cover
|
||||
INativeStringLine = IBytesLine
|
||||
|
||||
|
||||
class IPassword(ITextLine):
|
||||
"Field containing a unicode string without newlines that is a password."
|
||||
|
||||
|
||||
class IInt(IMinMax, IField):
|
||||
"""Field containing an Integer Value."""
|
||||
|
||||
min = Int(
|
||||
title=_("Start of the range"),
|
||||
required=False,
|
||||
default=None
|
||||
)
|
||||
|
||||
max = Int(
|
||||
title=_("End of the range (excluding the value itself)"),
|
||||
required=False,
|
||||
default=None
|
||||
)
|
||||
|
||||
default = Int(
|
||||
title=_("Default Value"),
|
||||
description=_("""The field default value may be None or a legal
|
||||
field value""")
|
||||
)
|
||||
|
||||
|
||||
class IFloat(IMinMax, IField):
|
||||
"""Field containing a Float."""
|
||||
|
||||
|
||||
class IDecimal(IMinMax, IField):
|
||||
"""Field containing a Decimal."""
|
||||
|
||||
|
||||
class IDatetime(IMinMax, IField):
|
||||
"""Field containing a DateTime."""
|
||||
|
||||
|
||||
class IDate(IMinMax, IField):
|
||||
"""Field containing a date."""
|
||||
|
||||
|
||||
class ITimedelta(IMinMax, IField):
|
||||
"""Field containing a timedelta."""
|
||||
|
||||
|
||||
class ITime(IMinMax, IField):
|
||||
"""Field containing a time."""
|
||||
|
||||
|
||||
def _is_field(value):
|
||||
if not IField.providedBy(value):
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
def _fields(values):
|
||||
for value in values:
|
||||
if not _is_field(value):
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
class IURI(INativeStringLine):
|
||||
"""A field containing an absolute URI
|
||||
"""
|
||||
|
||||
|
||||
class IId(INativeStringLine):
|
||||
"""A field containing a unique identifier
|
||||
|
||||
A unique identifier is either an absolute URI or a dotted name.
|
||||
If it's a dotted name, it should have a module/package name as a prefix.
|
||||
"""
|
||||
|
||||
|
||||
class IDottedName(INativeStringLine):
|
||||
"""Dotted name field.
|
||||
|
||||
Values of DottedName fields must be Python-style dotted names.
|
||||
"""
|
||||
|
||||
min_dots = Int(
|
||||
title=_("Minimum number of dots"),
|
||||
required=True,
|
||||
min=0,
|
||||
default=0
|
||||
)
|
||||
|
||||
max_dots = Int(
|
||||
title=_("Maximum number of dots (should not be less than min_dots)"),
|
||||
required=False,
|
||||
default=None
|
||||
)
|
||||
|
||||
|
||||
class IChoice(IField):
|
||||
"""Field whose value is contained in a predefined set
|
||||
|
||||
Only one, values or vocabulary, may be specified for a given choice.
|
||||
"""
|
||||
vocabulary = Field(
|
||||
title=_("Vocabulary or source providing values"),
|
||||
description=_("The ISource, IContextSourceBinder or IBaseVocabulary "
|
||||
"object that provides values for this field."),
|
||||
required=False,
|
||||
default=None
|
||||
)
|
||||
|
||||
vocabularyName = TextLine(
|
||||
title=_("Vocabulary name"),
|
||||
description=_("Vocabulary name to lookup in the vocabulary registry"),
|
||||
required=False,
|
||||
default=None
|
||||
)
|
||||
|
||||
# Collections:
|
||||
|
||||
# Abstract
|
||||
|
||||
|
||||
class ICollection(IMinMaxLen, IIterable, IContainer):
|
||||
"""Abstract interface containing a collection value.
|
||||
|
||||
The Value must be iterable and may have a min_length/max_length.
|
||||
"""
|
||||
|
||||
value_type = Field(
|
||||
title=_("Value Type"),
|
||||
description=_("Field value items must conform to the given type, "
|
||||
"expressed via a Field."))
|
||||
|
||||
unique = Bool(
|
||||
title=_('Unique Members'),
|
||||
description=_('Specifies whether the members of the collection '
|
||||
'must be unique.'),
|
||||
default=False)
|
||||
|
||||
|
||||
class ISequence(ICollection):
|
||||
"""Abstract interface specifying that the value is ordered"""
|
||||
|
||||
|
||||
class IUnorderedCollection(ICollection):
|
||||
"""Abstract interface specifying that the value cannot be ordered"""
|
||||
|
||||
|
||||
class IAbstractSet(IUnorderedCollection):
|
||||
"""An unordered collection of unique values."""
|
||||
|
||||
unique = Attribute("This ICollection interface attribute must be True")
|
||||
|
||||
|
||||
class IAbstractBag(IUnorderedCollection):
|
||||
"""An unordered collection of values, with no limitations on whether
|
||||
members are unique"""
|
||||
|
||||
unique = Attribute("This ICollection interface attribute must be False")
|
||||
|
||||
|
||||
# Concrete
|
||||
|
||||
|
||||
class ITuple(ISequence):
|
||||
"""Field containing a value that implements the API of a conventional
|
||||
Python tuple."""
|
||||
|
||||
|
||||
class IList(ISequence):
|
||||
"""Field containing a value that implements the API of a conventional
|
||||
Python list."""
|
||||
|
||||
|
||||
class ISet(IAbstractSet):
|
||||
"""Field containing a value that implements the API of a Python2.4+ set.
|
||||
"""
|
||||
|
||||
|
||||
class IFrozenSet(IAbstractSet):
|
||||
"""Field containing a value that implements the API of a conventional
|
||||
Python 2.4+ frozenset."""
|
||||
|
||||
# (end Collections)
|
||||
|
||||
|
||||
class IObject(IField):
|
||||
"""Field containing an Object value."""
|
||||
|
||||
schema = Attribute(
|
||||
"schema",
|
||||
_("The Interface that defines the Fields comprising the Object.")
|
||||
)
|
||||
|
||||
|
||||
class IBeforeObjectAssignedEvent(Interface):
|
||||
"""An object is going to be assigned to an attribute on another object.
|
||||
|
||||
Subscribers to this event can change the object on this event to change
|
||||
what object is going to be assigned. This is useful, e.g. for wrapping
|
||||
or replacing objects before they get assigned to conform to application
|
||||
policy.
|
||||
"""
|
||||
|
||||
object = Attribute("The object that is going to be assigned.")
|
||||
|
||||
name = Attribute("The name of the attribute under which the object "
|
||||
"will be assigned.")
|
||||
|
||||
context = Attribute("The context object where the object will be "
|
||||
"assigned to.")
|
||||
|
||||
|
||||
class IDict(IMinMaxLen, IIterable, IContainer):
|
||||
"""Field containing a conventional dict.
|
||||
|
||||
The key_type and value_type fields allow specification
|
||||
of restrictions for keys and values contained in the dict.
|
||||
"""
|
||||
|
||||
key_type = Attribute(
|
||||
"key_type",
|
||||
_("Field keys must conform to the given type, expressed via a Field.")
|
||||
)
|
||||
|
||||
value_type = Attribute(
|
||||
"value_type",
|
||||
_("Field values must conform to the given type, expressed "
|
||||
"via a Field.")
|
||||
)
|
||||
|
||||
|
||||
class ITerm(Interface):
|
||||
"""Object representing a single value in a vocabulary."""
|
||||
|
||||
value = Attribute(
|
||||
"value", "The value used to represent vocabulary term in a field.")
|
||||
|
||||
|
||||
class ITokenizedTerm(ITerm):
|
||||
"""Object representing a single value in a tokenized vocabulary.
|
||||
"""
|
||||
|
||||
# Should be a ``zope.schema.ASCIILine``, but `ASCIILine` is not a bootstrap
|
||||
# field.
|
||||
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 ITitledTokenizedTerm(ITokenizedTerm):
|
||||
"""A tokenized term that includes a title."""
|
||||
|
||||
title = TextLine(title=_("Title"))
|
||||
|
||||
|
||||
class ISource(Interface):
|
||||
"""A set of values from which to choose
|
||||
|
||||
Sources represent sets of values. They are used to specify the
|
||||
source for choice fields.
|
||||
|
||||
Sources can be large (even infinite), in which case, they need to
|
||||
be queried to find out what their values are.
|
||||
"""
|
||||
|
||||
def __contains__(value):
|
||||
"""Return whether the value is available in this source
|
||||
"""
|
||||
|
||||
|
||||
class ISourceQueriables(Interface):
|
||||
"""A collection of objects for querying sources
|
||||
"""
|
||||
|
||||
def getQueriables():
|
||||
"""Return an iterable of objects that can be queried
|
||||
|
||||
The returned obects should be two-tuples with:
|
||||
|
||||
- A unicode id
|
||||
|
||||
The id must uniquely identify the queriable object within
|
||||
the set of queriable objects. Furthermore, in subsequent
|
||||
calls, the same id should be used for a given queriable
|
||||
object.
|
||||
|
||||
- A queriable object
|
||||
|
||||
This is an object for which there is a view provided for
|
||||
searching for items.
|
||||
|
||||
"""
|
||||
|
||||
|
||||
class IContextSourceBinder(Interface):
|
||||
def __call__(context):
|
||||
"""Return a context-bound instance that implements ISource.
|
||||
"""
|
||||
|
||||
|
||||
class IBaseVocabulary(ISource):
|
||||
"""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 getTerm(value):
|
||||
"""Return the ITerm object for the term 'value'.
|
||||
|
||||
If 'value' is not a valid term, this method raises LookupError.
|
||||
"""
|
||||
|
||||
|
||||
class IIterableSource(ISource):
|
||||
"""Source which supports iteration over allowed values.
|
||||
|
||||
The objects iteration provides must be values from the source.
|
||||
"""
|
||||
|
||||
def __iter__():
|
||||
"""Return an iterator which provides the values from the source."""
|
||||
|
||||
def __len__():
|
||||
"""Return the number of valid values, or sys.maxint."""
|
||||
|
||||
|
||||
# BBB vocabularies are pending deprecation, hopefully in 3.3
|
||||
class IIterableVocabulary(Interface):
|
||||
"""Vocabulary which supports iteration over allowed values.
|
||||
|
||||
The objects iteration provides must conform to the ITerm
|
||||
interface.
|
||||
"""
|
||||
|
||||
def __iter__():
|
||||
"""Return an iterator which provides the terms from the vocabulary."""
|
||||
|
||||
def __len__():
|
||||
"""Return the number of valid terms, or sys.maxint."""
|
||||
|
||||
|
||||
class IVocabulary(IIterableVocabulary, IBaseVocabulary):
|
||||
"""Vocabulary which is iterable."""
|
||||
|
||||
|
||||
class IVocabularyTokenized(IVocabulary):
|
||||
"""Vocabulary that provides support for tokenized representation.
|
||||
|
||||
Terms returned from getTerm() and provided by iteration must
|
||||
conform to ITokenizedTerm.
|
||||
"""
|
||||
|
||||
def getTermByToken(token):
|
||||
"""Return an ITokenizedTerm for the passed-in token.
|
||||
|
||||
If `token` is not represented in the vocabulary, `LookupError`
|
||||
is raised.
|
||||
"""
|
||||
|
||||
|
||||
class ITreeVocabulary(IVocabularyTokenized, IEnumerableMapping):
|
||||
"""A tokenized vocabulary with a tree-like structure.
|
||||
|
||||
The tree is implemented as dictionary, with keys being ITokenizedTerm
|
||||
terms and the values being similar dictionaries. Leaf values are empty
|
||||
dictionaries.
|
||||
"""
|
||||
|
||||
|
||||
class IVocabularyRegistry(Interface):
|
||||
"""Registry that provides IBaseVocabulary objects for specific fields.
|
||||
"""
|
||||
|
||||
def get(object, name):
|
||||
"""Return the vocabulary named 'name' for the content object
|
||||
'object'.
|
||||
|
||||
When the vocabulary cannot be found, LookupError is raised.
|
||||
"""
|
||||
|
||||
|
||||
class IVocabularyFactory(Interface):
|
||||
"""Can create vocabularies."""
|
||||
|
||||
def __call__(context):
|
||||
"""The context provides a location that the vocabulary can make use of.
|
||||
"""
|
||||
|
||||
|
||||
class IFieldEvent(Interface):
|
||||
|
||||
field = Attribute("The field that has been changed")
|
||||
|
||||
object = Attribute("The object containing the field")
|
||||
|
||||
|
||||
class IFieldUpdatedEvent(IFieldEvent):
|
||||
"""
|
||||
A field has been modified
|
||||
|
||||
Subscribers will get the old and the new value together with the field
|
||||
"""
|
||||
|
||||
old_value = Attribute("The value of the field before modification")
|
||||
|
||||
new_value = Attribute("The value of the field after modification")
|
||||
49
zope/schema/tests/__init__.py
Normal file
49
zope/schema/tests/__init__.py
Normal file
@@ -0,0 +1,49 @@
|
||||
#
|
||||
# This file is necessary to make this directory a package.
|
||||
|
||||
import re
|
||||
|
||||
from zope.schema._compat import PY3
|
||||
from zope.testing import renormalizing
|
||||
|
||||
if PY3:
|
||||
py3_checker = renormalizing.RENormalizing([
|
||||
(re.compile(r"u'([^']*)'"),
|
||||
r"'\1'"),
|
||||
(re.compile(r"^b'([^']*)'"),
|
||||
r"'\1'"),
|
||||
(re.compile(r"([^'])b'([^']*)'"),
|
||||
r"\1'\2'"),
|
||||
(re.compile(r"<class 'bytes'>"),
|
||||
r"<type 'str'>"),
|
||||
(re.compile(r"<class 'str'>"),
|
||||
r"<type 'unicode'>"),
|
||||
(re.compile(r"zope.schema._bootstrapinterfaces.InvalidValue"),
|
||||
r"InvalidValue"),
|
||||
(re.compile(r"zope.schema.interfaces.InvalidId: '([^']*)'"),
|
||||
r"InvalidId: \1"),
|
||||
(re.compile(r"zope.schema.interfaces.InvalidId:"),
|
||||
r"InvalidId:"),
|
||||
(re.compile(r"zope.schema.interfaces.InvalidURI: '([^']*)'"),
|
||||
r"InvalidURI: \1"),
|
||||
(re.compile(r"zope.schema.interfaces.InvalidURI:"),
|
||||
r"InvalidURI:"),
|
||||
(re.compile(r"zope.schema.interfaces.InvalidDottedName: '([^']*)'"),
|
||||
r"InvalidDottedName: \1"),
|
||||
(re.compile(r"zope.schema.interfaces.InvalidDottedName:"),
|
||||
r"InvalidDottedName:"),
|
||||
(re.compile(
|
||||
r"zope.schema._bootstrapinterfaces.ConstraintNotSatisfied: '([^']*)'"
|
||||
),
|
||||
r"ConstraintNotSatisfied: \1"),
|
||||
(re.compile(
|
||||
r"zope.schema._bootstrapinterfaces.ConstraintNotSatisfied:"),
|
||||
r"ConstraintNotSatisfied:"),
|
||||
(re.compile(r"zope.schema._bootstrapinterfaces.WrongType:"),
|
||||
r"WrongType:"),
|
||||
])
|
||||
else:
|
||||
py3_checker = renormalizing.RENormalizing([
|
||||
(re.compile(r"([^'])b'([^']*)'"),
|
||||
r"\1'\2'"),
|
||||
])
|
||||
130
zope/schema/tests/states.py
Normal file
130
zope/schema/tests/states.py
Normal file
@@ -0,0 +1,130 @@
|
||||
##############################################################################
|
||||
#
|
||||
# Copyright (c) 2003 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.
|
||||
#
|
||||
##############################################################################
|
||||
"""Sample vocabulary supporting state abbreviations.
|
||||
"""
|
||||
from zope.schema._compat import u
|
||||
from zope.interface import implementer
|
||||
from zope.schema import interfaces
|
||||
from zope.schema import Choice
|
||||
|
||||
# This table is based on information from the United States Postal Service:
|
||||
# http://www.usps.com/ncsc/lookups/abbreviations.html#states
|
||||
_states = {
|
||||
'AL': u('Alabama'),
|
||||
'AK': u('Alaska'),
|
||||
'AS': u('American Samoa'),
|
||||
'AZ': u('Arizona'),
|
||||
'AR': u('Arkansas'),
|
||||
'CA': u('California'),
|
||||
'CO': u('Colorado'),
|
||||
'CT': u('Connecticut'),
|
||||
'DE': u('Delaware'),
|
||||
'DC': u('District of Columbia'),
|
||||
'FM': u('Federated States of Micronesia'),
|
||||
'FL': u('Florida'),
|
||||
'GA': u('Georgia'),
|
||||
'GU': u('Guam'),
|
||||
'HI': u('Hawaii'),
|
||||
'ID': u('Idaho'),
|
||||
'IL': u('Illinois'),
|
||||
'IN': u('Indiana'),
|
||||
'IA': u('Iowa'),
|
||||
'KS': u('Kansas'),
|
||||
'KY': u('Kentucky'),
|
||||
'LA': u('Louisiana'),
|
||||
'ME': u('Maine'),
|
||||
'MH': u('Marshall Islands'),
|
||||
'MD': u('Maryland'),
|
||||
'MA': u('Massachusetts'),
|
||||
'MI': u('Michigan'),
|
||||
'MN': u('Minnesota'),
|
||||
'MS': u('Mississippi'),
|
||||
'MO': u('Missouri'),
|
||||
'MT': u('Montana'),
|
||||
'NE': u('Nebraska'),
|
||||
'NV': u('Nevada'),
|
||||
'NH': u('New Hampshire'),
|
||||
'NJ': u('New Jersey'),
|
||||
'NM': u('New Mexico'),
|
||||
'NY': u('New York'),
|
||||
'NC': u('North Carolina'),
|
||||
'ND': u('North Dakota'),
|
||||
'MP': u('Northern Mariana Islands'),
|
||||
'OH': u('Ohio'),
|
||||
'OK': u('Oklahoma'),
|
||||
'OR': u('Oregon'),
|
||||
'PW': u('Palau'),
|
||||
'PA': u('Pennsylvania'),
|
||||
'PR': u('Puerto Rico'),
|
||||
'RI': u('Rhode Island'),
|
||||
'SC': u('South Carolina'),
|
||||
'SD': u('South Dakota'),
|
||||
'TN': u('Tennessee'),
|
||||
'TX': u('Texas'),
|
||||
'UT': u('Utah'),
|
||||
'VT': u('Vermont'),
|
||||
'VI': u('Virgin Islands'),
|
||||
'VA': u('Virginia'),
|
||||
'WA': u('Washington'),
|
||||
'WV': u('West Virginia'),
|
||||
'WI': u('Wisconsin'),
|
||||
'WY': u('Wyoming'),
|
||||
}
|
||||
|
||||
|
||||
@implementer(interfaces.ITerm)
|
||||
class State(object):
|
||||
__slots__ = 'value', 'title'
|
||||
|
||||
def __init__(self, value, title):
|
||||
self.value = value
|
||||
self.title = title
|
||||
|
||||
for v, p in _states.items():
|
||||
_states[v] = State(v, p)
|
||||
|
||||
|
||||
class IStateVocabulary(interfaces.IVocabulary):
|
||||
"""Vocabularies that support the states database conform to this."""
|
||||
|
||||
|
||||
@implementer(IStateVocabulary)
|
||||
class StateVocabulary(object):
|
||||
__slots__ = ()
|
||||
|
||||
def __init__(self, object=None):
|
||||
pass
|
||||
|
||||
def __contains__(self, value):
|
||||
return value in _states
|
||||
|
||||
def __iter__(self):
|
||||
return iter(_states.values())
|
||||
|
||||
def __len__(self):
|
||||
return len(_states)
|
||||
|
||||
def getTerm(self, value):
|
||||
return _states[value]
|
||||
|
||||
|
||||
class StateSelectionField(Choice):
|
||||
|
||||
vocabulary = StateVocabulary()
|
||||
|
||||
def __init__(self, **kw):
|
||||
super(StateSelectionField, self).__init__(
|
||||
vocabulary=StateSelectionField.vocabulary,
|
||||
**kw)
|
||||
self.vocabularyName = "states"
|
||||
822
zope/schema/tests/test__bootstrapfields.py
Normal file
822
zope/schema/tests/test__bootstrapfields.py
Normal file
@@ -0,0 +1,822 @@
|
||||
##############################################################################
|
||||
#
|
||||
# Copyright (c) 2012 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.
|
||||
#
|
||||
##############################################################################
|
||||
import unittest
|
||||
|
||||
|
||||
class ValidatedPropertyTests(unittest.TestCase):
|
||||
|
||||
def _getTargetClass(self):
|
||||
from zope.schema._bootstrapfields import ValidatedProperty
|
||||
return ValidatedProperty
|
||||
|
||||
def _makeOne(self, *args, **kw):
|
||||
return self._getTargetClass()(*args, **kw)
|
||||
|
||||
def test___set___not_missing_w_check(self):
|
||||
_checked = []
|
||||
|
||||
def _check(inst, value):
|
||||
_checked.append((inst, value))
|
||||
|
||||
class Test(DummyInst):
|
||||
_prop = None
|
||||
prop = self._makeOne('_prop', _check)
|
||||
inst = Test()
|
||||
inst.prop = 'PROP'
|
||||
self.assertEqual(inst._prop, 'PROP')
|
||||
self.assertEqual(_checked, [(inst, 'PROP')])
|
||||
|
||||
def test___set___not_missing_wo_check(self):
|
||||
class Test(DummyInst):
|
||||
_prop = None
|
||||
prop = self._makeOne('_prop')
|
||||
inst = Test(ValueError)
|
||||
|
||||
def _provoke(inst):
|
||||
inst.prop = 'PROP'
|
||||
self.assertRaises(ValueError, _provoke, inst)
|
||||
self.assertEqual(inst._prop, None)
|
||||
|
||||
def test___set___w_missing_wo_check(self):
|
||||
class Test(DummyInst):
|
||||
_prop = None
|
||||
prop = self._makeOne('_prop')
|
||||
inst = Test(ValueError)
|
||||
inst.prop = DummyInst.missing_value
|
||||
self.assertEqual(inst._prop, DummyInst.missing_value)
|
||||
|
||||
def test___get__(self):
|
||||
class Test(DummyInst):
|
||||
_prop = None
|
||||
prop = self._makeOne('_prop')
|
||||
inst = Test()
|
||||
inst._prop = 'PROP'
|
||||
self.assertEqual(inst.prop, 'PROP')
|
||||
|
||||
|
||||
class DefaultPropertyTests(unittest.TestCase):
|
||||
|
||||
def _getTargetClass(self):
|
||||
from zope.schema._bootstrapfields import DefaultProperty
|
||||
return DefaultProperty
|
||||
|
||||
def _makeOne(self, *args, **kw):
|
||||
return self._getTargetClass()(*args, **kw)
|
||||
|
||||
def test___get___wo_defaultFactory_miss(self):
|
||||
class Test(DummyInst):
|
||||
_prop = None
|
||||
prop = self._makeOne('_prop')
|
||||
inst = Test()
|
||||
inst.defaultFactory = None
|
||||
|
||||
def _provoke(inst):
|
||||
return inst.prop
|
||||
self.assertRaises(KeyError, _provoke, inst)
|
||||
|
||||
def test___get___wo_defaultFactory_hit(self):
|
||||
class Test(DummyInst):
|
||||
_prop = None
|
||||
prop = self._makeOne('_prop')
|
||||
inst = Test()
|
||||
inst.defaultFactory = None
|
||||
inst._prop = 'PROP'
|
||||
self.assertEqual(inst.prop, 'PROP')
|
||||
|
||||
def test__get___wo_defaultFactory_in_dict(self):
|
||||
class Test(DummyInst):
|
||||
_prop = None
|
||||
prop = self._makeOne('_prop')
|
||||
inst = Test()
|
||||
inst._prop = 'PROP'
|
||||
self.assertEqual(inst.prop, 'PROP')
|
||||
|
||||
def test___get___w_defaultFactory_not_ICAF_no_check(self):
|
||||
class Test(DummyInst):
|
||||
_prop = None
|
||||
prop = self._makeOne('_prop')
|
||||
inst = Test(ValueError)
|
||||
|
||||
def _factory():
|
||||
return 'PROP'
|
||||
inst.defaultFactory = _factory
|
||||
|
||||
def _provoke(inst):
|
||||
return inst.prop
|
||||
self.assertRaises(ValueError, _provoke, inst)
|
||||
|
||||
def test___get___w_defaultFactory_w_ICAF_w_check(self):
|
||||
from zope.interface import directlyProvides
|
||||
from zope.schema._bootstrapinterfaces \
|
||||
import IContextAwareDefaultFactory
|
||||
_checked = []
|
||||
|
||||
def _check(inst, value):
|
||||
_checked.append((inst, value))
|
||||
|
||||
class Test(DummyInst):
|
||||
_prop = None
|
||||
prop = self._makeOne('_prop', _check)
|
||||
inst = Test(ValueError)
|
||||
inst.context = object()
|
||||
_called_with = []
|
||||
|
||||
def _factory(context):
|
||||
_called_with.append(context)
|
||||
return 'PROP'
|
||||
directlyProvides(_factory, IContextAwareDefaultFactory)
|
||||
inst.defaultFactory = _factory
|
||||
self.assertEqual(inst.prop, 'PROP')
|
||||
self.assertEqual(_checked, [(inst, 'PROP')])
|
||||
self.assertEqual(_called_with, [inst.context])
|
||||
|
||||
|
||||
class FieldTests(unittest.TestCase):
|
||||
|
||||
def _getTargetClass(self):
|
||||
from zope.schema._bootstrapfields import Field
|
||||
return Field
|
||||
|
||||
def _makeOne(self, *args, **kw):
|
||||
return self._getTargetClass()(*args, **kw)
|
||||
|
||||
def test_ctor_defaults(self):
|
||||
from zope.schema._compat import u
|
||||
field = self._makeOne()
|
||||
self.assertEqual(field.__name__, u(''))
|
||||
self.assertEqual(field.__doc__, u(''))
|
||||
self.assertEqual(field.title, u(''))
|
||||
self.assertEqual(field.description, u(''))
|
||||
self.assertEqual(field.required, True)
|
||||
self.assertEqual(field.readonly, False)
|
||||
self.assertEqual(field.constraint(object()), True)
|
||||
self.assertEqual(field.default, None)
|
||||
self.assertEqual(field.defaultFactory, None)
|
||||
self.assertEqual(field.missing_value, None)
|
||||
self.assertEqual(field.context, None)
|
||||
|
||||
def test_ctor_w_title_wo_description(self):
|
||||
from zope.schema._compat import u
|
||||
field = self._makeOne(u('TITLE'))
|
||||
self.assertEqual(field.__name__, u(''))
|
||||
self.assertEqual(field.__doc__, u('TITLE'))
|
||||
self.assertEqual(field.title, u('TITLE'))
|
||||
self.assertEqual(field.description, u(''))
|
||||
|
||||
def test_ctor_wo_title_w_description(self):
|
||||
from zope.schema._compat import u
|
||||
field = self._makeOne(description=u('DESC'))
|
||||
self.assertEqual(field.__name__, u(''))
|
||||
self.assertEqual(field.__doc__, u('DESC'))
|
||||
self.assertEqual(field.title, u(''))
|
||||
self.assertEqual(field.description, u('DESC'))
|
||||
|
||||
def test_ctor_w_both_title_and_description(self):
|
||||
from zope.schema._compat import u
|
||||
field = self._makeOne(u('TITLE'), u('DESC'), u('NAME'))
|
||||
self.assertEqual(field.__name__, u('NAME'))
|
||||
self.assertEqual(field.__doc__, u('TITLE\n\nDESC'))
|
||||
self.assertEqual(field.title, u('TITLE'))
|
||||
self.assertEqual(field.description, u('DESC'))
|
||||
|
||||
def test_ctor_order_madness(self):
|
||||
klass = self._getTargetClass()
|
||||
order_before = klass.order
|
||||
field = self._makeOne()
|
||||
order_after = klass.order
|
||||
self.assertEqual(order_after, order_before + 1)
|
||||
self.assertEqual(field.order, order_after)
|
||||
|
||||
def test_explicit_required_readonly_missingValue(self):
|
||||
obj = object()
|
||||
field = self._makeOne(required=False, readonly=True, missing_value=obj)
|
||||
self.assertEqual(field.required, False)
|
||||
self.assertEqual(field.readonly, True)
|
||||
self.assertEqual(field.missing_value, obj)
|
||||
|
||||
def test_explicit_constraint_default(self):
|
||||
_called_with = []
|
||||
obj = object()
|
||||
|
||||
def _constraint(value):
|
||||
_called_with.append(value)
|
||||
return value is obj
|
||||
field = self._makeOne(
|
||||
required=False, readonly=True, constraint=_constraint, default=obj
|
||||
)
|
||||
self.assertEqual(field.required, False)
|
||||
self.assertEqual(field.readonly, True)
|
||||
self.assertEqual(_called_with, [obj])
|
||||
self.assertEqual(field.constraint(self), False)
|
||||
self.assertEqual(_called_with, [obj, self])
|
||||
self.assertEqual(field.default, obj)
|
||||
|
||||
def test_explicit_defaultFactory(self):
|
||||
_called_with = []
|
||||
obj = object()
|
||||
|
||||
def _constraint(value):
|
||||
_called_with.append(value)
|
||||
return value is obj
|
||||
|
||||
def _factory():
|
||||
return obj
|
||||
field = self._makeOne(
|
||||
required=False,
|
||||
readonly=True,
|
||||
constraint=_constraint,
|
||||
defaultFactory=_factory,
|
||||
)
|
||||
self.assertEqual(field.required, False)
|
||||
self.assertEqual(field.readonly, True)
|
||||
self.assertEqual(field.constraint(self), False)
|
||||
self.assertEqual(_called_with, [self])
|
||||
self.assertEqual(field.default, obj)
|
||||
self.assertEqual(_called_with, [self, obj])
|
||||
self.assertEqual(field.defaultFactory, _factory)
|
||||
|
||||
def test_explicit_defaultFactory_returning_missing_value(self):
|
||||
def _factory():
|
||||
return None
|
||||
field = self._makeOne(required=True,
|
||||
defaultFactory=_factory)
|
||||
self.assertEqual(field.default, None)
|
||||
|
||||
def test_bind(self):
|
||||
obj = object()
|
||||
field = self._makeOne()
|
||||
bound = field.bind(obj)
|
||||
self.assertEqual(bound.context, obj)
|
||||
expected = dict(field.__dict__)
|
||||
found = dict(bound.__dict__)
|
||||
found.pop('context')
|
||||
self.assertEqual(found, expected)
|
||||
self.assertEqual(bound.__class__, field.__class__)
|
||||
|
||||
def test_validate_missing_not_required(self):
|
||||
missing = object()
|
||||
|
||||
def _fail(value):
|
||||
return False
|
||||
field = self._makeOne(
|
||||
required=False, missing_value=missing, constraint=_fail,
|
||||
)
|
||||
self.assertEqual(field.validate(missing), None) # doesn't raise
|
||||
|
||||
def test_validate_missing_and_required(self):
|
||||
from zope.schema._bootstrapinterfaces import RequiredMissing
|
||||
missing = object()
|
||||
|
||||
def _fail(value):
|
||||
return False
|
||||
field = self._makeOne(
|
||||
required=True, missing_value=missing, constraint=_fail,
|
||||
)
|
||||
self.assertRaises(RequiredMissing, field.validate, missing)
|
||||
|
||||
def test_validate_wrong_type(self):
|
||||
from zope.schema._bootstrapinterfaces import WrongType
|
||||
|
||||
def _fail(value):
|
||||
return False
|
||||
field = self._makeOne(required=True, constraint=_fail)
|
||||
field._type = str
|
||||
self.assertRaises(WrongType, field.validate, 1)
|
||||
|
||||
def test_validate_constraint_fails(self):
|
||||
from zope.schema._bootstrapinterfaces import ConstraintNotSatisfied
|
||||
|
||||
def _fail(value):
|
||||
return False
|
||||
field = self._makeOne(required=True, constraint=_fail)
|
||||
field._type = int
|
||||
self.assertRaises(ConstraintNotSatisfied, field.validate, 1)
|
||||
|
||||
def test_validate_constraint_raises_StopValidation(self):
|
||||
from zope.schema._bootstrapinterfaces import StopValidation
|
||||
|
||||
def _fail(value):
|
||||
raise StopValidation
|
||||
field = self._makeOne(required=True, constraint=_fail)
|
||||
field._type = int
|
||||
field.validate(1) # doesn't raise
|
||||
|
||||
def test___eq___different_type(self):
|
||||
left = self._makeOne()
|
||||
|
||||
class Derived(self._getTargetClass()):
|
||||
pass
|
||||
right = Derived()
|
||||
self.assertEqual(left == right, False)
|
||||
self.assertEqual(left != right, True)
|
||||
|
||||
def test___eq___same_type_different_attrs(self):
|
||||
left = self._makeOne(required=True)
|
||||
right = self._makeOne(required=False)
|
||||
self.assertEqual(left == right, False)
|
||||
self.assertEqual(left != right, True)
|
||||
|
||||
def test___eq___same_type_same_attrs(self):
|
||||
left = self._makeOne()
|
||||
right = self._makeOne()
|
||||
self.assertEqual(left == right, True)
|
||||
self.assertEqual(left != right, False)
|
||||
|
||||
def test_get_miss(self):
|
||||
field = self._makeOne(__name__='nonesuch')
|
||||
inst = DummyInst()
|
||||
self.assertRaises(AttributeError, field.get, inst)
|
||||
|
||||
def test_get_hit(self):
|
||||
field = self._makeOne(__name__='extant')
|
||||
inst = DummyInst()
|
||||
inst.extant = 'EXTANT'
|
||||
self.assertEqual(field.get(inst), 'EXTANT')
|
||||
|
||||
def test_query_miss_no_default(self):
|
||||
field = self._makeOne(__name__='nonesuch')
|
||||
inst = DummyInst()
|
||||
self.assertEqual(field.query(inst), None)
|
||||
|
||||
def test_query_miss_w_default(self):
|
||||
field = self._makeOne(__name__='nonesuch')
|
||||
inst = DummyInst()
|
||||
self.assertEqual(field.query(inst, 'DEFAULT'), 'DEFAULT')
|
||||
|
||||
def test_query_hit(self):
|
||||
field = self._makeOne(__name__='extant')
|
||||
inst = DummyInst()
|
||||
inst.extant = 'EXTANT'
|
||||
self.assertEqual(field.query(inst), 'EXTANT')
|
||||
|
||||
def test_set_readonly(self):
|
||||
field = self._makeOne(__name__='lirame', readonly=True)
|
||||
inst = DummyInst()
|
||||
self.assertRaises(TypeError, field.set, inst, 'VALUE')
|
||||
|
||||
def test_set_hit(self):
|
||||
field = self._makeOne(__name__='extant')
|
||||
inst = DummyInst()
|
||||
inst.extant = 'BEFORE'
|
||||
field.set(inst, 'AFTER')
|
||||
self.assertEqual(inst.extant, 'AFTER')
|
||||
|
||||
|
||||
class ContainerTests(unittest.TestCase):
|
||||
|
||||
def _getTargetClass(self):
|
||||
from zope.schema._bootstrapfields import Container
|
||||
return Container
|
||||
|
||||
def _makeOne(self, *args, **kw):
|
||||
return self._getTargetClass()(*args, **kw)
|
||||
|
||||
def test_validate_not_required(self):
|
||||
field = self._makeOne(required=False)
|
||||
field.validate(None)
|
||||
|
||||
def test_validate_required(self):
|
||||
from zope.schema.interfaces import RequiredMissing
|
||||
field = self._makeOne()
|
||||
self.assertRaises(RequiredMissing, field.validate, None)
|
||||
|
||||
def test__validate_not_collection_not_iterable(self):
|
||||
from zope.schema._bootstrapinterfaces import NotAContainer
|
||||
cont = self._makeOne()
|
||||
self.assertRaises(NotAContainer, cont._validate, object())
|
||||
|
||||
def test__validate_collection_but_not_iterable(self):
|
||||
cont = self._makeOne()
|
||||
|
||||
class Dummy(object):
|
||||
def __contains__(self, item):
|
||||
return False
|
||||
cont._validate(Dummy()) # doesn't raise
|
||||
|
||||
def test__validate_not_collection_but_iterable(self):
|
||||
cont = self._makeOne()
|
||||
|
||||
class Dummy(object):
|
||||
def __iter__(self):
|
||||
return iter(())
|
||||
cont._validate(Dummy()) # doesn't raise
|
||||
|
||||
def test__validate_w_collections(self):
|
||||
cont = self._makeOne()
|
||||
cont._validate(()) # doesn't raise
|
||||
cont._validate([]) # doesn't raise
|
||||
cont._validate('') # doesn't raise
|
||||
cont._validate({}) # doesn't raise
|
||||
|
||||
|
||||
class IterableTests(ContainerTests):
|
||||
|
||||
def _getTargetClass(self):
|
||||
from zope.schema._bootstrapfields import Iterable
|
||||
return Iterable
|
||||
|
||||
def test__validate_collection_but_not_iterable(self):
|
||||
from zope.schema._bootstrapinterfaces import NotAnIterator
|
||||
itr = self._makeOne()
|
||||
|
||||
class Dummy(object):
|
||||
def __contains__(self, item):
|
||||
return False
|
||||
self.assertRaises(NotAnIterator, itr._validate, Dummy())
|
||||
|
||||
|
||||
class OrderableTests(unittest.TestCase):
|
||||
|
||||
def _getTargetClass(self):
|
||||
from zope.schema._bootstrapfields import Orderable
|
||||
return Orderable
|
||||
|
||||
def _makeOne(self, *args, **kw):
|
||||
# Orderable is a mixin for a type derived from Field
|
||||
from zope.schema._bootstrapfields import Field
|
||||
|
||||
class Mixed(self._getTargetClass(), Field):
|
||||
pass
|
||||
return Mixed(*args, **kw)
|
||||
|
||||
def test_ctor_defaults(self):
|
||||
ordb = self._makeOne()
|
||||
self.assertEqual(ordb.min, None)
|
||||
self.assertEqual(ordb.max, None)
|
||||
self.assertEqual(ordb.default, None)
|
||||
|
||||
def test_ctor_default_too_small(self):
|
||||
# This test exercises _validate, too
|
||||
from zope.schema._bootstrapinterfaces import TooSmall
|
||||
self.assertRaises(TooSmall, self._makeOne, min=0, default=-1)
|
||||
|
||||
def test_ctor_default_too_large(self):
|
||||
# This test exercises _validate, too
|
||||
from zope.schema._bootstrapinterfaces import TooBig
|
||||
self.assertRaises(TooBig, self._makeOne, max=10, default=11)
|
||||
|
||||
|
||||
class MinMaxLenTests(unittest.TestCase):
|
||||
|
||||
def _getTargetClass(self):
|
||||
from zope.schema._bootstrapfields import MinMaxLen
|
||||
return MinMaxLen
|
||||
|
||||
def _makeOne(self, *args, **kw):
|
||||
# MinMaxLen is a mixin for a type derived from Field
|
||||
from zope.schema._bootstrapfields import Field
|
||||
|
||||
class Mixed(self._getTargetClass(), Field):
|
||||
pass
|
||||
return Mixed(*args, **kw)
|
||||
|
||||
def test_ctor_defaults(self):
|
||||
mml = self._makeOne()
|
||||
self.assertEqual(mml.min_length, 0)
|
||||
self.assertEqual(mml.max_length, None)
|
||||
|
||||
def test_validate_too_short(self):
|
||||
from zope.schema._bootstrapinterfaces import TooShort
|
||||
mml = self._makeOne(min_length=1)
|
||||
self.assertRaises(TooShort, mml._validate, ())
|
||||
|
||||
def test_validate_too_long(self):
|
||||
from zope.schema._bootstrapinterfaces import TooLong
|
||||
mml = self._makeOne(max_length=2)
|
||||
self.assertRaises(TooLong, mml._validate, (0, 1, 2))
|
||||
|
||||
|
||||
class TextTests(unittest.TestCase):
|
||||
|
||||
def _getTargetClass(self):
|
||||
from zope.schema._bootstrapfields import Text
|
||||
return Text
|
||||
|
||||
def _makeOne(self, *args, **kw):
|
||||
return self._getTargetClass()(*args, **kw)
|
||||
|
||||
def test_ctor_defaults(self):
|
||||
from zope.schema._compat import text_type
|
||||
txt = self._makeOne()
|
||||
self.assertEqual(txt._type, text_type)
|
||||
|
||||
def test_validate_wrong_types(self):
|
||||
from zope.schema.interfaces import WrongType
|
||||
from zope.schema._compat import b
|
||||
field = self._makeOne()
|
||||
self.assertRaises(WrongType, field.validate, b(''))
|
||||
self.assertRaises(WrongType, field.validate, 1)
|
||||
self.assertRaises(WrongType, field.validate, 1.0)
|
||||
self.assertRaises(WrongType, field.validate, ())
|
||||
self.assertRaises(WrongType, field.validate, [])
|
||||
self.assertRaises(WrongType, field.validate, {})
|
||||
self.assertRaises(WrongType, field.validate, set())
|
||||
self.assertRaises(WrongType, field.validate, frozenset())
|
||||
self.assertRaises(WrongType, field.validate, object())
|
||||
|
||||
def test_validate_w_invalid_default(self):
|
||||
from zope.schema._compat import b
|
||||
from zope.schema.interfaces import ValidationError
|
||||
self.assertRaises(ValidationError, self._makeOne, default=b(''))
|
||||
|
||||
def test_validate_not_required(self):
|
||||
from zope.schema._compat import u
|
||||
field = self._makeOne(required=False)
|
||||
field.validate(u(''))
|
||||
field.validate(u('abc'))
|
||||
field.validate(u('abc\ndef'))
|
||||
field.validate(None)
|
||||
|
||||
def test_validate_required(self):
|
||||
from zope.schema.interfaces import RequiredMissing
|
||||
from zope.schema._compat import u
|
||||
field = self._makeOne()
|
||||
field.validate(u(''))
|
||||
field.validate(u('abc'))
|
||||
field.validate(u('abc\ndef'))
|
||||
self.assertRaises(RequiredMissing, field.validate, None)
|
||||
|
||||
def test_fromUnicode_miss(self):
|
||||
from zope.schema._bootstrapinterfaces import WrongType
|
||||
from zope.schema._compat import b
|
||||
deadbeef = b('DEADBEEF')
|
||||
txt = self._makeOne()
|
||||
self.assertRaises(WrongType, txt.fromUnicode, deadbeef)
|
||||
|
||||
def test_fromUnicode_hit(self):
|
||||
from zope.schema._compat import u
|
||||
deadbeef = u('DEADBEEF')
|
||||
txt = self._makeOne()
|
||||
self.assertEqual(txt.fromUnicode(deadbeef), deadbeef)
|
||||
|
||||
|
||||
class TextLineTests(unittest.TestCase):
|
||||
|
||||
def _getTargetClass(self):
|
||||
from zope.schema._field import TextLine
|
||||
return TextLine
|
||||
|
||||
def _makeOne(self, *args, **kw):
|
||||
return self._getTargetClass()(*args, **kw)
|
||||
|
||||
def test_class_conforms_to_ITextLine(self):
|
||||
from zope.interface.verify import verifyClass
|
||||
from zope.schema.interfaces import ITextLine
|
||||
verifyClass(ITextLine, self._getTargetClass())
|
||||
|
||||
def test_instance_conforms_to_ITextLine(self):
|
||||
from zope.interface.verify import verifyObject
|
||||
from zope.schema.interfaces import ITextLine
|
||||
verifyObject(ITextLine, self._makeOne())
|
||||
|
||||
def test_validate_wrong_types(self):
|
||||
from zope.schema.interfaces import WrongType
|
||||
from zope.schema._compat import b
|
||||
field = self._makeOne()
|
||||
self.assertRaises(WrongType, field.validate, b(''))
|
||||
self.assertRaises(WrongType, field.validate, 1)
|
||||
self.assertRaises(WrongType, field.validate, 1.0)
|
||||
self.assertRaises(WrongType, field.validate, ())
|
||||
self.assertRaises(WrongType, field.validate, [])
|
||||
self.assertRaises(WrongType, field.validate, {})
|
||||
self.assertRaises(WrongType, field.validate, set())
|
||||
self.assertRaises(WrongType, field.validate, frozenset())
|
||||
self.assertRaises(WrongType, field.validate, object())
|
||||
|
||||
def test_validate_not_required(self):
|
||||
from zope.schema._compat import u
|
||||
field = self._makeOne(required=False)
|
||||
field.validate(u(''))
|
||||
field.validate(u('abc'))
|
||||
field.validate(None)
|
||||
|
||||
def test_validate_required(self):
|
||||
from zope.schema.interfaces import RequiredMissing
|
||||
from zope.schema._compat import u
|
||||
field = self._makeOne()
|
||||
field.validate(u(''))
|
||||
field.validate(u('abc'))
|
||||
self.assertRaises(RequiredMissing, field.validate, None)
|
||||
|
||||
def test_constraint(self):
|
||||
from zope.schema._compat import u
|
||||
field = self._makeOne()
|
||||
self.assertEqual(field.constraint(u('')), True)
|
||||
self.assertEqual(field.constraint(u('abc')), True)
|
||||
self.assertEqual(field.constraint(u('abc\ndef')), False)
|
||||
|
||||
|
||||
class PasswordTests(unittest.TestCase):
|
||||
|
||||
def _getTargetClass(self):
|
||||
from zope.schema._bootstrapfields import Password
|
||||
return Password
|
||||
|
||||
def _makeOne(self, *args, **kw):
|
||||
return self._getTargetClass()(*args, **kw)
|
||||
|
||||
def test_set_unchanged(self):
|
||||
klass = self._getTargetClass()
|
||||
pw = self._makeOne()
|
||||
inst = DummyInst()
|
||||
before = dict(inst.__dict__)
|
||||
pw.set(inst, klass.UNCHANGED_PASSWORD) # doesn't raise, doesn't write
|
||||
after = dict(inst.__dict__)
|
||||
self.assertEqual(after, before)
|
||||
|
||||
def test_set_normal(self):
|
||||
pw = self._makeOne(__name__='password')
|
||||
inst = DummyInst()
|
||||
pw.set(inst, 'PASSWORD')
|
||||
self.assertEqual(inst.password, 'PASSWORD')
|
||||
|
||||
def test_validate_not_required(self):
|
||||
from zope.schema._compat import u
|
||||
field = self._makeOne(required=False)
|
||||
field.validate(u(''))
|
||||
field.validate(u('abc'))
|
||||
field.validate(None)
|
||||
|
||||
def test_validate_required(self):
|
||||
from zope.schema.interfaces import RequiredMissing
|
||||
from zope.schema._compat import u
|
||||
field = self._makeOne()
|
||||
field.validate(u(''))
|
||||
field.validate(u('abc'))
|
||||
self.assertRaises(RequiredMissing, field.validate, None)
|
||||
|
||||
def test_validate_unchanged_not_already_set(self):
|
||||
from zope.schema._bootstrapinterfaces import WrongType
|
||||
klass = self._getTargetClass()
|
||||
inst = DummyInst()
|
||||
pw = self._makeOne(__name__='password').bind(inst)
|
||||
self.assertRaises(WrongType,
|
||||
pw.validate, klass.UNCHANGED_PASSWORD)
|
||||
|
||||
def test_validate_unchanged_already_set(self):
|
||||
klass = self._getTargetClass()
|
||||
inst = DummyInst()
|
||||
inst.password = 'foobar'
|
||||
pw = self._makeOne(__name__='password').bind(inst)
|
||||
pw.validate(klass.UNCHANGED_PASSWORD) # doesn't raise
|
||||
|
||||
def test_constraint(self):
|
||||
from zope.schema._compat import u
|
||||
field = self._makeOne()
|
||||
self.assertEqual(field.constraint(u('')), True)
|
||||
self.assertEqual(field.constraint(u('abc')), True)
|
||||
self.assertEqual(field.constraint(u('abc\ndef')), False)
|
||||
|
||||
|
||||
class BoolTests(unittest.TestCase):
|
||||
|
||||
def _getTargetClass(self):
|
||||
from zope.schema._bootstrapfields import Bool
|
||||
return Bool
|
||||
|
||||
def _makeOne(self, *args, **kw):
|
||||
return self._getTargetClass()(*args, **kw)
|
||||
|
||||
def test_ctor_defaults(self):
|
||||
txt = self._makeOne()
|
||||
self.assertEqual(txt._type, bool)
|
||||
|
||||
def test__validate_w_int(self):
|
||||
boo = self._makeOne()
|
||||
boo._validate(0) # doesn't raise
|
||||
boo._validate(1) # doesn't raise
|
||||
|
||||
def test_set_w_int(self):
|
||||
boo = self._makeOne(__name__='boo')
|
||||
inst = DummyInst()
|
||||
boo.set(inst, 0)
|
||||
self.assertEqual(inst.boo, False)
|
||||
boo.set(inst, 1)
|
||||
self.assertEqual(inst.boo, True)
|
||||
|
||||
def test_fromUnicode_miss(self):
|
||||
from zope.schema._compat import u
|
||||
txt = self._makeOne()
|
||||
self.assertEqual(txt.fromUnicode(u('')), False)
|
||||
self.assertEqual(txt.fromUnicode(u('0')), False)
|
||||
self.assertEqual(txt.fromUnicode(u('1')), False)
|
||||
self.assertEqual(txt.fromUnicode(u('False')), False)
|
||||
self.assertEqual(txt.fromUnicode(u('false')), False)
|
||||
|
||||
def test_fromUnicode_hit(self):
|
||||
from zope.schema._compat import u
|
||||
txt = self._makeOne()
|
||||
self.assertEqual(txt.fromUnicode(u('True')), True)
|
||||
self.assertEqual(txt.fromUnicode(u('true')), True)
|
||||
|
||||
|
||||
class IntTests(unittest.TestCase):
|
||||
|
||||
def _getTargetClass(self):
|
||||
from zope.schema._bootstrapfields import Int
|
||||
return Int
|
||||
|
||||
def _makeOne(self, *args, **kw):
|
||||
return self._getTargetClass()(*args, **kw)
|
||||
|
||||
def test_ctor_defaults(self):
|
||||
from zope.schema._compat import integer_types
|
||||
txt = self._makeOne()
|
||||
self.assertEqual(txt._type, integer_types)
|
||||
|
||||
def test_validate_not_required(self):
|
||||
field = self._makeOne(required=False)
|
||||
field.validate(None)
|
||||
field.validate(10)
|
||||
field.validate(0)
|
||||
field.validate(-1)
|
||||
|
||||
def test_validate_required(self):
|
||||
from zope.schema.interfaces import RequiredMissing
|
||||
field = self._makeOne()
|
||||
field.validate(10)
|
||||
field.validate(0)
|
||||
field.validate(-1)
|
||||
self.assertRaises(RequiredMissing, field.validate, None)
|
||||
|
||||
def test_validate_min(self):
|
||||
from zope.schema.interfaces import TooSmall
|
||||
field = self._makeOne(min=10)
|
||||
field.validate(10)
|
||||
field.validate(20)
|
||||
self.assertRaises(TooSmall, field.validate, 9)
|
||||
self.assertRaises(TooSmall, field.validate, -10)
|
||||
|
||||
def test_validate_max(self):
|
||||
from zope.schema.interfaces import TooBig
|
||||
field = self._makeOne(max=10)
|
||||
field.validate(5)
|
||||
field.validate(9)
|
||||
field.validate(10)
|
||||
self.assertRaises(TooBig, field.validate, 11)
|
||||
self.assertRaises(TooBig, field.validate, 20)
|
||||
|
||||
def test_validate_min_and_max(self):
|
||||
from zope.schema.interfaces import TooBig
|
||||
from zope.schema.interfaces import TooSmall
|
||||
field = self._makeOne(min=0, max=10)
|
||||
field.validate(0)
|
||||
field.validate(5)
|
||||
field.validate(10)
|
||||
self.assertRaises(TooSmall, field.validate, -10)
|
||||
self.assertRaises(TooSmall, field.validate, -1)
|
||||
self.assertRaises(TooBig, field.validate, 11)
|
||||
self.assertRaises(TooBig, field.validate, 20)
|
||||
|
||||
def test_fromUnicode_miss(self):
|
||||
from zope.schema._compat import u
|
||||
txt = self._makeOne()
|
||||
self.assertRaises(ValueError, txt.fromUnicode, u(''))
|
||||
self.assertRaises(ValueError, txt.fromUnicode, u('False'))
|
||||
self.assertRaises(ValueError, txt.fromUnicode, u('True'))
|
||||
|
||||
def test_fromUnicode_hit(self):
|
||||
from zope.schema._compat import u
|
||||
txt = self._makeOne()
|
||||
self.assertEqual(txt.fromUnicode(u('0')), 0)
|
||||
self.assertEqual(txt.fromUnicode(u('1')), 1)
|
||||
self.assertEqual(txt.fromUnicode(u('-1')), -1)
|
||||
|
||||
|
||||
class DummyInst(object):
|
||||
missing_value = object()
|
||||
|
||||
def __init__(self, exc=None):
|
||||
self._exc = exc
|
||||
|
||||
def validate(self, value):
|
||||
if self._exc is not None:
|
||||
raise self._exc()
|
||||
|
||||
|
||||
def test_suite():
|
||||
return unittest.TestSuite((
|
||||
unittest.makeSuite(ValidatedPropertyTests),
|
||||
unittest.makeSuite(DefaultPropertyTests),
|
||||
unittest.makeSuite(FieldTests),
|
||||
unittest.makeSuite(ContainerTests),
|
||||
unittest.makeSuite(IterableTests),
|
||||
unittest.makeSuite(OrderableTests),
|
||||
unittest.makeSuite(MinMaxLenTests),
|
||||
unittest.makeSuite(TextTests),
|
||||
unittest.makeSuite(TextLineTests),
|
||||
unittest.makeSuite(PasswordTests),
|
||||
unittest.makeSuite(BoolTests),
|
||||
unittest.makeSuite(IntTests),
|
||||
))
|
||||
68
zope/schema/tests/test__bootstrapinterfaces.py
Normal file
68
zope/schema/tests/test__bootstrapinterfaces.py
Normal file
@@ -0,0 +1,68 @@
|
||||
##############################################################################
|
||||
#
|
||||
# Copyright (c) 2012 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.
|
||||
#
|
||||
##############################################################################
|
||||
import unittest
|
||||
|
||||
|
||||
def _skip_under_py3(testcase):
|
||||
from zope.schema._compat import PY3
|
||||
if not PY3:
|
||||
return testcase
|
||||
|
||||
|
||||
class ValidationErrorTests(unittest.TestCase):
|
||||
|
||||
def _getTargetClass(self):
|
||||
from zope.schema._bootstrapinterfaces import ValidationError
|
||||
return ValidationError
|
||||
|
||||
def _makeOne(self, *args, **kw):
|
||||
return self._getTargetClass()(*args, **kw)
|
||||
|
||||
def test_doc(self):
|
||||
class Derived(self._getTargetClass()):
|
||||
"""DERIVED"""
|
||||
inst = Derived()
|
||||
self.assertEqual(inst.doc(), 'DERIVED')
|
||||
|
||||
@_skip_under_py3
|
||||
def test___cmp___no_args(self):
|
||||
# Py3k??
|
||||
ve = self._makeOne()
|
||||
self.assertEqual(cmp(ve, object()), -1)
|
||||
|
||||
@_skip_under_py3
|
||||
def test___cmp___hit(self):
|
||||
# Py3k??
|
||||
left = self._makeOne('abc')
|
||||
right = self._makeOne('def')
|
||||
self.assertEqual(cmp(left, right), -1)
|
||||
self.assertEqual(cmp(left, left), 0)
|
||||
self.assertEqual(cmp(right, left), 1)
|
||||
|
||||
def test___eq___no_args(self):
|
||||
ve = self._makeOne()
|
||||
self.assertEqual(ve == object(), False)
|
||||
|
||||
def test___eq___w_args(self):
|
||||
left = self._makeOne('abc')
|
||||
right = self._makeOne('def')
|
||||
self.assertEqual(left == right, False)
|
||||
self.assertEqual(left == left, True)
|
||||
self.assertEqual(right == right, True)
|
||||
|
||||
|
||||
def test_suite():
|
||||
return unittest.TestSuite((
|
||||
unittest.makeSuite(ValidationErrorTests),
|
||||
))
|
||||
2122
zope/schema/tests/test__field.py
Normal file
2122
zope/schema/tests/test__field.py
Normal file
File diff suppressed because it is too large
Load Diff
315
zope/schema/tests/test_accessors.py
Normal file
315
zope/schema/tests/test_accessors.py
Normal file
@@ -0,0 +1,315 @@
|
||||
##############################################################################
|
||||
#
|
||||
# Copyright (c) 2003 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.
|
||||
#
|
||||
##############################################################################
|
||||
"""Test Interface accessor methods.
|
||||
"""
|
||||
import unittest
|
||||
|
||||
|
||||
class FieldReadAccessorTests(unittest.TestCase):
|
||||
|
||||
def _getTargetClass(self):
|
||||
from zope.schema.accessors import FieldReadAccessor
|
||||
return FieldReadAccessor
|
||||
|
||||
def _makeOne(self, field=None):
|
||||
from zope.schema import Text
|
||||
if field is None:
|
||||
field = Text(__name__='testing')
|
||||
return self._getTargetClass()(field)
|
||||
|
||||
def test_ctor_not_created_inside_interface(self):
|
||||
from zope.schema import Text
|
||||
from zope.schema._compat import u
|
||||
field = Text(title=u('Hmm'))
|
||||
wrapped = self._makeOne(field)
|
||||
self.assertTrue(wrapped.field is field)
|
||||
self.assertEqual(wrapped.__name__, '') # __name__ set when in iface
|
||||
self.assertEqual(wrapped.__doc__, 'get Hmm')
|
||||
|
||||
def test_ctor_created_inside_interface(self):
|
||||
from zope.interface import Interface
|
||||
from zope.schema import Text
|
||||
from zope.schema._compat import u
|
||||
field = Text(title=u('Hmm'))
|
||||
|
||||
class IFoo(Interface):
|
||||
getter = self._makeOne(field)
|
||||
getter = IFoo['getter']
|
||||
self.assertEqual(getter.__name__, 'getter')
|
||||
self.assertEqual(getter.__doc__, 'get Hmm')
|
||||
|
||||
def test___provides___w_field_no_provides(self):
|
||||
from zope.interface import implementedBy
|
||||
from zope.interface import providedBy
|
||||
wrapped = self._makeOne(object())
|
||||
self.assertEqual(list(providedBy(wrapped)),
|
||||
list(implementedBy(self._getTargetClass())))
|
||||
|
||||
def test___provides___w_field_w_provides(self):
|
||||
from zope.interface import implementedBy
|
||||
from zope.interface import providedBy
|
||||
from zope.schema import Text
|
||||
field = Text()
|
||||
field_provides = list(providedBy(field))
|
||||
wrapped = self._makeOne(field)
|
||||
wrapped_provides = list(providedBy(wrapped))
|
||||
self.assertEqual(wrapped_provides[:len(field_provides)],
|
||||
list(providedBy(field)))
|
||||
for iface in list(implementedBy(self._getTargetClass())):
|
||||
self.assertTrue(iface in wrapped_provides)
|
||||
|
||||
def test_getSignatureString(self):
|
||||
wrapped = self._makeOne()
|
||||
self.assertEqual(wrapped.getSignatureString(), '()')
|
||||
|
||||
def test_getSignatureInfo(self):
|
||||
wrapped = self._makeOne()
|
||||
info = wrapped.getSignatureInfo()
|
||||
self.assertEqual(info['positional'], ())
|
||||
self.assertEqual(info['required'], ())
|
||||
self.assertEqual(info['optional'], ())
|
||||
self.assertEqual(info['varargs'], None)
|
||||
self.assertEqual(info['kwargs'], None)
|
||||
|
||||
def test_get_miss(self):
|
||||
from zope.interface import Interface
|
||||
|
||||
class IFoo(Interface):
|
||||
getter = self._makeOne()
|
||||
getter = IFoo['getter']
|
||||
|
||||
class Foo(object):
|
||||
pass
|
||||
self.assertRaises(AttributeError, getter.get, Foo())
|
||||
|
||||
def test_get_hit(self):
|
||||
from zope.interface import Interface
|
||||
|
||||
class IFoo(Interface):
|
||||
getter = self._makeOne()
|
||||
getter = IFoo['getter']
|
||||
|
||||
class Foo(object):
|
||||
def getter(self):
|
||||
return '123'
|
||||
self.assertEqual(getter.get(Foo()), '123')
|
||||
|
||||
def test_query_miss_implicit_default(self):
|
||||
from zope.interface import Interface
|
||||
|
||||
class IFoo(Interface):
|
||||
getter = self._makeOne()
|
||||
getter = IFoo['getter']
|
||||
|
||||
class Foo(object):
|
||||
pass
|
||||
self.assertEqual(getter.query(Foo()), None)
|
||||
|
||||
def test_query_miss_explicit_default(self):
|
||||
from zope.interface import Interface
|
||||
|
||||
class IFoo(Interface):
|
||||
getter = self._makeOne()
|
||||
getter = IFoo['getter']
|
||||
|
||||
class Foo(object):
|
||||
pass
|
||||
self.assertEqual(getter.query(Foo(), 234), 234)
|
||||
|
||||
def test_query_hit(self):
|
||||
from zope.interface import Interface
|
||||
|
||||
class IFoo(Interface):
|
||||
getter = self._makeOne()
|
||||
getter = IFoo['getter']
|
||||
|
||||
class Foo(object):
|
||||
def getter(self):
|
||||
return '123'
|
||||
|
||||
self.assertEqual(getter.query(Foo()), '123')
|
||||
|
||||
def test_set_readonly(self):
|
||||
from zope.interface import Interface
|
||||
from zope.schema import Text
|
||||
field = Text(readonly=True)
|
||||
|
||||
class IFoo(Interface):
|
||||
getter = self._makeOne(field)
|
||||
getter = IFoo['getter']
|
||||
|
||||
class Foo(object):
|
||||
def getter(self):
|
||||
return '123'
|
||||
self.assertRaises(TypeError, getter.set, Foo(), '456')
|
||||
|
||||
def test_set_no_writer(self):
|
||||
from zope.interface import Interface
|
||||
|
||||
class IFoo(Interface):
|
||||
getter = self._makeOne()
|
||||
getter = IFoo['getter']
|
||||
|
||||
class Foo(object):
|
||||
def getter(self):
|
||||
return '123'
|
||||
|
||||
self.assertRaises(AttributeError, getter.set, Foo(), '456')
|
||||
|
||||
def test_set_w_writer(self):
|
||||
from zope.interface import Interface
|
||||
|
||||
class IFoo(Interface):
|
||||
getter = self._makeOne()
|
||||
|
||||
getter = IFoo['getter']
|
||||
_called_with = []
|
||||
|
||||
class Writer(object):
|
||||
pass
|
||||
|
||||
writer = Writer()
|
||||
writer.__name__ = 'setMe'
|
||||
getter.writer = writer
|
||||
|
||||
class Foo(object):
|
||||
def setMe(self, value):
|
||||
_called_with.append(value)
|
||||
|
||||
getter.set(Foo(), '456')
|
||||
self.assertEqual(_called_with, ['456'])
|
||||
|
||||
def test_bind(self):
|
||||
from zope.interface import Interface
|
||||
|
||||
class IFoo(Interface):
|
||||
getter = self._makeOne()
|
||||
|
||||
getter = IFoo['getter']
|
||||
context = object()
|
||||
bound = getter.bind(context)
|
||||
self.assertEqual(bound.__name__, 'getter')
|
||||
self.assertTrue(isinstance(bound.field, getter.field.__class__))
|
||||
self.assertTrue(bound.field.context is context)
|
||||
|
||||
|
||||
class FieldWriteAccessorTests(unittest.TestCase):
|
||||
|
||||
def _getTargetClass(self):
|
||||
from zope.schema.accessors import FieldWriteAccessor
|
||||
return FieldWriteAccessor
|
||||
|
||||
def _makeOne(self, field=None):
|
||||
from zope.schema import Text
|
||||
if field is None:
|
||||
field = Text(__name__='testing')
|
||||
return self._getTargetClass()(field)
|
||||
|
||||
def test_ctor_not_created_inside_interface(self):
|
||||
from zope.schema import Text
|
||||
from zope.schema._compat import u
|
||||
field = Text(title=u('Hmm'))
|
||||
wrapped = self._makeOne(field)
|
||||
self.assertTrue(wrapped.field is field)
|
||||
self.assertEqual(wrapped.__name__, '') # __name__ set when in iface
|
||||
self.assertEqual(wrapped.__doc__, 'set Hmm')
|
||||
|
||||
def test_ctor_created_inside_interface(self):
|
||||
from zope.interface import Interface
|
||||
from zope.schema import Text
|
||||
from zope.schema._compat import u
|
||||
field = Text(title=u('Hmm'))
|
||||
|
||||
class IFoo(Interface):
|
||||
setter = self._makeOne(field)
|
||||
|
||||
setter = IFoo['setter']
|
||||
self.assertEqual(setter.__name__, 'setter')
|
||||
self.assertEqual(setter.__doc__, 'set Hmm')
|
||||
|
||||
def test_getSignatureString(self):
|
||||
wrapped = self._makeOne()
|
||||
self.assertEqual(wrapped.getSignatureString(), '(newvalue)')
|
||||
|
||||
def test_getSignatureInfo(self):
|
||||
wrapped = self._makeOne()
|
||||
info = wrapped.getSignatureInfo()
|
||||
self.assertEqual(info['positional'], ('newvalue',))
|
||||
self.assertEqual(info['required'], ('newvalue',))
|
||||
self.assertEqual(info['optional'], ())
|
||||
self.assertEqual(info['varargs'], None)
|
||||
self.assertEqual(info['kwargs'], None)
|
||||
|
||||
|
||||
class Test_accessors(unittest.TestCase):
|
||||
|
||||
def _callFUT(self, *args, **kw):
|
||||
from zope.schema.accessors import accessors
|
||||
return accessors(*args, **kw)
|
||||
|
||||
def test_w_only_read_accessor(self):
|
||||
from zope.interface import Interface
|
||||
from zope.schema import Text
|
||||
from zope.schema._compat import u
|
||||
field = Text(title=u('Hmm'), readonly=True)
|
||||
|
||||
class IFoo(Interface):
|
||||
getter, = self._callFUT(field)
|
||||
|
||||
getter = IFoo['getter']
|
||||
self.assertEqual(getter.__name__, 'getter')
|
||||
self.assertEqual(getter.__doc__, 'get Hmm')
|
||||
self.assertEqual(getter.getSignatureString(), '()')
|
||||
info = getter.getSignatureInfo()
|
||||
self.assertEqual(info['positional'], ())
|
||||
self.assertEqual(info['required'], ())
|
||||
self.assertEqual(info['optional'], ())
|
||||
self.assertEqual(info['varargs'], None)
|
||||
self.assertEqual(info['kwargs'], None)
|
||||
|
||||
def test_w_read_and_write_accessors(self):
|
||||
from zope.interface import Interface
|
||||
from zope.schema import Text
|
||||
from zope.schema._compat import u
|
||||
field = Text(title=u('Hmm'))
|
||||
|
||||
class IFoo(Interface):
|
||||
getter, setter = self._callFUT(field)
|
||||
|
||||
getter = IFoo['getter']
|
||||
self.assertEqual(getter.__name__, 'getter')
|
||||
self.assertEqual(getter.getSignatureString(), '()')
|
||||
info = getter.getSignatureInfo()
|
||||
self.assertEqual(info['positional'], ())
|
||||
self.assertEqual(info['required'], ())
|
||||
self.assertEqual(info['optional'], ())
|
||||
self.assertEqual(info['varargs'], None)
|
||||
self.assertEqual(info['kwargs'], None)
|
||||
setter = IFoo['setter']
|
||||
self.assertEqual(setter.__name__, 'setter')
|
||||
self.assertEqual(setter.getSignatureString(), '(newvalue)')
|
||||
info = setter.getSignatureInfo()
|
||||
self.assertEqual(info['positional'], ('newvalue',))
|
||||
self.assertEqual(info['required'], ('newvalue',))
|
||||
self.assertEqual(info['optional'], ())
|
||||
self.assertEqual(info['varargs'], None)
|
||||
self.assertEqual(info['kwargs'], None)
|
||||
|
||||
|
||||
def test_suite():
|
||||
return unittest.TestSuite((
|
||||
unittest.makeSuite(FieldReadAccessorTests),
|
||||
unittest.makeSuite(FieldWriteAccessorTests),
|
||||
unittest.makeSuite(Test_accessors),
|
||||
))
|
||||
41
zope/schema/tests/test_equality.py
Normal file
41
zope/schema/tests/test_equality.py
Normal file
@@ -0,0 +1,41 @@
|
||||
##############################################################################
|
||||
#
|
||||
# Copyright (c) 2001, 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.
|
||||
#
|
||||
##############################################################################
|
||||
"""Field equality tests
|
||||
"""
|
||||
import unittest
|
||||
|
||||
|
||||
class FieldEqualityTests(unittest.TestCase):
|
||||
|
||||
def test_equality(self):
|
||||
|
||||
from zope.schema._compat import u
|
||||
from zope.schema import Int
|
||||
from zope.schema import Text
|
||||
|
||||
# pep 8 friendlyness
|
||||
u, Int, Text
|
||||
|
||||
equality = [
|
||||
'Text(title=u("Foo"), description=u("Bar"))',
|
||||
'Int(title=u("Foo"), description=u("Bar"))',
|
||||
]
|
||||
for text in equality:
|
||||
self.assertEqual(eval(text), eval(text))
|
||||
|
||||
|
||||
def test_suite():
|
||||
return unittest.TestSuite((
|
||||
unittest.makeSuite(FieldEqualityTests),
|
||||
))
|
||||
669
zope/schema/tests/test_fieldproperty.py
Normal file
669
zope/schema/tests/test_fieldproperty.py
Normal file
@@ -0,0 +1,669 @@
|
||||
##############################################################################
|
||||
#
|
||||
# Copyright (c) 2001, 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.
|
||||
#
|
||||
##############################################################################
|
||||
"""Field Properties tests
|
||||
"""
|
||||
|
||||
import unittest
|
||||
|
||||
|
||||
class _Base(unittest.TestCase):
|
||||
|
||||
def _makeOne(self, field=None, name=None):
|
||||
from zope.schema import Text
|
||||
if field is None:
|
||||
field = Text(__name__='testing')
|
||||
if name is None:
|
||||
return self._getTargetClass()(field)
|
||||
return self._getTargetClass()(field, name)
|
||||
|
||||
|
||||
class _Integration(object):
|
||||
|
||||
def _makeImplementer(self):
|
||||
schema = _getSchema()
|
||||
|
||||
class _Implementer(object):
|
||||
title = self._makeOne(schema['title'])
|
||||
weight = self._makeOne(schema['weight'])
|
||||
code = self._makeOne(schema['code'])
|
||||
date = self._makeOne(schema['date'])
|
||||
|
||||
return _Implementer()
|
||||
|
||||
def test_basic(self):
|
||||
from zope.schema._compat import b
|
||||
from zope.schema._compat import u
|
||||
from zope.schema.interfaces import ValidationError
|
||||
c = self._makeImplementer()
|
||||
self.assertEqual(c.title, u('say something'))
|
||||
self.assertEqual(c.weight, None)
|
||||
self.assertEqual(c.code, b('xxxxxx'))
|
||||
self.assertRaises(ValidationError, setattr, c, 'title', b('foo'))
|
||||
self.assertRaises(ValidationError, setattr, c, 'weight', b('foo'))
|
||||
self.assertRaises(ValidationError, setattr, c, 'weight', -1.0)
|
||||
self.assertRaises(ValidationError, setattr, c, 'weight', 2)
|
||||
self.assertRaises(ValidationError, setattr, c, 'code', -1)
|
||||
self.assertRaises(ValidationError, setattr, c, 'code', b('xxxx'))
|
||||
self.assertRaises(ValidationError, setattr, c, 'code', u('xxxxxx'))
|
||||
|
||||
c.title = u('c is good')
|
||||
c.weight = 10.0
|
||||
c.code = b('abcdef')
|
||||
|
||||
self.assertEqual(c.title, u('c is good'))
|
||||
self.assertEqual(c.weight, 10)
|
||||
self.assertEqual(c.code, b('abcdef'))
|
||||
|
||||
def test_readonly(self):
|
||||
c = self._makeImplementer()
|
||||
# The date should be only settable once
|
||||
c.date = 0.0
|
||||
# Setting the value a second time should fail.
|
||||
self.assertRaises(ValueError, setattr, c, 'date', 1.0)
|
||||
|
||||
|
||||
class FieldPropertyTests(_Base, _Integration):
|
||||
|
||||
def _getTargetClass(self):
|
||||
from zope.schema.fieldproperty import FieldProperty
|
||||
return FieldProperty
|
||||
|
||||
def test_ctor_defaults(self):
|
||||
from zope.schema import Text
|
||||
field = Text(__name__='testing')
|
||||
cname = self._getTargetClass().__name__
|
||||
prop = self._makeOne(field)
|
||||
self.assertTrue(getattr(prop, '_%s__field' % cname) is field)
|
||||
self.assertEqual(getattr(prop, '_%s__name' % cname), 'testing')
|
||||
self.assertEqual(prop.__name__, 'testing')
|
||||
self.assertEqual(prop.description, field.description)
|
||||
self.assertEqual(prop.default, field.default)
|
||||
self.assertEqual(prop.readonly, field.readonly)
|
||||
self.assertEqual(prop.required, field.required)
|
||||
|
||||
def test_ctor_explicit(self):
|
||||
from zope.schema import Text
|
||||
from zope.schema._compat import u
|
||||
field = Text(
|
||||
__name__='testing',
|
||||
description=u('DESCRIPTION'),
|
||||
default=u('DEFAULT'),
|
||||
readonly=True,
|
||||
required=True,
|
||||
)
|
||||
cname = self._getTargetClass().__name__
|
||||
prop = self._makeOne(field, name='override')
|
||||
self.assertTrue(getattr(prop, '_%s__field' % cname) is field)
|
||||
self.assertEqual(getattr(prop, '_%s__name' % cname), 'override')
|
||||
self.assertEqual(prop.description, field.description)
|
||||
self.assertEqual(prop.default, field.default)
|
||||
self.assertEqual(prop.readonly, field.readonly)
|
||||
self.assertEqual(prop.required, field.required)
|
||||
|
||||
def test_query_value_with_default(self):
|
||||
from zope.schema import Text
|
||||
from zope.schema._compat import u
|
||||
field = Text(
|
||||
__name__='testing',
|
||||
description=u('DESCRIPTION'),
|
||||
default=u('DEFAULT'),
|
||||
readonly=True,
|
||||
required=True,
|
||||
)
|
||||
|
||||
prop = self._makeOne(field=field)
|
||||
|
||||
class Foo(object):
|
||||
testing = prop
|
||||
foo = Foo()
|
||||
self.assertEqual(prop.queryValue(foo, 'test'), u('DEFAULT'))
|
||||
foo.testing = u('NO')
|
||||
self.assertEqual(prop.queryValue(foo, 'test'), u('NO'))
|
||||
|
||||
def test_query_value_without_default(self):
|
||||
from zope.schema import Text
|
||||
from zope.schema._compat import u
|
||||
field = Text(
|
||||
__name__='testing',
|
||||
description=u('DESCRIPTION'),
|
||||
readonly=True,
|
||||
required=True,
|
||||
)
|
||||
|
||||
prop = self._makeOne(field=field)
|
||||
|
||||
class Foo(object):
|
||||
testing = prop
|
||||
foo = Foo()
|
||||
# field initialize its default to None if it hasn't any default
|
||||
# it should be zope.schema.NO_VALUE as 'None' has another semantic
|
||||
self.assertEqual(prop.queryValue(foo, 'test'), None)
|
||||
|
||||
def test___get___from_class(self):
|
||||
prop = self._makeOne()
|
||||
|
||||
class Foo(object):
|
||||
testing = prop
|
||||
|
||||
self.assertTrue(Foo.testing is prop)
|
||||
|
||||
def test___get___from_instance_pseudo_field_wo_default(self):
|
||||
class _Faux(object):
|
||||
def bind(self, other):
|
||||
return self
|
||||
prop = self._makeOne(_Faux(), 'nonesuch')
|
||||
|
||||
class Foo(object):
|
||||
testing = prop
|
||||
|
||||
foo = Foo()
|
||||
self.assertRaises(AttributeError, getattr, foo, 'testing')
|
||||
|
||||
def test___get___from_instance_miss_uses_field_default(self):
|
||||
prop = self._makeOne()
|
||||
|
||||
class Foo(object):
|
||||
testing = prop
|
||||
|
||||
foo = Foo()
|
||||
self.assertEqual(foo.testing, None)
|
||||
|
||||
def test___get___from_instance_hit(self):
|
||||
prop = self._makeOne(name='other')
|
||||
|
||||
class Foo(object):
|
||||
testing = prop
|
||||
|
||||
foo = Foo()
|
||||
foo.other = '123'
|
||||
self.assertEqual(foo.testing, '123')
|
||||
|
||||
def test___get___from_instance_hit_after_bind(self):
|
||||
class _Faux(object):
|
||||
default = '456'
|
||||
|
||||
def bind(self, other):
|
||||
return self
|
||||
|
||||
prop = self._makeOne(_Faux(), 'testing')
|
||||
|
||||
class Foo(object):
|
||||
testing = prop
|
||||
|
||||
foo = Foo()
|
||||
self.assertEqual(foo.testing, '456')
|
||||
|
||||
def test___set___not_readonly(self):
|
||||
class _Faux(object):
|
||||
readonly = False
|
||||
default = '456'
|
||||
|
||||
def bind(self, other):
|
||||
return self
|
||||
|
||||
faux = _Faux()
|
||||
_validated = []
|
||||
faux.validate = _validated.append
|
||||
prop = self._makeOne(faux, 'testing')
|
||||
|
||||
class Foo(object):
|
||||
testing = prop
|
||||
|
||||
foo = Foo()
|
||||
foo.testing = '123'
|
||||
self.assertEqual(foo.__dict__['testing'], '123')
|
||||
|
||||
def test___set___w_readonly_not_already_set(self):
|
||||
class _Faux(object):
|
||||
readonly = True
|
||||
default = '456'
|
||||
|
||||
def bind(self, other):
|
||||
return self
|
||||
|
||||
faux = _Faux()
|
||||
_validated = []
|
||||
faux.validate = _validated.append
|
||||
prop = self._makeOne(faux, 'testing')
|
||||
|
||||
class Foo(object):
|
||||
testing = prop
|
||||
|
||||
foo = Foo()
|
||||
foo.testing = '123'
|
||||
self.assertEqual(foo.__dict__['testing'], '123')
|
||||
self.assertEqual(_validated, ['123'])
|
||||
|
||||
def test___set___w_readonly_and_already_set(self):
|
||||
class _Faux(object):
|
||||
readonly = True
|
||||
default = '456'
|
||||
|
||||
def bind(self, other):
|
||||
return self
|
||||
|
||||
faux = _Faux()
|
||||
_validated = []
|
||||
faux.validate = _validated.append
|
||||
prop = self._makeOne(faux, 'testing')
|
||||
|
||||
class Foo(object):
|
||||
testing = prop
|
||||
|
||||
foo = Foo()
|
||||
foo.__dict__['testing'] = '789'
|
||||
self.assertRaises(ValueError, setattr, foo, 'testing', '123')
|
||||
self.assertEqual(_validated, ['123'])
|
||||
|
||||
def test_field_event(self):
|
||||
from zope.schema import Text
|
||||
from zope.schema._compat import u
|
||||
from zope.event import subscribers
|
||||
from zope.schema.fieldproperty import FieldUpdatedEvent
|
||||
log = []
|
||||
subscribers.append(log.append)
|
||||
self.assertEqual(log, [])
|
||||
field = Text(
|
||||
__name__='testing',
|
||||
description=u('DESCRIPTION'),
|
||||
default=u('DEFAULT'),
|
||||
readonly=True,
|
||||
required=True,
|
||||
)
|
||||
self.assertEqual(len(log), 6)
|
||||
event = log[0]
|
||||
self.assertTrue(isinstance(event, FieldUpdatedEvent))
|
||||
self.assertEqual(event.inst, field)
|
||||
self.assertEqual(event.old_value, 0)
|
||||
self.assertEqual(event.new_value, 0)
|
||||
self.assertEqual(
|
||||
[ev.field.__name__ for ev in log],
|
||||
['min_length', 'max_length', 'title', 'description', 'required', 'readonly'])
|
||||
|
||||
def test_field_event_update(self):
|
||||
from zope.schema import Text
|
||||
from zope.schema._compat import u
|
||||
from zope.event import subscribers
|
||||
from zope.schema.fieldproperty import FieldUpdatedEvent
|
||||
field = Text(
|
||||
__name__='testing',
|
||||
description=u('DESCRIPTION'),
|
||||
default=u('DEFAULT'),
|
||||
required=True,
|
||||
)
|
||||
prop = self._makeOne(field=field)
|
||||
|
||||
class Foo(object):
|
||||
testing = prop
|
||||
foo = Foo()
|
||||
|
||||
log = []
|
||||
subscribers.append(log.append)
|
||||
foo.testing = u('Bar')
|
||||
foo.testing = u('Foo')
|
||||
self.assertEqual(len(log), 2)
|
||||
event = log[1]
|
||||
self.assertTrue(isinstance(event, FieldUpdatedEvent))
|
||||
self.assertEqual(event.inst, foo)
|
||||
self.assertEqual(event.field, field)
|
||||
self.assertEqual(event.old_value, u('Bar'))
|
||||
self.assertEqual(event.new_value, u('Foo'))
|
||||
|
||||
|
||||
class FieldPropertyStoredThroughFieldTests(_Base, _Integration):
|
||||
|
||||
def _getTargetClass(self):
|
||||
from zope.schema.fieldproperty import FieldPropertyStoredThroughField
|
||||
return FieldPropertyStoredThroughField
|
||||
|
||||
def test_ctor_defaults(self):
|
||||
from zope.schema import Text
|
||||
field = Text(__name__='testing')
|
||||
cname = self._getTargetClass().__name__
|
||||
prop = self._makeOne(field)
|
||||
self.assertTrue(isinstance(prop.field, field.__class__))
|
||||
self.assertFalse(prop.field is field)
|
||||
self.assertEqual(prop.field.__name__, '__st_testing_st')
|
||||
self.assertEqual(prop.__name__, '__st_testing_st')
|
||||
self.assertEqual(getattr(prop, '_%s__name' % cname), 'testing')
|
||||
self.assertEqual(prop.description, field.description)
|
||||
self.assertEqual(prop.default, field.default)
|
||||
self.assertEqual(prop.readonly, field.readonly)
|
||||
self.assertEqual(prop.required, field.required)
|
||||
|
||||
def test_ctor_explicit(self):
|
||||
from zope.schema import Text
|
||||
from zope.schema._compat import u
|
||||
field = Text(
|
||||
__name__='testing',
|
||||
description=u('DESCRIPTION'),
|
||||
default=u('DEFAULT'),
|
||||
readonly=True,
|
||||
required=True,
|
||||
)
|
||||
cname = self._getTargetClass().__name__
|
||||
prop = self._makeOne(field, name='override')
|
||||
self.assertTrue(isinstance(prop.field, field.__class__))
|
||||
self.assertFalse(prop.field is field)
|
||||
self.assertEqual(prop.field.__name__, '__st_testing_st')
|
||||
self.assertEqual(prop.__name__, '__st_testing_st')
|
||||
self.assertEqual(getattr(prop, '_%s__name' % cname), 'override')
|
||||
self.assertEqual(prop.description, field.description)
|
||||
self.assertEqual(prop.default, field.default)
|
||||
self.assertEqual(prop.readonly, field.readonly)
|
||||
self.assertEqual(prop.required, field.required)
|
||||
|
||||
def test_setValue(self):
|
||||
from zope.schema import Text
|
||||
|
||||
class Foo(object):
|
||||
pass
|
||||
|
||||
foo = Foo()
|
||||
prop = self._makeOne()
|
||||
field = Text(__name__='testing')
|
||||
prop.setValue(foo, field, '123')
|
||||
self.assertEqual(foo.testing, '123')
|
||||
|
||||
def test_getValue_miss(self):
|
||||
from zope.schema import Text
|
||||
from zope.schema.fieldproperty import _marker
|
||||
|
||||
class Foo(object):
|
||||
pass
|
||||
|
||||
foo = Foo()
|
||||
prop = self._makeOne()
|
||||
field = Text(__name__='testing')
|
||||
value = prop.getValue(foo, field)
|
||||
self.assertTrue(value is _marker)
|
||||
|
||||
def test_getValue_hit(self):
|
||||
from zope.schema import Text
|
||||
|
||||
class Foo(object):
|
||||
pass
|
||||
|
||||
foo = Foo()
|
||||
foo.testing = '123'
|
||||
prop = self._makeOne()
|
||||
field = Text(__name__='testing')
|
||||
value = prop.getValue(foo, field)
|
||||
self.assertEqual(value, '123')
|
||||
|
||||
def test_queryValue_miss(self):
|
||||
from zope.schema import Text
|
||||
|
||||
class Foo(object):
|
||||
pass
|
||||
|
||||
foo = Foo()
|
||||
prop = self._makeOne()
|
||||
field = Text(__name__='testing')
|
||||
default = object()
|
||||
value = prop.queryValue(foo, field, default)
|
||||
self.assertTrue(value is default)
|
||||
|
||||
def test_queryValue_hit(self):
|
||||
from zope.schema import Text
|
||||
|
||||
class Foo(object):
|
||||
pass
|
||||
|
||||
foo = Foo()
|
||||
foo.testing = '123'
|
||||
prop = self._makeOne()
|
||||
field = Text(__name__='testing')
|
||||
default = object()
|
||||
value = prop.queryValue(foo, field, default)
|
||||
self.assertEqual(value, '123')
|
||||
|
||||
def test___get___from_class(self):
|
||||
prop = self._makeOne()
|
||||
|
||||
class Foo(object):
|
||||
testing = prop
|
||||
|
||||
self.assertTrue(Foo.testing is prop)
|
||||
|
||||
def test___get___from_instance_pseudo_field_wo_default(self):
|
||||
class _Faux(object):
|
||||
__name__ = 'Faux'
|
||||
|
||||
def bind(self, other):
|
||||
return self
|
||||
|
||||
def query(self, inst, default):
|
||||
return default
|
||||
|
||||
prop = self._makeOne(_Faux(), 'nonesuch')
|
||||
|
||||
class Foo(object):
|
||||
testing = prop
|
||||
|
||||
foo = Foo()
|
||||
self.assertRaises(AttributeError, getattr, foo, 'testing')
|
||||
|
||||
def test___get___from_instance_miss_uses_field_default(self):
|
||||
prop = self._makeOne()
|
||||
|
||||
class Foo(object):
|
||||
testing = prop
|
||||
|
||||
foo = Foo()
|
||||
self.assertEqual(foo.testing, None)
|
||||
|
||||
def test___get___from_instance_hit(self):
|
||||
from zope.schema import Text
|
||||
field = Text(__name__='testing')
|
||||
prop = self._makeOne(field, name='other')
|
||||
|
||||
class Foo(object):
|
||||
testing = prop
|
||||
|
||||
foo = Foo()
|
||||
foo.__dict__['__st_testing_st'] = '456'
|
||||
foo.other = '123'
|
||||
self.assertEqual(foo.testing, '456')
|
||||
|
||||
def test___set___not_readonly(self):
|
||||
class _Faux(object):
|
||||
__name__ = 'Faux'
|
||||
readonly = False
|
||||
default = '456'
|
||||
|
||||
def query(self, inst, default):
|
||||
return default
|
||||
|
||||
def bind(self, other):
|
||||
return self
|
||||
|
||||
def set(self, inst, value):
|
||||
setattr(inst, 'faux', value)
|
||||
|
||||
faux = _Faux()
|
||||
_validated = []
|
||||
faux.validate = _validated.append
|
||||
prop = self._makeOne(faux, 'testing')
|
||||
|
||||
class Foo(object):
|
||||
testing = prop
|
||||
|
||||
foo = Foo()
|
||||
foo.testing = '123'
|
||||
self.assertEqual(foo.__dict__['faux'], '123')
|
||||
self.assertEqual(_validated, ['123'])
|
||||
|
||||
def test___set___w_readonly_not_already_set(self):
|
||||
class _Faux(object):
|
||||
__name__ = 'Faux'
|
||||
readonly = True
|
||||
default = '456'
|
||||
|
||||
def bind(self, other):
|
||||
return self
|
||||
|
||||
def query(self, inst, default):
|
||||
return default
|
||||
|
||||
def set(self, inst, value):
|
||||
if self.readonly:
|
||||
raise ValueError
|
||||
setattr(inst, 'faux', value)
|
||||
|
||||
faux = _Faux()
|
||||
_validated = []
|
||||
faux.validate = _validated.append
|
||||
prop = self._makeOne(faux, 'testing')
|
||||
|
||||
class Foo(object):
|
||||
testing = prop
|
||||
|
||||
foo = Foo()
|
||||
foo.testing = '123'
|
||||
self.assertEqual(foo.__dict__['faux'], '123')
|
||||
self.assertEqual(_validated, ['123'])
|
||||
|
||||
def test___set___w_readonly_and_already_set(self):
|
||||
class _Faux(object):
|
||||
__name__ = 'Faux'
|
||||
readonly = True
|
||||
default = '456'
|
||||
|
||||
def bind(self, other):
|
||||
return self
|
||||
|
||||
def query(self, inst, default):
|
||||
return '789'
|
||||
|
||||
faux = _Faux()
|
||||
_validated = []
|
||||
faux.validate = _validated.append
|
||||
prop = self._makeOne(faux, 'testing')
|
||||
|
||||
class Foo(object):
|
||||
testing = prop
|
||||
|
||||
foo = Foo()
|
||||
foo.__dict__['testing'] = '789'
|
||||
self.assertRaises(ValueError, setattr, foo, 'testing', '123')
|
||||
|
||||
def test_field_event_update(self):
|
||||
from zope.schema import Text
|
||||
from zope.schema._compat import u
|
||||
from zope.event import subscribers
|
||||
from zope.schema.fieldproperty import FieldUpdatedEvent
|
||||
field = Text(
|
||||
__name__='testing',
|
||||
description=u('DESCRIPTION'),
|
||||
default=u('DEFAULT'),
|
||||
required=True,
|
||||
)
|
||||
prop = self._makeOne(field=field)
|
||||
|
||||
class Foo(object):
|
||||
testing = prop
|
||||
foo = Foo()
|
||||
|
||||
log = []
|
||||
subscribers.append(log.append)
|
||||
foo.testing = u('Bar')
|
||||
foo.testing = u('Foo')
|
||||
self.assertEqual(len(log), 2)
|
||||
event = log[1]
|
||||
self.assertTrue(isinstance(event, FieldUpdatedEvent))
|
||||
self.assertEqual(event.inst, foo)
|
||||
self.assertEqual(event.field, field)
|
||||
self.assertEqual(event.old_value, u('Bar'))
|
||||
self.assertEqual(event.new_value, u('Foo'))
|
||||
|
||||
def test_field_event(self):
|
||||
# fieldproperties are everywhere including in field themselfs
|
||||
# so event are triggered
|
||||
from zope.schema import Text
|
||||
from zope.schema._compat import u
|
||||
from zope.event import subscribers
|
||||
from zope.schema.fieldproperty import FieldUpdatedEvent
|
||||
log = []
|
||||
subscribers.append(log.append)
|
||||
self.assertEqual(log, [])
|
||||
field = Text(
|
||||
__name__='testing',
|
||||
description=u('DESCRIPTION'),
|
||||
default=u('DEFAULT'),
|
||||
readonly=True,
|
||||
required=True,
|
||||
)
|
||||
self.assertEqual(len(log), 6)
|
||||
# these are fieldproperties in the field
|
||||
self.assertEqual(
|
||||
[ev.field.__name__ for ev in log],
|
||||
['min_length', 'max_length', 'title', 'description', 'required', 'readonly'])
|
||||
event = log[0]
|
||||
self.assertTrue(isinstance(event, FieldUpdatedEvent))
|
||||
self.assertEqual(event.inst, field)
|
||||
self.assertEqual(event.old_value, 0)
|
||||
self.assertEqual(event.new_value, 0)
|
||||
|
||||
|
||||
def _getSchema():
|
||||
from zope.interface import Interface
|
||||
from zope.schema import Bytes
|
||||
from zope.schema import Float
|
||||
from zope.schema import Text
|
||||
from zope.schema._compat import b
|
||||
from zope.schema._compat import u
|
||||
|
||||
class Schema(Interface):
|
||||
title = Text(description=u("Short summary"),
|
||||
default=u('say something'))
|
||||
weight = Float(min=0.0)
|
||||
code = Bytes(min_length=6, max_length=6, default=b('xxxxxx'))
|
||||
date = Float(title=u('Date'), readonly=True)
|
||||
|
||||
return Schema
|
||||
|
||||
|
||||
class CreateFieldPropertiesTests(unittest.TestCase):
|
||||
"""Testing ..fieldproperty.createFieldProperties."""
|
||||
|
||||
def test_creates_fieldproperties_on_class(self):
|
||||
from zope.schema.fieldproperty import createFieldProperties
|
||||
from zope.schema.fieldproperty import FieldProperty
|
||||
schema = _getSchema()
|
||||
|
||||
class Dummy(object):
|
||||
createFieldProperties(schema)
|
||||
|
||||
self.assertTrue(isinstance(Dummy.title, FieldProperty))
|
||||
self.assertTrue(isinstance(Dummy.date, FieldProperty))
|
||||
self.assertTrue(Dummy.date._FieldProperty__field is schema['date'])
|
||||
|
||||
def test_fields_in_omit_are_not_created_on_class(self):
|
||||
from zope.schema.fieldproperty import createFieldProperties
|
||||
|
||||
class Dummy(object):
|
||||
createFieldProperties(_getSchema(), omit=['date', 'code'])
|
||||
|
||||
self.assertFalse(hasattr(Dummy, 'date'))
|
||||
self.assertFalse(hasattr(Dummy, 'code'))
|
||||
self.assertTrue(hasattr(Dummy, 'title'))
|
||||
|
||||
|
||||
def test_suite():
|
||||
return unittest.TestSuite((
|
||||
unittest.makeSuite(FieldPropertyTests),
|
||||
unittest.makeSuite(FieldPropertyStoredThroughFieldTests),
|
||||
unittest.makeSuite(CreateFieldPropertiesTests),
|
||||
))
|
||||
99
zope/schema/tests/test_interfaces.py
Normal file
99
zope/schema/tests/test_interfaces.py
Normal file
@@ -0,0 +1,99 @@
|
||||
import unittest
|
||||
|
||||
|
||||
class Test__is_field(unittest.TestCase):
|
||||
|
||||
def _callFUT(self, value):
|
||||
from zope.schema.interfaces import _is_field
|
||||
return _is_field(value)
|
||||
|
||||
def test_non_fields(self):
|
||||
from zope.schema._compat import b
|
||||
from zope.schema._compat import u
|
||||
self.assertEqual(self._callFUT(None), False)
|
||||
self.assertEqual(self._callFUT(0), False)
|
||||
self.assertEqual(self._callFUT(0.0), False)
|
||||
self.assertEqual(self._callFUT(True), False)
|
||||
self.assertEqual(self._callFUT(b('')), False)
|
||||
self.assertEqual(self._callFUT(u('')), False)
|
||||
self.assertEqual(self._callFUT(()), False)
|
||||
self.assertEqual(self._callFUT([]), False)
|
||||
self.assertEqual(self._callFUT({}), False)
|
||||
self.assertEqual(self._callFUT(set()), False)
|
||||
self.assertEqual(self._callFUT(frozenset()), False)
|
||||
self.assertEqual(self._callFUT(object()), False)
|
||||
|
||||
def test_w_normal_fields(self):
|
||||
from zope.schema import Text
|
||||
from zope.schema import Bytes
|
||||
from zope.schema import Int
|
||||
from zope.schema import Float
|
||||
from zope.schema import Decimal
|
||||
self.assertEqual(self._callFUT(Text()), True)
|
||||
self.assertEqual(self._callFUT(Bytes()), True)
|
||||
self.assertEqual(self._callFUT(Int()), True)
|
||||
self.assertEqual(self._callFUT(Float()), True)
|
||||
self.assertEqual(self._callFUT(Decimal()), True)
|
||||
|
||||
def test_w_explicitly_provided(self):
|
||||
from zope.interface import directlyProvides
|
||||
from zope.schema.interfaces import IField
|
||||
|
||||
class Foo(object):
|
||||
pass
|
||||
|
||||
foo = Foo()
|
||||
self.assertEqual(self._callFUT(foo), False)
|
||||
directlyProvides(foo, IField)
|
||||
self.assertEqual(self._callFUT(foo), True)
|
||||
|
||||
|
||||
class Test__fields(unittest.TestCase):
|
||||
|
||||
def _callFUT(self, values):
|
||||
from zope.schema.interfaces import _fields
|
||||
return _fields(values)
|
||||
|
||||
def test_empty_containers(self):
|
||||
self.assertEqual(self._callFUT(()), True)
|
||||
self.assertEqual(self._callFUT([]), True)
|
||||
|
||||
def test_w_non_fields(self):
|
||||
self.assertEqual(self._callFUT([None]), False)
|
||||
self.assertEqual(self._callFUT(['']), False)
|
||||
self.assertEqual(self._callFUT([object()]), False)
|
||||
|
||||
def test_w_fields(self):
|
||||
from zope.schema import Text
|
||||
from zope.schema import Bytes
|
||||
from zope.schema import Int
|
||||
from zope.schema import Float
|
||||
from zope.schema import Decimal
|
||||
self.assertEqual(self._callFUT([Text()]), True)
|
||||
self.assertEqual(self._callFUT([Bytes()]), True)
|
||||
self.assertEqual(self._callFUT([Int()]), True)
|
||||
self.assertEqual(self._callFUT([Float()]), True)
|
||||
self.assertEqual(self._callFUT([Decimal()]), True)
|
||||
self.assertEqual(
|
||||
self._callFUT([Text(), Bytes(), Int(), Float(), Decimal()]),
|
||||
True
|
||||
)
|
||||
|
||||
def test_w_mixed(self):
|
||||
from zope.schema import Text
|
||||
from zope.schema import Bytes
|
||||
from zope.schema import Int
|
||||
from zope.schema import Float
|
||||
from zope.schema import Decimal
|
||||
self.assertEqual(self._callFUT([Text(), 0]), False)
|
||||
self.assertEqual(
|
||||
self._callFUT([Text(), Bytes(), Int(), Float(), Decimal(), 0]),
|
||||
False
|
||||
)
|
||||
|
||||
|
||||
def test_suite():
|
||||
return unittest.TestSuite((
|
||||
unittest.makeSuite(Test__is_field),
|
||||
unittest.makeSuite(Test__fields),
|
||||
))
|
||||
276
zope/schema/tests/test_schema.py
Normal file
276
zope/schema/tests/test_schema.py
Normal file
@@ -0,0 +1,276 @@
|
||||
##############################################################################
|
||||
#
|
||||
# Copyright (c) 2001, 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 field tests
|
||||
"""
|
||||
import unittest
|
||||
|
||||
|
||||
def _makeSchema():
|
||||
from zope.schema._compat import b
|
||||
from zope.schema._compat import u
|
||||
from zope.interface import Interface
|
||||
from zope.schema import Bytes
|
||||
|
||||
class ISchemaTest(Interface):
|
||||
title = Bytes(
|
||||
title=u("Title"),
|
||||
description=u("Title"),
|
||||
default=b(""),
|
||||
required=True)
|
||||
description = Bytes(
|
||||
title=u("Description"),
|
||||
description=u("Description"),
|
||||
default=b(""),
|
||||
required=True)
|
||||
spam = Bytes(
|
||||
title=u("Spam"),
|
||||
description=u("Spam"),
|
||||
default=b(""),
|
||||
required=True)
|
||||
return ISchemaTest
|
||||
|
||||
|
||||
def _makeDerivedSchema(base=None):
|
||||
from zope.schema._compat import b
|
||||
from zope.schema._compat import u
|
||||
from zope.schema import Bytes
|
||||
if base is None:
|
||||
base = _makeSchema()
|
||||
|
||||
class ISchemaTestSubclass(base):
|
||||
foo = Bytes(
|
||||
title=u('Foo'),
|
||||
description=u('Fooness'),
|
||||
default=b(""),
|
||||
required=False)
|
||||
return ISchemaTestSubclass
|
||||
|
||||
|
||||
class Test_getFields(unittest.TestCase):
|
||||
|
||||
def _callFUT(self, schema):
|
||||
from zope.schema import getFields
|
||||
return getFields(schema)
|
||||
|
||||
def test_simple(self):
|
||||
fields = self._callFUT(_makeSchema())
|
||||
|
||||
self.assertTrue('title' in fields)
|
||||
self.assertTrue('description' in fields)
|
||||
self.assertTrue('spam' in fields)
|
||||
|
||||
# test whether getName() has the right value
|
||||
for key, value in fields.items():
|
||||
self.assertEqual(key, value.getName())
|
||||
|
||||
def test_derived(self):
|
||||
fields = self._callFUT(_makeDerivedSchema())
|
||||
|
||||
self.assertTrue('title' in fields)
|
||||
self.assertTrue('description' in fields)
|
||||
self.assertTrue('spam' in fields)
|
||||
self.assertTrue('foo' in fields)
|
||||
|
||||
# test whether getName() has the right value
|
||||
for key, value in fields.items():
|
||||
self.assertEqual(key, value.getName())
|
||||
|
||||
|
||||
class Test_getFieldsInOrder(unittest.TestCase):
|
||||
|
||||
def _callFUT(self, schema):
|
||||
from zope.schema import getFieldsInOrder
|
||||
return getFieldsInOrder(schema)
|
||||
|
||||
def test_simple(self):
|
||||
fields = self._callFUT(_makeSchema())
|
||||
field_names = [name for name, field in fields]
|
||||
self.assertEqual(field_names, ['title', 'description', 'spam'])
|
||||
for key, value in fields:
|
||||
self.assertEqual(key, value.getName())
|
||||
|
||||
def test_derived(self):
|
||||
fields = self._callFUT(_makeDerivedSchema())
|
||||
field_names = [name for name, field in fields]
|
||||
self.assertEqual(field_names, ['title', 'description', 'spam', 'foo'])
|
||||
for key, value in fields:
|
||||
self.assertEqual(key, value.getName())
|
||||
|
||||
|
||||
class Test_getFieldNames(unittest.TestCase):
|
||||
|
||||
def _callFUT(self, schema):
|
||||
from zope.schema import getFieldNames
|
||||
return getFieldNames(schema)
|
||||
|
||||
def test_simple(self):
|
||||
names = self._callFUT(_makeSchema())
|
||||
self.assertEqual(len(names), 3)
|
||||
self.assertTrue('title' in names)
|
||||
self.assertTrue('description' in names)
|
||||
self.assertTrue('spam' in names)
|
||||
|
||||
def test_derived(self):
|
||||
names = self._callFUT(_makeDerivedSchema())
|
||||
self.assertEqual(len(names), 4)
|
||||
self.assertTrue('title' in names)
|
||||
self.assertTrue('description' in names)
|
||||
self.assertTrue('spam' in names)
|
||||
self.assertTrue('foo' in names)
|
||||
|
||||
|
||||
class Test_getFieldNamesInOrder(unittest.TestCase):
|
||||
|
||||
def _callFUT(self, schema):
|
||||
from zope.schema import getFieldNamesInOrder
|
||||
return getFieldNamesInOrder(schema)
|
||||
|
||||
def test_simple(self):
|
||||
names = self._callFUT(_makeSchema())
|
||||
self.assertEqual(names, ['title', 'description', 'spam'])
|
||||
|
||||
def test_derived(self):
|
||||
names = self._callFUT(_makeDerivedSchema())
|
||||
self.assertEqual(names, ['title', 'description', 'spam', 'foo'])
|
||||
|
||||
|
||||
class Test_getValidationErrors(unittest.TestCase):
|
||||
|
||||
def _callFUT(self, schema, object):
|
||||
from zope.schema import getValidationErrors
|
||||
return getValidationErrors(schema, object)
|
||||
|
||||
def test_schema(self):
|
||||
from zope.interface import Interface
|
||||
|
||||
class IEmpty(Interface):
|
||||
pass
|
||||
|
||||
errors = self._callFUT(IEmpty, object())
|
||||
self.assertEqual(len(errors), 0)
|
||||
|
||||
def test_schema_with_field_errors(self):
|
||||
from zope.interface import Interface
|
||||
from zope.schema import Text
|
||||
from zope.schema.interfaces import SchemaNotFullyImplemented
|
||||
|
||||
class IWithRequired(Interface):
|
||||
must = Text(required=True)
|
||||
|
||||
errors = self._callFUT(IWithRequired, object())
|
||||
self.assertEqual(len(errors), 1)
|
||||
self.assertEqual(errors[0][0], 'must')
|
||||
self.assertEqual(errors[0][1].__class__, SchemaNotFullyImplemented)
|
||||
|
||||
def test_schema_with_invariant_errors(self):
|
||||
from zope.interface import Interface
|
||||
from zope.interface import invariant
|
||||
from zope.interface.exceptions import Invalid
|
||||
|
||||
class IWithFailingInvariant(Interface):
|
||||
@invariant
|
||||
def _epic_fail(obj):
|
||||
raise Invalid('testing')
|
||||
|
||||
errors = self._callFUT(IWithFailingInvariant, object())
|
||||
self.assertEqual(len(errors), 1)
|
||||
self.assertEqual(errors[0][0], None)
|
||||
self.assertEqual(errors[0][1].__class__, Invalid)
|
||||
|
||||
def test_schema_with_invariant_ok(self):
|
||||
from zope.interface import Interface
|
||||
from zope.interface import invariant
|
||||
|
||||
class IWithPassingInvariant(Interface):
|
||||
@invariant
|
||||
def _hall_pass(obj):
|
||||
pass
|
||||
|
||||
errors = self._callFUT(IWithPassingInvariant, object())
|
||||
self.assertEqual(len(errors), 0)
|
||||
|
||||
|
||||
class Test_getSchemaValidationErrors(unittest.TestCase):
|
||||
|
||||
def _callFUT(self, schema, object):
|
||||
from zope.schema import getSchemaValidationErrors
|
||||
return getSchemaValidationErrors(schema, object)
|
||||
|
||||
def test_schema_wo_fields(self):
|
||||
from zope.interface import Interface
|
||||
from zope.interface import Attribute
|
||||
|
||||
class INoFields(Interface):
|
||||
def method():
|
||||
pass
|
||||
attr = Attribute('ignoreme')
|
||||
|
||||
errors = self._callFUT(INoFields, object())
|
||||
self.assertEqual(len(errors), 0)
|
||||
|
||||
def test_schema_with_fields_ok(self):
|
||||
from zope.interface import Interface
|
||||
from zope.schema import Text
|
||||
from zope.schema._compat import u
|
||||
|
||||
class IWithFields(Interface):
|
||||
foo = Text()
|
||||
bar = Text()
|
||||
|
||||
class Obj(object):
|
||||
foo = u('Foo')
|
||||
bar = u('Bar')
|
||||
|
||||
errors = self._callFUT(IWithFields, Obj())
|
||||
self.assertEqual(len(errors), 0)
|
||||
|
||||
def test_schema_with_missing_field(self):
|
||||
from zope.interface import Interface
|
||||
from zope.schema import Text
|
||||
from zope.schema.interfaces import SchemaNotFullyImplemented
|
||||
|
||||
class IWithRequired(Interface):
|
||||
must = Text(required=True)
|
||||
|
||||
errors = self._callFUT(IWithRequired, object())
|
||||
self.assertEqual(len(errors), 1)
|
||||
self.assertEqual(errors[0][0], 'must')
|
||||
self.assertEqual(errors[0][1].__class__, SchemaNotFullyImplemented)
|
||||
|
||||
def test_schema_with_invalid_field(self):
|
||||
from zope.interface import Interface
|
||||
from zope.schema import Int
|
||||
from zope.schema.interfaces import TooSmall
|
||||
|
||||
class IWithMinium(Interface):
|
||||
value = Int(required=True, min=0)
|
||||
|
||||
class Obj(object):
|
||||
value = -1
|
||||
|
||||
errors = self._callFUT(IWithMinium, Obj())
|
||||
self.assertEqual(len(errors), 1)
|
||||
self.assertEqual(errors[0][0], 'value')
|
||||
self.assertEqual(errors[0][1].__class__, TooSmall)
|
||||
|
||||
|
||||
def test_suite():
|
||||
return unittest.TestSuite((
|
||||
unittest.makeSuite(Test_getFields),
|
||||
unittest.makeSuite(Test_getFieldsInOrder),
|
||||
unittest.makeSuite(Test_getFieldNames),
|
||||
unittest.makeSuite(Test_getFieldNamesInOrder),
|
||||
unittest.makeSuite(Test_getValidationErrors),
|
||||
unittest.makeSuite(Test_getSchemaValidationErrors),
|
||||
))
|
||||
106
zope/schema/tests/test_states.py
Normal file
106
zope/schema/tests/test_states.py
Normal file
@@ -0,0 +1,106 @@
|
||||
##############################################################################
|
||||
#
|
||||
# Copyright (c) 2003 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.
|
||||
#
|
||||
##############################################################################
|
||||
"""Tests of the states example.
|
||||
"""
|
||||
import unittest
|
||||
|
||||
|
||||
class StateSelectionTest(unittest.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
from zope.schema.vocabulary import _clear
|
||||
from zope.schema.vocabulary import getVocabularyRegistry
|
||||
from zope.schema.tests.states import StateVocabulary
|
||||
_clear()
|
||||
vr = getVocabularyRegistry()
|
||||
vr.register("states", StateVocabulary)
|
||||
|
||||
def tearDown(self):
|
||||
from zope.schema.vocabulary import _clear
|
||||
_clear()
|
||||
|
||||
def _makeSchema(self):
|
||||
from zope.schema._compat import u
|
||||
from zope.interface import Interface
|
||||
from zope.schema import Choice
|
||||
from zope.schema.tests.states import StateVocabulary
|
||||
|
||||
class IBirthInfo(Interface):
|
||||
state1 = Choice(
|
||||
title=u('State of Birth'),
|
||||
description=u('The state in which you were born.'),
|
||||
vocabulary="states",
|
||||
default="AL",
|
||||
)
|
||||
state2 = Choice(
|
||||
title=u('State of Birth'),
|
||||
description=u('The state in which you were born.'),
|
||||
vocabulary="states",
|
||||
default="AL",
|
||||
)
|
||||
state3 = Choice(
|
||||
title=u('Favorite State'),
|
||||
description=u('The state you like the most.'),
|
||||
vocabulary=StateVocabulary(),
|
||||
)
|
||||
state4 = Choice(
|
||||
title=u("Name"),
|
||||
description=u("The name of your new state"),
|
||||
vocabulary="states",
|
||||
)
|
||||
return IBirthInfo
|
||||
|
||||
def test_default_presentation(self):
|
||||
from zope.interface.verify import verifyObject
|
||||
from zope.schema.interfaces import IVocabulary
|
||||
schema = self._makeSchema()
|
||||
field = schema.getDescriptionFor("state1")
|
||||
bound = field.bind(object())
|
||||
self.assertTrue(verifyObject(IVocabulary, bound.vocabulary))
|
||||
self.assertEqual(bound.vocabulary.getTerm("VA").title, "Virginia")
|
||||
|
||||
def test_contains(self):
|
||||
from zope.interface.verify import verifyObject
|
||||
from zope.schema.interfaces import IVocabulary
|
||||
from zope.schema.tests.states import StateVocabulary
|
||||
vocab = StateVocabulary()
|
||||
self.assertTrue(verifyObject(IVocabulary, vocab))
|
||||
count = 0
|
||||
L = list(vocab)
|
||||
for term in L:
|
||||
count += 1
|
||||
self.assertTrue(term.value in vocab)
|
||||
self.assertEqual(count, len(vocab))
|
||||
# make sure we get the same values the second time around:
|
||||
L = [term.value for term in L]
|
||||
L.sort()
|
||||
L2 = [term.value for term in vocab]
|
||||
L2.sort()
|
||||
self.assertEqual(L, L2)
|
||||
|
||||
def test_prebound_vocabulary(self):
|
||||
from zope.interface.verify import verifyObject
|
||||
from zope.schema.interfaces import IVocabulary
|
||||
schema = self._makeSchema()
|
||||
field = schema.getDescriptionFor("state3")
|
||||
bound = field.bind(None)
|
||||
self.assertTrue(bound.vocabularyName is None)
|
||||
self.assertTrue(verifyObject(IVocabulary, bound.vocabulary))
|
||||
self.assertTrue("AL" in bound.vocabulary)
|
||||
|
||||
|
||||
def test_suite():
|
||||
return unittest.TestSuite((
|
||||
unittest.makeSuite(StateSelectionTest),
|
||||
))
|
||||
642
zope/schema/tests/test_vocabulary.py
Normal file
642
zope/schema/tests/test_vocabulary.py
Normal file
@@ -0,0 +1,642 @@
|
||||
##############################################################################
|
||||
#
|
||||
# Copyright (c) 2003 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.
|
||||
#
|
||||
##############################################################################
|
||||
"""Test of the Vocabulary and related support APIs.
|
||||
"""
|
||||
import unittest
|
||||
|
||||
|
||||
class SimpleTermTests(unittest.TestCase):
|
||||
|
||||
def _getTargetClass(self):
|
||||
from zope.schema.vocabulary import SimpleTerm
|
||||
return SimpleTerm
|
||||
|
||||
def _makeOne(self, *args, **kw):
|
||||
return self._getTargetClass()(*args, **kw)
|
||||
|
||||
def test_class_conforms_to_ITokenizedTerm(self):
|
||||
from zope.interface.verify import verifyClass
|
||||
from zope.schema.interfaces import ITokenizedTerm
|
||||
verifyClass(ITokenizedTerm, self._getTargetClass())
|
||||
|
||||
def test_instance_conforms_to_ITokenizedTerm(self):
|
||||
from zope.interface.verify import verifyObject
|
||||
from zope.schema.interfaces import ITokenizedTerm
|
||||
verifyObject(ITokenizedTerm, self._makeOne('VALUE'))
|
||||
|
||||
def test_ctor_defaults(self):
|
||||
from zope.schema.interfaces import ITitledTokenizedTerm
|
||||
term = self._makeOne('VALUE')
|
||||
self.assertEqual(term.value, 'VALUE')
|
||||
self.assertEqual(term.token, 'VALUE')
|
||||
self.assertEqual(term.title, None)
|
||||
self.assertFalse(ITitledTokenizedTerm.providedBy(term))
|
||||
|
||||
def test_ctor_explicit(self):
|
||||
from zope.schema.interfaces import ITitledTokenizedTerm
|
||||
term = self._makeOne('TERM', 'TOKEN', 'TITLE')
|
||||
self.assertEqual(term.value, 'TERM')
|
||||
self.assertEqual(term.token, 'TOKEN')
|
||||
self.assertEqual(term.title, 'TITLE')
|
||||
self.assertTrue(ITitledTokenizedTerm.providedBy(term))
|
||||
|
||||
def test_bytes_value(self):
|
||||
from zope.schema.interfaces import ITitledTokenizedTerm
|
||||
term = self._makeOne(b'term')
|
||||
self.assertEqual(term.value, b'term')
|
||||
self.assertEqual(term.token, 'term')
|
||||
self.assertFalse(ITitledTokenizedTerm.providedBy(term))
|
||||
|
||||
|
||||
class SimpleVocabularyTests(unittest.TestCase):
|
||||
|
||||
def _getTargetClass(self):
|
||||
from zope.schema.vocabulary import SimpleVocabulary
|
||||
return SimpleVocabulary
|
||||
|
||||
def _makeOne(self, *args, **kw):
|
||||
return self._getTargetClass()(*args, **kw)
|
||||
|
||||
def test_class_conforms_to_IVocabularyTokenized(self):
|
||||
from zope.interface.verify import verifyClass
|
||||
from zope.schema.interfaces import IVocabularyTokenized
|
||||
verifyClass(IVocabularyTokenized, self._getTargetClass())
|
||||
|
||||
def test_instance_conforms_to_IVocabularyTokenized(self):
|
||||
from zope.interface.verify import verifyObject
|
||||
from zope.schema.interfaces import IVocabularyTokenized
|
||||
verifyObject(IVocabularyTokenized, self._makeOne(()))
|
||||
|
||||
def test_ctor_additional_interfaces(self):
|
||||
from zope.interface import Interface
|
||||
from zope.schema.vocabulary import SimpleTerm
|
||||
|
||||
class IStupid(Interface):
|
||||
pass
|
||||
|
||||
VALUES = [1, 4, 2, 9]
|
||||
vocabulary = self._makeOne([SimpleTerm(x) for x in VALUES], IStupid)
|
||||
self.assertTrue(IStupid.providedBy(vocabulary))
|
||||
self.assertEqual(len(vocabulary), len(VALUES))
|
||||
for value, term in zip(VALUES, vocabulary):
|
||||
self.assertEqual(term.value, value)
|
||||
for value in VALUES:
|
||||
self.assertTrue(value in vocabulary)
|
||||
self.assertFalse('ABC' in vocabulary)
|
||||
for term in vocabulary:
|
||||
self.assertTrue(vocabulary.getTerm(term.value) is term)
|
||||
self.assertTrue(vocabulary.getTermByToken(term.token) is term)
|
||||
|
||||
def test_fromValues(self):
|
||||
from zope.interface import Interface
|
||||
from zope.schema.interfaces import ITokenizedTerm
|
||||
|
||||
class IStupid(Interface):
|
||||
pass
|
||||
|
||||
VALUES = [1, 4, 2, 9]
|
||||
vocabulary = self._getTargetClass().fromValues(VALUES)
|
||||
self.assertEqual(len(vocabulary), len(VALUES))
|
||||
for value, term in zip(VALUES, vocabulary):
|
||||
self.assertTrue(ITokenizedTerm.providedBy(term))
|
||||
self.assertEqual(term.value, value)
|
||||
for value in VALUES:
|
||||
self.assertTrue(value in vocabulary)
|
||||
|
||||
def test_fromItems(self):
|
||||
from zope.interface import Interface
|
||||
from zope.schema.interfaces import ITokenizedTerm
|
||||
|
||||
class IStupid(Interface):
|
||||
pass
|
||||
|
||||
ITEMS = [('one', 1), ('two', 2), ('three', 3), ('fore!', 4)]
|
||||
vocabulary = self._getTargetClass().fromItems(ITEMS)
|
||||
self.assertEqual(len(vocabulary), len(ITEMS))
|
||||
for item, term in zip(ITEMS, vocabulary):
|
||||
self.assertTrue(ITokenizedTerm.providedBy(term))
|
||||
self.assertEqual(term.token, item[0])
|
||||
self.assertEqual(term.value, item[1])
|
||||
for item in ITEMS:
|
||||
self.assertTrue(item[1] in vocabulary)
|
||||
|
||||
def test_createTerm(self):
|
||||
from zope.schema.vocabulary import SimpleTerm
|
||||
VALUES = [1, 4, 2, 9]
|
||||
for value in VALUES:
|
||||
term = self._getTargetClass().createTerm(value)
|
||||
self.assertTrue(isinstance(term, SimpleTerm))
|
||||
self.assertEqual(term.value, value)
|
||||
self.assertEqual(term.token, str(value))
|
||||
|
||||
def test_getTerm_miss(self):
|
||||
vocabulary = self._makeOne(())
|
||||
self.assertRaises(LookupError, vocabulary.getTerm, 'nonesuch')
|
||||
|
||||
def test_getTermByToken_miss(self):
|
||||
vocabulary = self._makeOne(())
|
||||
self.assertRaises(LookupError, vocabulary.getTermByToken, 'nonesuch')
|
||||
|
||||
def test_nonunique_tokens(self):
|
||||
klass = self._getTargetClass()
|
||||
self.assertRaises(ValueError, klass.fromValues, [2, '2'])
|
||||
self.assertRaises(
|
||||
ValueError,
|
||||
klass.fromItems,
|
||||
[(1, 'one'), ('1', 'another one')]
|
||||
)
|
||||
self.assertRaises(
|
||||
ValueError,
|
||||
klass.fromItems,
|
||||
[(0, 'one'), (1, 'one')]
|
||||
)
|
||||
|
||||
def test_nonunique_tokens_swallow(self):
|
||||
klass = self._getTargetClass()
|
||||
items = [(0, 'one'), (1, 'one')]
|
||||
terms = [klass.createTerm(value, token) for (token, value) in items]
|
||||
vocab = self._getTargetClass()(terms, swallow_duplicates=True)
|
||||
self.assertEqual(vocab.getTerm('one').token, '1')
|
||||
|
||||
def test_nonunique_token_message(self):
|
||||
try:
|
||||
self._getTargetClass().fromValues([2, '2'])
|
||||
except ValueError as e:
|
||||
self.assertEqual(str(e), "term tokens must be unique: '2'")
|
||||
|
||||
def test_nonunique_token_messages(self):
|
||||
try:
|
||||
self._getTargetClass().fromItems([(0, 'one'), (1, 'one')])
|
||||
except ValueError as e:
|
||||
self.assertEqual(str(e), "term values must be unique: 'one'")
|
||||
|
||||
def test_overriding_createTerm(self):
|
||||
class MyTerm(object):
|
||||
def __init__(self, value):
|
||||
self.value = value
|
||||
self.token = repr(value)
|
||||
self.nextvalue = value + 1
|
||||
|
||||
class MyVocabulary(self._getTargetClass()):
|
||||
def createTerm(cls, value):
|
||||
return MyTerm(value)
|
||||
createTerm = classmethod(createTerm)
|
||||
|
||||
vocab = MyVocabulary.fromValues([1, 2, 3])
|
||||
for term in vocab:
|
||||
self.assertEqual(term.value + 1, term.nextvalue)
|
||||
|
||||
|
||||
# Test _createTermTree via TreeVocabulary.fromDict
|
||||
|
||||
|
||||
class TreeVocabularyTests(unittest.TestCase):
|
||||
|
||||
def _getTargetClass(self):
|
||||
from zope.schema.vocabulary import TreeVocabulary
|
||||
return TreeVocabulary
|
||||
|
||||
def tree_vocab_2(self):
|
||||
region_tree = {
|
||||
('regions', 'Regions'): {
|
||||
('aut', 'Austria'): {
|
||||
('tyr', 'Tyrol'): {
|
||||
('auss', 'Ausserfern'): {},
|
||||
}
|
||||
},
|
||||
('ger', 'Germany'): {
|
||||
('bav', 'Bavaria'): {}
|
||||
},
|
||||
}
|
||||
}
|
||||
return self._getTargetClass().fromDict(region_tree)
|
||||
|
||||
def business_tree(self):
|
||||
return {
|
||||
('services', 'services', 'Services'): {
|
||||
('reservations', 'reservations', 'Reservations'): {
|
||||
('res_host', 'res_host', 'Res Host'): {},
|
||||
('res_gui', 'res_gui', 'Res GUI'): {},
|
||||
},
|
||||
('check_in', 'check_in', 'Check-in'): {
|
||||
('dcs_host', 'dcs_host', 'DCS Host'): {},
|
||||
},
|
||||
},
|
||||
('infrastructure', 'infrastructure', 'Infrastructure'): {
|
||||
('communication_network', 'communication_network',
|
||||
'Communication/Network'): {
|
||||
('messaging', 'messaging', 'Messaging'): {},
|
||||
},
|
||||
('data_transaction', 'data_transaction',
|
||||
'Data/Transaction'): {
|
||||
('database', 'database', 'Database'): {},
|
||||
},
|
||||
('security', 'security', 'Security'): {},
|
||||
},
|
||||
}
|
||||
|
||||
def tree_vocab_3(self):
|
||||
return self._getTargetClass().fromDict(self.business_tree())
|
||||
|
||||
def test_implementation(self):
|
||||
from zope.interface.verify import verifyObject
|
||||
from zope.interface.common.mapping import IEnumerableMapping
|
||||
from zope.schema.interfaces import ITreeVocabulary
|
||||
from zope.schema.interfaces import IVocabulary
|
||||
from zope.schema.interfaces import IVocabularyTokenized
|
||||
for v in [self.tree_vocab_2(), self.tree_vocab_3()]:
|
||||
self.assertTrue(verifyObject(IEnumerableMapping, v))
|
||||
self.assertTrue(verifyObject(IVocabulary, v))
|
||||
self.assertTrue(verifyObject(IVocabularyTokenized, v))
|
||||
self.assertTrue(verifyObject(ITreeVocabulary, v))
|
||||
|
||||
def test_additional_interfaces(self):
|
||||
from zope.interface import Interface
|
||||
|
||||
class IStupid(Interface):
|
||||
pass
|
||||
|
||||
v = self._getTargetClass().fromDict({('one', '1'): {}}, IStupid)
|
||||
self.assertTrue(IStupid.providedBy(v))
|
||||
|
||||
def test_ordering(self):
|
||||
#The TreeVocabulary makes use of an OrderedDict to store it's
|
||||
#internal tree representation.
|
||||
#
|
||||
#Check that they keys are indeed oredered.
|
||||
from zope.schema._compat import OrderedDict
|
||||
|
||||
d = {
|
||||
(1, 'new_york', 'New York'): {
|
||||
(2, 'ny_albany', 'Albany'): {},
|
||||
(3, 'ny_new_york', 'New York'): {},
|
||||
},
|
||||
(4, 'california', 'California'): {
|
||||
(5, 'ca_los_angeles', 'Los Angeles'): {},
|
||||
(6, 'ca_san_francisco', 'San Francisco'): {},
|
||||
},
|
||||
(7, 'texas', 'Texas'): {},
|
||||
(8, 'florida', 'Florida'): {},
|
||||
(9, 'utah', 'Utah'): {},
|
||||
}
|
||||
dict_ = OrderedDict(sorted(d.items(), key=lambda t: t[0]))
|
||||
vocab = self._getTargetClass().fromDict(dict_)
|
||||
# Test keys
|
||||
self.assertEqual(
|
||||
[k.token for k in vocab.keys()],
|
||||
['1', '4', '7', '8', '9']
|
||||
)
|
||||
# Test __iter__
|
||||
self.assertEqual(
|
||||
[k.token for k in vocab],
|
||||
['1', '4', '7', '8', '9']
|
||||
)
|
||||
|
||||
self.assertEqual(
|
||||
[k.token for k in vocab[[k for k in vocab.keys()][0]].keys()],
|
||||
['2', '3']
|
||||
)
|
||||
self.assertEqual(
|
||||
[k.token for k in vocab[[k for k in vocab.keys()][1]].keys()],
|
||||
['5', '6']
|
||||
)
|
||||
|
||||
def test_indexes(self):
|
||||
# TreeVocabulary creates three indexes for quick lookups,
|
||||
# term_by_value, term_by_value and path_by_value.
|
||||
tv2 = self.tree_vocab_2()
|
||||
self.assertEqual(
|
||||
[k for k in sorted(tv2.term_by_value.keys())],
|
||||
['Ausserfern', 'Austria', 'Bavaria', 'Germany', 'Regions', 'Tyrol']
|
||||
)
|
||||
|
||||
self.assertEqual(
|
||||
[k for k in sorted(tv2.term_by_token.keys())],
|
||||
['auss', 'aut', 'bav', 'ger', 'regions', 'tyr']
|
||||
)
|
||||
|
||||
self.assertEqual(
|
||||
[k for k in sorted(tv2.path_by_value.keys())],
|
||||
['Ausserfern', 'Austria', 'Bavaria', 'Germany', 'Regions', 'Tyrol']
|
||||
)
|
||||
|
||||
self.assertEqual(
|
||||
[k for k in sorted(tv2.path_by_value.values())],
|
||||
[
|
||||
['Regions'],
|
||||
['Regions', 'Austria'],
|
||||
['Regions', 'Austria', 'Tyrol'],
|
||||
['Regions', 'Austria', 'Tyrol', 'Ausserfern'],
|
||||
['Regions', 'Germany'],
|
||||
['Regions', 'Germany', 'Bavaria'],
|
||||
]
|
||||
)
|
||||
|
||||
self.assertEqual(
|
||||
[k for k in sorted(self.tree_vocab_3().term_by_value.keys())],
|
||||
[
|
||||
'check_in',
|
||||
'communication_network',
|
||||
'data_transaction',
|
||||
'database',
|
||||
'dcs_host',
|
||||
'infrastructure',
|
||||
'messaging',
|
||||
'res_gui',
|
||||
'res_host',
|
||||
'reservations',
|
||||
'security',
|
||||
'services',
|
||||
]
|
||||
)
|
||||
|
||||
self.assertEqual(
|
||||
[k for k in sorted(self.tree_vocab_3().term_by_token.keys())],
|
||||
[
|
||||
'check_in',
|
||||
'communication_network',
|
||||
'data_transaction',
|
||||
'database',
|
||||
'dcs_host',
|
||||
'infrastructure',
|
||||
'messaging',
|
||||
'res_gui',
|
||||
'res_host',
|
||||
'reservations',
|
||||
'security',
|
||||
'services',
|
||||
]
|
||||
)
|
||||
|
||||
self.assertEqual(
|
||||
[k for k in sorted(self.tree_vocab_3().path_by_value.values())],
|
||||
[
|
||||
['infrastructure'],
|
||||
['infrastructure', 'communication_network'],
|
||||
['infrastructure', 'communication_network', 'messaging'],
|
||||
['infrastructure', 'data_transaction'],
|
||||
['infrastructure', 'data_transaction', 'database'],
|
||||
['infrastructure', 'security'],
|
||||
['services'],
|
||||
['services', 'check_in'],
|
||||
['services', 'check_in', 'dcs_host'],
|
||||
['services', 'reservations'],
|
||||
['services', 'reservations', 'res_gui'],
|
||||
['services', 'reservations', 'res_host'],
|
||||
]
|
||||
)
|
||||
|
||||
def test_termpath(self):
|
||||
tv2 = self.tree_vocab_2()
|
||||
tv3 = self.tree_vocab_3()
|
||||
self.assertEqual(
|
||||
tv2.getTermPath('Bavaria'),
|
||||
['Regions', 'Germany', 'Bavaria']
|
||||
)
|
||||
self.assertEqual(
|
||||
tv2.getTermPath('Austria'),
|
||||
['Regions', 'Austria']
|
||||
)
|
||||
self.assertEqual(
|
||||
tv2.getTermPath('Ausserfern'),
|
||||
['Regions', 'Austria', 'Tyrol', 'Ausserfern']
|
||||
)
|
||||
self.assertEqual(
|
||||
tv2.getTermPath('Non-existent'),
|
||||
[]
|
||||
)
|
||||
self.assertEqual(
|
||||
tv3.getTermPath('database'),
|
||||
["infrastructure", "data_transaction", "database"]
|
||||
)
|
||||
|
||||
def test_len(self):
|
||||
# len returns the number of all nodes in the dict
|
||||
self.assertEqual(len(self.tree_vocab_2()), 1)
|
||||
self.assertEqual(len(self.tree_vocab_3()), 2)
|
||||
|
||||
def test_contains(self):
|
||||
tv2 = self.tree_vocab_2()
|
||||
self.assertTrue('Regions' in tv2 and
|
||||
'Austria' in tv2 and
|
||||
'Bavaria' in tv2)
|
||||
|
||||
self.assertTrue('bav' not in tv2)
|
||||
self.assertTrue('foo' not in tv2)
|
||||
self.assertTrue({} not in tv2) # not hashable
|
||||
|
||||
tv3 = self.tree_vocab_3()
|
||||
self.assertTrue('database' in tv3 and
|
||||
'security' in tv3 and
|
||||
'services' in tv3)
|
||||
|
||||
self.assertTrue('Services' not in tv3)
|
||||
self.assertTrue('Database' not in tv3)
|
||||
self.assertTrue({} not in tv3) # not hashable
|
||||
|
||||
def test_values_and_items(self):
|
||||
for v in (self.tree_vocab_2(), self.tree_vocab_3()):
|
||||
for term in v:
|
||||
self.assertEqual([i for i in v.values()],
|
||||
[i for i in v._terms.values()])
|
||||
self.assertEqual([i for i in v.items()],
|
||||
[i for i in v._terms.items()])
|
||||
|
||||
def test_get(self):
|
||||
for v in [self.tree_vocab_2(), self.tree_vocab_3()]:
|
||||
for key, value in v.items():
|
||||
self.assertEqual(v.get(key), value)
|
||||
self.assertEqual(v[key], value)
|
||||
|
||||
def test_get_term(self):
|
||||
for v in (self.tree_vocab_2(), self.tree_vocab_3()):
|
||||
for term in v:
|
||||
self.assertTrue(v.getTerm(term.value) is term)
|
||||
self.assertTrue(v.getTermByToken(term.token) is term)
|
||||
self.assertRaises(LookupError, v.getTerm, 'non-present-value')
|
||||
self.assertRaises(LookupError,
|
||||
v.getTermByToken, 'non-present-token')
|
||||
|
||||
def test_nonunique_values_and_tokens(self):
|
||||
# Since we do term and value lookups, all terms' values and tokens
|
||||
# must be unique. This rule applies recursively.
|
||||
self.assertRaises(
|
||||
ValueError, self._getTargetClass().fromDict,
|
||||
{
|
||||
('one', '1'): {},
|
||||
('two', '1'): {},
|
||||
})
|
||||
self.assertRaises(
|
||||
ValueError, self._getTargetClass().fromDict,
|
||||
{
|
||||
('one', '1'): {},
|
||||
('one', '2'): {},
|
||||
})
|
||||
# Even nested tokens must be unique.
|
||||
self.assertRaises(
|
||||
ValueError, self._getTargetClass().fromDict,
|
||||
{
|
||||
('new_york', 'New York'): {
|
||||
('albany', 'Albany'): {},
|
||||
('new_york', 'New York'): {},
|
||||
},
|
||||
})
|
||||
# The same applies to nested values.
|
||||
self.assertRaises(
|
||||
ValueError, self._getTargetClass().fromDict,
|
||||
{
|
||||
('1', 'new_york'): {
|
||||
('2', 'albany'): {},
|
||||
('3', 'new_york'): {},
|
||||
},
|
||||
})
|
||||
# The title attribute does however not have to be unique.
|
||||
self._getTargetClass().fromDict({
|
||||
('1', 'new_york', 'New York'): {
|
||||
('2', 'ny_albany', 'Albany'): {},
|
||||
('3', 'ny_new_york', 'New York'): {},
|
||||
},
|
||||
})
|
||||
self._getTargetClass().fromDict({
|
||||
('one', '1', 'One'): {},
|
||||
('two', '2', 'One'): {},
|
||||
})
|
||||
|
||||
def test_nonunique_value_message(self):
|
||||
try:
|
||||
self._getTargetClass().fromDict({
|
||||
('one', '1'): {},
|
||||
('two', '1'): {},
|
||||
})
|
||||
except ValueError as e:
|
||||
self.assertEqual(str(e), "Term values must be unique: '1'")
|
||||
|
||||
def test_nonunique_token_message(self):
|
||||
try:
|
||||
self._getTargetClass().fromDict({
|
||||
('one', '1'): {},
|
||||
('one', '2'): {},
|
||||
})
|
||||
except ValueError as e:
|
||||
self.assertEqual(str(e), "Term tokens must be unique: 'one'")
|
||||
|
||||
def test_recursive_methods(self):
|
||||
#Test the _createTermTree and _getPathToTreeNode methods
|
||||
from zope.schema.vocabulary import _createTermTree
|
||||
tree = _createTermTree({}, self.business_tree())
|
||||
vocab = self._getTargetClass().fromDict(self.business_tree())
|
||||
|
||||
term_path = vocab._getPathToTreeNode(tree, "infrastructure")
|
||||
vocab_path = vocab._getPathToTreeNode(vocab, "infrastructure")
|
||||
self.assertEqual(term_path, vocab_path)
|
||||
self.assertEqual(term_path, ["infrastructure"])
|
||||
|
||||
term_path = vocab._getPathToTreeNode(tree, "security")
|
||||
vocab_path = vocab._getPathToTreeNode(vocab, "security")
|
||||
self.assertEqual(term_path, vocab_path)
|
||||
self.assertEqual(term_path, ["infrastructure", "security"])
|
||||
|
||||
term_path = vocab._getPathToTreeNode(tree, "database")
|
||||
vocab_path = vocab._getPathToTreeNode(vocab, "database")
|
||||
self.assertEqual(term_path, vocab_path)
|
||||
self.assertEqual(term_path,
|
||||
["infrastructure", "data_transaction", "database"])
|
||||
|
||||
term_path = vocab._getPathToTreeNode(tree, "dcs_host")
|
||||
vocab_path = vocab._getPathToTreeNode(vocab, "dcs_host")
|
||||
self.assertEqual(term_path, vocab_path)
|
||||
self.assertEqual(term_path, ["services", "check_in", "dcs_host"])
|
||||
|
||||
term_path = vocab._getPathToTreeNode(tree, "dummy")
|
||||
vocab_path = vocab._getPathToTreeNode(vocab, "dummy")
|
||||
self.assertEqual(term_path, vocab_path)
|
||||
self.assertEqual(term_path, [])
|
||||
|
||||
|
||||
class RegistryTests(unittest.TestCase):
|
||||
#Tests of the simple vocabulary and presentation registries.
|
||||
|
||||
def setUp(self):
|
||||
from zope.schema.vocabulary import _clear
|
||||
_clear()
|
||||
|
||||
def tearDown(self):
|
||||
from zope.schema.vocabulary import _clear
|
||||
_clear()
|
||||
|
||||
def test_setVocabularyRegistry(self):
|
||||
from zope.schema.vocabulary import setVocabularyRegistry
|
||||
from zope.schema.vocabulary import getVocabularyRegistry
|
||||
r = _makeDummyRegistry()
|
||||
setVocabularyRegistry(r)
|
||||
self.assertTrue(getVocabularyRegistry() is r)
|
||||
|
||||
def test_getVocabularyRegistry(self):
|
||||
from zope.schema.interfaces import IVocabularyRegistry
|
||||
from zope.schema.vocabulary import getVocabularyRegistry
|
||||
r = getVocabularyRegistry()
|
||||
self.assertTrue(IVocabularyRegistry.providedBy(r))
|
||||
|
||||
# TODO: still need to test the default implementation
|
||||
|
||||
|
||||
def _makeSampleVocabulary():
|
||||
from zope.interface import implementer
|
||||
from zope.schema.interfaces import IVocabulary
|
||||
|
||||
class SampleTerm(object):
|
||||
pass
|
||||
|
||||
@implementer(IVocabulary)
|
||||
class SampleVocabulary(object):
|
||||
|
||||
def __iter__(self):
|
||||
return iter([self.getTerm(x) for x in range(0, 10)])
|
||||
|
||||
def __contains__(self, value):
|
||||
return 0 <= value < 10
|
||||
|
||||
def __len__(self):
|
||||
return 10
|
||||
|
||||
def getTerm(self, value):
|
||||
if value in self:
|
||||
t = SampleTerm()
|
||||
t.value = value
|
||||
t.double = 2 * value
|
||||
return t
|
||||
raise LookupError("no such value: %r" % value)
|
||||
|
||||
return SampleVocabulary()
|
||||
|
||||
|
||||
def _makeDummyRegistry():
|
||||
from zope.schema.vocabulary import VocabularyRegistry
|
||||
|
||||
class DummyRegistry(VocabularyRegistry):
|
||||
def get(self, object, name):
|
||||
v = _makeSampleVocabulary()
|
||||
v.object = object
|
||||
v.name = name
|
||||
return v
|
||||
return DummyRegistry()
|
||||
|
||||
|
||||
def test_suite():
|
||||
return unittest.TestSuite((
|
||||
unittest.makeSuite(SimpleTermTests),
|
||||
unittest.makeSuite(SimpleVocabularyTests),
|
||||
unittest.makeSuite(TreeVocabularyTests),
|
||||
unittest.makeSuite(RegistryTests),
|
||||
))
|
||||
409
zope/schema/vocabulary.py
Normal file
409
zope/schema/vocabulary.py
Normal file
@@ -0,0 +1,409 @@
|
||||
##############################################################################
|
||||
#
|
||||
# Copyright (c) 2003 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.
|
||||
#
|
||||
##############################################################################
|
||||
"""Vocabulary support for schema.
|
||||
"""
|
||||
|
||||
from zope.interface import directlyProvides
|
||||
from zope.interface import implementer
|
||||
from zope.schema.interfaces import ITitledTokenizedTerm
|
||||
from zope.schema.interfaces import ITokenizedTerm
|
||||
from zope.schema.interfaces import ITreeVocabulary
|
||||
from zope.schema.interfaces import IVocabularyRegistry
|
||||
from zope.schema.interfaces import IVocabularyTokenized
|
||||
from zope.schema._compat import OrderedDict
|
||||
|
||||
# simple vocabularies performing enumerated-like tasks
|
||||
_marker = object()
|
||||
|
||||
|
||||
@implementer(ITokenizedTerm)
|
||||
class SimpleTerm(object):
|
||||
"""Simple tokenized term used by SimpleVocabulary."""
|
||||
|
||||
def __init__(self, value, token=None, title=None):
|
||||
"""Create a term for value and token. If token is omitted,
|
||||
str(value) is used for the token. If title is provided,
|
||||
term implements ITitledTokenizedTerm.
|
||||
"""
|
||||
self.value = value
|
||||
if token is None:
|
||||
token = value
|
||||
# In Python 3 str(bytes) returns str(repr(bytes)), which is not what
|
||||
# we want here. On the other hand, we want to try to keep the token as
|
||||
# readable as possible.
|
||||
self.token = str(token) \
|
||||
if not isinstance(token, bytes) \
|
||||
else str(token.decode('ascii', 'ignore'))
|
||||
self.title = title
|
||||
if title is not None:
|
||||
directlyProvides(self, ITitledTokenizedTerm)
|
||||
|
||||
|
||||
@implementer(IVocabularyTokenized)
|
||||
class SimpleVocabulary(object):
|
||||
"""Vocabulary that works from a sequence of terms."""
|
||||
|
||||
def __init__(self, terms, *interfaces, **kwargs):
|
||||
"""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.
|
||||
|
||||
By default, ValueErrors are thrown if duplicate values or tokens
|
||||
are passed in. If you want to swallow these exceptions, pass
|
||||
in swallow_duplicates=True. In this case, the values will
|
||||
override themselves.
|
||||
"""
|
||||
self.by_value = {}
|
||||
self.by_token = {}
|
||||
self._terms = terms
|
||||
for term in self._terms:
|
||||
swallow_dupes = kwargs.get('swallow_duplicates', False)
|
||||
if not swallow_dupes:
|
||||
if term.value in self.by_value:
|
||||
raise ValueError(
|
||||
'term values must be unique: %s' % repr(term.value))
|
||||
if term.token in self.by_token:
|
||||
raise ValueError(
|
||||
'term tokens must be unique: %s' % repr(term.token))
|
||||
self.by_value[term.value] = term
|
||||
self.by_token[term.token] = term
|
||||
if interfaces:
|
||||
directlyProvides(self, *interfaces)
|
||||
|
||||
@classmethod
|
||||
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)
|
||||
|
||||
@classmethod
|
||||
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)
|
||||
|
||||
@classmethod
|
||||
def createTerm(cls, *args):
|
||||
"""Create a single term from data.
|
||||
|
||||
Subclasses may override this with a class method that creates
|
||||
a term of the appropriate type from the arguments.
|
||||
"""
|
||||
return SimpleTerm(*args)
|
||||
|
||||
def __contains__(self, value):
|
||||
"""See zope.schema.interfaces.IBaseVocabulary"""
|
||||
try:
|
||||
return value in self.by_value
|
||||
except TypeError:
|
||||
# sometimes values are not hashable
|
||||
return False
|
||||
|
||||
def getTerm(self, value):
|
||||
"""See zope.schema.interfaces.IBaseVocabulary"""
|
||||
try:
|
||||
return self.by_value[value]
|
||||
except KeyError:
|
||||
raise LookupError(value)
|
||||
|
||||
def getTermByToken(self, token):
|
||||
"""See zope.schema.interfaces.IVocabularyTokenized"""
|
||||
try:
|
||||
return self.by_token[token]
|
||||
except KeyError:
|
||||
raise LookupError(token)
|
||||
|
||||
def __iter__(self):
|
||||
"""See zope.schema.interfaces.IIterableVocabulary"""
|
||||
return iter(self._terms)
|
||||
|
||||
def __len__(self):
|
||||
"""See zope.schema.interfaces.IIterableVocabulary"""
|
||||
return len(self.by_value)
|
||||
|
||||
|
||||
def _createTermTree(ttree, dict_):
|
||||
""" Helper method that creates a tree-like dict with ITokenizedTerm
|
||||
objects as keys from a similar tree with tuples as keys.
|
||||
|
||||
See fromDict for more details.
|
||||
"""
|
||||
for key in sorted(dict_.keys()):
|
||||
term = SimpleTerm(key[1], key[0], key[-1])
|
||||
ttree[term] = TreeVocabulary.terms_factory()
|
||||
_createTermTree(ttree[term], dict_[key])
|
||||
return ttree
|
||||
|
||||
|
||||
@implementer(ITreeVocabulary)
|
||||
class TreeVocabulary(object):
|
||||
""" Vocabulary that relies on a tree (i.e nested) structure.
|
||||
"""
|
||||
# The default implementation uses a dict to create the tree structure. This
|
||||
# can however be overridden in a subclass by any other IEnumerableMapping
|
||||
# compliant object type. Python 2.7's OrderedDict for example.
|
||||
terms_factory = OrderedDict
|
||||
|
||||
def __init__(self, terms, *interfaces):
|
||||
"""Initialize the vocabulary given a recursive dict (i.e a tree) with
|
||||
ITokenizedTerm objects for keys and self-similar dicts representing the
|
||||
branches for values.
|
||||
|
||||
Refer to the method fromDict for more details.
|
||||
|
||||
Concerning the ITokenizedTerm keys, the 'value' and 'token' attributes
|
||||
of each key (including nested ones) must be unique.
|
||||
|
||||
One or more interfaces may also be provided so that alternate
|
||||
widgets may be bound without subclassing.
|
||||
"""
|
||||
self._terms = self.terms_factory()
|
||||
self._terms.update(terms)
|
||||
|
||||
self.path_by_value = {}
|
||||
self.term_by_value = {}
|
||||
self.term_by_token = {}
|
||||
self._populateIndexes(terms)
|
||||
|
||||
if interfaces:
|
||||
directlyProvides(self, *interfaces)
|
||||
|
||||
def __contains__(self, value):
|
||||
""" See zope.schema.interfaces.IBaseVocabulary
|
||||
|
||||
D.__contains__(k) -> True if D has a key k, else False
|
||||
"""
|
||||
try:
|
||||
return value in self.term_by_value
|
||||
except TypeError:
|
||||
# sometimes values are not hashable
|
||||
return False
|
||||
|
||||
def __getitem__(self, key):
|
||||
"""x.__getitem__(y) <==> x[y]
|
||||
"""
|
||||
return self._terms.__getitem__(key)
|
||||
|
||||
def __iter__(self):
|
||||
"""See zope.schema.interfaces.IIterableVocabulary
|
||||
|
||||
x.__iter__() <==> iter(x)
|
||||
"""
|
||||
return self._terms.__iter__()
|
||||
|
||||
def __len__(self):
|
||||
"""x.__len__() <==> len(x)
|
||||
"""
|
||||
return self._terms.__len__()
|
||||
|
||||
def get(self, key, default=None):
|
||||
"""Get a value for a key
|
||||
|
||||
The default is returned if there is no value for the key.
|
||||
"""
|
||||
return self._terms.get(key, default)
|
||||
|
||||
def keys(self):
|
||||
"""Return the keys of the mapping object.
|
||||
"""
|
||||
return self._terms.keys()
|
||||
|
||||
def values(self):
|
||||
"""Return the values of the mapping object.
|
||||
"""
|
||||
return self._terms.values()
|
||||
|
||||
def items(self):
|
||||
"""Return the items of the mapping object.
|
||||
"""
|
||||
return self._terms.items()
|
||||
|
||||
@classmethod
|
||||
def fromDict(cls, dict_, *interfaces):
|
||||
"""Constructs a vocabulary from a dictionary-like object (like dict or
|
||||
OrderedDict), that has tuples for keys.
|
||||
|
||||
The tuples should have either 2 or 3 values, i.e:
|
||||
(token, value, title) or (token, value)
|
||||
|
||||
For example, a dict with 2-valued tuples:
|
||||
|
||||
dict_ = {
|
||||
('exampleregions', 'Regions used in ATVocabExample'): {
|
||||
('aut', 'Austria'): {
|
||||
('tyr', 'Tyrol'): {
|
||||
('auss', 'Ausserfern'): {},
|
||||
}
|
||||
},
|
||||
('ger', 'Germany'): {
|
||||
('bav', 'Bavaria'):{}
|
||||
},
|
||||
}
|
||||
}
|
||||
One or more interfaces may also be provided so that alternate
|
||||
widgets may be bound without subclassing.
|
||||
"""
|
||||
return cls(_createTermTree(cls.terms_factory(), dict_), *interfaces)
|
||||
|
||||
def _populateIndexes(self, tree):
|
||||
""" The TreeVocabulary contains three helper indexes for quick lookups.
|
||||
They are: term_by_value, term_by_token and path_by_value
|
||||
|
||||
This method recurses through the tree and populates these indexes.
|
||||
|
||||
tree: The tree (a nested/recursive dictionary).
|
||||
"""
|
||||
for term in tree.keys():
|
||||
value = getattr(term, 'value')
|
||||
token = getattr(term, 'token')
|
||||
|
||||
if value in self.term_by_value:
|
||||
raise ValueError(
|
||||
"Term values must be unique: '%s'" % value)
|
||||
|
||||
if token in self.term_by_token:
|
||||
raise ValueError(
|
||||
"Term tokens must be unique: '%s'" % token)
|
||||
|
||||
self.term_by_value[value] = term
|
||||
self.term_by_token[token] = term
|
||||
|
||||
if value not in self.path_by_value:
|
||||
self.path_by_value[value] = self._getPathToTreeNode(self,
|
||||
value)
|
||||
self._populateIndexes(tree[term])
|
||||
|
||||
def getTerm(self, value):
|
||||
"""See zope.schema.interfaces.IBaseVocabulary"""
|
||||
try:
|
||||
return self.term_by_value[value]
|
||||
except KeyError:
|
||||
raise LookupError(value)
|
||||
|
||||
def getTermByToken(self, token):
|
||||
"""See zope.schema.interfaces.IVocabularyTokenized"""
|
||||
try:
|
||||
return self.term_by_token[token]
|
||||
except KeyError:
|
||||
raise LookupError(token)
|
||||
|
||||
def _getPathToTreeNode(self, tree, node):
|
||||
"""Helper method that computes the path in the tree from the root
|
||||
to the given node.
|
||||
|
||||
The tree must be a recursive IEnumerableMapping object.
|
||||
"""
|
||||
path = []
|
||||
for parent, child in tree.items():
|
||||
if node == parent.value:
|
||||
return [node]
|
||||
path = self._getPathToTreeNode(child, node)
|
||||
if path:
|
||||
path.insert(0, parent.value)
|
||||
break
|
||||
return path
|
||||
|
||||
def getTermPath(self, value):
|
||||
"""Returns a list of strings representing the path from the root node
|
||||
to the node with the given value in the tree.
|
||||
|
||||
Returns an empty string if no node has that value.
|
||||
"""
|
||||
return self.path_by_value.get(value, [])
|
||||
|
||||
|
||||
# registry code
|
||||
class VocabularyRegistryError(LookupError):
|
||||
def __init__(self, name):
|
||||
self.name = name
|
||||
Exception.__init__(self, str(self))
|
||||
|
||||
def __str__(self):
|
||||
return "unknown vocabulary: %r" % self.name
|
||||
|
||||
|
||||
@implementer(IVocabularyRegistry)
|
||||
class VocabularyRegistry(object):
|
||||
__slots__ = '_map',
|
||||
|
||||
def __init__(self):
|
||||
self._map = {}
|
||||
|
||||
def get(self, object, name):
|
||||
"""See zope.schema.interfaces.IVocabularyRegistry"""
|
||||
try:
|
||||
vtype = self._map[name]
|
||||
except KeyError:
|
||||
raise VocabularyRegistryError(name)
|
||||
return vtype(object)
|
||||
|
||||
def register(self, name, factory):
|
||||
self._map[name] = factory
|
||||
|
||||
_vocabularies = None
|
||||
|
||||
|
||||
def getVocabularyRegistry():
|
||||
"""Return the vocabulary registry.
|
||||
|
||||
If the registry has not been created yet, an instance of
|
||||
VocabularyRegistry will be installed and used.
|
||||
"""
|
||||
if _vocabularies is None:
|
||||
setVocabularyRegistry(VocabularyRegistry())
|
||||
return _vocabularies
|
||||
|
||||
|
||||
def setVocabularyRegistry(registry):
|
||||
"""Set the vocabulary registry."""
|
||||
global _vocabularies
|
||||
_vocabularies = registry
|
||||
|
||||
|
||||
def _clear():
|
||||
"""Remove the registries (for use by tests)."""
|
||||
global _vocabularies
|
||||
_vocabularies = None
|
||||
|
||||
|
||||
try:
|
||||
from zope.testing.cleanup import addCleanUp
|
||||
except ImportError: # pragma: no cover
|
||||
# don't have that part of Zope
|
||||
pass
|
||||
else: # pragma: no cover
|
||||
addCleanUp(_clear)
|
||||
del addCleanUp
|
||||
Reference in New Issue
Block a user