use decimal.Decimal instead of float to store amounts of money

This commit is contained in:
Romain Bignon 2012-03-29 16:14:32 +02:00
commit b157e92d5b
28 changed files with 111 additions and 69 deletions

View file

@ -21,6 +21,7 @@
# python2.5 compatibility
from __future__ import with_statement
from decimal import Decimal
from datetime import datetime, timedelta
from weboob.capabilities.bank import ICapBank, AccountNotFound, Account, Recipient
@ -108,7 +109,7 @@ class BNPorcBackend(BaseBackend, ICapBank, ICapMessages):
try:
assert account.isdigit()
assert to.isdigit()
amount = float(amount)
amount = Decimal(amount)
except (AssertionError, ValueError):
raise AccountNotFound()

View file

@ -18,6 +18,8 @@
# along with weboob. If not, see <http://www.gnu.org/licenses/>.
from decimal import Decimal
from weboob.capabilities.bank import Account
from weboob.capabilities.base import NotAvailable
from weboob.tools.browser import BasePage, BrokenPageError
@ -68,7 +70,7 @@ class AccountsList(BasePage):
return account
def _parse_amount(self, elem):
return float(elem.text.replace('.', '').replace(',', '.').strip(u' \t\u20ac\xa0\n\r'))
return Decimal(elem.text.replace('.', '').replace(',', '.').strip(u' \t\u20ac\xa0\n\r'))
def get_list(self):
l = []

View file

@ -19,6 +19,7 @@
# along with weboob. If not, see <http://www.gnu.org/licenses/>.
from decimal import Decimal
from datetime import date
from weboob.tools.browser import BasePage
@ -55,9 +56,9 @@ class AccountHistory(BasePage):
amount = tds[4].text
amount = amount.strip(u' \n\t\x80').replace(' ', '').replace(',', '.')
# if we don't have exactly one '.', this is not a floatm try the next
# if we don't have exactly one '.', this is not a Decimal try the next
operation = Transaction(len(self.operations))
operation.amount = float(amount)
operation.amount = Decimal(amount)
operation.date = d
operation.label = label

View file

@ -19,6 +19,8 @@
# along with weboob. If not, see <http://www.gnu.org/licenses/>.
from decimal import Decimal
from weboob.capabilities.bank import Account
from weboob.tools.browser import BasePage
@ -55,9 +57,9 @@ class AccountsList(BasePage):
balance = span.text
balance = balance.strip(u' \n\t€+').replace(',', '.').replace(' ', '')
if balance != "":
account.balance = float(balance)
account.balance = Decimal(balance)
else:
account.balance = 0.0
account.balance = Decimal(0.0)
else:
# because of some weird useless <tr>

View file

@ -18,6 +18,7 @@
# along with weboob. If not, see <http://www.gnu.org/licenses/>.
from decimal import Decimal
from datetime import date
import re
@ -56,9 +57,9 @@ class AccountHistory(BasePage):
amount = t.text
amount = ''.join(amount.replace('.', '').replace(',', '.').split())
if amount[0] == "-":
operation.amount = -float(amount[1:])
operation.amount = - Decimal(amount[1:])
else:
operation.amount = float(amount)
operation.amount = Decimal(amount)
operations.append(operation)
return operations

View file

@ -18,8 +18,9 @@
# along with weboob. If not, see <http://www.gnu.org/licenses/>.
from weboob.capabilities.bank import Account, AccountNotFound
from decimal import Decimal
from weboob.capabilities.bank import Account, AccountNotFound
from weboob.tools.browser import BasePage
@ -59,7 +60,7 @@ class AccountList(BasePage):
tmp_balance = tmp[0].text
account.id = tmp_id
account.balance = float(''.join(tmp_balance.replace('.','').replace(',','.').split()))
account.balance = Decimal(''.join(tmp_balance.replace('.','').replace(',','.').split()))
self.account_list.append(account)
def get_account(self, id):

View file

