Replace float with Decimal

Having float type as a base type for floating point parameters in
ipalib introduces several issues, e.g. problem with representation
or value comparison. Python language provides a Decimal type which
help overcome these issues.

This patch replaces a float type and Float parameter with a
decimal.Decimal type in Decimal parameter. A precision attribute
was added to Decimal parameter that can be used to limit a number
of decimal places in parameter representation. This approach fixes
a problem with API.txt validation where comparison of float values
may fail on different architectures due to float representation error.

In order to safely transfer the parameter value over RPC it is
being converted to string which is then converted back to
decimal.Decimal number on a server side.

https://fedorahosted.org/freeipa/ticket/2260
This commit is contained in:
Martin Kosek 2012-01-17 11:19:00 +01:00
parent d906fa50c1
commit 092dd8db12
11 changed files with 159 additions and 87 deletions

36
API.txt
View File

@ -654,16 +654,16 @@ option: Str('kx_part_exchanger', attribute=False, cli_name='kx_exchanger', multi
option: LOCRecord('locrecord', attribute=True, cli_name='loc_rec', csv=True, multivalue=True, option_group=u'LOC Record', required=False)
option: Int('loc_part_lat_deg', attribute=False, cli_name='loc_lat_deg', maxvalue=90, minvalue=0, multivalue=False, option_group=u'LOC Record', required=False)
option: Int('loc_part_lat_min', attribute=False, cli_name='loc_lat_min', maxvalue=59, minvalue=0, multivalue=False, option_group=u'LOC Record', required=False)
option: Float('loc_part_lat_sec', attribute=False, cli_name='loc_lat_sec', maxvalue=59.999, minvalue=0.0, multivalue=False, option_group=u'LOC Record', required=False)
option: Decimal('loc_part_lat_sec', attribute=False, cli_name='loc_lat_sec', maxvalue=Decimal('59.999'), minvalue=Decimal('0.0'), multivalue=False, option_group=u'LOC Record', precision=3, required=False)
option: StrEnum('loc_part_lat_dir', attribute=False, cli_name='loc_lat_dir', multivalue=False, option_group=u'LOC Record', required=False, values=(u'N', u'S'))
option: Int('loc_part_lon_deg', attribute=False, cli_name='loc_lon_deg', maxvalue=180, minvalue=0, multivalue=False, option_group=u'LOC Record', required=False)
option: Int('loc_part_lon_min', attribute=False, cli_name='loc_lon_min', maxvalue=59, minvalue=0, multivalue=False, option_group=u'LOC Record', required=False)
option: Float('loc_part_lon_sec', attribute=False, cli_name='loc_lon_sec', maxvalue=59.999, minvalue=0.0, multivalue=False, option_group=u'LOC Record', required=False)
option: Decimal('loc_part_lon_sec', attribute=False, cli_name='loc_lon_sec', maxvalue=Decimal('59.999'), minvalue=Decimal('0.0'), multivalue=False, option_group=u'LOC Record', precision=3, required=False)
option: StrEnum('loc_part_lon_dir', attribute=False, cli_name='loc_lon_dir', multivalue=False, option_group=u'LOC Record', required=False, values=(u'E', u'W'))
option: Float('loc_part_altitude', attribute=False, cli_name='loc_altitude', maxvalue=42849672.95, minvalue=-100000.0, multivalue=False, option_group=u'LOC Record', required=False)
option: Float('loc_part_size', attribute=False, cli_name='loc_size', maxvalue=90000000.0, minvalue=0.0, multivalue=False, option_group=u'LOC Record', required=False)
option: Float('loc_part_h_precision', attribute=False, cli_name='loc_h_precision', maxvalue=90000000.0, minvalue=0.0, multivalue=False, option_group=u'LOC Record', required=False)
option: Float('loc_part_v_precision', attribute=False, cli_name='loc_v_precision', maxvalue=90000000.0, minvalue=0.0, multivalue=False, option_group=u'LOC Record', required=False)
option: Decimal('loc_part_altitude', attribute=False, cli_name='loc_altitude', maxvalue=Decimal('42849672.95'), minvalue=Decimal('-100000.00'), multivalue=False, option_group=u'LOC Record', precision=2, required=False)
option: Decimal('loc_part_size', attribute=False, cli_name='loc_size', maxvalue=Decimal('90000000.00'), minvalue=Decimal('0.0'), multivalue=False, option_group=u'LOC Record', precision=2, required=False)
option: Decimal('loc_part_h_precision', attribute=False, cli_name='loc_h_precision', maxvalue=Decimal('90000000.00'), minvalue=Decimal('0.0'), multivalue=False, option_group=u'LOC Record', precision=2, required=False)
option: Decimal('loc_part_v_precision', attribute=False, cli_name='loc_v_precision', maxvalue=Decimal('90000000.00'), minvalue=Decimal('0.0'), multivalue=False, option_group=u'LOC Record', precision=2, required=False)
option: MXRecord('mxrecord', attribute=True, cli_name='mx_rec', csv=True, multivalue=True, option_group=u'MX Record', required=False)
option: Int('mx_part_preference', attribute=False, cli_name='mx_preference', maxvalue=65535, minvalue=0, multivalue=False, option_group=u'MX Record', required=False)
option: Str('mx_part_exchanger', attribute=False, cli_name='mx_exchanger', multivalue=False, option_group=u'MX Record', required=False)
@ -831,16 +831,16 @@ option: Str('kx_part_exchanger', attribute=False, autofill=False, cli_name='kx_e
option: LOCRecord('locrecord', attribute=True, autofill=False, cli_name='loc_rec', csv=True, multivalue=True, option_group=u'LOC Record', query=True, required=False)
option: Int('loc_part_lat_deg', attribute=False, autofill=False, cli_name='loc_lat_deg', maxvalue=90, minvalue=0, multivalue=False, option_group=u'LOC Record', query=True, required=False)
option: Int('loc_part_lat_min', attribute=False, autofill=False, cli_name='loc_lat_min', maxvalue=59, minvalue=0, multivalue=False, option_group=u'LOC Record', query=True, required=False)
option: Float('loc_part_lat_sec', attribute=False, autofill=False, cli_name='loc_lat_sec', maxvalue=59.999, minvalue=0.0, multivalue=False, option_group=u'LOC Record', query=True, required=False)
option: Decimal('loc_part_lat_sec', attribute=False, autofill=False, cli_name='loc_lat_sec', maxvalue=Decimal('59.999'), minvalue=Decimal('0.0'), multivalue=False, option_group=u'LOC Record', precision=3, query=True, required=False)
option: StrEnum('loc_part_lat_dir', attribute=False, autofill=False, cli_name='loc_lat_dir', multivalue=False, option_group=u'LOC Record', query=True, required=False, values=(u'N', u'S'))
option: Int('loc_part_lon_deg', attribute=False, autofill=False, cli_name='loc_lon_deg', maxvalue=180, minvalue=0, multivalue=False, option_group=u'LOC Record', query=True, required=False)
option: Int('loc_part_lon_min', attribute=False, autofill=False, cli_name='loc_lon_min', maxvalue=59, minvalue=0, multivalue=False, option_group=u'LOC Record', query=True, required=False)
option: Float('loc_part_lon_sec', attribute=False, autofill=False, cli_name='loc_lon_sec', maxvalue=59.999, minvalue=0.0, multivalue=False, option_group=u'LOC Record', query=True, required=False)
option: Decimal('loc_part_lon_sec', attribute=False, autofill=False, cli_name='loc_lon_sec', maxvalue=Decimal('59.999'), minvalue=Decimal('0.0'), multivalue=False, option_group=u'LOC Record', precision=3, query=True, required=False)
option: StrEnum('loc_part_lon_dir', attribute=False, autofill=False, cli_name='loc_lon_dir', multivalue=False, option_group=u'LOC Record', query=True, required=False, values=(u'E', u'W'))
option: Float('loc_part_altitude', attribute=False, autofill=False, cli_name='loc_altitude', maxvalue=42849672.95, minvalue=-100000.0, multivalue=False, option_group=u'LOC Record', query=True, required=False)
option: Float('loc_part_size', attribute=False, autofill=False, cli_name='loc_size', maxvalue=90000000.0, minvalue=0.0, multivalue=False, option_group=u'LOC Record', query=True, required=False)
option: Float('loc_part_h_precision', attribute=False, autofill=False, cli_name='loc_h_precision', maxvalue=90000000.0, minvalue=0.0, multivalue=False, option_group=u'LOC Record', query=True, required=False)
option: Float('loc_part_v_precision', attribute=False, autofill=False, cli_name='loc_v_precision', maxvalue=90000000.0, minvalue=0.0, multivalue=False, option_group=u'LOC Record', query=True, required=False)
option: Decimal('loc_part_altitude', attribute=False, autofill=False, cli_name='loc_altitude', maxvalue=Decimal('42849672.95'), minvalue=Decimal('-100000.00'), multivalue=False, option_group=u'LOC Record', precision=2, query=True, required=False)
option: Decimal('loc_part_size', attribute=False, autofill=False, cli_name='loc_size', maxvalue=Decimal('90000000.00'), minvalue=Decimal('0.0'), multivalue=False, option_group=u'LOC Record', precision=2, query=True, required=False)
option: Decimal('loc_part_h_precision', attribute=False, autofill=False, cli_name='loc_h_precision', maxvalue=Decimal('90000000.00'), minvalue=Decimal('0.0'), multivalue=False, option_group=u'LOC Record', precision=2, query=True, required=False)
option: Decimal('loc_part_v_precision', attribute=False, autofill=False, cli_name='loc_v_precision', maxvalue=Decimal('90000000.00'), minvalue=Decimal('0.0'), multivalue=False, option_group=u'LOC Record', precision=2, query=True, required=False)
option: MXRecord('mxrecord', attribute=True, autofill=False, cli_name='mx_rec', csv=True, multivalue=True, option_group=u'MX Record', query=True, required=False)
option: Int('mx_part_preference', attribute=False, autofill=False, cli_name='mx_preference', maxvalue=65535, minvalue=0, multivalue=False, option_group=u'MX Record', query=True, required=False)
option: Str('mx_part_exchanger', attribute=False, autofill=False, cli_name='mx_exchanger', multivalue=False, option_group=u'MX Record', query=True, required=False)
@ -952,16 +952,16 @@ option: Str('kx_part_exchanger', attribute=False, autofill=False, cli_name='kx_e
option: LOCRecord('locrecord', attribute=True, autofill=False, cli_name='loc_rec', csv=True, multivalue=True, option_group=u'LOC Record', required=False)
option: Int('loc_part_lat_deg', attribute=False, autofill=False, cli_name='loc_lat_deg', maxvalue=90, minvalue=0, multivalue=False, option_group=u'LOC Record', required=False)
option: Int('loc_part_lat_min', attribute=False, autofill=False, cli_name='loc_lat_min', maxvalue=59, minvalue=0, multivalue=False, option_group=u'LOC Record', required=False)
option: Float('loc_part_lat_sec', attribute=False, autofill=False, cli_name='loc_lat_sec', maxvalue=59.999, minvalue=0.0, multivalue=False, option_group=u'LOC Record', required=False)
option: Decimal('loc_part_lat_sec', attribute=False, autofill=False, cli_name='loc_lat_sec', maxvalue=Decimal('59.999'), minvalue=Decimal('0.0'), multivalue=False, option_group=u'LOC Record', precision=3, required=False)
option: StrEnum('loc_part_lat_dir', attribute=False, autofill=False, cli_name='loc_lat_dir', multivalue=False, option_group=u'LOC Record', required=False, values=(u'N', u'S'))
option: Int('loc_part_lon_deg', attribute=False, autofill=False, cli_name='loc_lon_deg', maxvalue=180, minvalue=0, multivalue=False, option_group=u'LOC Record', required=False)
option: Int('loc_part_lon_min', attribute=False, autofill=False, cli_name='loc_lon_min', maxvalue=59, minvalue=0, multivalue=False, option_group=u'LOC Record', required=False)
option: Float('loc_part_lon_sec', attribute=False, autofill=False, cli_name='loc_lon_sec', maxvalue=59.999, minvalue=0.0, multivalue=False, option_group=u'LOC Record', required=False)
option: Decimal('loc_part_lon_sec', attribute=False, autofill=False, cli_name='loc_lon_sec', maxvalue=Decimal('59.999'), minvalue=Decimal('0.0'), multivalue=False, option_group=u'LOC Record', precision=3, required=False)
option: StrEnum('loc_part_lon_dir', attribute=False, autofill=False, cli_name='loc_lon_dir', multivalue=False, option_group=u'LOC Record', required=False, values=(u'E', u'W'))
option: Float('loc_part_altitude', attribute=False, autofill=False, cli_name='loc_altitude', maxvalue=42849672.95, minvalue=-100000.0, multivalue=False, option_group=u'LOC Record', required=False)
option: Float('loc_part_size', attribute=False, autofill=False, cli_name='loc_size', maxvalue=90000000.0, minvalue=0.0, multivalue=False, option_group=u'LOC Record', required=False)
option: Float('loc_part_h_precision', attribute=False, autofill=False, cli_name='loc_h_precision', maxvalue=90000000.0, minvalue=0.0, multivalue=False, option_group=u'LOC Record', required=False)
option: Float('loc_part_v_precision', attribute=False, autofill=False, cli_name='loc_v_precision', maxvalue=90000000.0, minvalue=0.0, multivalue=False, option_group=u'LOC Record', required=False)
option: Decimal('loc_part_altitude', attribute=False, autofill=False, cli_name='loc_altitude', maxvalue=Decimal('42849672.95'), minvalue=Decimal('-100000.00'), multivalue=False, option_group=u'LOC Record', precision=2, required=False)
option: Decimal('loc_part_size', attribute=False, autofill=False, cli_name='loc_size', maxvalue=Decimal('90000000.00'), minvalue=Decimal('0.0'), multivalue=False, option_group=u'LOC Record', precision=2, required=False)
option: Decimal('loc_part_h_precision', attribute=False, autofill=False, cli_name='loc_h_precision', maxvalue=Decimal('90000000.00'), minvalue=Decimal('0.0'), multivalue=False, option_group=u'LOC Record', precision=2, required=False)
option: Decimal('loc_part_v_precision', attribute=False, autofill=False, cli_name='loc_v_precision', maxvalue=Decimal('90000000.00'), minvalue=Decimal('0.0'), multivalue=False, option_group=u'LOC Record', precision=2, required=False)
option: MXRecord('mxrecord', attribute=True, autofill=False, cli_name='mx_rec', csv=True, multivalue=True, option_group=u'MX Record', required=False)
option: Int('mx_part_preference', attribute=False, autofill=False, cli_name='mx_preference', maxvalue=65535, minvalue=0, multivalue=False, option_group=u'MX Record', required=False)
option: Str('mx_part_exchanger', attribute=False, autofill=False, cli_name='mx_exchanger', multivalue=False, option_group=u'MX Record', required=False)

View File

@ -227,7 +227,7 @@ verbose = Flag('verbose', default=True)
specified when constructing =Int= parameter:
- /minvalue/ :: minimal value that this parameter accepts, defaults to =MININT=
- /maxvalue/ :: maximum value this parameter can accept, defaults to =MAXINT=
- /Float/ :: floating point parameters that are stored in Python's float type. =Float= has
- /Decimal/ :: floating point parameters that are stored in Python's Decimal type. =Decimal= has
the same two additional properties as =Int=. Unlike =Int=, there are no
default values for the minimal and maximum boundaries.
- /Bytes/ :: a parameter to represent binary data.
@ -294,9 +294,9 @@ class tank(Object):
takes_params = (
StrEnum('species*', label=u'Species', doc=u'Fish species',
values=(u'Angelfish', u'Betta', u'Cichlid', u'Firemouth')),
Float('height', label=u'Height', doc=u'height in mm', default=400.0),
Float('width', label=u'Width', doc=u'width in mm', default=400.0),
Float('depth', label=u'Depth', doc=u'Depth in mm', default=300.0)
Decimal('height', label=u'Height', doc=u'height in mm', default='400.0'),
Decimal('width', label=u'Width', doc=u'width in mm', default='400.0'),
Decimal('depth', label=u'Depth', doc=u'Depth in mm', default='300.0')
)
api.register(tank) (ref:register)

View File

@ -878,7 +878,7 @@ from backend import Backend
from frontend import Command, LocalOrRemote, Updater
from frontend import Object, Method, Property
from crud import Create, Retrieve, Update, Delete, Search
from parameters import DefaultFrom, Bool, Flag, Int, Float, Bytes, Str, IA5Str, Password
from parameters import DefaultFrom, Bool, Flag, Int, Decimal, Bytes, Str, IA5Str, Password
from parameters import BytesEnum, StrEnum, AccessTime, File
from errors import SkipPluginModule
from text import _, ngettext, GettextFactory, NGettextFactory

View File

@ -20,6 +20,8 @@
Encoding capabilities.
"""
from decimal import Decimal
class EncoderSettings(object):
"""
Container for encoder settings.
@ -77,7 +79,7 @@ class Encoder(object):
return self.encoder_settings.encode_postprocessor(
var.encode(self.encoder_settings.encode_to)
)
elif isinstance(var, (bool, float, int, long)):
elif isinstance(var, (bool, float, Decimal, int, long)):
return self.encoder_settings.encode_postprocessor(
str(var).encode(self.encoder_settings.encode_to)
)
@ -131,7 +133,7 @@ class Encoder(object):
return self.encoder_settings.decode_postprocessor(
var.decode(self.encoder_settings.decode_from)
)
elif isinstance(var, (bool, float, int, long)):
elif isinstance(var, (bool, float, Decimal, int, long)):
return var
elif isinstance(var, list):
return [self.decode(m) for m in var]

View File

@ -100,6 +100,7 @@ a more detailed description for clarity.
"""
import re
import decimal
from types import NoneType
from util import make_repr
from text import _ as ugettext
@ -723,8 +724,6 @@ class Param(ReadOnly):
else:
newval += (v,)
value = newval
if self.normalizer is None:
return value
if self.multivalue:
return tuple(
self._normalize_scalar(v) for v in value
@ -740,6 +739,8 @@ class Param(ReadOnly):
"""
if type(value) is not unicode:
return value
if self.normalizer is None:
return value
try:
return self.normalizer(value)
except StandardError:
@ -1100,7 +1101,7 @@ class Flag(Bool):
class Number(Param):
"""
Base class for the `Int` and `Float` parameters.
Base class for the `Int` and `Decimal` parameters.
"""
def _convert_scalar(self, value, index=None):
@ -1225,36 +1226,59 @@ class Int(Number):
)
class Float(Number):
class Decimal(Number):
"""
A parameter for floating-point values (stored in the ``float`` type).
A parameter for floating-point values (stored in the ``Decimal`` type).
Python Decimal type helps overcome problems tied to plain "float" type,
e.g. problem with representation or value comparison. In order to safely
transfer the value over RPC libraries, it is being converted to string
which is then converted back to Decimal number.
"""
type = float
type = decimal.Decimal
type_error = _('must be a decimal number')
kwargs = Param.kwargs + (
('minvalue', float, None),
('maxvalue', float, None),
('minvalue', decimal.Decimal, None),
('maxvalue', decimal.Decimal, None),
('precision', int, None),
)
def __init__(self, name, *rules, **kw):
#pylint: disable=E1003
super(Number, self).__init__(name, *rules, **kw)
for kwparam in ('minvalue', 'maxvalue', 'default'):
value = kw.get(kwparam)
if value is None:
continue
if isinstance(value, (basestring, float)):
try:
value = decimal.Decimal(value)
except Exception, e:
raise ValueError(
'%s: cannot parse kwarg %s: %s' % (
name, kwparam, str(e)))
kw[kwparam] = value
if (self.minvalue > self.maxvalue) and (self.minvalue is not None and self.maxvalue is not None):
super(Decimal, self).__init__(name, *rules, **kw)
if (self.minvalue > self.maxvalue) \
and (self.minvalue is not None and \
self.maxvalue is not None):
raise ValueError(
'%s: minvalue > maxvalue (minvalue=%r, maxvalue=%r)' % (
'%s: minvalue > maxvalue (minvalue=%s, maxvalue=%s)' % (
self.nice, self.minvalue, self.maxvalue)
)
if self.precision is not None and self.precision < 0:
raise ValueError('%s: precision must be at least 0' % self.nice)
def _rule_minvalue(self, _, value):
"""
Check min constraint.
"""
assert type(value) is float
assert type(value) is decimal.Decimal
if value < self.minvalue:
return _('must be at least %(minvalue)f') % dict(
return _('must be at least %(minvalue)s') % dict(
minvalue=self.minvalue,
)
@ -1262,12 +1286,39 @@ class Float(Number):
"""
Check max constraint.
"""
assert type(value) is float
assert type(value) is decimal.Decimal
if value > self.maxvalue:
return _('can be at most %(maxvalue)f') % dict(
return _('can be at most %(maxvalue)s') % dict(
maxvalue=self.maxvalue,
)
def _enforce_precision(self, value):
assert type(value) is decimal.Decimal
if self.precision is not None:
quantize_exp = decimal.Decimal(10) ** -self.precision
return value.quantize(quantize_exp)
return value
def _convert_scalar(self, value, index=None):
if isinstance(value, (basestring, float)):
try:
value = decimal.Decimal(value)
except Exception, e:
raise ConversionError(name=self.name, index=index,
error=unicode(e))
if isinstance(value, decimal.Decimal):
x = self._enforce_precision(value)
return x
return super(Decimal, self)._convert_scalar(value, index)
def _normalize_scalar(self, value):
if isinstance(value, decimal.Decimal):
value = self._enforce_precision(value)
return super(Decimal, self)._normalize_scalar(value)
class Data(Param):
"""
@ -1423,7 +1474,7 @@ class Str(Data):
"""
if type(value) is self.type:
return value
if type(value) in (int, float):
if type(value) in (int, float, decimal.Decimal):
return self.type(value)
if type(value) in (tuple, list):
raise ConversionError(name=self.name, index=index,

View File

@ -25,7 +25,7 @@ import re
from ipalib.request import context
from ipalib import api, errors, output
from ipalib import Command
from ipalib.parameters import Flag, Bool, Int, Float, Str, StrEnum, Any
from ipalib.parameters import Flag, Bool, Int, Decimal, Str, StrEnum, Any
from ipalib.plugins.baseldap import *
from ipalib import _, ngettext
from ipalib.util import validate_zonemgr, normalize_zonemgr, validate_hostname
@ -345,7 +345,12 @@ class DNSRecord(Str):
if not values:
return value
new_values = [ part.normalize(values[part_id]) \
converted_values = [ part._convert_scalar(values[part_id]) \
if values[part_id] is not None else None
for part_id, part in enumerate(self.parts)
]
new_values = [ part.normalize(converted_values[part_id]) \
for part_id, part in enumerate(self.parts) ]
value = self._convert_scalar(new_values)
@ -626,10 +631,11 @@ class LOCRecord(DNSRecord):
minvalue=0,
maxvalue=59,
),
Float('lat_sec?',
Decimal('lat_sec?',
label=_('Seconds Latitude'),
minvalue=0.0,
maxvalue=59.999,
minvalue='0.0',
maxvalue='59.999',
precision=3,
),
StrEnum('lat_dir',
label=_('Direction Latitude'),
@ -645,34 +651,39 @@ class LOCRecord(DNSRecord):
minvalue=0,
maxvalue=59,
),
Float('lon_sec?',
Decimal('lon_sec?',
label=_('Seconds Longtitude'),
minvalue=0.0,
maxvalue=59.999,
minvalue='0.0',
maxvalue='59.999',
precision=3,
),
StrEnum('lon_dir',
label=_('Direction Longtitude'),
values=(u'E', u'W',),
),
Float('altitude',
Decimal('altitude',
label=_('Altitude'),
minvalue=-100000.00,
maxvalue=42849672.95,
minvalue='-100000.00',
maxvalue='42849672.95',
precision=2,
),
Float('size?',
Decimal('size?',
label=_('Size'),
minvalue=0.0,
maxvalue=90000000.00,
minvalue='0.0',
maxvalue='90000000.00',
precision=2,
),
Float('h_precision?',
Decimal('h_precision?',
label=_('Horizontal Precision'),
minvalue=0.0,
maxvalue=90000000.00,
minvalue='0.0',
maxvalue='90000000.00',
precision=2,
),
Float('v_precision?',
Decimal('v_precision?',
label=_('Vertical Precision'),
minvalue=0.0,
maxvalue=90000000.00,
minvalue='0.0',
maxvalue='90000000.00',
precision=2,
),
)

View File

@ -31,6 +31,7 @@ Also see the `ipaserver.rpcserver` module.
"""
from types import NoneType
from decimal import Decimal
import threading
import sys
import os
@ -86,6 +87,9 @@ def xml_wrap(value):
)
if type(value) is str:
return Binary(value)
if type(value) is Decimal:
# transfer Decimal as a string
return unicode(value)
assert type(value) in (unicode, int, float, bool, NoneType)
return value

View File

@ -37,6 +37,7 @@ from ipapython.version import VERSION
import base64
import os
import string
from decimal import Decimal
_not_found_template = """<html>
<head>
<title>404 Not Found</title>
@ -385,6 +386,8 @@ def json_encode_binary(val):
return new_list
elif isinstance(val, str):
return {'__base64__' : base64.b64encode(val)}
elif isinstance(val, Decimal):
return {'__base64__' : base64.b64encode(str(val))}
else:
return val

View File

@ -61,7 +61,7 @@ class IPATypeChecker(TypeChecker):
'sortorder', 'csv', 'csv_separator', 'csv_skipspace'],
'ipalib.parameters.Bool': ['truths', 'falsehoods'],
'ipalib.parameters.Int': ['minvalue', 'maxvalue'],
'ipalib.parameters.Float': ['minvalue', 'maxvalue'],
'ipalib.parameters.Decimal': ['minvalue', 'maxvalue', 'precision'],
'ipalib.parameters.Data': ['minlength', 'maxlength', 'length',
'pattern', 'pattern_errmsg'],
'ipalib.parameters.Enum': ['values'],

View File

@ -25,6 +25,7 @@ Test the `ipalib.parameters` module.
import re
import sys
from types import NoneType
from decimal import Decimal
from inspect import isclass
from tests.util import raises, ClassChecker, read_only
from tests.util import dummy_ugettext, assert_equal
@ -1300,77 +1301,77 @@ class test_Int(ClassChecker):
assert o._convert_scalar(u'0x10') == 16
assert o._convert_scalar(u'020') == 16
class test_Float(ClassChecker):
class test_Decimal(ClassChecker):
"""
Test the `ipalib.parameters.Float` class.
Test the `ipalib.parameters.Decimal` class.
"""
_cls = parameters.Float
_cls = parameters.Decimal
def test_init(self):
"""
Test the `ipalib.parameters.Float.__init__` method.
Test the `ipalib.parameters.Decimal.__init__` method.
"""
# Test with no kwargs:
o = self.cls('my_number')
assert o.type is float
assert isinstance(o, parameters.Float)
assert o.type is Decimal
assert isinstance(o, parameters.Decimal)
assert o.minvalue is None
assert o.maxvalue is None
# Test when min > max:
e = raises(ValueError, self.cls, 'my_number', minvalue=22.5, maxvalue=15.1)
e = raises(ValueError, self.cls, 'my_number', minvalue=Decimal('22.5'), maxvalue=Decimal('15.1'))
assert str(e) == \
"Float('my_number'): minvalue > maxvalue (minvalue=22.5, maxvalue=15.1)"
"Decimal('my_number'): minvalue > maxvalue (minvalue=22.5, maxvalue=15.1)"
def test_rule_minvalue(self):
"""
Test the `ipalib.parameters.Float._rule_minvalue` method.
Test the `ipalib.parameters.Decimal._rule_minvalue` method.
"""
o = self.cls('my_number', minvalue=3.1)
assert o.minvalue == 3.1
o = self.cls('my_number', minvalue='3.1')
assert o.minvalue == Decimal('3.1')
rule = o._rule_minvalue
translation = u'minvalue=%(minvalue)r'
translation = u'minvalue=%(minvalue)s'
dummy = dummy_ugettext(translation)
assert dummy.translation is translation
# Test with passing values:
for value in (3.2, 99.0):
for value in (Decimal('3.2'), Decimal('99.0')):
assert rule(dummy, value) is None
assert dummy.called() is False
# Test with failing values:
for value in (-1.2, 0.0, 3.0):
for value in (Decimal('-1.2'), Decimal('0.0'), Decimal('3.0')):
assert_equal(
rule(dummy, value),
translation % dict(minvalue=3.1)
translation % dict(minvalue=Decimal('3.1'))
)
assert dummy.message == 'must be at least %(minvalue)f'
assert dummy.message == 'must be at least %(minvalue)s'
assert dummy.called() is True
dummy.reset()
def test_rule_maxvalue(self):
"""
Test the `ipalib.parameters.Float._rule_maxvalue` method.
Test the `ipalib.parameters.Decimal._rule_maxvalue` method.
"""
o = self.cls('my_number', maxvalue=4.7)
assert o.maxvalue == 4.7
o = self.cls('my_number', maxvalue='4.7')
assert o.maxvalue == Decimal('4.7')
rule = o._rule_maxvalue
translation = u'maxvalue=%(maxvalue)r'
dummy = dummy_ugettext(translation)
assert dummy.translation is translation
# Test with passing values:
for value in (-1.0, 0.1, 4.2):
for value in (Decimal('-1.0'), Decimal('0.1'), Decimal('4.2')):
assert rule(dummy, value) is None
assert dummy.called() is False
# Test with failing values:
for value in (5.3, 99.9):
for value in (Decimal('5.3'), Decimal('99.9')):
assert_equal(
rule(dummy, value),
translation % dict(maxvalue=4.7)
translation % dict(maxvalue=Decimal('4.7'))
)
assert dummy.message == 'can be at most %(maxvalue)f'
assert dummy.message == 'can be at most %(maxvalue)s'
assert dummy.called() is True
dummy.reset()

View File

@ -598,7 +598,7 @@ class test_dns(Declarative):
'idnsname': [dnszone1],
'mxrecord': [u"0 %s" % dnszone1_mname],
'nsrecord': [dnszone1_mname],
'locrecord': [u"49 11 42.4 N 16 36 29.6 E 227.64"],
'locrecord': [u"49 11 42.400 N 16 36 29.600 E 227.64"],
},
},
),