summaryrefslogtreecommitdiffstats
path: root/ipalib
diff options
context:
space:
mode:
Diffstat (limited to 'ipalib')
-rw-r--r--ipalib/__init__.py2
-rw-r--r--ipalib/encoder.py6
-rw-r--r--ipalib/parameters.py87
-rw-r--r--ipalib/plugins/dns.py51
-rw-r--r--ipalib/rpc.py4
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