@ -28,6 +28,7 @@ from weboob.tools.browser import BrowserIncorrectPassword, BrokenPageError
from re import match, compile, sub
from httplib import HTTPSConnection
from urllib import urlencode
from decimal import Decimal
from lxml import etree
from datetime import date
@ -169,13 +170,13 @@ class CmbBackend(BaseBackend, ICapBank):
balance = td[1].text
balance = balance.replace(',', '.').replace(u"\xa0", '')
account.balance = float(balance)
account.balance = Decimal(balance)
span = td[3].xpath('a/span')
if len(span):
coming = span[0].text.replace(' ', '').replace(',', '.')
coming = coming.replace(u"\xa0", '')
account.coming = float(coming)
account.coming = Decimal(coming)
else:
account.coming = NotAvailable
@ -262,10 +263,10 @@ class CmbBackend(BaseBackend, ICapBank):
if amount.count(',') != 1:
amount = td[4].text
amount = amount.replace(',', '.').replace(u'\xa0', '')
operation.amount = float(amount)
operation.amount = Decimal(amount)
else:
amount = amount.replace(',', '.').replace(u'\xa0', '')
operation.amount = - float(amount)
operation.amount = - Decimal(amount)
i += 1
yield operation

View file

@ -18,6 +18,7 @@
# along with weboob. If not, see <http://www.gnu.org/licenses/>.
from decimal import Decimal
import re
from datetime import date
from weboob.capabilities.bank import Account
@ -27,13 +28,13 @@ from weboob.capabilities.bank import Transaction
def clean_amount(amount):
"""
Removes weird characters and converts to a float
Removes weird characters and converts to a Decimal
>>> clean_amount(u'1 000,00 $')
1000.0
"""
data = amount.replace(',', '.').replace(' ', '').replace(u'\xa0', '')
matches = re.findall('^(-?[0-9]+\.[0-9]{2}).*$', data)
return float(matches[0]) if (matches) else 0.0
return Decimal(matches[0]) if (matches) else Decimal(0.0)
class AccountsList(CragrBasePage):

View file

@ -19,6 +19,8 @@
from __future__ import with_statement
from decimal import Decimal
from weboob.capabilities.bank import ICapBank, AccountNotFound, Recipient, Account
from weboob.tools.backend import BaseBackend, BackendConfig
from weboob.tools.value import ValueBackendPassword
@ -78,7 +80,7 @@ class CreditMutuelBackend(BaseBackend, ICapBank):
try:
assert account.isdigit()
assert to.isdigit()
amount = float(amount)
amount = Decimal(amount)
except (AssertionError, ValueError):
raise AccountNotFound()

View file

@ -18,6 +18,7 @@
# along with weboob. If not, see <http://www.gnu.org/licenses/>.
from decimal import Decimal
import re
from weboob.tools.browser import BasePage
@ -70,7 +71,7 @@ class AccountsPage(BasePage):
balance += c
if c == ',':
balance += '.'
account.balance = float(balance)
account.balance = Decimal(balance)
l.append(account)
#raise NotImplementedError()
return l
@ -135,7 +136,7 @@ class OperationsPage(BasePage):
balance += c
if c == ',':
balance += '.'
operation.amount = float(balance)
operation.amount = Decimal(balance)
yield operation
def next_page_url(self):

View file

@ -18,9 +18,11 @@
# along with weboob. If not, see <http://www.gnu.org/licenses/>.
from datetime import datetime, date, time
from decimal import Decimal
from weboob.tools.browser import BasePage
from weboob.capabilities.bill import Detail, Bill
from datetime import datetime, date, time
__all__ = ['HistoryPage', 'DetailsPage']
@ -30,9 +32,9 @@ def convert_price(div):
try:
price = div.find('div[@class="horsForfait"]/p/span').text
price = price.encode('utf-8', 'replace').replace('', '').replace(',', '.')
return float(price)
return Decimal(price)
except:
return 0.
return Decimal(0)
class DetailsPage(BasePage):
@ -118,9 +120,9 @@ class HistoryPage(BasePage):
detail.datetime = datetime.combine(mydate, mytime)
detail.label = tds[1].text.lstrip().rstrip() + " " + tds[2].text.lstrip().rstrip() + " " + tds[3].text.lstrip().rstrip()
try:
detail.price = float(tds[4].text[0:4].replace(',', '.'))
detail.price = Decimal(tds[4].text[0:4].replace(',', '.'))
except:
detail.price = 0.
detail.price = Decimal(0.0)
self.calls.append(detail)

