############################################################################## # # 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 decimal import doctest import unicodedata import unittest # pylint:disable=protected-access,inherit-non-class,blacklisted-name # pylint:disable=attribute-defined-outside-init class InterfaceConformanceTestsMixin(object): def _getTargetClass(self): raise NotImplementedError def _getTargetInterface(self): raise NotImplementedError def _getTargetInterfaces(self): # Return the primary and any secondary interfaces return [self._getTargetInterface()] def _makeOne(self, *args, **kwargs): return self._makeOneFromClass(self._getTargetClass(), *args, **kwargs) def _makeOneFromClass(self, cls, *args, **kwargs): return cls(*args, **kwargs) def test_class_conforms_to_iface(self): from zope.interface.verify import verifyClass cls = self._getTargetClass() __traceback_info__ = cls for iface in self._getTargetInterfaces(): verifyClass(iface, cls) def test_instance_conforms_to_iface(self): from zope.interface.verify import verifyObject instance = self._makeOne() __traceback_info__ = instance for iface in self._getTargetInterfaces(): verifyObject(iface, instance) def test_iface_is_first_in_sro(self): from zope.interface import implementedBy implemented = implementedBy(self._getTargetClass()) __traceback_info__ = implemented.__sro__ self.assertIs(implemented, implemented.__sro__[0]) self.assertIs(self._getTargetInterface(), implemented.__sro__[1]) def test_implements_consistent__sro__(self): from zope.interface import implementedBy from zope.interface import ro __traceback_info__ = implementedBy(self._getTargetClass()).__sro__ self.assertTrue( ro.is_consistent(implementedBy(self._getTargetClass()))) def test_iface_consistent_ro(self): from zope.interface import ro __traceback_info__ = self._getTargetInterface().__iro__ self.assertTrue(ro.is_consistent(self._getTargetInterface())) class EqualityTestsMixin(InterfaceConformanceTestsMixin): def test_is_hashable(self): field = self._makeOne() hash(field) # doesn't raise def test_equal_instances_have_same_hash(self): # Equal objects should have equal hashes field1 = self._makeOne() field2 = self._makeOne() self.assertIsNot(field1, field2) self.assertEqual(field1, field2) self.assertEqual(hash(field1), hash(field2)) def test_instances_in_different_interfaces_not_equal(self): from zope import interface field1 = self._makeOne() field2 = self._makeOne() self.assertEqual(field1, field2) self.assertEqual(hash(field1), hash(field2)) class IOne(interface.Interface): one = field1 class ITwo(interface.Interface): two = field2 self.assertEqual(field1, field1) self.assertEqual(field2, field2) self.assertNotEqual(field1, field2) self.assertNotEqual(hash(field1), hash(field2)) def test_hash_across_unequal_instances(self): # Hash equality does not imply equal objects. # Our implementation only considers property names, # not values. That's OK, a dict still does the right thing. field1 = self._makeOne(title=u'foo') field2 = self._makeOne(title=u'bar') self.assertIsNot(field1, field2) self.assertNotEqual(field1, field2) self.assertEqual(hash(field1), hash(field2)) d = {field1: 42} self.assertIn(field1, d) self.assertEqual(42, d[field1]) self.assertNotIn(field2, d) with self.assertRaises(KeyError): d.__getitem__(field2) def test___eq___different_type(self): left = self._makeOne() class Derived(self._getTargetClass()): pass right = self._makeOneFromClass(Derived) self.assertNotEqual(left, right) self.assertTrue(left != right) def test___eq___same_type_different_attrs(self): left = self._makeOne(required=True) right = self._makeOne(required=False) self.assertNotEqual(left, right) self.assertTrue(left != right) def test___eq___same_type_same_attrs(self): left = self._makeOne() self.assertEqual(left, left) right = self._makeOne() self.assertEqual(left, right) self.assertFalse(left != right) class OrderableMissingValueMixin(object): mvm_missing_value = -1 mvm_default = 0 def test_missing_value_no_min_or_max(self): # We should be able to provide a missing_value without # also providing a min or max. But note that we must still # provide a default. # See https://github.com/zopefoundation/zope.schema/issues/9 Kind = self._getTargetClass() self.assertTrue(Kind.min._allow_none) self.assertTrue(Kind.max._allow_none) field = self._makeOne(missing_value=self.mvm_missing_value, default=self.mvm_default) self.assertIsNone(field.min) self.assertIsNone(field.max) self.assertEqual(self.mvm_missing_value, field.missing_value) class OrderableTestsMixin(object): def assertRaisesTooBig(self, field, value): from zope.schema.interfaces import TooBig with self.assertRaises(TooBig) as exc: field.validate(value) ex = exc.exception self.assertEqual(value, ex.value) self.assertEqual(field.max, ex.bound) self.assertEqual(TooBig.TOO_LARGE, ex.violation_direction) def assertRaisesTooSmall(self, field, value): from zope.schema.interfaces import TooSmall with self.assertRaises(TooSmall) as exc: field.validate(value) ex = exc.exception self.assertEqual(value, ex.value) self.assertEqual(field.min, ex.bound) self.assertEqual(TooSmall.TOO_SMALL, ex.violation_direction) MIN = 10 MAX = 20 VALID = (10, 11, 19, 20) TOO_SMALL = (9, -10) TOO_BIG = (21, 22) def test_validate_min(self): field = self._makeOne(min=self.MIN) for value in self.VALID + self.TOO_BIG: field.validate(value) for value in self.TOO_SMALL: self.assertRaisesTooSmall(field, value) def test_validate_max(self): field = self._makeOne(max=self.MAX) for value in self.VALID + self.TOO_SMALL: field.validate(value) for value in self.TOO_BIG: self.assertRaisesTooBig(field, value) def test_validate_min_and_max(self): field = self._makeOne(min=self.MIN, max=self.MAX) for value in self.TOO_SMALL: self.assertRaisesTooSmall(field, value) for value in self.VALID: field.validate(value) for value in self.TOO_BIG: self.assertRaisesTooBig(field, value) class LenTestsMixin(object): def assertRaisesTooLong(self, field, value): from zope.schema.interfaces import TooLong with self.assertRaises(TooLong) as exc: field.validate(value) ex = exc.exception self.assertEqual(value, ex.value) self.assertEqual(field.max_length, ex.bound) self.assertEqual(TooLong.TOO_LARGE, ex.violation_direction) def assertRaisesTooShort(self, field, value): from zope.schema.interfaces import TooShort with self.assertRaises(TooShort) as exc: field.validate(value) ex = exc.exception self.assertEqual(value, ex.value) self.assertEqual(field.min_length, ex.bound) self.assertEqual(TooShort.TOO_SMALL, ex.violation_direction) class WrongTypeTestsMixin(object): def assertRaisesWrongType(self, field_or_meth, expected_type, *args, **kwargs): from zope.schema.interfaces import WrongType field = None with self.assertRaises(WrongType) as exc: if hasattr(field_or_meth, 'validate'): field = field_or_meth field.validate(*args, **kwargs) else: field_or_meth(*args, **kwargs) ex = exc.exception self.assertIs(ex.expected_type, expected_type) if field is not None: self.assertIs(ex.field, field) if len(args) == 1 and not kwargs: # Just a value self.assertIs(ex.value, args[0]) if not args and len(kwargs) == 1: # A single keyword argument self.assertIs(ex.value, kwargs.popitem()[1]) def assertAllRaiseWrongType(self, field, expected_type, *values): for value in values: __traceback_info__ = value self.assertRaisesWrongType(field, expected_type, value) 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(EqualityTestsMixin, WrongTypeTestsMixin, unittest.TestCase): def _getTargetClass(self): from zope.schema._bootstrapfields import Field return Field def _getTargetInterface(self): from zope.schema.interfaces import IField return IField def test_getDoc(self): import textwrap field = self._makeOne(readonly=True, required=False) doc = field.getDoc() self.assertIn(':Read Only: True', doc) self.assertIn(':Required: False', doc) self.assertIn(":Default Value:", doc) self.assertNotIn(':Default Factory:', doc) field._type = str doc = field.getDoc() self.assertIn(':Allowed Type: :class:`str`', doc) self.assertNotIn(':Default Factory:', doc) field.defaultFactory = 'default' doc = field.getDoc() self.assertNotIn(":Default Value:", doc) self.assertIn(':Default Factory:', doc) field._type = (str, object) doc = field.getDoc() self.assertIn(':Allowed Type: :class:`str`, :class:`object`', doc) self.assertNotIn('..rubric', doc) # value_type and key_type are automatically picked up field.value_type = self._makeOne() # Make sure the formatting works also with fields that have a title field.key_type = self._makeOne(title=u'Key Type') doc = field.getDoc() self.assertIn('.. rubric:: Key Type', doc) self.assertIn('.. rubric:: Value Type', doc) self.assertEqual( doc, textwrap.dedent(""" :Implementation: :class:`zope.schema.Field` :Read Only: True :Required: False :Default Factory: 'default' :Allowed Type: :class:`str`, :class:`object` .. rubric:: Key Type Key Type :Implementation: :class:`zope.schema.Field` :Read Only: False :Required: True :Default Value: None .. rubric:: Value Type :Implementation: :class:`zope.schema.Field` :Read Only: False :Required: True :Default Value: None """) ) field = self._makeOne( title=u'A title', description=u"""Multiline description. Some lines have leading whitespace. It gets stripped. """) doc = field.getDoc() self.assertEqual( field.getDoc(), textwrap.dedent("""\ A title Multiline description. Some lines have leading whitespace. It gets stripped. :Implementation: :class:`zope.schema.Field` :Read Only: False :Required: True :Default Value: None """) ) def test_ctor_description_preserved(self): # The exact value of the description is preserved, # allowing for MessageID objects. import textwrap from zope.i18nmessageid import MessageFactory msg_factory = MessageFactory('zope') description = msg_factory(u"""Multiline description. Some lines have leading whitespace. It gets stripped. """) title = msg_factory(u'A title') field = self._makeOne(title=title, description=description) self.assertIs(field.title, title) self.assertIs(field.description, description) self.assertEqual( field.getDoc(), textwrap.dedent("""\ A title Multiline description. Some lines have leading whitespace. It gets stripped. :Implementation: :class:`zope.schema.Field` :Read Only: False :Required: True :Default Value: None """) ) def test_ctor_description_none(self): # None values for description don't break the docs. import textwrap description = None title = u'A title' field = self._makeOne(title=title, description=description) self.assertIs(field.title, title) self.assertIs(field.description, description) self.assertEqual( field.getDoc(), textwrap.dedent("""\ A title :Implementation: :class:`zope.schema.Field` :Read Only: False :Required: True :Default Value: None """) ) def test_ctor_defaults(self): 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): 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): 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): 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() field = self._makeOne( # pragma: no branch required=False, missing_value=missing, constraint=lambda x: False, ) self.assertEqual(field.validate(missing), None) # doesn't raise def test_validate_missing_and_required(self): from zope.schema._bootstrapinterfaces import RequiredMissing missing = object() field = self._makeOne( # pragma: no branch required=True, missing_value=missing, constraint=lambda x: False, ) self.assertRaises(RequiredMissing, field.validate, missing) def test_validate_wrong_type(self): field = self._makeOne( # pragma: no branch required=True, constraint=lambda x: False, ) field._type = str self.assertRaisesWrongType(field, str, 1) def test_validate_constraint_fails(self): from zope.schema._bootstrapinterfaces import ConstraintNotSatisfied field = self._makeOne(required=True, constraint=lambda x: False) 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_validate_constraint_raises_custom_exception(self): from zope.schema._bootstrapinterfaces import ValidationError def _fail(value): raise ValidationError field = self._makeOne(constraint=_fail) with self.assertRaises(ValidationError) as exc: field.validate(1) self.assertIs(exc.exception.field, field) self.assertEqual(exc.exception.value, 1) def test_validate_constraint_raises_custom_exception_no_overwrite(self): from zope.schema._bootstrapinterfaces import ValidationError def _fail(value): raise ValidationError(value).with_field_and_value(self, self) field = self._makeOne(constraint=_fail) with self.assertRaises(ValidationError) as exc: field.validate(1) self.assertIs(exc.exception.field, self) self.assertIs(exc.exception.value, self) 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(EqualityTestsMixin, unittest.TestCase): def _getTargetClass(self): from zope.schema._bootstrapfields import Container return Container def _getTargetInterface(self): from zope.schema.interfaces import IContainer return IContainer 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() bad_value = object() with self.assertRaises(NotAContainer) as exc: cont._validate(bad_value) not_cont = exc.exception self.assertIs(not_cont.field, cont) self.assertIs(not_cont.value, bad_value) def test__validate_collection_but_not_iterable(self): cont = self._makeOne() class Dummy(object): def __contains__(self, item): raise AssertionError("Not called") 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 _getTargetInterface(self): from zope.schema.interfaces import IIterable return IIterable def test__validate_collection_but_not_iterable(self): from zope.schema._bootstrapinterfaces import NotAnIterator itr = self._makeOne() class Dummy(object): def __contains__(self, item): raise AssertionError("Not called") dummy = Dummy() with self.assertRaises(NotAnIterator) as exc: itr._validate(dummy) not_it = exc.exception self.assertIs(not_it.field, itr) self.assertIs(not_it.value, 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(LenTestsMixin, 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): mml = self._makeOne(min_length=1) self.assertRaisesTooShort(mml, ()) def test_validate_too_long(self): mml = self._makeOne(max_length=2) self.assertRaisesTooLong(mml, (0, 1, 2)) class TextTests(EqualityTestsMixin, WrongTypeTestsMixin, unittest.TestCase): def _getTargetClass(self): from zope.schema._bootstrapfields import Text return Text def _getTargetInterface(self): from zope.schema.interfaces import IText return IText def test_ctor_defaults(self): txt = self._makeOne() self.assertEqual(txt._type, str) def test_validate_wrong_types(self): field = self._makeOne() self.assertAllRaiseWrongType( field, field._type, b'', 1, 1.0, (), [], {}, set(), frozenset(), object()) def test_validate_w_invalid_default(self): from zope.schema.interfaces import ValidationError self.assertRaises(ValidationError, self._makeOne, default=b'') def test_validate_not_required(self): 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 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): deadbeef = b'DEADBEEF' txt = self._makeOne() self.assertRaisesWrongType(txt.fromUnicode, txt._type, deadbeef) def test_fromUnicode_hit(self): deadbeef = u'DEADBEEF' txt = self._makeOne() self.assertEqual(txt.fromUnicode(deadbeef), deadbeef) def test_normalization(self): deadbeef = unicodedata.normalize( 'NFD', b'\xc3\x84\xc3\x96\xc3\x9c'.decode('utf-8')) txt = self._makeOne() self.assertEqual(txt.unicode_normalization, 'NFC') self.assertEqual( [unicodedata.name(c) for c in txt.fromUnicode(deadbeef)], [ 'LATIN CAPITAL LETTER A WITH DIAERESIS', 'LATIN CAPITAL LETTER O WITH DIAERESIS', 'LATIN CAPITAL LETTER U WITH DIAERESIS', ] ) txt = self._makeOne(unicode_normalization=None) self.assertEqual(txt.unicode_normalization, None) self.assertEqual( [unicodedata.name(c) for c in txt.fromUnicode(deadbeef)], [ 'LATIN CAPITAL LETTER A', 'COMBINING DIAERESIS', 'LATIN CAPITAL LETTER O', 'COMBINING DIAERESIS', 'LATIN CAPITAL LETTER U', 'COMBINING DIAERESIS', ] ) def test_normalization_after_pickle(self): # test an edge case where `Text` is persisted # see https://github.com/zopefoundation/zope.schema/issues/90 import pickle orig_makeOne = self._makeOne def makeOne(**kwargs): result = orig_makeOne(**kwargs) if not kwargs: # We should have no state to preserve result.__dict__.clear() result = pickle.loads(pickle.dumps(result)) return result self._makeOne = makeOne self.test_normalization() class TextLineTests(EqualityTestsMixin, WrongTypeTestsMixin, unittest.TestCase): def _getTargetClass(self): from zope.schema._field import TextLine return TextLine def _getTargetInterface(self): from zope.schema.interfaces import ITextLine return ITextLine def test_validate_wrong_types(self): field = self._makeOne() self.assertAllRaiseWrongType( field, field._type, b'', 1, 1.0, (), [], {}, set(), frozenset(), object()) def test_validate_not_required(self): 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 field = self._makeOne() field.validate(u'') field.validate(u'abc') self.assertRaises(RequiredMissing, field.validate, None) def test_constraint(self): 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(EqualityTestsMixin, WrongTypeTestsMixin, unittest.TestCase): def _getTargetClass(self): from zope.schema._bootstrapfields import Password return Password def _getTargetInterface(self): from zope.schema.interfaces import IPassword return IPassword 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): 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 field = self._makeOne() field.validate(u'') field.validate(u'abc') self.assertRaises(RequiredMissing, field.validate, None) def test_validate_unchanged_not_already_set(self): klass = self._getTargetClass() inst = DummyInst() pw = self._makeOne(__name__='password').bind(inst) self.assertRaisesWrongType(pw, pw._type, 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): 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(EqualityTestsMixin, WrongTypeTestsMixin, unittest.TestCase): def _getTargetClass(self): from zope.schema._bootstrapfields import Bool return Bool def _getTargetInterface(self): from zope.schema.interfaces import IBool return IBool def test_ctor_defaults(self): txt = self._makeOne() self.assertEqual(txt._type, bool) def test_validate_wrong_type(self): boo = self._makeOne() self.assertRaisesWrongType(boo, boo._type, '') def test__validate_w_int(self): boo = self._makeOne() boo._validate(0) # doesn't raise boo._validate(1) # doesn't raise def test__validate_w_bool(self): boo = self._makeOne() boo._validate(False) # doesn't raise boo._validate(True) # 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_set_w_bool(self): boo = self._makeOne(__name__='boo') inst = DummyInst() boo.set(inst, False) self.assertEqual(inst.boo, False) boo.set(inst, True) self.assertEqual(inst.boo, True) def test_fromUnicode_miss(self): 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): txt = self._makeOne() self.assertEqual(txt.fromUnicode(u'True'), True) self.assertEqual(txt.fromUnicode(u'true'), True) class NumberTests(EqualityTestsMixin, OrderableMissingValueMixin, OrderableTestsMixin, unittest.TestCase): def _getTargetClass(self): from zope.schema._bootstrapfields import Number return Number def _getTargetInterface(self): from zope.schema.interfaces import INumber return INumber def test_class_conforms_to_iface(self): from zope.interface.verify import verifyClass from zope.schema._bootstrapinterfaces import IFromBytes from zope.schema._bootstrapinterfaces import IFromUnicode super(NumberTests, self).test_class_conforms_to_iface() verifyClass(IFromUnicode, self._getTargetClass()) verifyClass(IFromBytes, self._getTargetClass()) def test_instance_conforms_to_iface(self): from zope.interface.verify import verifyObject from zope.schema._bootstrapinterfaces import IFromBytes from zope.schema._bootstrapinterfaces import IFromUnicode super(NumberTests, self).test_instance_conforms_to_iface() verifyObject(IFromUnicode, self._makeOne()) verifyObject(IFromBytes, self._makeOne()) class ComplexTests(NumberTests): def _getTargetClass(self): from zope.schema._bootstrapfields import Complex return Complex def _getTargetInterface(self): from zope.schema.interfaces import IComplex return IComplex class RealTests(WrongTypeTestsMixin, NumberTests): def _getTargetClass(self): from zope.schema._bootstrapfields import Real return Real def _getTargetInterface(self): from zope.schema.interfaces import IReal return IReal def test_ctor_real_min_max(self): from fractions import Fraction self.assertRaisesWrongType( self._makeOne, self._getTargetClass()._type, min='') self.assertRaisesWrongType( self._makeOne, self._getTargetClass()._type, max='') field = self._makeOne(min=Fraction(1, 2), max=2) field.validate(1.0) field.validate(2.0) self.assertRaisesTooSmall(field, 0) self.assertRaisesTooSmall(field, 0.4) self.assertRaisesTooBig(field, 2.1) class RationalTests(NumberTests): def _getTargetClass(self): from zope.schema._bootstrapfields import Rational return Rational def _getTargetInterface(self): from zope.schema.interfaces import IRational return IRational class IntegralTests(RationalTests): def _getTargetClass(self): from zope.schema._bootstrapfields import Integral return Integral def _getTargetInterface(self): from zope.schema.interfaces import IIntegral return IIntegral 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_fromUnicode_miss(self): 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): 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 IntTests(IntegralTests): def _getTargetClass(self): from zope.schema._bootstrapfields import Int return Int def _getTargetInterface(self): from zope.schema.interfaces import IInt return IInt def test_ctor_defaults(self): txt = self._makeOne() self.assertEqual(txt._type, int) class DecimalTests(NumberTests): mvm_missing_value = decimal.Decimal("-1") mvm_default = decimal.Decimal("0") MIN = decimal.Decimal(NumberTests.MIN) MAX = decimal.Decimal(NumberTests.MAX) VALID = tuple(decimal.Decimal(x) for x in NumberTests.VALID) TOO_SMALL = tuple(decimal.Decimal(x) for x in NumberTests.TOO_SMALL) TOO_BIG = tuple(decimal.Decimal(x) for x in NumberTests.TOO_BIG) def _getTargetClass(self): from zope.schema._bootstrapfields import Decimal return Decimal def _getTargetInterface(self): from zope.schema.interfaces import IDecimal return IDecimal def test_validate_not_required(self): field = self._makeOne(required=False) field.validate(decimal.Decimal("10.0")) field.validate(decimal.Decimal("0.93")) field.validate(decimal.Decimal("1000.0003")) field.validate(None) def test_validate_required(self): from zope.schema.interfaces import RequiredMissing field = self._makeOne() field.validate(decimal.Decimal("10.0")) field.validate(decimal.Decimal("0.93")) field.validate(decimal.Decimal("1000.0003")) self.assertRaises(RequiredMissing, field.validate, None) def test_fromUnicode_miss(self): from zope.schema.interfaces import ValidationError flt = self._makeOne() self.assertRaises(ValueError, flt.fromUnicode, u'') self.assertRaises(ValueError, flt.fromUnicode, u'abc') with self.assertRaises(ValueError) as exc: flt.fromUnicode(u'1.4G') value_error = exc.exception self.assertIs(value_error.field, flt) self.assertEqual(value_error.value, u'1.4G') self.assertIsInstance(value_error, ValidationError) def test_fromUnicode_hit(self): from decimal import Decimal flt = self._makeOne() self.assertEqual(flt.fromUnicode(u'0'), Decimal('0.0')) self.assertEqual(flt.fromUnicode(u'1.23'), Decimal('1.23')) self.assertEqual(flt.fromUnicode(u'12345.6'), Decimal('12345.6')) class ObjectTests(EqualityTestsMixin, WrongTypeTestsMixin, unittest.TestCase): def setUp(self): from zope.event import subscribers self._before = subscribers[:] def tearDown(self): from zope.event import subscribers subscribers[:] = self._before def _getTargetClass(self): from zope.schema._field import Object return Object def _getTargetInterface(self): from zope.schema.interfaces import IObject return IObject def _makeOneFromClass(self, cls, schema=None, *args, **kw): if schema is None: schema = self._makeSchema() return super(ObjectTests, self)._makeOneFromClass( cls, schema, *args, **kw) def _makeSchema(self, **kw): from zope.interface import Interface from zope.interface.interface import InterfaceClass return InterfaceClass('ISchema', (Interface,), kw) def _getErrors(self, f, *args, **kw): from zope.schema.interfaces import SchemaNotCorrectlyImplemented with self.assertRaises(SchemaNotCorrectlyImplemented) as e: f(*args, **kw) return e.exception.errors def _makeCycles(self): from zope.interface import Interface from zope.interface import implementer from zope.schema import List from zope.schema import Object from zope.schema._messageid import _ class IUnit(Interface): """A schema that participate to a cycle""" boss = Object( schema=Interface, title=_("Boss"), description=_("Boss description"), required=False, ) members = List( value_type=Object(schema=Interface), title=_("Member List"), description=_("Member list description"), required=False, ) class IPerson(Interface): """A schema that participate to a cycle""" unit = Object( schema=IUnit, title=_("Unit"), description=_("Unit description"), required=False, ) IUnit['boss'].schema = IPerson IUnit['members'].value_type.schema = IPerson @implementer(IUnit) class Unit(object): def __init__(self, person, person_list): self.boss = person self.members = person_list @implementer(IPerson) class Person(object): def __init__(self, unit): self.unit = unit return IUnit, Person, Unit def test_class_conforms_to_IObject(self): from zope.interface.verify import verifyClass from zope.schema.interfaces import IObject verifyClass(IObject, self._getTargetClass()) def test_instance_conforms_to_IObject(self): from zope.interface.verify import verifyObject from zope.schema.interfaces import IObject verifyObject(IObject, self._makeOne()) def test_ctor_w_bad_schema(self): from zope.interface.interfaces import IInterface self.assertRaisesWrongType(self._makeOne, IInterface, object()) def test_validate_not_required(self): schema = self._makeSchema() objf = self._makeOne(schema, required=False) objf.validate(None) # doesn't raise def test_validate_required(self): from zope.schema.interfaces import RequiredMissing field = self._makeOne(required=True) self.assertRaises(RequiredMissing, field.validate, None) def test__validate_w_empty_schema(self): from zope.interface import Interface objf = self._makeOne(Interface) objf.validate(object()) # doesn't raise def test__validate_w_value_not_providing_schema(self): from zope.schema._bootstrapfields import Text from zope.schema.interfaces import SchemaNotProvided schema = self._makeSchema(foo=Text(), bar=Text()) objf = self._makeOne(schema) bad_value = object() with self.assertRaises(SchemaNotProvided) as exc: objf.validate(bad_value) not_provided = exc.exception self.assertIs(not_provided.field, objf) self.assertIs(not_provided.value, bad_value) self.assertEqual(not_provided.args, (schema, bad_value), ) def test__validate_w_value_providing_schema_but_missing_fields(self): from zope.interface import implementer from zope.schema._bootstrapfields import Text from zope.schema.interfaces import SchemaNotCorrectlyImplemented from zope.schema.interfaces import SchemaNotFullyImplemented schema = self._makeSchema(foo=Text(), bar=Text()) @implementer(schema) class Broken(object): pass objf = self._makeOne(schema) broken = Broken() with self.assertRaises(SchemaNotCorrectlyImplemented) as exc: objf.validate(broken) wct = exc.exception self.assertIs(wct.field, objf) self.assertIs(wct.value, broken) self.assertEqual(wct.invariant_errors, []) self.assertEqual( sorted(wct.schema_errors), ['bar', 'foo'] ) for name in ('foo', 'bar'): error = wct.schema_errors[name] self.assertIsInstance(error, SchemaNotFullyImplemented) self.assertEqual(schema[name], error.field) self.assertIsNone(error.value) # The legacy arg[0] errors list errors = self._getErrors(objf.validate, Broken()) self.assertEqual(len(errors), 2) errors = sorted(errors, key=lambda x: (type(x).__name__, str(x.args[0]))) err = errors[0] self.assertIsInstance(err, SchemaNotFullyImplemented) nested = err.args[0] self.assertIsInstance(nested, AttributeError) self.assertIn("'bar'", str(nested)) err = errors[1] self.assertIsInstance(err, SchemaNotFullyImplemented) nested = err.args[0] self.assertIsInstance(nested, AttributeError) self.assertIn("'foo'", str(nested)) def test__validate_w_value_providing_schema_but_invalid_fields(self): from zope.interface import implementer from zope.schema._bootstrapfields import Text from zope.schema.interfaces import RequiredMissing from zope.schema.interfaces import SchemaNotCorrectlyImplemented from zope.schema.interfaces import WrongType schema = self._makeSchema(foo=Text(), bar=Text()) @implementer(schema) class Broken(object): foo = None bar = 1 objf = self._makeOne(schema) broken = Broken() with self.assertRaises(SchemaNotCorrectlyImplemented) as exc: objf.validate(broken) wct = exc.exception self.assertIs(wct.field, objf) self.assertIs(wct.value, broken) self.assertEqual(wct.invariant_errors, []) self.assertEqual( sorted(wct.schema_errors), ['bar', 'foo'] ) self.assertIsInstance(wct.schema_errors['foo'], RequiredMissing) self.assertIsInstance(wct.schema_errors['bar'], WrongType) # The legacy arg[0] errors list errors = self._getErrors(objf.validate, Broken()) self.assertEqual(len(errors), 2) errors = sorted(errors, key=lambda x: type(x).__name__) err = errors[0] self.assertIsInstance(err, RequiredMissing) self.assertEqual(err.args, ('foo',)) err = errors[1] self.assertIsInstance(err, WrongType) self.assertEqual(err.args, (1, str, 'bar')) def test__validate_w_value_providing_schema(self): from zope.interface import implementer from zope.schema._bootstrapfields import Text from zope.schema._field import Choice schema = self._makeSchema( foo=Text(), bar=Text(), baz=Choice(values=[1, 2, 3]), ) @implementer(schema) class OK(object): foo = u'Foo' bar = u'Bar' baz = 2 objf = self._makeOne(schema) objf.validate(OK()) # doesn't raise def test_validate_w_cycles(self): IUnit, Person, Unit = self._makeCycles() field = self._makeOne(schema=IUnit) person1 = Person(None) person2 = Person(None) unit = Unit(person1, [person1, person2]) person1.unit = unit person2.unit = unit field.validate(unit) # doesn't raise def test_validate_w_cycles_object_not_valid(self): from zope.schema.interfaces import SchemaNotCorrectlyImplemented from zope.schema.interfaces import SchemaNotProvided IUnit, Person, Unit = self._makeCycles() field = self._makeOne(schema=IUnit) person1 = Person(None) person2 = Person(None) boss_unit = object() boss = Person(boss_unit) unit = Unit(boss, [person1, person2]) person1.unit = unit person2.unit = unit with self.assertRaises(SchemaNotCorrectlyImplemented) as exc: field.validate(unit) ex = exc.exception self.assertEqual(1, len(ex.schema_errors)) self.assertEqual(1, len(ex.errors)) self.assertEqual(0, len(ex.invariant_errors)) boss_error = ex.schema_errors['boss'] self.assertIsInstance(boss_error, SchemaNotCorrectlyImplemented) self.assertEqual(1, len(boss_error.schema_errors)) self.assertEqual(1, len(boss_error.errors)) self.assertEqual(0, len(boss_error.invariant_errors)) unit_error = boss_error.schema_errors['unit'] self.assertIsInstance(unit_error, SchemaNotProvided) self.assertIs(IUnit, unit_error.schema) self.assertIs(boss_unit, unit_error.value) def test_validate_w_cycles_collection_not_valid(self): from zope.schema.interfaces import SchemaNotCorrectlyImplemented IUnit, Person, Unit = self._makeCycles() field = self._makeOne(schema=IUnit) person1 = Person(None) person2 = Person(None) person3 = Person(object()) unit = Unit(person1, [person2, person3]) person1.unit = unit person2.unit = unit self.assertRaises(SchemaNotCorrectlyImplemented, field.validate, unit) def test_set_emits_IBOAE(self): from zope.event import subscribers from zope.interface import implementer from zope.schema._bootstrapfields import Text from zope.schema._field import Choice from zope.schema.interfaces import IBeforeObjectAssignedEvent schema = self._makeSchema( foo=Text(), bar=Text(), baz=Choice(values=[1, 2, 3]), ) @implementer(schema) class OK(object): foo = u'Foo' bar = u'Bar' baz = 2 log = [] subscribers.append(log.append) objf = self._makeOne(schema, __name__='field') inst = DummyInst() value = OK() objf.set(inst, value) self.assertIs(inst.field, value) self.assertEqual(len(log), 5) self.assertEqual(IBeforeObjectAssignedEvent.providedBy(log[-1]), True) self.assertEqual(log[-1].object, value) self.assertEqual(log[-1].name, 'field') self.assertEqual(log[-1].context, inst) def test_set_allows_IBOAE_subscr_to_replace_value(self): from zope.event import subscribers from zope.interface import implementer from zope.schema._bootstrapfields import Text from zope.schema._field import Choice schema = self._makeSchema( foo=Text(), bar=Text(), baz=Choice(values=[1, 2, 3]), ) @implementer(schema) class OK(object): def __init__(self, foo=u'Foo', bar=u'Bar', baz=2): self.foo = foo self.bar = bar self.baz = baz ok1 = OK() ok2 = OK(u'Foo2', u'Bar2', 3) log = [] subscribers.append(log.append) def _replace(event): event.object = ok2 subscribers.append(_replace) objf = self._makeOne(schema, __name__='field') inst = DummyInst() self.assertEqual(len(log), 4) objf.set(inst, ok1) self.assertIs(inst.field, ok2) self.assertEqual(len(log), 5) self.assertEqual(log[-1].object, ok2) self.assertEqual(log[-1].name, 'field') self.assertEqual(log[-1].context, inst) def test_validates_invariants_by_default(self): from zope.interface import Interface from zope.interface import Invalid from zope.interface import implementer from zope.interface import invariant from zope.schema import Bytes from zope.schema import Text class ISchema(Interface): foo = Text() bar = Bytes() @invariant def check_foo(self): if self.foo == u'bar': raise Invalid("Foo is not valid") @invariant def check_bar(self): if self.bar == b'foo': raise Invalid("Bar is not valid") @implementer(ISchema) class Obj(object): foo = u'' bar = b'' field = self._makeOne(ISchema) inst = Obj() # Fine at first field.validate(inst) inst.foo = u'bar' errors = self._getErrors(field.validate, inst) self.assertEqual(len(errors), 1) self.assertEqual(errors[0].args[0], "Foo is not valid") del inst.foo inst.bar = b'foo' errors = self._getErrors(field.validate, inst) self.assertEqual(len(errors), 1) self.assertEqual(errors[0].args[0], "Bar is not valid") # Both invalid inst.foo = u'bar' errors = self._getErrors(field.validate, inst) self.assertEqual(len(errors), 2) errors.sort(key=lambda i: i.args) self.assertEqual(errors[0].args[0], "Bar is not valid") self.assertEqual(errors[1].args[0], "Foo is not valid") # We can specifically ask for invariants to be turned off. field = self._makeOne(ISchema, validate_invariants=False) field.validate(inst) def test_schema_defined_by_subclass(self): from zope import interface from zope.schema.interfaces import SchemaNotProvided class IValueType(interface.Interface): "The value type schema" class Field(self._getTargetClass()): schema = IValueType field = Field() self.assertIs(field.schema, IValueType) # Non implementation is bad with self.assertRaises(SchemaNotProvided) as exc: field.validate(object()) self.assertIs(IValueType, exc.exception.schema) # Actual implementation works @interface.implementer(IValueType) class ValueType(object): "The value type" field.validate(ValueType()) def test_bound_field_of_collection_with_choice(self): # https://github.com/zopefoundation/zope.schema/issues/17 from zope.interface import Attribute from zope.interface import Interface from zope.interface import implementer from zope.schema import Choice from zope.schema import Object from zope.schema import Set from zope.schema.fieldproperty import FieldProperty from zope.schema.interfaces import ConstraintNotSatisfied from zope.schema.interfaces import IContextSourceBinder from zope.schema.interfaces import SchemaNotCorrectlyImplemented from zope.schema.interfaces import WrongContainedType from zope.schema.vocabulary import SimpleVocabulary @implementer(IContextSourceBinder) class EnumContext(object): def __call__(self, context): return SimpleVocabulary.fromValues(list(context)) class IMultipleChoice(Interface): choices = Set(value_type=Choice(source=EnumContext())) # Provide a regular attribute to prove that binding doesn't # choke. NOTE: We don't actually verify the existence of this # attribute. non_field = Attribute("An attribute") @implementer(IMultipleChoice) class Choices(object): def __init__(self, choices): self.choices = choices def __iter__(self): # EnumContext calls this to make the vocabulary. # Fields of the schema of the IObject are bound to the value # being validated. return iter(range(5)) class IFavorites(Interface): fav = Object(title=u"Favorites number", schema=IMultipleChoice) @implementer(IFavorites) class Favorites(object): fav = FieldProperty(IFavorites['fav']) # must not raise good_choices = Choices({1, 3}) IFavorites['fav'].validate(good_choices) # Ranges outside the context fail bad_choices = Choices({1, 8}) with self.assertRaises(SchemaNotCorrectlyImplemented) as exc: IFavorites['fav'].validate(bad_choices) e = exc.exception self.assertEqual(IFavorites['fav'], e.field) self.assertEqual(bad_choices, e.value) self.assertEqual(1, len(e.schema_errors)) self.assertEqual(0, len(e.invariant_errors)) self.assertEqual(1, len(e.errors)) fav_error = e.schema_errors['choices'] self.assertIs(fav_error, e.errors[0]) self.assertIsInstance(fav_error, WrongContainedType) self.assertNotIsInstance(fav_error, SchemaNotCorrectlyImplemented) # The field is not actually equal to the one in the interface # anymore because its bound. self.assertEqual('choices', fav_error.field.__name__) self.assertEqual(bad_choices, fav_error.field.context) self.assertEqual({1, 8}, fav_error.value) self.assertEqual(1, len(fav_error.errors)) self.assertIsInstance(fav_error.errors[0], ConstraintNotSatisfied) # Validation through field property favorites = Favorites() favorites.fav = good_choices # And validation through a field that wants IFavorites favorites_field = Object(IFavorites) favorites_field.validate(favorites) # Check the field property error with self.assertRaises(SchemaNotCorrectlyImplemented) as exc: favorites.fav = bad_choices e = exc.exception self.assertEqual(IFavorites['fav'], e.field) self.assertEqual(bad_choices, e.value) self.assertEqual(['choices'], list(e.schema_errors)) def test_getDoc(self): field = self._makeOne() doc = field.getDoc() self.assertIn(":Must Provide: :class:", doc) class DummyInst(object): missing_value = object() def __init__(self, exc=None): self._exc = exc def validate(self, value): if self._exc is not None: # pragma: no branch raise self._exc() def test_suite(): import zope.schema._bootstrapfields suite = unittest.defaultTestLoader.loadTestsFromName(__name__) suite.addTests(doctest.DocTestSuite( zope.schema._bootstrapfields, optionflags=doctest.ELLIPSIS )) return suite