summaryrefslogtreecommitdiff
path: root/src/zope/schema/tests/test__bootstrapfields.py
diff options
context:
space:
mode:
Diffstat (limited to 'src/zope/schema/tests/test__bootstrapfields.py')
-rw-r--r--src/zope/schema/tests/test__bootstrapfields.py492
1 files changed, 491 insertions, 1 deletions
diff --git a/src/zope/schema/tests/test__bootstrapfields.py b/src/zope/schema/tests/test__bootstrapfields.py
index 419378a..733d5c9 100644
--- a/src/zope/schema/tests/test__bootstrapfields.py
+++ b/src/zope/schema/tests/test__bootstrapfields.py
@@ -14,7 +14,7 @@
import doctest
import unittest
-# pylint:disable=protected-access
+# pylint:disable=protected-access,inherit-non-class,blacklisted-name
class EqualityTestsMixin(object):
@@ -991,6 +991,496 @@ class IntTests(IntegralTests):
self.assertEqual(txt._type, integer_types)
+class ObjectTests(EqualityTestsMixin,
+ 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 WrongContainedType
+ with self.assertRaises(WrongContainedType) as e:
+ f(*args, **kw)
+ return e.exception.args[0]
+
+ def _makeCycles(self):
+ from zope.interface import Interface
+ from zope.interface import implementer
+ from zope.schema import Object
+ from zope.schema import List
+ 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.schema.interfaces import WrongType
+ self.assertRaises(WrongType, self._makeOne, 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.interfaces import SchemaNotProvided
+ from zope.schema._bootstrapfields import Text
+ 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.interfaces import SchemaNotFullyImplemented
+ from zope.schema.interfaces import SchemaNotCorrectlyImplemented
+ from zope.schema._bootstrapfields import Text
+ 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.interfaces import SchemaNotCorrectlyImplemented
+ from zope.schema.interfaces import RequiredMissing
+ from zope.schema.interfaces import WrongType
+ from zope.schema._bootstrapfields import Text
+ from zope.schema._compat import text_type
+ 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, text_type, '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 WrongContainedType
+ IUnit, Person, Unit = self._makeCycles()
+ field = self._makeOne(schema=IUnit)
+ person1 = Person(None)
+ person2 = Person(None)
+ person3 = Person(object())
+ unit = Unit(person3, [person1, person2])
+ person1.unit = unit
+ person2.unit = unit
+ self.assertRaises(WrongContainedType, field.validate, unit)
+
+ def test_validate_w_cycles_collection_not_valid(self):
+ from zope.schema.interfaces import WrongContainedType
+ 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(WrongContainedType, field.validate, unit)
+
+ def test_set_emits_IBOAE(self):
+ from zope.event import subscribers
+ from zope.interface import implementer
+ from zope.schema.interfaces import IBeforeObjectAssignedEvent
+ 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
+ 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 invariant
+ from zope.interface import Interface
+ from zope.interface import implementer
+ from zope.interface import Invalid
+ from zope.schema import Text
+ from zope.schema import Bytes
+
+ 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 O(object):
+ foo = u''
+ bar = b''
+
+
+ field = self._makeOne(ISchema)
+ inst = O()
+
+ # 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
+ self.assertRaises(SchemaNotProvided, field.validate, object())
+
+ # 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 Interface, implementer
+ from zope.interface import Attribute
+
+ from zope.schema import Choice, Object, Set
+ from zope.schema.fieldproperty import FieldProperty
+ from zope.schema.interfaces import IContextSourceBinder
+ from zope.schema.interfaces import WrongContainedType
+ from zope.schema.interfaces import SchemaNotCorrectlyImplemented
+ 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(WrongContainedType) as exc:
+ IFavorites['fav'].validate(bad_choices)
+
+ e = exc.exception
+ self.assertEqual(IFavorites['fav'], e.field)
+ self.assertEqual(bad_choices, e.value)
+
+ # 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))
+
+
class DummyInst(object):
missing_value = object()