View file

@ -18,6 +18,7 @@
# along with weboob. If not, see <http://www.gnu.org/licenses/>.
from decimal import Decimal
import re
from weboob.tools.browser import BasePage
@ -49,7 +50,7 @@ class AccountsListPage(BasePage):
tag = tds[2].find('font')
if tag is None:
tag = tds[2]
account.balance = float(tag.text.replace('.','').replace(',','.').replace(' ', '').strip(u' \t\u20ac\xa0\n\r'))
account.balance = Decimal(tag.text.replace('.','').replace(',','.').replace(' ', '').strip(u' \t\u20ac\xa0\n\r'))
account.coming = NotAvailable
yield account

View file

@ -18,6 +18,7 @@
# along with weboob. If not, see <http://www.gnu.org/licenses/>.
from decimal import Decimal
from datetime import date
from weboob.tools.browser import BasePage
@ -52,7 +53,7 @@ class AccountHistoryCC(BasePage):
amount = texte[5].replace('\t', '').strip().replace(u'', '').\
replace(',', '.').replace(u'\xa0', u'')
op.amount = float(amount)
op.amount = Decimal(amount)
self.transactions.append(op)
i += 1
@ -81,7 +82,7 @@ class AccountHistoryLA(BasePage):
amount = texte[length - 1].replace('\t', '').strip().\
replace('.', '').replace(u'', '').\
replace(',', '.').replace(u'\xa0', u'')
op.amount = float(amount)
op.amount = Decimal(amount)
self.transactions.append(op)
i += 1

View file

@ -18,6 +18,7 @@
# along with weboob. If not, see <http://www.gnu.org/licenses/>.
from decimal import Decimal
import re
from weboob.capabilities.bank import Account
@ -46,8 +47,8 @@ class AccountsList(BasePage):
linkbis = self.document.xpath(urltofind).pop()
if linkbis.text == link.text:
linkbis = self.document.xpath(urltofind)[1]
account.balance = float(linkbis.text.replace('.', '').\
replace(' ', '').replace(',', '.'))
account.balance = Decimal(linkbis.text.replace('.', '').\
replace(' ', '').replace(',', '.'))
account.coming = NotAvailable
l.append(account)

View file

@ -19,6 +19,8 @@
import base64
from datetime import date
from decimal import Decimal
from weboob.capabilities.bank import Transaction
from weboob.capabilities.bank import Account
from weboob.tools.browser import BasePage, BrowserUnavailable
@ -149,7 +151,7 @@ class AccountsPage(BasePage):
balance=a.text.replace(u"\u00A0",'').replace(' ','').replace('.','').replace('+','').replace(',','.')
if '-' in balance:
balance='-'+balance.strip().replace('-', '')
account.balance=float(balance)
account.balance=Decimal(balance)
l.append(account)
return l
@ -200,7 +202,7 @@ class AccountHistoryPage(BasePage):
mntColumn+=1
amount=u''.join([txt.strip() for txt in td.itertext()])
if amount != "":
amount = float(amount.replace('.','').replace(',','.').replace(u"\u00A0",'').replace(' ',''))
amount = Decimal(amount.replace('.','').replace(',','.').replace(u"\u00A0",'').replace(' ',''))
if value.startswith("soldeDeb") or mntColumn==1:
amount=-amount
operation.amount=amount

View file

@ -19,6 +19,7 @@
import re
from decimal import Decimal
from dateutil.parser import parse as parse_date
from weboob.tools.browser import BasePage
@ -53,9 +54,9 @@ class SearchResultsPage(BasePage):
housing.title = a.text.strip()
m = re.match('(\w+) (.+) (\d+)\xa0m\xb2 (.*)', housing.title)
if m:
housing.area = float(m.group(3))
housing.area = Decimal(m.group(3))
housing.cost = float(div.cssselect('td.prix')[0].text.strip(u' \t\u20ac\xa0\n\r').replace('.', '').replace(',', '.'))
housing.cost = Decimal(div.cssselect('td.prix')[0].text.strip(u' \t\u20ac\xa0\n\r').replace('.', '').replace(',', '.'))
housing.currency = u''
m = self.DATE_RE.match(div.cssselect('p.date-publication')[0].text.strip())
@ -90,12 +91,12 @@ class HousingPage(BasePage):
parts = div.find('h1').text.split(' - ')
housing.title = parts[0].strip()
housing.cost = float(parts[1].strip(u' \t\u20ac\xa0\n\r').replace('.', '').replace(',', '.'))
housing.cost = Decimal(parts[1].strip(u' \t\u20ac\xa0\n\r').replace('.', '').replace(',', '.'))
housing.currency = u''
m = re.match('(\w+) (.+) (\d+)\xa0m\xb2 (.*)', housing.title)
if m:
housing.area = float(m.group(3))
housing.area = Decimal(m.group(3))
housing.date = housing.station = housing.location = housing.phone = NotAvailable

