summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAdam Groszer <agroszer@gmail.com>2020-03-06 13:28:14 +0100
committerAdam Groszer <agroszer@gmail.com>2020-03-06 13:53:32 +0100
commit5f5cfdd24d20dfb1bdaa8f8315fa8cc17b4ef089 (patch)
tree2a40cdf376441718411302a422f7f7ca53fad86f
parentee3aa418d1c9425dccf3fbb210924b77340f04a0 (diff)
downloadzope-schema-5f5cfdd24d20dfb1bdaa8f8315fa8cc17b4ef089.tar.gz
Set `IDecimal` attributes `min`, `max` and `default` as `Decimal` type instead of `Number`.adamg-decimal
-rw-r--r--CHANGES.rst4
-rw-r--r--src/zope/schema/_bootstrapfields.py63
-rw-r--r--src/zope/schema/_field.py68
-rw-r--r--src/zope/schema/interfaces.py19
-rw-r--r--src/zope/schema/tests/test__bootstrapfields.py60
-rw-r--r--src/zope/schema/tests/test__field.py57
6 files changed, 146 insertions, 125 deletions
diff --git a/CHANGES.rst b/CHANGES.rst
index 13cb2af..09c4157 100644
--- a/CHANGES.rst
+++ b/CHANGES.rst
@@ -5,6 +5,10 @@
5.0 (unreleased)
================
+- Set ``IDecimal`` attributes ``min``, ``max`` and ``default`` as ``Decimal``
+ type instead of ``Number``.
+ See `issue 88 <https://github.com/zopefoundation/zope.schema/issues/88>`_.
+
- Enable unicode normalization for ``Text`` fields.
The default is NFC normalization. Valid forms are 'NFC', 'NFKC', 'NFD', and
'NFKD'. To disable normalization, set ``unicode_normalization`` to ``False``
diff --git a/src/zope/schema/_bootstrapfields.py b/src/zope/schema/_bootstrapfields.py
index 84f6148..7152b82 100644
--- a/src/zope/schema/_bootstrapfields.py
+++ b/src/zope/schema/_bootstrapfields.py
@@ -901,6 +901,69 @@ class Int(Integral):
_unicode_converters = (int,)
+class InvalidDecimalLiteral(ValueError, ValidationError):
+ "Raised by decimal fields"
+
+
+class Decimal(Number):
+ """
+ A field representing a native :class:`decimal.Decimal` and implementing
+ :class:`zope.schema.interfaces.IDecimal`.
+
+ The :meth:`fromUnicode` method only accepts values that can be parsed
+ by the ``Decimal`` constructor::
+
+ >>> from zope.schema._field import Decimal
+ >>> f = Decimal()
+ >>> f.fromUnicode("1")
+ Decimal('1')
+ >>> f.fromUnicode("125.6")
+ Decimal('125.6')
+ >>> f.fromUnicode("1+0j") # doctest: +IGNORE_EXCEPTION_DETAIL
+ Traceback (most recent call last):
+ ...
+ InvalidDecimalLiteral: Invalid literal for Decimal(): 1+0j
+ >>> f.fromUnicode("1/2") # doctest: +IGNORE_EXCEPTION_DETAIL
+ Traceback (most recent call last):
+ ...
+ InvalidDecimalLiteral: Invalid literal for Decimal(): 1/2
+ >>> f.fromUnicode(str(2**31234) + '.' + str(2**256)) # doctest: +ELLIPSIS
+ Decimal('2349...936')
+ >>> f.fromUnicode("not a number") # doctest: +IGNORE_EXCEPTION_DETAIL
+ Traceback (most recent call last):
+ ...
+ InvalidDecimalLiteral: could not convert string to float: not a number
+
+ Likewise for :meth:`fromBytes`::
+
+ >>> from zope.schema._field import Decimal
+ >>> f = Decimal()
+ >>> f.fromBytes(b"1")
+ Decimal('1')
+ >>> f.fromBytes(b"125.6")
+ Decimal('125.6')
+ >>> f.fromBytes(b"1+0j") # doctest: +IGNORE_EXCEPTION_DETAIL
+ Traceback (most recent call last):
+ ...
+ InvalidDecimalLiteral: Invalid literal for Decimal(): 1+0j
+ >>> f.fromBytes(b"1/2") # doctest: +IGNORE_EXCEPTION_DETAIL
+ Traceback (most recent call last):
+ ...
+ InvalidDecimalLiteral: Invalid literal for Decimal(): 1/2
+ >>> f.fromBytes((str(2**31234) + '.' + str(2**256)).encode("ascii")) # doctest: +ELLIPSIS
+ Decimal('2349...936')
+ >>> f.fromBytes(b"not a number") # doctest: +IGNORE_EXCEPTION_DETAIL
+ Traceback (most recent call last):
+ ...
+ InvalidDecimalLiteral: could not convert string to float: not a number
+
+
+ """
+ _type = decimal.Decimal
+ _unicode_converters = (decimal.Decimal,)
+ _validation_error = InvalidDecimalLiteral
+
+
class _ObjectsBeingValidated(threading.local):
def __init__(self):
diff --git a/src/zope/schema/_field.py b/src/zope/schema/_field.py
index 336b291..cf0f7fe 100644
--- a/src/zope/schema/_field.py
+++ b/src/zope/schema/_field.py
@@ -25,7 +25,6 @@ from datetime import datetime
from datetime import date
from datetime import timedelta
from datetime import time
-import decimal
import re
@@ -106,6 +105,8 @@ from zope.schema._bootstrapfields import Bool
from zope.schema._bootstrapfields import Int
from zope.schema._bootstrapfields import Integral
from zope.schema._bootstrapfields import Number
+from zope.schema._bootstrapfields import InvalidDecimalLiteral # reimport
+from zope.schema._bootstrapfields import Decimal
from zope.schema._bootstrapfields import Password
from zope.schema._bootstrapfields import Rational
from zope.schema._bootstrapfields import Real
@@ -147,6 +148,7 @@ classImplements(Real, IReal)
classImplements(Rational, IRational)
classImplements(Integral, IIntegral)
classImplements(Int, IInt)
+classImplements(Decimal, IDecimal)
classImplements(Object, IObject)
@@ -315,70 +317,6 @@ class Float(Real):
_validation_error = InvalidFloatLiteral
-class InvalidDecimalLiteral(ValueError, ValidationError):
- "Raised by decimal fields"
-
-
-@implementer(IDecimal)
-class Decimal(Number):
- """
- A field representing a native :class:`decimal.Decimal` and implementing
- :class:`zope.schema.interfaces.IDecimal`.
-
- The :meth:`fromUnicode` method only accepts values that can be parsed
- by the ``Decimal`` constructor::
-
- >>> from zope.schema._field import Decimal
- >>> f = Decimal()
- >>> f.fromUnicode("1")
- Decimal('1')
- >>> f.fromUnicode("125.6")
- Decimal('125.6')
- >>> f.fromUnicode("1+0j") # doctest: +IGNORE_EXCEPTION_DETAIL
- Traceback (most recent call last):
- ...
- InvalidDecimalLiteral: Invalid literal for Decimal(): 1+0j
- >>> f.fromUnicode("1/2") # doctest: +IGNORE_EXCEPTION_DETAIL
- Traceback (most recent call last):
- ...
- InvalidDecimalLiteral: Invalid literal for Decimal(): 1/2
- >>> f.fromUnicode(str(2**31234) + '.' + str(2**256)) # doctest: +ELLIPSIS
- Decimal('2349...936')
- >>> f.fromUnicode("not a number") # doctest: +IGNORE_EXCEPTION_DETAIL
- Traceback (most recent call last):
- ...
- InvalidDecimalLiteral: could not convert string to float: not a number
-
- Likewise for :meth:`fromBytes`::
-
- >>> from zope.schema._field import Decimal
- >>> f = Decimal()
- >>> f.fromBytes(b"1")
- Decimal('1')
- >>> f.fromBytes(b"125.6")
- Decimal('125.6')
- >>> f.fromBytes(b"1+0j") # doctest: +IGNORE_EXCEPTION_DETAIL
- Traceback (most recent call last):
- ...
- InvalidDecimalLiteral: Invalid literal for Decimal(): 1+0j
- >>> f.fromBytes(b"1/2") # doctest: +IGNORE_EXCEPTION_DETAIL
- Traceback (most recent call last):
- ...
- InvalidDecimalLiteral: Invalid literal for Decimal(): 1/2
- >>> f.fromBytes((str(2**31234) + '.' + str(2**256)).encode("ascii")) # doctest: +ELLIPSIS
- Decimal('2349...936')
- >>> f.fromBytes(b"not a number") # doctest: +IGNORE_EXCEPTION_DETAIL
- Traceback (most recent call last):
- ...
- InvalidDecimalLiteral: could not convert string to float: not a number
-
-
- """
- _type = decimal.Decimal
- _unicode_converters = (decimal.Decimal,)
- _validation_error = InvalidDecimalLiteral
-
-
@implementer(IDatetime)
class Datetime(Orderable, Field):
__doc__ = IDatetime.__doc__
diff --git a/src/zope/schema/interfaces.py b/src/zope/schema/interfaces.py
index b6542f3..303380a 100644
--- a/src/zope/schema/interfaces.py
+++ b/src/zope/schema/interfaces.py
@@ -23,6 +23,7 @@ from zope.interface.interfaces import IInterface
from zope.schema._bootstrapfields import Bool
from zope.schema._bootstrapfields import Complex
+from zope.schema._bootstrapfields import Decimal
from zope.schema._bootstrapfields import Field
from zope.schema._bootstrapfields import Int
from zope.schema._bootstrapfields import Integral
@@ -644,6 +645,24 @@ class IFloat(IReal):
class IDecimal(INumber):
"""Field containing a :class:`decimal.Decimal`"""
+ min = Decimal(
+ title=_("Start of the range"),
+ required=False,
+ default=None
+ )
+
+ max = Decimal(
+ title=_("End of the range (including the value itself)"),
+ required=False,
+ default=None
+ )
+
+ default = Decimal(
+ title=_("Default Value"),
+ description=_("""The field default value may be None or a legal
+ field value""")
+ )
+
###
# End numbers
###
diff --git a/src/zope/schema/tests/test__bootstrapfields.py b/src/zope/schema/tests/test__bootstrapfields.py
index 1cb3562..34bdfc8 100644
--- a/src/zope/schema/tests/test__bootstrapfields.py
+++ b/src/zope/schema/tests/test__bootstrapfields.py
@@ -11,6 +11,7 @@
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
+import decimal
import doctest
import unittest
import unicodedata
@@ -1245,10 +1246,7 @@ class IntegralTests(RationalTests):
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')
@@ -1278,6 +1276,62 @@ class IntTests(IntegralTests):
self.assertEqual(txt._type, integer_types)
+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):
diff --git a/src/zope/schema/tests/test__field.py b/src/zope/schema/tests/test__field.py
index 7b0499f..2e78b8e 100644
--- a/src/zope/schema/tests/test__field.py
+++ b/src/zope/schema/tests/test__field.py
@@ -12,7 +12,6 @@
#
##############################################################################
import datetime
-import decimal
import doctest
import unittest
@@ -314,62 +313,6 @@ class FloatTests(NumberTests):
self.assertEqual(flt.fromUnicode(u'1.23e6'), 1230000.0)
-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._field 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 DatetimeTests(OrderableMissingValueMixin,
OrderableTestsMixin,
EqualityTestsMixin,