diff --git a/modules/barclays/browser.py b/modules/barclays/browser.py index 70f58f96148bab86c4cc5c7fdbc7ee2f58ffa4d0..c9ed8486e4c30b99f6b7f2165c2d4da46554559d 100644 --- a/modules/barclays/browser.py +++ b/modules/barclays/browser.py @@ -17,6 +17,7 @@ # You should have received a copy of the GNU Lesser General Public License # along with this weboob module. If not, see . +# flake8: compatible from __future__ import unicode_literals @@ -41,16 +42,16 @@ class Barclays(LoginBrowser): logout = URL('https://www.milleis.fr/deconnexion') milleis_ajax = URL('/BconnectDesk/ajaxservletcontroller') - login = URL('/BconnectDesk/servletcontroller', LoginPage) - accounts = URL('/BconnectDesk/servletcontroller', AccountsPage) - loan_account = URL('/BconnectDesk/servletcontroller', LoanAccountPage) - account = URL('/BconnectDesk/servletcontroller', AccountPage) - card_account = URL('/BconnectDesk/servletcontroller', CardPage) - market_account = URL('/BconnectDesk/servletcontroller', MarketAccountPage) + login = URL('/BconnectDesk/servletcontroller', LoginPage) + accounts = URL('/BconnectDesk/servletcontroller', AccountsPage) + loan_account = URL('/BconnectDesk/servletcontroller', LoanAccountPage) + account = URL('/BconnectDesk/servletcontroller', AccountPage) + card_account = URL('/BconnectDesk/servletcontroller', CardPage) + market_account = URL('/BconnectDesk/servletcontroller', MarketAccountPage) life_insurance_account = URL('/BconnectDesk/servletcontroller', LifeInsuranceAccountPage) - revolving_account = URL('/BconnectDesk/servletcontroller', RevolvingAccountPage) - actionNeededPage = URL('/BconnectDesk/servletcontroller', ActionNeededPage) - iban = URL('/BconnectDesk/editique', IbanPDFPage) + revolving_account = URL('/BconnectDesk/servletcontroller', RevolvingAccountPage) + actionNeededPage = URL('/BconnectDesk/servletcontroller', ActionNeededPage) + iban = URL('/BconnectDesk/editique', IbanPDFPage) def __init__(self, secret, *args, **kwargs): super(Barclays, self).__init__(*args, **kwargs) @@ -72,7 +73,8 @@ def _go_to_account(self, account, refresh=False): else: if not self.accounts.is_here(): self.page.go_to_menu('Comptes et contrats') - if not self.accounts.is_here(): # Sometime we can't go out from account page, so re-login + if not self.accounts.is_here(): + # Sometime we can't go out from account page, so re-login self._relogin() self.page.go_to_account(account) @@ -93,8 +95,8 @@ def _go_to_account_space(self, space, account): 'controllername': 'servletcontroller', 'disable': 'false', 'title': 'Milleis', - token[0]: token[1] - } + token[0]: token[1], + } self.milleis_ajax.open(data=data) self._go_to_account(account, refresh=True) @@ -132,7 +134,7 @@ def iter_accounts(self): if not self.accounts.is_here(): self.page.go_to_menu('Comptes et contrats') - if not 'accounts' in self.cache: + if 'accounts' not in self.cache: accounts = list(self.page.iter_accounts()) traccounts = [] @@ -145,7 +147,10 @@ def iter_accounts(self): if account.type == Account.TYPE_CHECKING: # Only checking accounts have an IBAN self._go_to_account(account) - account.iban = self.iban.open().get_iban() if self.page.has_iban() else NotAvailable + if self.page.has_iban(): + account.iban = self.iban.open().get_iban() + else: + account.iban = NotAvailable if account.type == Account.TYPE_LOAN: self._go_to_account(account) @@ -159,7 +164,9 @@ def iter_accounts(self): if not self.page.has_history(): continue - account._attached_account = self.page.do_account_attachment([a for a in accounts if a.type == Account.TYPE_CHECKING]) + account._attached_account = self.page.do_account_attachment([ + a for a in accounts if a.type == Account.TYPE_CHECKING + ]) if account.type == Account.TYPE_REVOLVING_CREDIT: self._go_to_account(account) @@ -174,8 +181,10 @@ def iter_accounts(self): # is not specified, therefore to avoid transaction duplicates, # we only return transactions from the 'EUR' twin account. for account in self.cache['accounts']: - if (account.id.replace(account.currency, '') in - [acc.id.replace(acc.currency, '') for acc in self.cache['accounts'] if acc.id != account.id]): + accounts_id_without_currency = [ + acc.id.replace(acc.currency, '') for acc in self.cache['accounts'] if acc.id != account.id + ] + if account.id.replace(account.currency, '') in accounts_id_without_currency: account._twin = True else: account._twin = False @@ -208,7 +217,9 @@ def iter_history(self, account): history_page = self.page if account.type != Account.TYPE_LIFE_INSURANCE: - for _ in range(100): # on new history page they take previous results too, so go to the last page before starts recover history + for _ in range(100): + # on new history page they take previous results too, + # so go to the last page before starts recover history form = history_page.form_to_history_page() if not form: @@ -216,13 +227,14 @@ def iter_history(self, account): try: history_page = self.account.open(data=form) - except ConnectionError: # Sometime accounts have too much history and website crash + except ConnectionError: + # Sometime accounts have too much history and website crash # Need to relogin self._relogin() break else: - assert False, "Too many iterations" + raise AssertionError('Too many iterations') if history_page.has_history(): return list(history_page.iter_history()) diff --git a/modules/barclays/module.py b/modules/barclays/module.py index ea366137f440f2f8e71f1c114fc9767fea5aa0b9..d9c9585013bc8e959ff1ae40e2ba7fd55dde40b4 100644 --- a/modules/barclays/module.py +++ b/modules/barclays/module.py @@ -17,14 +17,13 @@ # You should have received a copy of the GNU Lesser General Public License # along with this weboob module. If not, see . +# flake8: compatible from __future__ import unicode_literals -from weboob.capabilities.bank import AccountNotFound from weboob.capabilities.wealth import CapBankWealth from weboob.tools.backend import Module, BackendConfig from weboob.tools.value import ValueBackendPassword -from weboob.capabilities.base import find_object from .browser import Barclays @@ -34,27 +33,28 @@ class BarclaysModule(Module, CapBankWealth): NAME = 'barclays' - MAINTAINER = u'Jean Walrave' + MAINTAINER = 'Jean Walrave' EMAIL = 'jwalrave@budget-insight.com' VERSION = '2.1' - DESCRIPTION = u'Barclays' + DESCRIPTION = 'Barclays' LICENSE = 'LGPLv3+' - CONFIG = BackendConfig(ValueBackendPassword('login', label=u"N° d'abonné", masked=False), - ValueBackendPassword('password', label='Code confidentiel'), - ValueBackendPassword('secret', label='Mot secret')) + CONFIG = BackendConfig( + ValueBackendPassword('login', label="N° d'abonné", masked=False), + ValueBackendPassword('password', label='Code confidentiel'), + ValueBackendPassword('secret', label='Mot secret'), + ) BROWSER = Barclays def create_default_browser(self): - return self.create_browser(self.config['secret'].get(), - self.config['login'].get(), - self.config['password'].get()) + return self.create_browser( + self.config['secret'].get(), + self.config['login'].get(), + self.config['password'].get(), + ) def iter_accounts(self): return self.browser.iter_accounts() - def get_account(self, _id): - return find_object(self.browser.iter_accounts(), id=_id, error=AccountNotFound) - def iter_history(self, account): return self.browser.iter_history(account) diff --git a/modules/barclays/pages.py b/modules/barclays/pages.py index 4d68e3902c10ac94f0fd5265f3907ea00725ab39..743d1eb72856e05553bac31adbae252bdb20fb9e 100644 --- a/modules/barclays/pages.py +++ b/modules/barclays/pages.py @@ -17,13 +17,18 @@ # You should have received a copy of the GNU Lesser General Public License # along with this weboob module. If not, see . +# flake8: compatible + from __future__ import unicode_literals import re from weboob.browser.pages import HTMLPage, PDFPage, LoggedPage from weboob.browser.elements import TableElement, ListElement, ItemElement, method -from weboob.browser.filters.standard import CleanText, CleanDecimal, Regexp, Field, Date, Eval +from weboob.browser.filters.standard import ( + CleanText, CleanDecimal, Regexp, Field, Date, Coalesce, + Map, Currency, +) from weboob.browser.filters.html import Attr, TableCell, ReplaceEntities from weboob.capabilities.base import NotAvailable from weboob.capabilities.bank import Account, Loan @@ -34,11 +39,6 @@ from weboob.exceptions import ActionNeeded, BrowserUnavailable -def MyDecimal(*args, **kwargs): - kwargs.update(replace_dots=True, default=NotAvailable) - return CleanDecimal(*args, **kwargs) - - class SecretTooShort(Exception): # secret is a word which contains at least 8 char and website ask us to enter 2 chars of it # char 3 and 4 or 6 and 7 @@ -49,9 +49,12 @@ class SecretTooShort(Exception): class StatefulPage(LoggedPage, HTMLPage): def get_form_for_menu(self, menu): - btn = Regexp(Attr('//div[@class="menuvert"]//a[contains(., "%s")]' % (menu), 'onclick'), r"\('', '(.*?)',")(self.doc) + btn = Regexp( + Attr('//div[@class="menuvert"]//a[contains(., "%s")]' % menu, 'onclick'), + r"\('', '(.*?)'," + )(self.doc) form = self.get_form(id='form1') - form['MODE'] = 'NAVMENU_' + btn + form['MODE'] = 'NAVMENU_%s' % btn return form def go_to_menu(self, menu): @@ -64,7 +67,12 @@ def go_to_account(self, account): form = self.get_form(id='form1') for attr in list(form): - if attr not in ['MENUSTATE', 'DEVICE_SIZE_INFO', 'C11__GETMODULENOTEPAD[1].IOGETMODULENOTEPAD[1].OUTPUTPARAMETER[1].TEXT', token[0]]: + if attr not in ( + 'MENUSTATE', + 'DEVICE_SIZE_INFO', + 'C11__GETMODULENOTEPAD[1].IOGETMODULENOTEPAD[1].OUTPUTPARAMETER[1].TEXT', + token[0], + ): del form[attr] form['MODE'] = account._btn @@ -74,7 +82,10 @@ def go_to_account(self, account): form.submit() def isolate_token(self): - return (Attr('(//input[@type="hidden"])[2]', 'name')(self.doc), Attr('(//input[@type="hidden"])[2]', 'value')(self.doc)) + return ( + Attr('(//input[@type="hidden"])[2]', 'name')(self.doc), + Attr('(//input[@type="hidden"])[2]', 'value')(self.doc), + ) class LoginPage(HTMLPage): @@ -101,7 +112,7 @@ def login_secret(self, secret): label = CleanText('//label[@for="C1__IdTwoLetters"]')(self.doc).strip() letters = '' - for n in re.findall('(\d+)', label): + for n in re.findall(r'(\d+)', label): if int(n) > len(secret): raise SecretTooShort() letters += secret[int(n) - 1] @@ -119,23 +130,29 @@ def get_error_message(self): return CleanText('//div[@class="bloc-message error" and not(@style)]')(self.doc) -class AccountsPage(StatefulPage): - ACCOUNT_TYPES = {'Liquidités': Account.TYPE_CHECKING, - 'Epargne': Account.TYPE_SAVINGS, - 'Titres': Account.TYPE_MARKET, - 'Engagement/Crédits': Account.TYPE_LOAN, - } - ACCOUNT_EXTRA_TYPES = {'BMOOVIE': Account.TYPE_LIFE_INSURANCE, - 'B. GESTION VIE': Account.TYPE_LIFE_INSURANCE, - 'E VIE MILLEIS': Account.TYPE_LIFE_INSURANCE, - 'BANQUE PRIVILEGE': Account.TYPE_REVOLVING_CREDIT, - 'PRET PERSONNEL': Account.TYPE_LOAN, - 'CREDIT IMMOBILIE': Account.TYPE_LOAN, - } - ACCOUNT_TYPE_TO_STR = {Account.TYPE_MARKET: 'TTR', - Account.TYPE_CARD: 'CRT' - } +ACCOUNT_TYPES = { + 'Liquidités': Account.TYPE_CHECKING, + 'Epargne': Account.TYPE_SAVINGS, + 'Titres': Account.TYPE_MARKET, + 'Engagement/Crédits': Account.TYPE_LOAN, +} + +ACCOUNT_EXTRA_TYPES = { + 'BMOOVIE': Account.TYPE_LIFE_INSURANCE, + 'B. GESTION VIE': Account.TYPE_LIFE_INSURANCE, + 'E VIE MILLEIS': Account.TYPE_LIFE_INSURANCE, + 'BANQUE PRIVILEGE': Account.TYPE_REVOLVING_CREDIT, + 'PRET PERSONNEL': Account.TYPE_LOAN, + 'CREDIT IMMOBILIE': Account.TYPE_LOAN, +} +ACCOUNT_TYPE_TO_STR = { + Account.TYPE_MARKET: 'TTR', + Account.TYPE_CARD: 'CRT', +} + + +class AccountsPage(StatefulPage): def is_here(self): return bool(self.doc.xpath('//h1[contains(., "Mes comptes")]')) @@ -149,90 +166,112 @@ class item(ItemElement): obj_label = CleanText('.//td[1]//span') obj__uncleaned_id = CleanText('.//td[2]//a') obj__btn = Attr('.//button', 'name', default=None) - obj__attached_account = NotAvailable # for card account only + obj__attached_account = NotAvailable # for card account only def obj_id(self): - return re.sub(r'\s', '', str(Field('_uncleaned_id')(self))) + self.page.ACCOUNT_TYPE_TO_STR.get(Field('type')(self), '') + return '%s%s' % ( + re.sub(r'\s', '', str(Field('_uncleaned_id')(self))), + ACCOUNT_TYPE_TO_STR.get(Field('type')(self), ''), + ) def is_card(self): - return bool(self.xpath('.//div[contains(@id, "9385968FC88E7527131931") and not(contains(@style, "display: none;"))]')) + return bool(self.xpath( + './/div[contains(@id, "9385968FC88E7527131931") and not(contains(@style, "display: none;"))]' + )) def obj_balance(self): if self.is_card(): return 0 - return MyDecimal('.//td[4]//div[1]/a')(self) + return CleanDecimal.French('.//td[4]//div[1]/a', default=NotAvailable)(self) def obj_coming(self): if self.is_card(): - return MyDecimal('.//td[4]//div[1]/a')(self) + return CleanDecimal.French('.//td[4]//div[1]/a', default=NotAvailable)(self) return NotAvailable - def obj_currency(self): - return Account.get_currency(CleanText('.//td[5]//div[1]/a')(self)) + obj_currency = Currency(CleanText('.//td[5]//div[1]/a')) def obj_type(self): if self.is_card(): return Account.TYPE_CARD - type = CleanText('./ancestor::node()[7]//button[contains(@id, "C4__BUT_787E7BC48BF75E723710")]')(self) - return self.page.ACCOUNT_EXTRA_TYPES.get(Field('label')(self)) or self.page.ACCOUNT_TYPES.get(type, Account.TYPE_UNKNOWN) + return Coalesce( + Map(Field('label'), ACCOUNT_EXTRA_TYPES, ''), + Map( + CleanText('./ancestor::node()[7]//button[contains(@id, "C4__BUT_787E7BC48BF75E723710")]'), + ACCOUNT_TYPES, + '' + ), + default=Account.TYPE_UNKNOWN + )(self) def obj__multiple_type(self): - # Sometime account can be twice declared with different types but same id, we flag them to avoid some errors + # Sometimes an account can be declared twice with different types but the same id. + # We flag them to avoid some errors for account in self.parent.objects.values(): if account._uncleaned_id == Field('_uncleaned_id')(self): if not account._multiple_type: account._multiple_type = True - return True - return False class Transaction(FrenchTransaction): PATTERNS = [ - (re.compile(r'\w+ FRAIS RET DAB '), FrenchTransaction.TYPE_BANK), - (re.compile('^RET DAB (?P.*?) RETRAIT DU (?P
\d{2})(?P\d{2})(?P\d{2}).*'), - FrenchTransaction.TYPE_WITHDRAWAL), - (re.compile('^RET DAB (?P.*?) CARTE ?:.*'), - FrenchTransaction.TYPE_WITHDRAWAL), - (re.compile('^RET DAB (?P
\d{2})/(?P\d{2})/(?P\d{2}) (?P.*?) CARTE .*'), - FrenchTransaction.TYPE_WITHDRAWAL), - (re.compile(r'(?P.*) RET DAB DU (?P
\d{2})/(?P\d{2})/(?P\d{2}) (?P.*?) CARTE .*'), - FrenchTransaction.TYPE_WITHDRAWAL), - (re.compile('^(?P.*) RETRAIT DU (?P
\d{2})(?P\d{2})(?P\d{2}) .*'), - FrenchTransaction.TYPE_WITHDRAWAL), - (re.compile('(\w+) (?P
\d{2})(?P\d{2})(?P\d{2}) CB[:\*][^ ]+ (?P.*)'), - FrenchTransaction.TYPE_CARD), - (re.compile('^(?PVIR(EMEN)?T? (SEPA)?(RECU|FAVEUR)?)( /FRM)?(?P.*)'), - FrenchTransaction.TYPE_TRANSFER), - (re.compile(r'^PRLV (?P.*) (?:REF: \w+ DE (?P.*))?$'),FrenchTransaction.TYPE_ORDER), - (re.compile(r'PRELEVEMENT (?P.*)'), FrenchTransaction.TYPE_ORDER), - (re.compile('^CHEQUE.*? (REF \w+)?$'), FrenchTransaction.TYPE_CHECK), - (re.compile('^(AGIOS /|FRAIS) (?P.*)'), FrenchTransaction.TYPE_BANK), - (re.compile('^(CONVENTION \d+ )?COTIS(ATION)? (?P.*)'), - FrenchTransaction.TYPE_BANK), - (re.compile('^REMISE (?P.*)'), FrenchTransaction.TYPE_DEPOSIT), - (re.compile('^(?P.*)( \d+)? QUITTANCE .*'), - FrenchTransaction.TYPE_ORDER), - (re.compile('^.* LE (?P
\d{2})/(?P\d{2})/(?P\d{2})$'), - FrenchTransaction.TYPE_UNKNOWN), - (re.compile('^CARTE .*'), FrenchTransaction.TYPE_CARD_SUMMARY), - (re.compile(r'CONTRIBUTIONS SOCIALES'), FrenchTransaction.TYPE_BANK), - (re.compile(r'COMMISSION INTERVENTION'), FrenchTransaction.TYPE_BANK), - (re.compile(r'INTERETS CREDITEURS'), FrenchTransaction.TYPE_BANK), - (re.compile(r'(ANNUL |ANNULATION |)FRAIS '), FrenchTransaction.TYPE_BANK), - (re.compile(r'(ANNUL |ANNULATION |)INT DEB'), FrenchTransaction.TYPE_BANK), - (re.compile(r'TAEG APPLIQUE '), FrenchTransaction.TYPE_BANK), - ] + (re.compile(r'\w+ FRAIS RET DAB '), FrenchTransaction.TYPE_BANK), + ( + re.compile(r'^RET DAB (?P.*?) RETRAIT DU (?P
\d{2})(?P\d{2})(?P\d{2}).*'), + FrenchTransaction.TYPE_WITHDRAWAL, + ), + (re.compile(r'^RET DAB (?P.*?) CARTE ?:.*'), FrenchTransaction.TYPE_WITHDRAWAL), + ( + re.compile(r'^RET DAB (?P
\d{2})/(?P\d{2})/(?P\d{2}) (?P.*?) CARTE .*'), + FrenchTransaction.TYPE_WITHDRAWAL, + ), + ( + re.compile(r'(?P.*) RET DAB DU (?P
\d{2})/(?P\d{2})/(?P\d{2}) (?P.*?) CARTE .*'), + FrenchTransaction.TYPE_WITHDRAWAL, + ), + ( + re.compile(r'^(?P.*) RETRAIT DU (?P
\d{2})(?P\d{2})(?P\d{2}) .*'), + FrenchTransaction.TYPE_WITHDRAWAL, + ), + ( + re.compile(r'(\w+) (?P
\d{2})(?P\d{2})(?P\d{2}) CB[:\*][^ ]+ (?P.*)'), + FrenchTransaction.TYPE_CARD, + ), + ( + re.compile(r'^(?PVIR(EMEN)?T? (SEPA)?(RECU|FAVEUR)?)( /FRM)?(?P.*)'), + FrenchTransaction.TYPE_TRANSFER, + ), + (re.compile(r'^PRLV (?P.*) (?:REF: \w+ DE (?P.*))?$'), FrenchTransaction.TYPE_ORDER), + (re.compile(r'PRELEVEMENT (?P.*)'), FrenchTransaction.TYPE_ORDER), + (re.compile(r'^CHEQUE.*? (REF \w+)?$'), FrenchTransaction.TYPE_CHECK), + (re.compile(r'^(AGIOS /|FRAIS) (?P.*)'), FrenchTransaction.TYPE_BANK), + (re.compile(r'^(CONVENTION \d+ )?COTIS(ATION)? (?P.*)'), FrenchTransaction.TYPE_BANK), + (re.compile(r'^REMISE (?P.*)'), FrenchTransaction.TYPE_DEPOSIT), + (re.compile(r'^(?P.*)( \d+)? QUITTANCE .*'), FrenchTransaction.TYPE_ORDER), + (re.compile(r'^.* LE (?P
\d{2})/(?P\d{2})/(?P\d{2})$'), FrenchTransaction.TYPE_UNKNOWN), + (re.compile(r'^CARTE .*'), FrenchTransaction.TYPE_CARD_SUMMARY), + (re.compile(r'CONTRIBUTIONS SOCIALES'), FrenchTransaction.TYPE_BANK), + (re.compile(r'COMMISSION INTERVENTION'), FrenchTransaction.TYPE_BANK), + (re.compile(r'INTERETS CREDITEURS'), FrenchTransaction.TYPE_BANK), + (re.compile(r'(ANNUL |ANNULATION |)FRAIS '), FrenchTransaction.TYPE_BANK), + (re.compile(r'(ANNUL |ANNULATION |)INT DEB'), FrenchTransaction.TYPE_BANK), + (re.compile(r'TAEG APPLIQUE '), FrenchTransaction.TYPE_BANK), + ] class AbstractAccountPage(StatefulPage): def has_iban(self): - return len(self.doc.xpath('//a[contains(., "Edition RIB")]/ancestor::node()[2][not(contains(@style, "display: none;"))]')) > 1 + return len(self.doc.xpath( + '//a[contains(., "Edition RIB")]/ancestor::node()[2][not(contains(@style, "display: none;"))]' + )) > 1 def has_history(self): - return bool(self.doc.xpath(u'//div[contains(@id, "83B48AC016951684534547") and contains(@style, "display: none;")]')) + return bool(self.doc.xpath( + '//div[contains(@id, "83B48AC016951684534547") and contains(@style, "display: none;")]' + )) def form_to_history_page(self): btn = Attr('//button[contains(@id, "moreOperations")]', 'name', default=NotAvailable)(self.doc) @@ -244,7 +283,13 @@ def form_to_history_page(self): form = self.get_form(id='form1') for attr in list(form): - if attr not in ['MENUSTATE', 'DEVICE_SIZE_INFO', 'C4__WORKING[1].IDENTINTCONTRAT', 'C9__GETMODULENOTEPAD[1].IOGETMODULENOTEPAD[1].OUTPUTPARAMETER[1].TEXT', token[0]]: + if attr not in ( + 'MENUSTATE', + 'DEVICE_SIZE_INFO', + 'C4__WORKING[1].IDENTINTCONTRAT', + 'C9__GETMODULENOTEPAD[1].IOGETMODULENOTEPAD[1].OUTPUTPARAMETER[1].TEXT', + token[0], + ): del form[attr] form['MODE'] = btn @@ -256,18 +301,25 @@ class iter_history(TableElement): head_xpath = '//table[@class="table_operations"]/thead/tr/th//a/text()' item_xpath = '//table[@class="table_operations"]/tbody/tr' - col_date = u'Date Opération' + col_date = 'Date Opération' col_vdate = 'Date valeur' - col_debit = u'Débit' - col_credit = u'Crédit' + col_debit = 'Débit' + col_credit = 'Crédit' class item(ItemElement): klass = Transaction obj_date = Date(CleanText(TableCell('date')), dayfirst=True) obj_vdate = Date(CleanText(TableCell('vdate')), dayfirst=True) - obj_amount = MyDecimal(TableCell('credit'), default=TableCell('debit')) - obj_raw = Transaction.Raw(ReplaceEntities(Regexp(CleanText('.//script[1]'), r"toggleDetails\([^,]+,[^,]+, '(.*?)', '(.*?)', '(.*?)',", r'\1 \2 \3'))) + obj_amount = Coalesce( + CleanDecimal.French(TableCell('credit'), default=None), + CleanDecimal.French(TableCell('debit')), + ) + obj_raw = Transaction.Raw(ReplaceEntities(Regexp( + CleanText('.//script[1]'), + r"toggleDetails\([^,]+,[^,]+, '(.*?)', '(.*?)', '(.*?)',", + r'\1 \2 \3' + ))) class AccountPage(AbstractAccountPage): @@ -288,7 +340,12 @@ def get_space_attrs(self, space): a = Regexp(Attr('.', 'onclick'), r'\((.*?)\)')(a[0]).replace('\'', '').split(', ') form = self.get_form(id='form1') - return (a[1], 'C4__WORKING[1].SELECTEDSECURITYACCOUNTID', form['C4__WORKING[1].SELECTEDSECURITYACCOUNTID'], a[2]) + return ( + a[1], + 'C4__WORKING[1].SELECTEDSECURITYACCOUNTID', + form['C4__WORKING[1].SELECTEDSECURITYACCOUNTID'], + a[2], + ) @method class iter_investments(TableElement): @@ -348,7 +405,12 @@ def get_space_attrs(self, space): a = Regexp(Attr('.', 'onclick'), r'\((.*?)\)')(a[0]).replace('\'', '').split(', ') form = self.get_form(id='form1') - return (a[1], 'C4__WORKING[1].IDENTCONTRACTLIST', form['C4__WORKING[1].IDENTCONTRACTLIST'], a[2]) + return ( + a[1], + 'C4__WORKING[1].IDENTCONTRACTLIST', + form['C4__WORKING[1].IDENTCONTRACTLIST'], + a[2], + ) @method class iter_history(TableElement): @@ -366,14 +428,13 @@ class item(ItemElement): obj_vdate = Date(CleanText(TableCell('vdate')), dayfirst=True) def obj_amount(self): - return MyDecimal('.//div/span')(TableCell('amount')(self)[0]) + return CleanDecimal.French('.//div/span', default=NotAvailable)(TableCell('amount')(self)[0]) def obj_date(self): return Date(CleanText('.//span[contains(@id, "C4__QUE_50FADFF19F566198286748")]'), dayfirst=True)(self) @method class iter_investments(TableElement): - def condition(self): return not self.xpath('//h1[text()="Aucune position"]') @@ -390,10 +451,16 @@ class item(ItemElement): klass = Investment obj_label = CleanText(TableCell('label')) - obj_quantity = MyDecimal(TableCell('quantity')) - obj_unitvalue = MyDecimal(TableCell('unitvalue')) - obj_valuation = MyDecimal(TableCell('valuation')) - obj_portfolio_share = Eval(lambda x: x / 100, MyDecimal(TableCell('portfolio_share'))) + obj_quantity = CleanDecimal.French(TableCell('quantity'), default=NotAvailable) + obj_unitvalue = CleanDecimal.French(TableCell('unitvalue'), default=NotAvailable) + obj_valuation = CleanDecimal.French(TableCell('valuation')) + + def obj_portfolio_share(self): + portfolio_share_percent = CleanDecimal.French(TableCell('portfolio_share'), default=None)(self) + if portfolio_share_percent is not None: + return portfolio_share_percent / 100 + return NotAvailable + obj_code = NotAvailable obj_code_type = NotAvailable @@ -420,7 +487,9 @@ def do_account_attachment(self, accounts): return NotAvailable def has_history(self): - return bool(self.doc.xpath('//h1[contains(@id, "C0B43C670D16A2667437")]/ancestor::node()[2][contains(@style, "display: none;")]')) + return bool(self.doc.xpath( + '//h1[contains(@id, "C0B43C670D16A2667437")]/ancestor::node()[2][contains(@style, "display: none;")]' + )) @method class iter_history(TableElement): @@ -448,10 +517,13 @@ def obj_date(self): return self.page.get_debit_date() def obj_amount(self): - return MyDecimal('./td[5]//div/span')(self) + return CleanDecimal.French('./td[5]//div/span', default=NotAvailable)(self) def get_debit_date(self): - return Date(Regexp(CleanText('//label[starts-with(text(),"Echéance au ")]'), r'(\d{2}/\d{2}/\d{4})'), dayfirst=True)(self.doc) + return Date( + Regexp(CleanText('//label[starts-with(text(),"Echéance au ")]'), r'(\d{2}/\d{2}/\d{4})'), + dayfirst=True, + )(self.doc) def get_space_attrs(self, space): a = self.doc.xpath('//a[contains(span, $space)]', space=space) @@ -462,7 +534,12 @@ def get_space_attrs(self, space): a = Regexp(Attr('.', 'onclick'), r'\((.*?)\)')(a[0]).replace('\'', '').split(', ') form = self.get_form(id='form1') - return (a[1], 'C4__WORKING[1].LISTCONTRATS', form['C4__WORKING[1].LISTCONTRATS'], a[2]) + return ( + a[1], + 'C4__WORKING[1].LISTCONTRATS', + form['C4__WORKING[1].LISTCONTRATS'], + a[2], + ) class RevolvingAccountPage(AbstractAccountPage): @@ -475,11 +552,25 @@ def has_iban(self): def get_revolving_attributes(self, account): loan = Loan() - loan.available_amount = CleanDecimal('//div/span[contains(text(), "Montant disponible")]/following-sibling::*[1]', replace_dots=True)(self.doc) - loan.used_amount = CleanDecimal('//div/span[contains(text(), "Montant Utilisé")]/following-sibling::*[1]', replace_dots=True)(self.doc) - loan.total_amount = CleanDecimal('//div/span[contains(text(), "Réserve accordée")]/following-sibling::*[1]', replace_dots=True)(self.doc) - loan.last_payment_amount = CleanDecimal('//div/span[contains(text(), "Echéance Précédente")]/following-sibling::*[1]', replace_dots=True)(self.doc) - loan.last_payment_date = Date(Regexp(CleanText('//div/span[contains(text(), "Echéance Précédente")]/following-sibling::*[2]'), r'(\d{2}\/\d{2}\/\d{4})'), dayfirst=True)(self.doc) + loan.available_amount = CleanDecimal.French( + '//div/span[contains(text(), "Montant disponible")]/following-sibling::*[1]' + )(self.doc) + loan.used_amount = CleanDecimal.French( + '//div/span[contains(text(), "Montant Utilisé")]/following-sibling::*[1]' + )(self.doc) + loan.total_amount = CleanDecimal.French( + '//div/span[contains(text(), "Réserve accordée")]/following-sibling::*[1]' + )(self.doc) + loan.last_payment_amount = CleanDecimal.French( + '//div/span[contains(text(), "Echéance Précédente")]/following-sibling::*[1]' + )(self.doc) + loan.last_payment_date = Date( + Regexp( + CleanText('//div/span[contains(text(), "Echéance Précédente")]/following-sibling::*[2]'), + r'(\d{2}\/\d{2}\/\d{4})' + ), + dayfirst=True + )(self.doc) owner_name = CleanText('//a[@class="lien-entete login"]/span')(self.doc) loan.name = ' '.join(owner_name.split()[1:]) @@ -503,14 +594,31 @@ def has_iban(self): def get_loan_attributes(self, account): loan = Loan() - loan.total_amount = CleanDecimal('//div/span[contains(text(), "Capital initial")]/following-sibling::*[1]', replace_dots=True)(self.doc) + loan.total_amount = CleanDecimal.French( + '//div/span[contains(text(), "Capital initial")]/following-sibling::*[1]' + )(self.doc) owner_name = CleanText('//a[@class="lien-entete login"]/span')(self.doc) loan.name = ' '.join(owner_name.split()[1:]) - loan.subscription_date = Date(Regexp(CleanText('//h4[span[contains(text(), "Date de départ du prêt")]]'), r'(\d{2}\/\d{2}\/\d{4})'), dayfirst=True)(self.doc) - loan.maturity_date = Date(Regexp(CleanText('//h4[span[contains(text(), "Date de fin du prêt")]]'), r'(\d{2}\/\d{2}\/\d{4})'), dayfirst=True)(self.doc) - loan.rate = Eval(lambda x: x / 100, CleanDecimal('//div/span[contains(text(), "Taux fixe")]/following-sibling::*[1]', replace_dots=True))(self.doc) - loan.last_payment_amount = CleanDecimal('//div[@class="txt-detail " and not (@style)]//span[contains(text(), "Echéance du")]/following-sibling::span[1]')(self.doc) - loan.last_payment_date = Date(Regexp(CleanText('//div[@class="txt-detail " and not (@style)]//span[contains(text(), "Echéance du")]'), r'(\d{2}\/\d{2}\/\d{4})'), dayfirst=True)(self.doc) + loan.subscription_date = Date( + Regexp(CleanText('//h4[span[contains(text(), "Date de départ du prêt")]]'), r'(\d{2}\/\d{2}\/\d{4})'), + dayfirst=True, + )(self.doc) + loan.maturity_date = Date( + Regexp(CleanText('//h4[span[contains(text(), "Date de fin du prêt")]]'), r'(\d{2}\/\d{2}\/\d{4})'), + dayfirst=True, + )(self.doc) + + loan.rate = CleanDecimal.French('//div/span[contains(text(), "Taux fixe")]/following-sibling::*[1]')(self.doc) + loan.last_payment_amount = CleanDecimal.SI( + '//div[@class="txt-detail " and not (@style)]//span[contains(text(), "Echéance du")]/following-sibling::span[1]' + )(self.doc) + loan.last_payment_date = Date( + Regexp( + CleanText('//div[@class="txt-detail " and not (@style)]//span[contains(text(), "Echéance du")]'), + r'(\d{2}\/\d{2}\/\d{4})' + ), + dayfirst=True, + )(self.doc) loan.id = account.id loan.currency = account.currency