View file

@ -18,6 +18,7 @@
# along with weboob. If not, see <http://www.gnu.org/licenses/>.
from decimal import Decimal
import re
from weboob.tools.browser import BasePage
@ -59,7 +60,7 @@ class ComparisonResultsPage(BasePage):
price.product = product
tds = tr.findall('td')
price.cost = float(tds[4].text.replace(',', '.'))
price.cost = Decimal(tds[4].text.replace(',', '.'))
price.currency = u''
shop = Shop(price.id)

View file

@ -18,6 +18,7 @@
# along with weboob. If not, see <http://www.gnu.org/licenses/>.
from decimal import Decimal
from dateutil.parser import parse as parse_date
from weboob.tools.browser import BasePage
@ -41,9 +42,9 @@ class SearchResultsPage(BasePage):
housing = Housing(a.find('idannonce').text)
housing.title = a.find('titre').text
housing.date = parse_date(a.find('dtfraicheur').text)
housing.cost = float(a.find('prix').text)
housing.cost = Decimal(a.find('prix').text)
housing.currency = u''
housing.area = float(a.find('surface').text)
housing.area = Decimal(a.find('surface').text)
housing.text = a.find('descriptif').text.strip()
housing.location = a.find('ville').text
try:
@ -69,10 +70,10 @@ class HousingPage(BasePage):
housing.title = details.find('titre').text
housing.text = details.find('descriptif').text.strip()
housing.cost = float(details.find('prix').text)
housing.cost = Decimal(details.find('prix').text)
housing.currency = u''
housing.date = parse_date(details.find('dtfraicheur').text)
housing.area = float(details.find('surface').text)
housing.area = Decimal(details.find('surface').text)
housing.phone = details.find('contact').find('telephone').text
try:

View file

@ -18,6 +18,7 @@
# along with weboob. If not, see <http://www.gnu.org/licenses/>.
from decimal import Decimal
import re
from weboob.capabilities.bank import Account
@ -52,9 +53,9 @@ class AccountsList(BasePage):
balance = td.find('div').text
if balance != None:
balance = balance.replace(u'\xa0','').replace(',','.')
account.balance = float(balance)
account.balance = Decimal(balance)
else:
account.balance = 0.0
account.balance = Decimal(0.0)
l.append(account)

View file

@ -18,6 +18,7 @@
# along with weboob. If not, see <http://www.gnu.org/licenses/>.
from decimal import Decimal
import sys
from weboob.capabilities.bank import ICapBank, Account, Transaction
@ -123,8 +124,8 @@ class AccountListFormatter(IFormatter):
MANDATORY_FIELDS = ('id', 'label', 'balance', 'coming')
count = 0
tot_balance = 0.0
tot_coming = 0.0
tot_balance = Decimal(0)
tot_coming = Decimal(0)
def flush(self):
if self.count < 1:
@ -134,8 +135,8 @@ class AccountListFormatter(IFormatter):
result += u'%s Total %8s %8s' % ((' ' * 15) if not self.interactive else '',
'%.2f' % self.tot_balance, '%.2f' % self.tot_coming)
self.after_format(result)
self.tot_balance = 0.0
self.tot_coming = 0.0
self.tot_balance = Decimal(0)
self.tot_coming = Decimal(0)
self.count = 0
def format_dict(self, item):
@ -152,7 +153,7 @@ class AccountListFormatter(IFormatter):
result += '------------------------------------------%s+----------+----------\n' % (('-' * 15) if not self.interactive else '')
result += (u' %s%-' + (u'15' if self.interactive else '30') + u's%s %-25s %8s %8s') % \
(self.BOLD, id, self.NC,
item['label'], '%.2f' % item['balance'], '%.2f' % (item['coming'] or 0.0))
item['label'], '%.2f' % item['balance'], '%.2f' % (item['coming'] or Decimal(0.0)))
self.tot_balance += item['balance']
if item['coming']:
@ -281,7 +282,7 @@ class Boobank(ReplApplication):
id_to, backend_name_to = self.parse_id(id_to)
try:
amount = float(amount)
amount = Decimal(amount)
except (TypeError, ValueError):
print >>sys.stderr, 'Error: please give a decimal amount to transfer'
return 2

