Skip to content

Commit

Permalink
Merge pull request #41 from zopefoundation/issue9
Browse files Browse the repository at this point in the history
Allow `missing_value` without setting `min` and `max`
  • Loading branch information
jamadden committed Aug 13, 2018
2 parents ca3e6f8 + 4a8b026 commit ec25d71
Show file tree
Hide file tree
Showing 4 changed files with 81 additions and 27 deletions.
6 changes: 6 additions & 0 deletions CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,12 @@ Changes
<https://github.com/zopefoundation/zope.schema/issues/36>`_.


- Orderable fields, including ``Int``, ``Float``, ``Decimal``,
``Timedelta``, ``Date`` and ``Time``, can now have a
``missing_value`` without needing to specify concrete ``min`` and
``max`` values (they must still specify a ``default`` value). See
`issue 9 <https://github.com/zopefoundation/zope.schema/issues/9>`_.

4.5.0 (2017-07-10)
------------------

Expand Down
34 changes: 19 additions & 15 deletions src/zope/schema/_bootstrapfields.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,40 +40,44 @@

class ValidatedProperty(object):

def __init__(self, name, check=None):
self._info = name, check
def __init__(self, name, check=None, allow_none=False):
self._name = name
self._check = check
self._allow_none = allow_none

def __set__(self, inst, value):
name, check = self._info
if value != inst.missing_value:
if check is not None:
check(inst, value)
bypass_validation = (value is None and self._allow_none) or value == inst.missing_value
if not bypass_validation:
if self._check is not None:
self._check(inst, value)
else:
inst.validate(value)
inst.__dict__[name] = value
inst.__dict__[self._name] = value

def __get__(self, inst, owner):
name, check = self._info
return inst.__dict__[name]
if inst is None:
return self
return inst.__dict__[self._name]


class DefaultProperty(ValidatedProperty):

def __get__(self, inst, owner):
name, check = self._info
if inst is None:
return self
defaultFactory = inst.__dict__.get('defaultFactory')
# If there is no default factory, simply return the default.
if defaultFactory is None:
return inst.__dict__[name]
return inst.__dict__[self._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)
if self._check is not None:
self._check(inst, value)
elif value != inst.missing_value:
inst.validate(value)
return value
Expand Down Expand Up @@ -279,8 +283,8 @@ class Orderable(object):
Orderable is a mixin used in combination with Field.
"""

min = ValidatedProperty('min')
max = ValidatedProperty('max')
min = ValidatedProperty('min', allow_none=True)
max = ValidatedProperty('max', allow_none=True)

def __init__(self, min=None, max=None, default=None, **kw):

Expand Down
24 changes: 23 additions & 1 deletion src/zope/schema/tests/test__bootstrapfields.py
Original file line number Diff line number Diff line change
Expand Up @@ -741,7 +741,29 @@ def test_fromUnicode_hit(self):
self.assertEqual(txt.fromUnicode(u'true'), True)


class IntTests(unittest.TestCase):
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 IntTests(OrderableMissingValueMixin,
unittest.TestCase):

def _getTargetClass(self):
from zope.schema._bootstrapfields import Int
Expand Down
44 changes: 33 additions & 11 deletions src/zope/schema/tests/test__field.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,11 @@
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
import datetime
import decimal
import unittest

from zope.schema.tests.test__bootstrapfields import OrderableMissingValueMixin

class BytesTests(unittest.TestCase):

Expand Down Expand Up @@ -249,7 +252,11 @@ def test_constraint(self):
self.assertEqual(field.constraint('abc\ndef'), False)


class FloatTests(unittest.TestCase):
class FloatTests(OrderableMissingValueMixin,
unittest.TestCase):

mvm_missing_value = -1.0
mvm_default = 0.0

def _getTargetClass(self):
from zope.schema._field import Float
Expand Down Expand Up @@ -326,7 +333,11 @@ def test_fromUnicode_hit(self):
self.assertEqual(flt.fromUnicode(u'1.23e6'), 1230000.0)


class DecimalTests(unittest.TestCase):
class DecimalTests(OrderableMissingValueMixin,
unittest.TestCase):

mvm_missing_value = decimal.Decimal("-1")
mvm_default = decimal.Decimal("0")

def _getTargetClass(self):
from zope.schema._field import Decimal
Expand All @@ -346,15 +357,13 @@ def test_instance_conforms_to_IDecimal(self):
verifyObject(IDecimal, self._makeOne())

def test_validate_not_required(self):
import decimal
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):
import decimal
from zope.schema.interfaces import RequiredMissing
field = self._makeOne()
field.validate(decimal.Decimal("10.0"))
Expand All @@ -363,7 +372,6 @@ def test_validate_required(self):
self.assertRaises(RequiredMissing, field.validate, None)

def test_validate_min(self):
import decimal
from zope.schema.interfaces import TooSmall
field = self._makeOne(min=decimal.Decimal("10.5"))
field.validate(decimal.Decimal("10.6"))
Expand All @@ -372,7 +380,6 @@ def test_validate_min(self):
self.assertRaises(TooSmall, field.validate, decimal.Decimal("10.4"))

def test_validate_max(self):
import decimal
from zope.schema.interfaces import TooBig
field = self._makeOne(max=decimal.Decimal("10.5"))
field.validate(decimal.Decimal("5.3"))
Expand All @@ -381,7 +388,6 @@ def test_validate_max(self):
self.assertRaises(TooBig, field.validate, decimal.Decimal("20.7"))

def test_validate_min_and_max(self):
import decimal
from zope.schema.interfaces import TooBig
from zope.schema.interfaces import TooSmall
field = self._makeOne(min=decimal.Decimal("-0.6"),
Expand Down Expand Up @@ -410,7 +416,11 @@ def test_fromUnicode_hit(self):
self.assertEqual(flt.fromUnicode(u'12345.6'), Decimal('12345.6'))


class DatetimeTests(unittest.TestCase):
class DatetimeTests(OrderableMissingValueMixin,
unittest.TestCase):

mvm_missing_value = datetime.datetime.now()
mvm_default = datetime.datetime.now()

def _getTargetClass(self):
from zope.schema._field import Datetime
Expand Down Expand Up @@ -496,7 +506,11 @@ def test_validate_w_min_and_max(self):
self.assertRaises(TooBig, field.validate, d5)


class DateTests(unittest.TestCase):
class DateTests(OrderableMissingValueMixin,
unittest.TestCase):

mvm_missing_value = datetime.date.today()
mvm_default = datetime.date.today()

def _getTargetClass(self):
from zope.schema._field import Date
Expand Down Expand Up @@ -586,7 +600,11 @@ def test_validate_w_min_and_max(self):
self.assertRaises(TooBig, field.validate, d5)


class TimedeltaTests(unittest.TestCase):
class TimedeltaTests(OrderableMissingValueMixin,
unittest.TestCase):

mvm_missing_value = datetime.timedelta(minutes=15)
mvm_default = datetime.timedelta(minutes=12)

def _getTargetClass(self):
from zope.schema._field import Timedelta
Expand Down Expand Up @@ -656,7 +674,11 @@ def test_validate_min_and_max(self):
self.assertRaises(TooBig, field.validate, t5)


class TimeTests(unittest.TestCase):
class TimeTests(OrderableMissingValueMixin,
unittest.TestCase):

mvm_missing_value = datetime.time(12, 15, 37)
mvm_default = datetime.time(12, 25, 42)

def _getTargetClass(self):
from zope.schema._field import Time
Expand Down

0 comments on commit ec25d71

Please sign in to comment.