diff options
-rw-r--r-- | API.txt | 36 | ||||
-rw-r--r-- | doc/guide/guide.org | 8 | ||||
-rw-r--r-- | ipalib/__init__.py | 2 | ||||
-rw-r--r-- | ipalib/encoder.py | 6 | ||||
-rw-r--r-- | ipalib/parameters.py | 87 | ||||
-rw-r--r-- | ipalib/plugins/dns.py | 51 | ||||
-rw-r--r-- | ipalib/rpc.py | 4 | ||||
-rw-r--r-- | ipaserver/rpcserver.py | 3 | ||||
-rwxr-xr-x | make-lint | 2 | ||||
-rw-r--r-- | tests/test_ipalib/test_parameters.py | 47 | ||||
-rw-r--r-- | tests/test_xmlrpc/test_dns_plugin.py | 2 |
11 files changed, 160 insertions, 88 deletions
@@ -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) diff --git a/doc/guide/guide.org b/doc/guide/guide.org index 68858166e..bca7b34fa 100644 --- a/doc/guide/guide.org +++ b/doc/guide/guide.org @@ -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) diff --git a/ipalib/__init__.py b/ipalib/__init__.py index 29ba0bb90..1efeeab4a 100644 --- a/ipalib/__init__.py +++ b/ipalib/__init__.py @@ -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 diff --git a/ipalib/encoder.py b/ipalib/encoder.py index f23e5659e..8d59bd316 100644 --- a/ipalib/encoder.py +++ b/ipalib/encoder.py @@ -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] diff --git a/ipalib/parameters.py b/ipalib/parameters.py index be210864f..d918a5737 100644 --- a/ipalib/parameters.py +++ b/ipalib/parameters.py @@ -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) - - if (self.minvalue > self.maxvalue) and (self.minvalue is not None and self.maxvalue is not None): + 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 + + 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, diff --git a/ipalib/plugins/dns.py b/ipalib/plugins/dns.py index 8042c3a1b..abb2e90b8 100644 --- a/ipalib/plugins/dns.py +++ b/ipalib/plugins/dns.py @@ -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, ), ) diff --git a/ipalib/rpc.py b/ipalib/rpc.py index 8ec3a2f27..5a59ae654 100644 --- a/ipalib/rpc.py +++ b/ipalib/rpc.py @@ -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 diff --git a/ipaserver/rpcserver.py b/ipaserver/rpcserver.py index 26850db55..955c11b7f 100644 --- a/ipaserver/rpcserver.py +++ b/ipaserver/rpcserver.py @@ -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'], diff --git a/tests/test_ipalib/test_parameters.py b/tests/test_ipalib/test_parameters.py index 5cb7abf2a..ad8d84044 100644 --- a/tests/test_ipalib/test_parameters.py +++ b/tests/test_ipalib/test_parameters.py @@ -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() diff --git a/tests/test_xmlrpc/test_dns_plugin.py b/tests/test_xmlrpc/test_dns_plugin.py index 00d4f9b7d..bded2ad42 100644 --- a/tests/test_xmlrpc/test_dns_plugin.py +++ b/tests/test_xmlrpc/test_dns_plugin.py @@ -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"], }, }, ), |