diff options
Diffstat (limited to 'ipalib')
-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 |
5 files changed, 109 insertions, 41 deletions
diff --git a/ipalib/__init__.py b/ipalib/__init__.py index 29ba0bb9..1efeeab4 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 f23e5659..8d59bd31 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 be210864..d918a573 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 8042c3a1..abb2e90b 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 8ec3a2f2..5a59ae65 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 |