-
Notifications
You must be signed in to change notification settings - Fork 153
/
number.py
112 lines (85 loc) · 3.34 KB
/
number.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
#!/usr/bin/env python
# -*- coding: utf8 -*-
try:
from cdecimal import Decimal, InvalidOperation
except ImportError: # pragma: no cover
from decimal import Decimal, InvalidOperation
import warnings
from babel.core import Locale
import six
from agate.data_types.base import DataType
from agate.exceptions import CastError
#: A list of currency symbols sourced from `Xe <http://www.xe.com/symbols.php>`_.
DEFAULT_CURRENCY_SYMBOLS = [u'؋', u'$', u'ƒ', u'៛', u'¥', u'₡', u'₱', u'£', u'€', u'¢', u'﷼', u'₪', u'₩', u'₭', u'₮', u'₦', u'฿', u'₤', u'₫']
POSITIVE = Decimal('1')
NEGATIVE = Decimal('-1')
class Number(DataType):
"""
Data representing numbers.
:param locale:
A locale specification such as :code:`en_US` or :code:`de_DE` to use
for parsing formatted numbers.
:param group_symbol:
A grouping symbol used in the numbers. Overrides the value provided by
the specified :code:`locale`.
:param decimal_symbol:
A decimal separate symbol used in the numbers. Overrides the value
provided by the specified :code:`locale`.
:param currency_symbols:
A sequence of currency symbols to strip from numbers.
"""
def __init__(self, locale='en_US', group_symbol=None, decimal_symbol=None, currency_symbols=DEFAULT_CURRENCY_SYMBOLS, **kwargs):
super(Number, self).__init__(**kwargs)
self.locale = Locale.parse(locale)
self.currency_symbols = currency_symbols
# Suppress Babel warning on Python 3.6
# See #665
with warnings.catch_warnings():
warnings.simplefilter("ignore")
self.group_symbol = group_symbol or self.locale.number_symbols.get('group', ',')
self.decimal_symbol = decimal_symbol or self.locale.number_symbols.get('decimal', '.')
def cast(self, d):
"""
Cast a single value to a :class:`decimal.Decimal`.
:returns:
:class:`decimal.Decimal` or :code:`None`.
"""
if isinstance(d, Decimal) or d is None:
return d
t = type(d)
if t is int:
return Decimal(d)
elif six.PY2 and t is long:
return Decimal(d)
elif t is float:
return Decimal(repr(d))
elif d is False:
return Decimal(0)
elif d is True:
return Decimal(1)
elif not isinstance(d, six.string_types):
raise CastError('Can not parse value "%s" as Decimal.' % d)
d = d.strip()
if d.lower() in self.null_values:
return None
d = d.strip('%')
if len(d) > 0 and d[0] == '-':
d = d[1:]
sign = NEGATIVE
else:
sign = POSITIVE
for symbol in self.currency_symbols:
d = d.strip(symbol)
d = d.replace(self.group_symbol, '')
d = d.replace(self.decimal_symbol, '.')
try:
return Decimal(d) * sign
# The Decimal class will return an InvalidOperation exception on most Python implementations,
# but PyPy3 may return a ValueError if the string is not translatable to ASCII
except (InvalidOperation, ValueError):
pass
raise CastError('Can not parse value "%s" as Decimal.' % d)
def jsonify(self, d):
if d is None:
return d
return float(d)