View file

@ -20,6 +20,8 @@
from PyQt4.QtGui import QListWidgetItem, QImage, QPixmap, QLabel, QIcon, QBrush, QColor
from PyQt4.QtCore import SIGNAL, Qt
from decimal import Decimal
from weboob.tools.application.qt import QtMainWindow, QtDo, HTMLDelegate
from weboob.tools.application.qt.backendcfg import BackendCfg
from weboob.capabilities.housing import ICapHousing, Query, City
@ -35,8 +37,8 @@ class HousingListWidgetItem(QListWidgetItem):
self.read = True
def __lt__(self, other):
return '%s%s' % (self.read, float(self.housing.cost or 0) / float(self.housing.area or 1)) < \
'%s%s' % (other.read, float(other.housing.cost or 0) / float(other.housing.area or 1))
return '%s%s' % (self.read, Decimal(self.housing.cost or 0) / Decimal(self.housing.area or 1)) < \
'%s%s' % (other.read, Decimal(other.housing.cost or 0) / Decimal(other.housing.area or 1))
def setAttrs(self, storage):
text = u'<h2>%s</h2>' % self.housing.title

View file

@ -20,7 +20,7 @@
from datetime import date, datetime
from .base import CapBaseObject, Field, StringField, DateField, FloatField, IntField
from .base import CapBaseObject, Field, StringField, DateField, DecimalField, IntField
from .collection import ICapCollection
@ -66,8 +66,8 @@ class Account(Recipient):
TYPE_JOINT = 6 # Joint account
type = IntField('Type of account', default=TYPE_UNKNOWN)
balance = FloatField('Balance on this bank account')
coming = FloatField('Coming balance')
balance = DecimalField('Balance on this bank account')
coming = DecimalField('Coming balance')
def __repr__(self):
return u"<Account id=%r label=%r>" % (self.id, self.label)
@ -94,7 +94,7 @@ class Transaction(CapBaseObject):
raw = StringField('Raw label of the transaction')
category = StringField('Category of transaction')
label = StringField('Pretty label')
amount = FloatField('Amount of transaction')
amount = DecimalField('Amount of transaction')
def __repr__(self):
return "<Transaction date='%s' label='%s' amount=%s>" % (self.date,
@ -105,7 +105,7 @@ class Transfer(CapBaseObject):
Transfer from an account to a recipient.
"""
amount = FloatField('Amount to transfer')
amount = DecimalField('Amount to transfer')
date = Field('Date of transfer', basestring, date, datetime)
origin = Field('Origin of transfer', int, long, basestring)
recipient = Field('Recipient', int, long, basestring)
@ -193,7 +193,7 @@ class ICapBank(ICapCollection):
:param recipient: account to send money
:type recipient: :class:`Recipient`
:param amount: amount
:type amount: :class:`float`
:type amount: :class:`decimal.Decimal`
:param reason: reason of transfer
:type reason: :class:`unicode`
:rtype: :class:`Transfer`

View file

@ -20,6 +20,7 @@
import warnings
import datetime
from decimal import Decimal
from copy import deepcopy
from weboob.tools.misc import to_unicode
@ -27,8 +28,8 @@ from weboob.tools.ordereddict import OrderedDict
__all__ = ['FieldNotFound', 'NotAvailable', 'NotLoaded', 'IBaseCap',
'Field', 'IntField', 'FloatField', 'StringField', 'BytesField',
'DateField', 'DeltaField', 'CapBaseObject', 'empty']
'Field', 'IntField', 'DecimalField', 'FloatField', 'StringField',
'BytesField', 'DateField', 'DeltaField', 'CapBaseObject', 'empty']
def empty(value):
@ -154,6 +155,18 @@ class IntField(Field):
def convert(self, value):
return int(value)
class DecimalField(Field):
"""
A field which accepts only :class:`decimal` type.
"""
def __init__(self, doc, **kwargs):
Field.__init__(self, doc, Decimal, **kwargs)
def convert(self, value):
if isinstance(value, Decimal):
return value
return Decimal(value)
class FloatField(Field):
"""
A field which accepts only :class:`float` type.
@ -244,7 +257,7 @@ class CapBaseObject(object):
class Transfer(CapBaseObject):
" Transfer from an account to a recipient. "
amount = FloatField('Amount to transfer')
amount = DecimalField('Amount to transfer')
date = Field('Date of transfer', basestring, date, datetime)
origin = Field('Origin of transfer', int, long, basestring)
recipient = Field('Recipient', int, long, basestring)

View file

@ -17,7 +17,7 @@
# You should have received a copy of the GNU Affero General Public License
# along with weboob. If not, see <http://www.gnu.org/licenses/>.
from .base import CapBaseObject, StringField, DateField, FloatField
from .base import CapBaseObject, StringField, DateField, DecimalField
from .collection import ICapCollection
@ -47,7 +47,7 @@ class Detail(CapBaseObject):
label = StringField('label of the detail line')
infos = StringField('information')
datetime = DateField('date information')
price = FloatField('price')
price = DecimalField('price')
def __init__(self):
CapBaseObject.__init__(self, 0)

View file

@ -18,7 +18,7 @@
# along with weboob. If not, see <http://www.gnu.org/licenses/>.
from .base import IBaseCap, CapBaseObject, Field, IntField, FloatField, \
from .base import IBaseCap, CapBaseObject, Field, IntField, DecimalField, \
StringField, BytesField, DateField
@ -50,8 +50,8 @@ class Housing(CapBaseObject):
Content of a housing.
"""
title = StringField('Title of housing')
area = FloatField('Area of housing, in m2')
cost = FloatField('Cost of housing')
area = DecimalField('Area of housing, in m2')
cost = DecimalField('Cost of housing')
currency = StringField('Currency of cost')
date = DateField('Date when the housing has been published')
location = StringField('Location of housing')

View file

@ -18,7 +18,7 @@
# along with weboob. If not, see <http://www.gnu.org/licenses/>.
from .base import IBaseCap, CapBaseObject, Field, FloatField, \
from .base import IBaseCap, CapBaseObject, Field, DecimalField, \
StringField, DateField
@ -44,7 +44,7 @@ class Price(CapBaseObject):
Price.
"""
date = DateField('Date when this price has been published')
cost = FloatField('Cost of the product in this shop')
cost = DecimalField('Cost of the product in this shop')
currency = StringField('Currency of the price')
message = StringField('Message related to this price')
shop = Field('Shop information', Shop)

View file

@ -18,6 +18,7 @@
# along with weboob. If not, see <http://www.gnu.org/licenses/>.
from decimal import Decimal
import re
import datetime
@ -53,9 +54,9 @@ class FrenchTransaction(Transaction):
debit = self.clean_amount(debit)
if len(debit) > 0:
self.amount = - float(debit)
self.amount = - Decimal(debit)
else:
self.amount = float(credit)
self.amount = Decimal(credit)
def parse(self, date, raw):
"""

View file

@ -21,6 +21,7 @@
from __future__ import with_statement
from ConfigParser import RawConfigParser, DEFAULTSECT
from decimal import Decimal
import logging
import os
@ -70,7 +71,7 @@ class INIConfig(IConfig):
def save(self):
def save_section(values, root_section=self.ROOTSECT):
for k, v in values.iteritems():
if isinstance(v, (int, float, basestring)):
if isinstance(v, (int, Decimal, float, basestring)):
if not self.config.has_section(root_section):
self.config.add_section(root_section)
self.config.set(root_section, k, unicode(v))