mirror of
https://salsa.debian.org/freeipa-team/freeipa.git
synced 2025-02-25 18:55:28 -06:00
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:
parent
d906fa50c1
commit
092dd8db12
36
API.txt
36
API.txt
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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]
|
||||
|
@ -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,
|
||||
|
@ -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,
|
||||
),
|
||||
)
|
||||
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
|
||||
|
@ -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'],
|
||||
|
@ -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()
|
||||
|
||||
|
@ -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"],
|
||||
},
|
||||
},
|
||||
),
|
||||
|
Loading…
Reference in New Issue
Block a user