diff --git a/modules/caissedepargne/base_pages.py b/modules/caissedepargne/base_pages.py index 9aa1298b44f6c97d43f9d69984f0c7b077973d6d..b7fd6d5dcd2fdbfbbc4405a3f1e80ffdf46ceb1f 100644 --- a/modules/caissedepargne/base_pages.py +++ b/modules/caissedepargne/base_pages.py @@ -17,12 +17,16 @@ # You should have received a copy of the GNU Lesser General Public License # along with this weboob module. If not, see . +# flake8: compatible + from weboob.browser.pages import HTMLPage def fix_form(form): - keys = ['MM$HISTORIQUE_COMPTE$btnCumul', 'Cartridge$imgbtnMessagerie', 'MM$m_CH$ButtonImageFondMessagerie', - 'MM$m_CH$ButtonImageMessagerie'] + keys = [ + 'MM$HISTORIQUE_COMPTE$btnCumul', 'Cartridge$imgbtnMessagerie', 'MM$m_CH$ButtonImageFondMessagerie', + 'MM$m_CH$ButtonImageMessagerie', + ] for name in keys: form.pop(name, None) diff --git a/modules/caissedepargne/browser.py b/modules/caissedepargne/browser.py index ccd5d7ec24886dcd30c3c0000a072e8cc390defd..4ac3d7db8c14b0747ed2a2e844de533f1c3207f6 100644 --- a/modules/caissedepargne/browser.py +++ b/modules/caissedepargne/browser.py @@ -17,6 +17,8 @@ # 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 time @@ -56,12 +58,16 @@ from weboob.tools.decorators import retry from .pages import ( - IndexPage, ErrorPage, MarketPage, LifeInsurance, LifeInsuranceHistory, LifeInsuranceInvestments, GarbagePage, MessagePage, LoginPage, + IndexPage, ErrorPage, MarketPage, LifeInsurance, LifeInsuranceHistory, LifeInsuranceInvestments, + GarbagePage, MessagePage, LoginPage, TransferPage, ProTransferPage, TransferConfirmPage, TransferSummaryPage, ProTransferConfirmPage, ProTransferSummaryPage, ProAddRecipientOtpPage, ProAddRecipientPage, - SmsPage, ValidationPageOption, AuthentPage, RecipientPage, CanceledAuth, CaissedepargneKeyboard, CaissedepargneNewKeyboard, - TransactionsDetailsPage, LoadingPage, ConsLoanPage, MeasurePage, NatixisLIHis, NatixisLIInv, NatixisRedirectPage, - SubscriptionPage, CreditCooperatifMarketPage, UnavailablePage, CardsPage, CardsComingPage, CardsOldWebsitePage, TransactionPopupPage, + SmsPage, ValidationPageOption, AuthentPage, RecipientPage, CanceledAuth, + CaissedepargneKeyboard, CaissedepargneNewKeyboard, + TransactionsDetailsPage, LoadingPage, ConsLoanPage, MeasurePage, + NatixisLIHis, NatixisLIInv, NatixisRedirectPage, + SubscriptionPage, CreditCooperatifMarketPage, UnavailablePage, + CardsPage, CardsComingPage, CardsOldWebsitePage, TransactionPopupPage, OldLeviesPage, NewLeviesPage, NewLoginPage, JsFilePage, AuthorizePage, AuthenticationMethodPage, VkImagePage, AuthenticationStepPage, LoginTokensPage, AppValidationPage, @@ -146,7 +152,8 @@ class CaisseEpargne(LoginBrowser, StatesMixin): # Login and transfer authentication authentication_step = URL( - r'https://(?Pwww.icgauth.[^/]+)/dacsrest/api/v1u0/transaction/(?P[^/]+)/step', AuthenticationStepPage + r'https://(?Pwww.icgauth.[^/]+)/dacsrest/api/v1u0/transaction/(?P[^/]+)/step', + AuthenticationStepPage ) authentication_method_page = URL( r'https://(?Pwww.icgauth.[^/]+)/dacsrest/api/v1u0/transaction/(?P)', @@ -161,13 +168,22 @@ class CaisseEpargne(LoginBrowser, StatesMixin): # eg of both possible regexes: # https://www.icgauth.caisse-epargne.fr/dacstemplate-SOL/index.html?transactionID=CtxDACSP[a-f0-9]+ # https://www.icgauth.caisse-epargne.fr/dacstemplate-SOL/_12579/index.html?transactionID=CtxDACSP[a-f0-9]+ - validation_option = URL(r'https://(?Pwww.icgauth.[^/]+)/dacstemplate-SOL/(?:[^/]+/)?index.html\?transactionID=.*', ValidationPageOption) + validation_option = URL( + r'https://(?Pwww.icgauth.[^/]+)/dacstemplate-SOL/(?:[^/]+/)?index.html\?transactionID=.*', + ValidationPageOption + ) sms = URL(r'https://(?Pwww.icgauth.[^/]+)/dacswebssoissuer/AuthnRequestServlet', SmsPage) app_validation = URL(r'https://(?Pwww.icgauth.[^/]+)/dacsrest/WaitingCallbackHandler', AppValidationPage) - account_login = URL(r'/authentification/manage\?step=account&identifiant=(?P.*)&account=(?P.*)', LoginPage) + account_login = URL( + r'/authentification/manage\?step=account&identifiant=(?P.*)&account=(?P.*)', + LoginPage + ) loading = URL(r'https://.*/CreditConso/ReroutageCreditConso.aspx', LoadingPage) - cons_loan = URL(r'https://www.credit-conso-cr.caisse-epargne.fr/websavcr-web/rest/contrat/getContrat\?datePourIe=(?P)', ConsLoanPage) + cons_loan = URL( + r'https://www.credit-conso-cr.caisse-epargne.fr/websavcr-web/rest/contrat/getContrat\?datePourIe=(?P)', + ConsLoanPage + ) transaction_detail = URL(r'https://.*/Portail.aspx.*', TransactionsDetailsPage) recipient = URL(r'https://.*/Portail.aspx.*', RecipientPage) checking = URL(r'https://.*/Portail.aspx.*', CheckingPage) @@ -211,15 +227,27 @@ class CaisseEpargne(LoginBrowser, StatesMixin): r'https://www.espace-assurances.caisse-epargne.fr/espaceinternet-ce/views/common/routage-itce.xhtml\?windowId=automatedEntryPoint', NatixisRedirectPage ) - life_insurance_history = URL(r'https://www.extranet2.caisse-epargne.fr/cin-front/contrats/evenements', LifeInsuranceHistory) - life_insurance_investments = URL(r'https://www.extranet2.caisse-epargne.fr/cin-front/contrats/details', LifeInsuranceInvestments) + life_insurance_history = URL( + r'https://www.extranet2.caisse-epargne.fr/cin-front/contrats/evenements', + LifeInsuranceHistory + ) + life_insurance_investments = URL( + r'https://www.extranet2.caisse-epargne.fr/cin-front/contrats/details', + LifeInsuranceInvestments + ) life_insurance = URL( r'https://.*/Assurance/Pages/Assurance.aspx', r'https://www.extranet2.caisse-epargne.fr.*', LifeInsurance ) - natixis_life_ins_his = URL(r'https://www.espace-assurances.caisse-epargne.fr/espaceinternet-ce/rest/v2/contratVie/load-operation/(?P\w+)/(?P\w+)/(?P)', NatixisLIHis) - natixis_life_ins_inv = URL(r'https://www.espace-assurances.caisse-epargne.fr/espaceinternet-ce/rest/v2/contratVie/load/(?P\w+)/(?P\w+)/(?P)', NatixisLIInv) + natixis_life_ins_his = URL( + r'https://www.espace-assurances.caisse-epargne.fr/espaceinternet-ce/rest/v2/contratVie/load-operation/(?P\w+)/(?P\w+)/(?P)', + NatixisLIHis + ) + natixis_life_ins_inv = URL( + r'https://www.espace-assurances.caisse-epargne.fr/espaceinternet-ce/rest/v2/contratVie/load/(?P\w+)/(?P\w+)/(?P)', + NatixisLIInv + ) message = URL(r'https://www.caisse-epargne.offrebourse.com/DetailMessage\?refresh=O', MessagePage) garbage = URL( r'https://www.caisse-epargne.offrebourse.com/Portefeuille', @@ -236,23 +264,25 @@ class CaisseEpargne(LoginBrowser, StatesMixin): # Accounts managed in life insurance space (not in linebourse) - insurance_accounts = ('AIKIDO', - 'ASSURECUREUIL', - 'ECUREUIL PROJET', - 'GARANTIE RETRAITE EU', - 'INITIATIVES PLUS', - 'INITIATIVES TRANSMIS', - 'LIVRET ASSURANCE VIE', - 'OCEOR EVOLUTION', - 'PATRIMONIO CRESCENTE', - 'PEP TRANSMISSION', - 'PERP', - 'PERSPECTIVES ECUREUI', - 'POINTS RETRAITE ECUR', - 'RICOCHET', - 'SOLUTION PERP', - 'TENDANCES', - 'YOGA', ) + insurance_accounts = ( + 'AIKIDO', + 'ASSURECUREUIL', + 'ECUREUIL PROJET', + 'GARANTIE RETRAITE EU', + 'INITIATIVES PLUS', + 'INITIATIVES TRANSMIS', + 'LIVRET ASSURANCE VIE', + 'OCEOR EVOLUTION', + 'PATRIMONIO CRESCENTE', + 'PEP TRANSMISSION', + 'PERP', + 'PERSPECTIVES ECUREUI', + 'POINTS RETRAITE ECUR', + 'RICOCHET', + 'SOLUTION PERP', + 'TENDANCES', + 'YOGA', + ) def __init__(self, nuser, *args, **kwargs): self.BASEURL = kwargs.pop('domain', self.BASEURL) @@ -411,7 +441,7 @@ def check_connection_data(self, data): self.typeAccount = accounts_types[self.inexttype] else: - assert False, 'should have logged in with at least one connection type' + raise AssertionError('should have logged in with at least one connection type') self.inexttype += 1 data = self.account_login.go(login=self.username, accountType=self.typeAccount).get_response() @@ -435,7 +465,7 @@ def do_old_login(self, data, type_account, accounts_types): 'step': 'authentification', 'ctx': 'typsrv={}'.format(type_account), 'clavierSecurise': '1', - 'nuabbd': self.username + 'nuabbd': self.username, } try: @@ -485,7 +515,7 @@ def get_auth_mechanisms_validation_info(self): if transaction_id: transaction_id = transaction_id.group(1) else: - assert False, 'Transfer transaction id was not found in url' + raise AssertionError('Transfer transaction id was not found in url') otp_validation_domain = urlparse(self.url).netloc @@ -699,6 +729,29 @@ def do_new_login(self, data): # 'Accept': 'applcation/json'. If we do not add this header, we # instead have a form that we can directly send to complete # the login. + + claims = { + 'userinfo': { + 'cdetab': None, + 'authMethod': None, + 'authLevel': None, + }, + 'id_token': { + 'auth_time': {"essential": True}, + "last_login": None, + }, + } + bpcesta = { + "csid": csid, + "typ_app": "rest", + "enseigne": "ce", + "typ_sp": "out-band", + "typ_act": "auth", + "snid": snid, + "cdetab": url_params['cdetab'][0], + "typ_srv": "part", + } + self.authorize.go( params={ 'nonce': nonce, @@ -709,9 +762,10 @@ def do_new_login(self, data): 'login_hint': self.username, 'display': 'page', 'client_id': client_id, - 'claims': '{"userinfo":{"cdetab":null,"authMethod":null,"authLevel":null},"id_token":{"auth_time":{"essential":true},"last_login":null}}', - 'bpcesta': '{"csid":"%s","typ_app":"rest","enseigne":"ce","typ_sp":"out-band","typ_act":"auth","snid":"%s","cdetab":"%s","typ_srv":"part"}' % (csid, snid, url_params['cdetab'][0]), - }, + # don't know if the separators= is really needed + 'claims': json.dumps(claims, separators=(',', ':')), + 'bpcesta': json.dumps(bpcesta, separators=(',', ':')), + } ) self.page.send_form() @@ -758,7 +812,10 @@ def loans_conso(self): # for non-DST # d = '%s %s %s %s %s:%s:%s GMT+0100 (heure normale d’Europe centrale)' % (days[now.weekday()], now.day, month[now.month - 1], now.year, now.hour, format(now.minute, "02"), now.second) # TODO use babel library to simplify this code - d = '%s %s %s %s %s:%s:%s GMT+0200 (heure d’été d’Europe centrale)' % (days[now.weekday()], now.day, month[now.month - 1], now.year, now.hour, format(now.minute, "02"), now.second) + d = '%s %s %s %s %s:%s:%s GMT+0200 (heure d’été d’Europe centrale)' % ( + days[now.weekday()], now.day, month[now.month - 1], now.year, + now.hour, format(now.minute, "02"), now.second, + ) if self.home.is_here(): msg = self.page.loan_unavailable_msg() if msg: @@ -767,11 +824,11 @@ def loans_conso(self): self.cons_loan.go(datepourie=d) return self.page.get_conso() - def go_measure_list(self, page_num = 0): + def go_measure_list(self, page_num=0): self.home.go() if not self.measure_page.is_here(): - assert False, 'Should be on measure_page' + raise AssertionError('Should be on measure_page') self.page.go_measure_list() for _ in range(page_num): @@ -812,7 +869,6 @@ def get_measure_accounts_list(self): break self.page.goto_next_page() - for account in self.accounts: if 'acc_type' in account._info and account._info['acc_type'] == Account.TYPE_LIFE_INSURANCE: self.go_measure_list(account._info['measure_id_page_num']) @@ -868,7 +924,7 @@ def add_linebourse_accounts_data(self): # We need to go back to the synthesis, else we can not go home later self.home_tache.go(tache='CPTSYNT0') else: - assert False, "new domain that hasn't been seen so far ?" + raise AssertionError("new domain that hasn't been seen so far?") def add_card_accounts(self): """ @@ -973,7 +1029,7 @@ def get_loans_list(self): if self.page.check_no_accounts() or self.page.check_no_loans(): return [] - for trial in range(5): + for _ in range(5): for _ in range(3): self.home_tache.go(tache='CRESYNT0') if self.home.is_here(): @@ -1049,7 +1105,10 @@ def _get_history(self, info, account_card=None): if self.card_matches(tr.card, account_card.number): card_and_forms.append((tr.card, self.page.get_form_to_detail(tr))) else: - self.logger.debug('will skip summary detail (%r) for different card %r', tr, account_card.number) + self.logger.debug( + 'will skip summary detail (%r) for different card %r', + tr, account_card.number + ) elif tr.type == FrenchTransaction.TYPE_CARD and 'fac cb' in tr.raw.lower() and not account_card: # for immediate debits made with a def card the label is way too empty for certain clients # we therefore open a popup and find the rest of the label @@ -1084,7 +1143,7 @@ def _get_history(self, info, account_card=None): self.page.go_form_to_summary() # going back to summary goes back to first page - for j in range(i): + for _ in range(i): assert self.page.go_next() #  order by date the transactions without the summaries @@ -1098,7 +1157,7 @@ def _get_history(self, info, account_card=None): if not self.page.go_next(): return - assert False, 'More than {} history pages'.format(self.HISTORY_MAX_PAGE) + raise AssertionError('More than {} history pages'.format(self.HISTORY_MAX_PAGE)) @need_login def _get_history_invests(self, account): @@ -1171,7 +1230,10 @@ def match_cb(tr): if not hasattr(account, '_info'): raise NotImplementedError - if account.type in (Account.TYPE_LIFE_INSURANCE, Account.TYPE_CAPITALISATION) and 'measure_id' not in account._info: + if ( + account.type in (Account.TYPE_LIFE_INSURANCE, Account.TYPE_CAPITALISATION) + and 'measure_id' not in account._info + ): return self._get_history_invests(account) if account.type in (Account.TYPE_MARKET, Account.TYPE_PEA): self.page.go_history(account._info) @@ -1241,7 +1303,15 @@ def get_coming_card(self, account): @need_login def get_investment(self, account): self.deleteCTX() - if account.type not in (Account.TYPE_LIFE_INSURANCE, Account.TYPE_CAPITALISATION, Account.TYPE_MARKET, Account.TYPE_PEA) or 'measure_id' in account._info: + + investable_types = ( + Account.TYPE_LIFE_INSURANCE, Account.TYPE_CAPITALISATION, + Account.TYPE_MARKET, Account.TYPE_PEA, + ) + if ( + account.type not in investable_types + or 'measure_id' in account._info + ): raise NotImplementedError() if account.type == Account.TYPE_PEA and account.label == 'PEA NUMERAIRE': @@ -1435,7 +1505,7 @@ def otp_sms_continue_transfer(self, transfer, **params): if self.transfer.is_here(): self.page.continue_transfer(transfer.account_label, transfer.recipient_label, transfer.label) return self.page.update_transfer(transfer) - assert False, 'Blank page instead of the TransferPage' + raise AssertionError('Blank page instead of the TransferPage') @need_login def execute_transfer(self, transfer): @@ -1503,13 +1573,18 @@ def new_recipient(self, recipient, **params): if 'pro_password' in params: return self.end_pro_recipient(recipient, **params) - self.pre_transfer(next(acc for acc in self.get_accounts_list() if acc.type in (Account.TYPE_CHECKING, Account.TYPE_SAVINGS))) + first_transfer_account = next( + acc + for acc in self.get_accounts_list() + if acc.type in (Account.TYPE_CHECKING, Account.TYPE_SAVINGS) + ) + self.pre_transfer(first_transfer_account) # This send sms to user. self.page.go_add_recipient() if self.transfer.is_here(): self.page.handle_error() - assert False, 'We should not be on this page.' + raise AssertionError('We should not be on this page') if self.home.is_here(): # If we land here it might be because the user has no 2fa method @@ -1542,12 +1617,17 @@ def new_recipient(self, recipient, **params): # pro add recipient. elif self.page.need_auth(): self.page.set_browser_form() - raise AddRecipientStep(self.get_recipient_obj(recipient), Value('pro_password', label=self.page.get_prompt_text())) - + raise AddRecipientStep( + self.get_recipient_obj(recipient), + Value('pro_password', label=self.page.get_prompt_text()) + ) else: self.page.check_canceled_auth() self.page.set_browser_form() - raise AddRecipientStep(self.get_recipient_obj(recipient), Value('sms_password', label=self.page.get_prompt_text())) + raise AddRecipientStep( + self.get_recipient_obj(recipient), + Value('sms_password', label=self.page.get_prompt_text()) + ) def go_documents_without_sub(self): self.home_tache.go(tache='CPTSYNT0') diff --git a/modules/caissedepargne/cenet/browser.py b/modules/caissedepargne/cenet/browser.py index e305c2f87813af89a1a5f959034969738979888b..340363b7be344d58cb684458640cf8ab9780affa 100644 --- a/modules/caissedepargne/cenet/browser.py +++ b/modules/caissedepargne/cenet/browser.py @@ -17,8 +17,9 @@ # You should have received a copy of the GNU Lesser General Public License # along with this weboob module. If not, see . -from __future__ import unicode_literals +# flake8: compatible +from __future__ import unicode_literals from collections import Counter from fnmatch import fnmatch @@ -57,7 +58,10 @@ class CenetBrowser(LoginBrowser, StatesMixin): r'https://.*/login.aspx', LoginPage, ) - account_login = URL(r'https://(?P[^/]+)/authentification/manage\?step=account&identifiant=(?P.*)&account=(?P.*)', LoginPage) + account_login = URL( + r'https://(?P[^/]+)/authentification/manage\?step=account&identifiant=(?P.*)&account=(?P.*)', + LoginPage + ) cenet_vk = URL(r'https://www.cenet.caisse-epargne.fr/Web/Api/ApiAuthentification.asmx/ChargerClavierVirtuel') cenet_home = URL(r'/Default.aspx$', CenetHomePage) cenet_accounts = URL(r'/Web/Api/ApiComptes.asmx/ChargerSyntheseComptes', CenetAccountsPage) @@ -127,7 +131,7 @@ def do_login(self): post_data = { 'CodeEtablissement': data['codeCaisse'], 'NumeroBad': self.username, - 'NumeroUtilisateur': self.nuser + 'NumeroUtilisateur': self.nuser, } self.location(data['url'], data=post_data, headers={'Referer': 'https://www.cenet.caisse-epargne.fr/'}) @@ -141,7 +145,7 @@ def get_accounts_list(self): 'contexte': '', 'dateEntree': None, 'donneesEntree': 'null', - 'filtreEntree': None + 'filtreEntree': None, } # get accounts from CenetAccountsPage @@ -238,7 +242,7 @@ def get_history_base(self, account, card_number=None): 'contexte': '', 'dateEntree': None, 'donneesEntree': json.dumps(donneesEntree).replace('/', '\\/'), - 'filtreEntree': json.dumps(tr._data).replace('/', '\\/') + 'filtreEntree': json.dumps(tr._data).replace('/', '\\/'), } tr_detail_page = self.cenet_tr_detail.open(json=deferred_data) @@ -267,7 +271,7 @@ def get_coming(self, account): 'contexte': '', 'dateEntree': None, 'donneesEntree': json.dumps(account._hist), - 'filtreEntree': None + 'filtreEntree': None, } self.cenet_account_coming.go(json=data) @@ -305,7 +309,7 @@ def iter_subscription(self): 'contexte': '', 'dateEntree': None, 'donneesEntree': 'null', - 'filtreEntree': None + 'filtreEntree': None, } self.subscription.go(json=json_data) return self.page.iter_subscription(subscriber=subscriber) @@ -328,7 +332,7 @@ def iter_documents(self, subscription): 'contexte': '', 'dateEntree': None, 'donneesEntree': 'null', - 'filtreEntree': json.dumps(input_filter) + 'filtreEntree': json.dumps(input_filter), } self.documents.go(json=json_data) return self.page.iter_documents(sub_id=sub_id, sub_label=subscription.label, username=self.username) diff --git a/modules/caissedepargne/cenet/pages.py b/modules/caissedepargne/cenet/pages.py index 6e922b443bda9730bfd70952abeb0db4af67716c..bb719a55cc29055d54d78f2c53c2b9a4f21924c9 100644 --- a/modules/caissedepargne/cenet/pages.py +++ b/modules/caissedepargne/cenet/pages.py @@ -17,6 +17,8 @@ # 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 copy import deepcopy @@ -41,9 +43,18 @@ class Transaction(FrenchTransaction): PATTERNS = [ - (re.compile(r'^CB (?P.*?) FACT (?P
\d{2})(?P\d{2})(?P\d{2})', re.IGNORECASE), FrenchTransaction.TYPE_CARD), + ( + re.compile(r'^CB (?P.*?) FACT (?P
\d{2})(?P\d{2})(?P\d{2})', re.IGNORECASE), + FrenchTransaction.TYPE_CARD, + ), (re.compile(r'^RET(RAIT)? DAB (?P
\d+)-(?P\d+)-.*', re.IGNORECASE), FrenchTransaction.TYPE_WITHDRAWAL), - (re.compile(r'^RET(RAIT)? DAB (?P.*?) (?P
\d{2})(?P\d{2})(?P\d{2}) (?P\d{2})H(?P\d{2})', re.IGNORECASE), FrenchTransaction.TYPE_WITHDRAWAL), + ( + re.compile( + r'^RET(RAIT)? DAB (?P.*?) (?P
\d{2})(?P\d{2})(?P\d{2}) (?P\d{2})H(?P\d{2})', + re.IGNORECASE, + ), + FrenchTransaction.TYPE_WITHDRAWAL, + ), (re.compile(r'^VIR(EMENT)?(\.PERIODIQUE)? (?P.*)', re.IGNORECASE), FrenchTransaction.TYPE_TRANSFER), (re.compile(r'^PRLV (?P.*)', re.IGNORECASE), FrenchTransaction.TYPE_ORDER), (re.compile(r'^CHEQUE.*', re.IGNORECASE), FrenchTransaction.TYPE_CHECK), @@ -53,8 +64,14 @@ class Transaction(FrenchTransaction): (re.compile(r'^(?P.*)( \d+)? QUITTANCE .*', re.IGNORECASE), FrenchTransaction.TYPE_ORDER), (re.compile(r'^CB [\d\*]+ TOT DIF .*', re.IGNORECASE), FrenchTransaction.TYPE_CARD_SUMMARY), (re.compile(r'^CB [\d\*]+ (?P.*)', re.IGNORECASE), FrenchTransaction.TYPE_CARD), - (re.compile(r'^CB (?P.*?) (?P
\d{2})(?P\d{2})(?P\d{2})', re.IGNORECASE), FrenchTransaction.TYPE_CARD), - (re.compile(r'\*CB (?P.*?) (?P
\d{2})(?P\d{2})(?P\d{2})', re.IGNORECASE), FrenchTransaction.TYPE_CARD), + ( + re.compile(r'^CB (?P.*?) (?P
\d{2})(?P\d{2})(?P\d{2})', re.IGNORECASE), + FrenchTransaction.TYPE_CARD, + ), + ( + re.compile(r'\*CB (?P.*?) (?P
\d{2})(?P\d{2})(?P\d{2})', re.IGNORECASE), + FrenchTransaction.TYPE_CARD, + ), (re.compile(r'^FAC CB (?P.*?) (?P
\d{2})/(?P\d{2})', re.IGNORECASE), FrenchTransaction.TYPE_CARD), ] @@ -65,13 +82,20 @@ def get_response(self): class CenetLoginPage(HTMLPage): - def login(self, username, password, nuser, codeCaisse, _id, vkpass): + def login(self, username, password, nuser, codeCaisse, vkid, vkpass): form = self.get_form(id='aspnetForm') - form['__EVENTTARGET'] = "btn_authentifier_securise" - form['__EVENTARGUMENT'] = '{"CodeCaisse":"%s","NumeroBad":"%s","NumeroUsager":"%s",\ - "MotDePasse":"%s","IdentifiantClavier":"%s","ChaineConnexion":"%s"}' \ - % (codeCaisse, username, nuser, password, _id, vkpass) + form['__EVENTARGUMENT'] = json.dumps( + { + "CodeCaisse": codeCaisse, + "NumeroBad": username, + "NumeroUsager": nuser, + "MotDePasse": password, + "IdentifiantClavier": vkid, + "ChaineConnexion": vkpass, + }, + separators=(',', ':') + ) form.submit() @@ -305,8 +329,10 @@ def obj_type(self): def obj_amount(self): amount = CleanDecimal(Dict('Montant/Valeur'))(self) - - return -amount if Dict('Montant/CodeSens')(self) == "D" else amount + if Dict('Montant/CodeSens')(self) == "D": + return -amount + else: + return amount def obj__data(self): return self.el @@ -341,15 +367,17 @@ def obj_raw(self): return label def obj_rdate(self): - rdate = re.search('(FACT\s)(\d{6})', Field('label')(self)) + rdate = re.search(r'(FACT\s)(\d{6})', Field('label')(self)) if rdate.group(2): return Date(dayfirst=True).filter(rdate.group(2)) return NotAvailable def obj_amount(self): amount = CleanDecimal(Dict('Montant/Valeur'))(self) - - return -amount if Dict('Montant/CodeSens')(self) == "D" else amount + if Dict('Montant/CodeSens')(self) == "D": + return -amount + else: + return amount class _LogoutPage(HTMLPage): @@ -411,7 +439,7 @@ def download_form(self, document): 'Numero': document._numero, 'Libelle': document._sub_label.replace(' ', '+'), 'DateArrete': '', - 'IdDocument': document._download_id + 'IdDocument': document._download_id, } form = self.get_form(id='aspnetForm') form['__EVENTTARGET'] = 'btn_telecharger' diff --git a/modules/caissedepargne/linebourse_browser.py b/modules/caissedepargne/linebourse_browser.py index e3ce553a4cb34d310bfca3084663a55ec7583b57..516fdfcb34a28ffad69aca97edb94d9b690077b2 100644 --- a/modules/caissedepargne/linebourse_browser.py +++ b/modules/caissedepargne/linebourse_browser.py @@ -17,6 +17,8 @@ # You should have received a copy of the GNU Lesser General Public License # along with this weboob module. If not, see . +# flake8: compatible + from weboob.browser import AbstractBrowser diff --git a/modules/caissedepargne/module.py b/modules/caissedepargne/module.py index c7b77a4530ac06df172ccf616796fc002421982b..7259ea226f9733083a3221cbcab73cd05632e36c 100644 --- a/modules/caissedepargne/module.py +++ b/modules/caissedepargne/module.py @@ -17,6 +17,8 @@ # 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 @@ -49,25 +51,36 @@ class CaisseEpargneModule(Module, CapBankWealth, CapBankTransferAddRecipient, Ca DESCRIPTION = 'Caisse d\'Épargne' LICENSE = 'LGPLv3+' BROWSER = ProxyBrowser - website_choices = OrderedDict([(k, u'%s (%s)' % (v, k)) for k, v in sorted({ - 'www.caisse-epargne.fr': u'Caisse d\'Épargne', - 'www.banquebcp.fr': u'Banque BCP', - }.items(), key=lambda k_v: (k_v[1], k_v[0]))]) + website_choices = { + 'www.caisse-epargne.fr': u"Caisse d'Épargne", + 'www.banquebcp.fr': u'Banque BCP', + } + website_choices = OrderedDict( + [ + (k, u'%s (%s)' % (v, k)) + for k, v in sorted( + website_choices.items(), + key=lambda k_v: (k_v[1], k_v[0]) + ) + ] + ) CONFIG = BackendConfig( - Value('website', label='Banque', choices=website_choices, default='www.caisse-epargne.fr'), + Value('website', label='Banque', choices=website_choices, default='www.caisse-epargne.fr'), ValueBackendPassword('login', label='Identifiant client', masked=False), - ValueBackendPassword('password', label='Code personnel', regexp='\d+'), - Value('nuser', label='User ID (optional)', default='', regexp='[A-Z\d]{0,8}'), + ValueBackendPassword('password', label='Code personnel', regexp=r'\d+'), + Value('nuser', label='User ID (optional)', default='', regexp=r'[A-Z0-9]{0,8}'), ) accepted_document_types = (DocumentTypes.STATEMENT, DocumentTypes.OTHER,) def create_default_browser(self): - return self.create_browser(nuser=self.config['nuser'].get(), - username=self.config['login'].get(), - password=self.config['password'].get(), - domain=self.config['website'].get(), - weboob=self.weboob) + return self.create_browser( + nuser=self.config['nuser'].get(), + username=self.config['login'].get(), + password=self.config['password'].get(), + domain=self.config['website'].get(), + weboob=self.weboob + ) def iter_accounts(self): for account in self.browser.get_accounts_list(): @@ -106,16 +119,23 @@ def init_transfer(self, transfer, **params): return self.browser.otp_sms_continue_transfer(transfer, **params) self.logger.info('Going to do a new transfer') - transfer.label = ' '.join(w for w in re.sub('[^0-9a-zA-Z/\-\?:\(\)\.,\'\+ ]+', '', transfer.label).split()).upper() + transfer.label = re.sub(r"[^0-9A-Z/?:().,'+ -]+", '', transfer.label.upper()) + transfer.label = re.sub(r'\s+', ' ', transfer.label) if transfer.account_iban: account = find_object(self.iter_accounts(), iban=transfer.account_iban, error=AccountNotFound) else: account = find_object(self.iter_accounts(), id=transfer.account_id, error=AccountNotFound) if transfer.recipient_iban: - recipient = find_object(self.iter_transfer_recipients(account.id), iban=transfer.recipient_iban, error=RecipientNotFound) + recipient = find_object( + self.iter_transfer_recipients(account.id), iban=transfer.recipient_iban, + error=RecipientNotFound + ) else: - recipient = find_object(self.iter_transfer_recipients(account.id), id=transfer.recipient_id, error=RecipientNotFound) + recipient = find_object( + self.iter_transfer_recipients(account.id), id=transfer.recipient_id, + error=RecipientNotFound + ) transfer.amount = transfer.amount.quantize(Decimal(10) ** -2) @@ -125,7 +145,6 @@ def execute_transfer(self, transfer, **params): return self.browser.execute_transfer(transfer) def new_recipient(self, recipient, **params): - # recipient.label = ' '.join(w for w in re.sub('[^0-9a-zA-Z:\/\-\?\(\)\.,\'\+ ]+', '', recipient.label).split()) return self.browser.new_recipient(recipient, **params) def iter_resources(self, objs, split_path): diff --git a/modules/caissedepargne/pages.py b/modules/caissedepargne/pages.py index dee24d03203de19b39e8eff009f41d7e09bbba7c..824d04e9922eead89084d5d79109c4c970fa40b4 100644 --- a/modules/caissedepargne/pages.py +++ b/modules/caissedepargne/pages.py @@ -17,6 +17,8 @@ # 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 division from __future__ import unicode_literals @@ -39,7 +41,7 @@ Field, Eval, Format, Currency, Coalesce, ) from weboob.browser.filters.html import Link, Attr, TableCell -from weboob.capabilities import NotAvailable +from weboob.capabilities.base import NotAvailable, empty from weboob.capabilities.bank import ( Account, Loan, AccountOwnership, Transfer, TransferBankError, TransferInvalidOTP, @@ -95,11 +97,11 @@ def get_response(self): def get_wrongpass_message(self): error_msg = Dict('error')(self.doc) if ( - "Nous n'avons pas réussi à vous authentifier" in error_msg or - 'abonnement est bloqué' in error_msg + "Nous n'avons pas réussi à vous authentifier" in error_msg + or 'abonnement est bloqué' in error_msg ): return error_msg - assert False, 'Other error message to catch on LoginPage' + raise AssertionError('Other error message to catch on LoginPage') class JsFilePage(RawPage): @@ -128,7 +130,9 @@ def get_wrong_pre_login_status(self): # 'validationUnits' informs about auth method # not having any is faulty for the connection status = self.doc['response']['status'] - assert status in ('AUTHENTICATION_FAILED',), 'Unhandled status when checking if authentication method is informed: %s' % status + assert status in ('AUTHENTICATION_FAILED',), ( + 'Unhandled status when checking if authentication method is informed: %s' % status + ) return status @property @@ -196,7 +200,7 @@ def check_errors(self, feature): } FEATURES_ERRORS[feature](error=result) - assert False, 'Error during %s authentication is not handled yet: %s' % (feature, result) + raise AssertionError('Error during %s authentication is not handled yet: %s' % (feature, result)) class AuthenticationStepPage(AuthenticationMethodPage): @@ -227,7 +231,10 @@ class CaissedepargneNewKeyboard(SplitKeyboard): '1': ('529819241cce382b429b4624cb019b56', '0ea8c08e52d992a28aa26043ffc7c044'), '2': 'fab68678204198b794ce580015c8637f', '3': '3fc5280d17cf057d1c4b58e4f442ceb8', - '4': ('dea8800bdd5fcaee1903a2b097fbdef0', 'e413098a4d69a92d08ccae226cea9267', '61f720966ccac6c0f4035fec55f61fe6', '2cbd19a4b01c54b82483f0a7a61c88a1'), + '4': ( + 'dea8800bdd5fcaee1903a2b097fbdef0', 'e413098a4d69a92d08ccae226cea9267', + '61f720966ccac6c0f4035fec55f61fe6', '2cbd19a4b01c54b82483f0a7a61c88a1', + ), '5': 'ff1909c3b256e7ab9ed0d4805bdbc450', '6': '7b014507ffb92a80f7f0534a3af39eaa', '7': '7d598ff47a5607022cab932c6ad7bc5b', @@ -247,7 +254,13 @@ def __init__(self, browser, images): threshold=3, )) img = img.convert('L', dither=None) - img = Image.eval(img, lambda x: 0 if x < 20 else 255) + + def threshold(px): + if px < 20: + return 0 + return 255 + + img = Image.eval(img, threshold) b = BytesIO() img.save(b, format='PNG') code_to_filedata[img_item['value']] = b.getvalue() @@ -286,7 +299,7 @@ def on_load(self): if go_back_link is not NotAvailable: assert len(go_back_link) != 1 - go_back_link = re.search('\(~deibaseurl\)(.*)$', go_back_link).group(1) + go_back_link = re.search(r'\(~deibaseurl\)(.*)$', go_back_link).group(1) self.browser.location('%s%s' % (self.browser.BASEURL, go_back_link)) @@ -314,9 +327,18 @@ class ErrorPage(_LogoutPage): class Transaction(FrenchTransaction): PATTERNS = [ - (re.compile(r'^CB (?P.*?) FACT (?P
\d{2})(?P\d{2})(?P\d{2})\b', re.IGNORECASE), FrenchTransaction.TYPE_CARD), + ( + re.compile(r'^CB (?P.*?) FACT (?P
\d{2})(?P\d{2})(?P\d{2})\b', re.IGNORECASE), + FrenchTransaction.TYPE_CARD, + ), (re.compile(r'^RET(RAIT)? DAB (?P
\d+)-(?P\d+)-.*', re.IGNORECASE), FrenchTransaction.TYPE_WITHDRAWAL), - (re.compile(r'^RET(RAIT)? DAB (?P.*?) (?P
\d{2})(?P\d{2})(?P\d{2}) (?P\d{2})H(?P\d{2})\b', re.IGNORECASE), FrenchTransaction.TYPE_WITHDRAWAL), + ( + re.compile( + r'^RET(RAIT)? DAB (?P.*?) (?P
\d{2})(?P\d{2})(?P\d{2}) (?P\d{2})H(?P\d{2})\b', + re.IGNORECASE + ), + FrenchTransaction.TYPE_WITHDRAWAL, + ), (re.compile(r'^VIR(EMENT)?(\.PERIODIQUE)? (?P.*)', re.IGNORECASE), FrenchTransaction.TYPE_TRANSFER), (re.compile(r'^PRLV (?P.*)', re.IGNORECASE), FrenchTransaction.TYPE_ORDER), (re.compile(r'^CHEQUE.*', re.IGNORECASE), FrenchTransaction.TYPE_CHECK), @@ -327,9 +349,18 @@ class Transaction(FrenchTransaction): (re.compile(r'^(?P.*)( \d+)? QUITTANCE .*', re.IGNORECASE), FrenchTransaction.TYPE_ORDER), (re.compile(r'^CB [\d\*]+ TOT DIF .*', re.IGNORECASE), FrenchTransaction.TYPE_CARD_SUMMARY), (re.compile(r'^CB [\d\*]+ (?P.*)', re.IGNORECASE), FrenchTransaction.TYPE_CARD), - (re.compile(r'^CB (?P.*?) (?P
\d{2})(?P\d{2})(?P\d{2})\b', re.IGNORECASE), FrenchTransaction.TYPE_CARD), - (re.compile(r'\*CB (?P.*?) (?P
\d{2})(?P\d{2})(?P\d{2})\b', re.IGNORECASE), FrenchTransaction.TYPE_CARD), - (re.compile(r'^FAC CB (?P.*?) (?P
\d{2})/(?P\d{2})\b', re.IGNORECASE), FrenchTransaction.TYPE_CARD), + ( + re.compile(r'^CB (?P.*?) (?P
\d{2})(?P\d{2})(?P\d{2})\b', re.IGNORECASE), + FrenchTransaction.TYPE_CARD, + ), + ( + re.compile(r'\*CB (?P.*?) (?P
\d{2})(?P\d{2})(?P\d{2})\b', re.IGNORECASE), + FrenchTransaction.TYPE_CARD, + ), + ( + re.compile(r'^FAC CB (?P.*?) (?P
\d{2})/(?P\d{2})\b', re.IGNORECASE), + FrenchTransaction.TYPE_CARD, + ), (re.compile(r'^\*?CB (?P.*)', re.IGNORECASE), FrenchTransaction.TYPE_CARD), # For life insurances and capitalisation contracts (re.compile(r'^VERSEMENT', re.IGNORECASE), FrenchTransaction.TYPE_DEPOSIT), @@ -392,15 +423,21 @@ def on_load(self): self.browser.location(link) else: message = CleanText(self.doc.xpath('//span[contains(@id, "OIC_QCF")]/p'))(self) - if message and "investissement financier (QCF) n’est plus valide à ce jour ou que vous avez refusé d’y répondre" in message: + expected = "investissement financier (QCF) n’est plus valide à ce jour ou que vous avez refusé d’y répondre" + if message and expected in message: raise ActionNeeded(message) - mess = CleanText('//body/div[@class="content"]//p[contains(text(), "indisponible pour cause de maintenance")]')(self.doc) - if mess: - raise BrowserUnavailable(mess) + message = CleanText( + '//body/div[@class="content"]//p[contains(text(), "indisponible pour cause de maintenance")]' + )(self.doc) + if message: + raise BrowserUnavailable(message) # This page is sometimes an useless step to the market website. - bourse_link = Link('//div[@id="MM_COMPTE_TITRE_pnlbourseoic"]//a[contains(text(), "Accédez à la consultation")]', default=None)(self.doc) + bourse_link = Link( + '//div[@id="MM_COMPTE_TITRE_pnlbourseoic"]//a[contains(text(), "Accédez à la consultation")]', + default=None + )(self.doc) if bourse_link: self.browser.location(bourse_link) @@ -415,10 +452,14 @@ def check_no_loans(self): ) def check_measure_accounts(self): - return not CleanText('//div[@class="MessageErreur"]/ul/li[contains(text(), "Aucun compte disponible")]')(self.doc) + return not CleanText( + '//div[@class="MessageErreur"]/ul/li[contains(text(), "Aucun compte disponible")]' + )(self.doc) def check_no_accounts(self): - no_account_message = CleanText('//span[@id="MM_LblMessagePopinError"]/p[contains(text(), "Aucun compte disponible")]')(self.doc) + no_account_message = CleanText( + '//span[@id="MM_LblMessagePopinError"]/p[contains(text(), "Aucun compte disponible")]' + )(self.doc) if no_account_message: raise NoAccountsException(no_account_message) @@ -435,7 +476,10 @@ def find_and_replace(self, info, acc_id): return def _get_account_info(self, a, accounts): - m = re.search("PostBack(Options)?\([\"'][^\"']+[\"'],\s*['\"]([HISTORIQUE_\w|SYNTHESE_ASSURANCE_CNP|BOURSE|COMPTE_TITRE][\d\w&]+)?['\"]", a.attrib.get('href', '')) + m = re.search( + r"PostBack(Options)?\([\"'][^\"']+[\"'],\s*['\"]([HISTORIQUE_\w|SYNTHESE_ASSURANCE_CNP|BOURSE|COMPTE_TITRE][\d\w&]+)?['\"]", + a.attrib.get('href', '') + ) if m is None: return None else: @@ -445,12 +489,16 @@ def _get_account_info(self, a, accounts): parts = link.split('&') info = {} info['link'] = link - id = re.search("([\d]+)", a.attrib.get('title', '')) + id = re.search(r"([\d]+)", a.attrib.get('title', '')) if len(parts) > 1: info['type'] = parts[0] info['id'] = info['_id'] = parts[1] if id or info['id'] in [acc._info['_id'] for acc in accounts.values()]: - _id = id.group(1) if id else next(iter({k for k, v in accounts.items() if info['id'] == v._info['_id']})) + if id: + _id = id.group(1) + else: + unique_ids = {k for k, v in accounts.items() if info['id'] == v._info['_id']} + _id = list(unique_ids)[0] self.find_and_replace(info, _id) else: info['type'] = link @@ -479,7 +527,7 @@ def _add_account(self, accounts, link, label, account_type, balance, number=None account.number = number account.label = label account.ownership = ownership - account.type = self.ACCOUNT_TYPES.get(label, info['acc_type'] if 'acc_type' in info else account_type) + account.type = self.ACCOUNT_TYPES.get(label, info.get('acc_type', account_type)) if 'PERP' in account.label: account.type = Account.TYPE_PERP if 'NUANCES CAPITALISATI' in account.label: @@ -489,9 +537,12 @@ def _add_account(self, accounts, link, label, account_type, balance, number=None balance = balance or self.get_balance(account) - account.balance = Decimal(FrenchTransaction.clean_amount(balance)) if balance and balance is not NotAvailable else NotAvailable + if not empty(balance): + account.balance = Decimal(FrenchTransaction.clean_amount(balance)) + account.currency = account.get_currency(balance) + else: + account.currency = account.balance = NotAvailable - account.currency = account.get_currency(balance) if balance and balance is not NotAvailable else NotAvailable account._card_links = [] # Set coming history link to the parent account. At this point, we don't have card account yet. @@ -508,16 +559,21 @@ def get_balance(self, account): if account.type not in (Account.TYPE_LIFE_INSURANCE, Account.TYPE_PERP, Account.TYPE_CAPITALISATION): return NotAvailable page = self.go_history(account._info).page - balance = page.doc.xpath('.//tr[td[contains(@id,"NumContrat")]]/td[@class="somme"]/a[contains(@href, $id)]', id=account.id) + balance = page.doc.xpath( + './/tr[td[contains(@id,"NumContrat")]]/td[@class="somme"]/a[contains(@href, $id)]', + id=account.id + ) if len(balance) > 0: balance = CleanText('.')(balance[0]) - balance = balance if balance != '' else NotAvailable + if balance == '': + balance = NotAvailable else: # Specific xpath for some Life Insurances: balance = page.doc.xpath('//tr[td[contains(text(), $id)]]/td/div[contains(@id, "Solde")]', id=account.id) if len(balance) > 0: balance = CleanText('.')(balance[0]) - balance = balance if balance != '' else NotAvailable + if balance == '': + balance = NotAvailable else: # sometimes the accounts are attached but no info is available balance = NotAvailable @@ -535,7 +591,7 @@ def get_measure_balance(self, account): def get_measure_ids(self): accounts_id = [] for a in self.doc.xpath('//table[@cellpadding="1"]/tr/td[2]/a'): - accounts_id.append(re.search("(\d{6,})", Attr('.', 'href')(a)).group(1)) + accounts_id.append(re.search(r"(\d{6,})", Attr('.', 'href')(a)).group(1)) return accounts_id def has_next_page(self): @@ -586,7 +642,10 @@ def get_list(self, owner_name): balance = CleanText('.')(tds[-1].xpath('./a')[i]) self._add_account(accounts, a, label, account_type, balance, ownership=ownership) - self.logger.warning('we are on the %s website', 'old' if accounts else 'new') + website = 'old' + if accounts: + website = 'new' + self.logger.debug('we are on the %s website', website) if len(accounts) == 0: # New website @@ -622,7 +681,12 @@ def get_ownership(self, tds, owner_name): if len(tds) > 2: account_owner = CleanText('.', default=None)(tds[2]).upper() if account_owner and any(title in account_owner for title in ('M', 'MR', 'MLLE', 'MLE', 'MME')): - if re.search(r'(m|mr|me|mme|mlle|mle|ml)\.? ?(.*)\bou (m|mr|me|mme|mlle|mle|ml)\b(.*)', account_owner, re.IGNORECASE): + pattern = re.compile( + r'(m|mr|me|mme|mlle|mle|ml)\.? ?(.*)\bou (m|mr|me|mme|mlle|mle|ml)\b(.*)', + re.IGNORECASE + ) + + if pattern.search(account_owner): return AccountOwnership.CO_OWNER elif all(n in account_owner for n in owner_name.split()): return AccountOwnership.OWNER @@ -639,7 +703,7 @@ def is_access_error(self): def go_loans_conso(self, tr): link = tr.xpath('./td/a[contains(@id, "IdaCreditPerm")]') - m = re.search('CREDITCONSO&(\w+)', link[0].attrib['href']) + m = re.search(r'CREDITCONSO&(\w+)', link[0].attrib['href']) if m: account = m.group(1) @@ -665,7 +729,10 @@ def get_loan_list(self): account.currency = account.get_currency(CleanText('./a')(tds[4])) accounts[account.id] = account - self.logger.debug('we are on the %s website', 'old' if accounts else 'new') + website = 'old' + if accounts: + website = 'new' + self.logger.debug('we are on the %s website', website) if len(accounts) == 0: # New website @@ -682,12 +749,19 @@ def get_loan_list(self): for i in tds[0].xpath('.//a/strong'): label = i.text.strip() break - if len(tds) == 3 and Decimal(FrenchTransaction.clean_amount(CleanText('.')(tds[-2]))) and any(cls in Attr('.', 'id')(tr) for cls in ['dgImmo', 'dgConso']) is False: - # in case of Consumer credit or revolving credit, we substract avalaible amount with max amout - # to get what was spend - balance = Decimal(FrenchTransaction.clean_amount(CleanText('.')(tds[-2]))) - Decimal(FrenchTransaction.clean_amount(CleanText('.')(tds[-1]))) - else: - balance = Decimal(FrenchTransaction.clean_amount(CleanText('.')(tds[-1]))) + + balance = Decimal(FrenchTransaction.clean_amount(CleanText('.')(tds[-1]))) + if len(tds) == 3: + available = Decimal(FrenchTransaction.clean_amount(CleanText('.')(tds[-2]))) + + if ( + available + and not any(cls in Attr('.', 'id')(tr) for cls in ['dgImmo', 'dgConso']) + ): + # in case of Consumer credit or revolving credit, we substract avalaible amount with max amout + # to get what was spend + balance = available - balance + account = Loan() account.id = label.split(' ')[-1] account.label = unicode(label) @@ -707,14 +781,18 @@ def get_loan_list(self): self.go_loans_conso(tr) except ClientError as e: if e.response.status_code == 401: - raise ActionNeeded('La situation actuelle de votre dossier ne vous permet pas d\'accéder à cette fonctionnalité. ' - 'Nous vous invitons à contacter votre Centre de relation Clientèle pour accéder à votre prêt.') + raise ActionNeeded( + 'La situation actuelle de votre dossier ne vous permet pas d\'accéder à cette fonctionnalité. ' + + 'Nous vous invitons à contacter votre Centre de relation Clientèle pour accéder à votre prêt.' + ) raise d = self.browser.loans_conso() if d: account.total_amount = float_to_decimal(d['contrat']['creditMaxAutorise']) account.available_amount = float_to_decimal(d['situationCredit']['disponible']) - account.next_payment_amount = float_to_decimal(d['situationCredit']['mensualiteEnCours']) + account.next_payment_amount = float_to_decimal( + d['situationCredit']['mensualiteEnCours'] + ) accounts[account.id] = account return list(accounts.values()) @@ -751,7 +829,13 @@ class item(ItemElement): obj_currency = Currency(MyTableCell("balance")) obj_last_payment_date = Date(CleanText(MyTableCell("last_payment_date"))) obj_next_payment_amount = MyDecimal(MyTableCell("next_payment_amount")) - obj_next_payment_date = Date(CleanText(MyTableCell("next_payment_date", default=''), default=NotAvailable), default=NotAvailable) + obj_next_payment_date = Date( + CleanText( + MyTableCell("next_payment_date", default=''), + default=NotAvailable + ), + default=NotAvailable + ) obj_rate = MyDecimal(MyTableCell("rate", default=NotAvailable), default=NotAvailable) # The website doesn't show any information relative to the loan # owner, we can then assume they all belong to the credentials owner. @@ -812,7 +896,10 @@ def go_list(self): def go_cards(self): # Do not try to go the card summary if we have no card, it breaks the session - if self.browser.new_website and not CleanText('//form[@id="main"]//a/span[text()="Mes cartes bancaires"]')(self.doc): + if ( + self.browser.new_website + and not CleanText('//form[@id="main"]//a/span[text()="Mes cartes bancaires"]')(self.doc) + ): self.logger.info("Do not try to go the CardsPage, there is not link on the main page") return @@ -942,7 +1029,12 @@ def is_history_of(self, account_id): def go_history(self, info, is_cbtab=False): form = self.get_form(id='main') - form['__EVENTTARGET'] = 'MM$%s' % (info['type'] if is_cbtab else 'SYNTHESE') + if is_cbtab: + target = info['type'] + else: + target = 'SYNTHESE' + + form['__EVENTTARGET'] = 'MM$%s' % target form['__EVENTARGUMENT'] = info['link'] if "MM$m_CH$IsMsgInit" in form and (form['MM$m_CH$IsMsgInit'] == "0" or info['type'] == 'ASSURANCE_VIE'): @@ -966,7 +1058,7 @@ def go_history_netpro(self, info, ): return form.submit() def get_form_to_detail(self, transaction): - m = re.match('.*\("(.*)", "(DETAIL_OP&[\d]+).*\)\)', transaction._link) + m = re.match(r'.*\("(.*)", "(DETAIL_OP&[\d]+).*\)\)', transaction._link) # go to detailcard page form = self.get_form(id='main') form['__EVENTTARGET'] = m.group(1) @@ -1039,7 +1131,7 @@ def go_next(self): return False account_type = 'COMPTE' - m = re.search('HISTORIQUE_(\w+)', link[0].attrib['href']) + m = re.search(r'HISTORIQUE_(\w+)', link[0].attrib['href']) if m: account_type = m.group(1) @@ -1062,7 +1154,10 @@ def go_life_insurance(self, account): return link = self.doc.xpath('//tr[td[contains(., ' + account.id + ') ]]//a')[0] - m = re.search("PostBackOptions?\([\"']([^\"']+)[\"'],\s*['\"]((REDIR_ASS_VIE)?[\d\w&]+)?['\"]", link.attrib.get('href', '')) + m = re.search( + r"PostBackOptions?\([\"']([^\"']+)[\"'],\s*['\"]((REDIR_ASS_VIE)?[\d\w&]+)?['\"]", + link.attrib.get('href', '') + ) if m is not None: form = self.get_form(id='main') @@ -1096,7 +1191,10 @@ def go_transfer_page(self): return False else: link = link[0] - m = re.search("PostBackOptions?\([\"']([^\"']+)[\"'],\s*['\"]([^\"']+)?['\"]", link.attrib.get('href', '')) + m = re.search( + r"PostBackOptions?\([\"']([^\"']+)[\"'],\s*['\"]([^\"']+)?['\"]", + link.attrib.get('href', '') + ) form = self.get_form(id='main') if 'MM$HISTORIQUE_COMPTE$btnCumul' in form: del form['MM$HISTORIQUE_COMPTE$btnCumul'] @@ -1112,7 +1210,9 @@ def go_emitters(self): return self.go_transfer_page() def transfer_unavailable(self): - return CleanText('//li[contains(text(), "Pour accéder à cette fonctionnalité, vous devez disposer d’un moyen d’authentification renforcée")]')(self.doc) + return CleanText( + '''//li[contains(text(), "Pour accéder à cette fonctionnalité, vous devez disposer d’un moyen d’authentification renforcée")]''' + )(self.doc) def loan_unavailable_msg(self): msg = CleanText('//span[@id="MM_LblMessagePopinError"] | //p[@id="MM_ERREUR_PAGE_BLANCHE_pAlert"]')(self.doc) @@ -1157,10 +1257,14 @@ def get_unavailable_2fa_message(self): class TransactionPopupPage(LoggedPage, HTMLPage): def is_here(self): - return CleanText('''//div[@class="scrollPane"]/table[//caption[contains(text(), "Détail de l'opération")]]''')(self.doc) + return CleanText( + '''//div[@class="scrollPane"]/table[//caption[contains(text(), "Détail de l'opération")]]''' + )(self.doc) def complete_label(self): - return CleanText('''//div[@class="scrollPane"]/table[//caption[contains(text(), "Détail de l'opération")]]//tr[2]''')(self.doc) + return CleanText( + '''//div[@class="scrollPane"]/table[//caption[contains(text(), "Détail de l'opération")]]//tr[2]''' + )(self.doc) class NewLeviesPage(IndexPage): @@ -1194,7 +1298,11 @@ class item(ItemElement): obj_date = Date(CleanText(TableCell('date')), dayfirst=True) def condition(self): - return not CleanText('''//p[contains(text(), "Vous n'avez pas de prélèvement en attente d'exécution.")]''')(self) + return ( + not CleanText(''' + //p[contains(text(), "Vous n'avez pas de prélèvement en attente d'exécution.")] + ''')(self) + ) class OldLeviesPage(IndexPage): @@ -1228,7 +1336,9 @@ class item(ItemElement): obj_date = Date(CleanText(TableCell('date')), dayfirst=True) def condition(self): - return not CleanText('''//table[@id="MM_SYNTHESE_SDD_RECUS_rpt_dgList_0"]//td[contains(text(), "Vous n'avez pas de prélèvements")]''')(self) + return not CleanText(''' + //table[@id="MM_SYNTHESE_SDD_RECUS_rpt_dgList_0"]//td[contains(text(), "Vous n'avez pas de prélèvements")] + ''')(self) class CardsPage(IndexPage): @@ -1277,7 +1387,11 @@ def condition(self): # for now to make the debug easier immediate_str = '[Immediate card]' - self.logger.warning('Skip card %s (no history/coming information) %s', Field('number')(self), immediate_str) + self.logger.warning( + 'Skip card %s (no history/coming information) %s', + Field('number')(self), + immediate_str, + ) return False @@ -1341,7 +1455,9 @@ def get_card_coming_info(self, number, info): class CardsOldWebsitePage(IndexPage): def is_here(self): - return CleanText('//span[@id="MM_m_CH_lblTitle" and contains(text(), "Historique de vos encours CB")]')(self.doc) + return CleanText(''' + //span[@id="MM_m_CH_lblTitle" and contains(text(), "Historique de vos encours CB")] + ''')(self.doc) def get_account(self): infos = CleanText('.//span[@id="MM_HISTORIQUE_CB"]/table[position()=1]//td')(self.doc) @@ -1460,7 +1576,10 @@ def iter_investment(self): inv = Investment() inv.label = CleanText('.')(tbody.xpath('./tr[1]/td[1]/a/span')[0]) inv.code = CleanText('.')(tbody.xpath('./tr[1]/td[1]/a')[0]).split(' - ')[1] - inv.code_type = Investment.CODE_TYPE_ISIN if is_isin_valid(inv.code) else NotAvailable + if is_isin_valid(inv.code): + inv.code_type = Investment.CODE_TYPE_ISIN + else: + inv.code_type = NotAvailable inv.quantity = self.parse_decimal(tbody.xpath('./tr[2]/td[2]')[0]) inv.unitvalue = self.parse_decimal(tbody.xpath('./tr[2]/td[3]')[0]) inv.unitprice = self.parse_decimal(tbody.xpath('./tr[2]/td[5]')[0]) @@ -1471,10 +1590,13 @@ def iter_investment(self): def get_valuation_diff(self, account): val = CleanText(self.doc.xpath('//td[contains(text(), "values latentes")]/following-sibling::*[1]')) - account.valuation_diff = CleanDecimal(Regexp(val, '([^\(\)]+)'), replace_dots=True)(self) + account.valuation_diff = CleanDecimal(Regexp(val, r'([^\(\)]+)'), replace_dots=True)(self) def is_on_right_portfolio(self, account): - return len(self.doc.xpath('//form[@class="choixCompte"]//option[@selected and contains(text(), $id)]', id=account._info['id'])) + return len(self.doc.xpath( + '//form[@class="choixCompte"]//option[@selected and contains(text(), $id)]', + id=account._info['id'] + )) def get_compte(self, account): return self.doc.xpath('//option[contains(text(), $id)]/@value', id=account._info['id'])[0] @@ -1698,7 +1820,12 @@ def parse(self, el): # Autres comptes if value == 'AC': raise SkipItem() - self.env['category'] = 'Interne' if value[0] == 'I' else 'Externe' + + if value[0] == 'I': + self.env['category'] = 'Interne' + else: + self.env['category'] = 'Externe' + if self.env['category'] == 'Interne': # TODO use after 'I'? _id = Regexp(CleanText('.'), r'- (\w+\d\w+)')(self) # at least one digit @@ -1749,22 +1876,34 @@ def is_here(self): def can_transfer(self, account): for o in self.doc.xpath('//select[@id="MM_VIREMENT_SAISIE_VIREMENT_ddlCompteDebiter"]/option'): - if Regexp(CleanText('.'), '- (\d+)')(o) in account.id: + if Regexp(CleanText('.'), r'- (\d+)')(o) in account.id: return True def get_origin_account_value(self, account): - origin_value = [Attr('.', 'value')(o) for o in self.doc.xpath('//select[@id="MM_VIREMENT_SAISIE_VIREMENT_ddlCompteDebiter"]/option') if - Regexp(CleanText('.'), '- (\d+)')(o) in account.id] + origin_value = [ + Attr('.', 'value')(o) + for o in self.doc.xpath('//select[@id="MM_VIREMENT_SAISIE_VIREMENT_ddlCompteDebiter"]/option') + if Regexp(CleanText('.'), r'- (\d+)')(o) in account.id + ] assert len(origin_value) == 1, 'error during origin account matching' return origin_value[0] def get_recipient_value(self, recipient): if recipient.category == 'Externe': - recipient_value = [Attr('.', 'value')(o) for o in self.doc.xpath(self.RECIPIENT_XPATH) if - Regexp(CleanText('.'), '.* - ([A-Za-z0-9]*) -', default=NotAvailable)(o) == recipient.iban] + recipient_value = [ + Attr('.', 'value')(o) + for o in self.doc.xpath(self.RECIPIENT_XPATH) + if Regexp(CleanText('.'), r'.* - ([A-Za-z0-9]*) -', default=NotAvailable)(o) == recipient.iban + ] elif recipient.category == 'Interne': - recipient_value = [Attr('.', 'value')(o) for o in self.doc.xpath(self.RECIPIENT_XPATH) if - Regexp(CleanText('.'), '- (\d+)', default=NotAvailable)(o) and Regexp(CleanText('.'), '- (\d+)', default=NotAvailable)(o) in recipient.id] + recipient_value = [ + Attr('.', 'value')(o) + for o in self.doc.xpath(self.RECIPIENT_XPATH) + if ( + Regexp(CleanText('.'), r'- (\d+)', default=NotAvailable)(o) + and Regexp(CleanText('.'), r'- (\d+)', default=NotAvailable)(o) in recipient.id + ) + ] assert len(recipient_value) == 1, 'error during recipient matching' return recipient_value[0] @@ -1802,7 +1941,10 @@ def continue_transfer(self, origin_label, recipient_label, label): form = self.get_form(id='main') transfer_type = self.get_transfer_type() - fill = lambda s, t: s % (t.upper(), t.capitalize()) + + def fill(s, t): + return s % (t.upper(), t.capitalize()) + form['__EVENTTARGET'] = 'MM$VIREMENT$m_WizardBar$m_lnkNext$m_lnkButton' form[fill('MM$VIREMENT$SAISIE_VIREMENT_%s$m_Virement%s$txtIdentBenef', transfer_type)] = recipient_label form[fill('MM$VIREMENT$SAISIE_VIREMENT_%s$m_Virement%s$txtIdent', transfer_type)] = origin_label @@ -1813,7 +1955,10 @@ def continue_transfer(self, origin_label, recipient_label, label): def go_add_recipient(self): form = self.get_form(id='main') link = self.doc.xpath('//a[span[contains(text(), "Ajouter un compte bénéficiaire")]]')[0] - m = re.search("PostBackOptions?\([\"']([^\"']+)[\"'],\s*['\"]([^\"']+)?['\"]", link.attrib.get('href', '')) + m = re.search( + r"PostBackOptions?\([\"']([^\"']+)[\"'],\s*['\"]([^\"']+)?['\"]", + link.attrib.get('href', '') + ) form['__EVENTTARGET'] = m.group(1) form['__EVENTARGUMENT'] = m.group(2) form.submit() @@ -1869,11 +2014,20 @@ def update_transfer(self, transfer, account=None, recipient=None): or CleanText('.//tr[td[contains(text(), "Libellé")]]/td[not(@class)]')(self.doc) or CleanText('.//tr[th[contains(text(), "Libellé")]]/td[not(@class)]')(self.doc) ) - transfer.exec_date = Date(CleanText('.//tr[th[contains(text(), "En date du")]]/td[not(@class)]'), dayfirst=True)(self.doc) - transfer.amount = CleanDecimal('.//tr[td[contains(text(), "Montant")]]/td[not(@class)] | \ - .//tr[th[contains(text(), "Montant")]]/td[not(@class)]', replace_dots=True)(self.doc) - transfer.currency = FrenchTransaction.Currency('.//tr[td[contains(text(), "Montant")]]/td[not(@class)] | \ - .//tr[th[contains(text(), "Montant")]]/td[not(@class)]')(self.doc) + transfer.exec_date = Date( + CleanText('.//tr[th[contains(text(), "En date du")]]/td[not(@class)]'), + dayfirst=True + )(self.doc) + transfer.amount = CleanDecimal( + ''' + .//tr[td[contains(text(), "Montant")]]/td[not(@class)] + | .//tr[th[contains(text(), "Montant")]]/td[not(@class)] + ''', + replace_dots=True)(self.doc) + transfer.currency = FrenchTransaction.Currency(''' + .//tr[td[contains(text(), "Montant")]]/td[not(@class)] + | .//tr[th[contains(text(), "Montant")]]/td[not(@class)] + ''')(self.doc) # recipient transfer informations, update information if there is no OTP SMS validation if recipient: @@ -1881,12 +2035,16 @@ def update_transfer(self, transfer, account=None, recipient=None): transfer.recipient_id = recipient.id if recipient.category == 'Externe': - for word in Upper(CleanText('.//tr[th[contains(text(), "Compte à créditer")]]/td[not(@class)]'))(self.doc).split(): + all_text = Upper(CleanText( + '''.//tr[th[contains(text(), "Compte à créditer")]]/td[not(@class)]''' + ))(self.doc) + + for word in all_text.split(): if is_iban_valid(word): transfer.recipient_iban = word break else: - assert False, 'Unable to find IBAN (original was %s)' % recipient.iban + raise AssertionError('Unable to find IBAN (original was %s)' % recipient.iban) else: transfer.recipient_iban = recipient.iban @@ -1902,7 +2060,9 @@ def update_transfer(self, transfer, account=None, recipient=None): class ProTransferConfirmPage(TransferConfirmPage): def is_here(self): - return bool(CleanText('//span[@id="MM_m_CH_lblTitle" and contains(text(), "Confirmez votre virement")]')(self.doc)) + return bool(CleanText(''' + //span[@id="MM_m_CH_lblTitle" and contains(text(), "Confirmez votre virement")] + ''')(self.doc)) def continue_transfer(self, origin_label, recipient, label): # Pro internal transfer initiation doesn't need a second step. @@ -1910,18 +2070,26 @@ def continue_transfer(self, origin_label, recipient, label): def create_transfer(self, account, recipient, transfer): t = Transfer() - t.currency = FrenchTransaction.Currency('//span[@id="MM_VIREMENT_CONF_VIREMENT_MontantVir"] | \ - //span[@id="MM_VIREMENT_CONF_VIREMENT_lblMontantSelect"]')(self.doc) - t.amount = CleanDecimal('//span[@id="MM_VIREMENT_CONF_VIREMENT_MontantVir"] | \ - //span[@id="MM_VIREMENT_CONF_VIREMENT_lblMontantSelect"]', replace_dots=True)(self.doc) + t.currency = FrenchTransaction.Currency(''' + //span[@id="MM_VIREMENT_CONF_VIREMENT_MontantVir"] + | //span[@id="MM_VIREMENT_CONF_VIREMENT_lblMontantSelect"] + ''')(self.doc) + t.amount = CleanDecimal( + ''' + //span[@id="MM_VIREMENT_CONF_VIREMENT_MontantVir"] + | //span[@id="MM_VIREMENT_CONF_VIREMENT_lblMontantSelect"] + ''', + replace_dots=True + )(self.doc) t.account_iban = account.iban if recipient.category == 'Externe': - for word in Upper(CleanText('//span[@id="MM_VIREMENT_CONF_VIREMENT_lblCptCrediterResult"]'))(self.doc).split(): + all_text = Upper(CleanText('//span[@id="MM_VIREMENT_CONF_VIREMENT_lblCptCrediterResult"]'))(self.doc) + for word in all_text.split(): if is_iban_valid(word): t.recipient_iban = word break else: - assert False, 'Unable to find IBAN (original was %s)' % recipient.iban + raise AssertionError('Unable to find IBAN (original was %s)' % recipient.iban) else: t.recipient_iban = recipient.iban t.recipient_iban = recipient.iban @@ -1931,8 +2099,10 @@ def create_transfer(self, account, recipient, transfer): t.recipient_label = recipient.label t._account = account t._recipient = recipient - t.label = CleanText('//span[@id="MM_VIREMENT_CONF_VIREMENT_Libelle"] | \ - //span[@id="MM_VIREMENT_CONF_VIREMENT_lblMotifSelect"]')(self.doc) + t.label = CleanText(''' + //span[@id="MM_VIREMENT_CONF_VIREMENT_Libelle"] + | //span[@id="MM_VIREMENT_CONF_VIREMENT_lblMotifSelect"] + ''')(self.doc) t.exec_date = Date(CleanText('//span[@id="MM_VIREMENT_CONF_VIREMENT_DateVir"]'), dayfirst=True)(self.doc) t.account_balance = account.balance return t @@ -1943,7 +2113,7 @@ def is_here(self): return bool(CleanText('//h2[contains(text(), "Accusé de réception")]')(self.doc)) def populate_reference(self, transfer): - transfer.id = Regexp(CleanText('//p[contains(text(), "a bien été enregistré")]'), '(\d+)')(self.doc) + transfer.id = Regexp(CleanText('//p[contains(text(), "a bien été enregistré")]'), r'(\d+)')(self.doc) return transfer @@ -1952,7 +2122,10 @@ def is_here(self): return bool(CleanText('//span[@id="MM_m_CH_lblTitle" and contains(text(), "Accusé de réception")]')(self.doc)) def populate_reference(self, transfer): - transfer.id = Regexp(CleanText('//span[@id="MM_VIREMENT_AR_VIREMENT_lblVirementEnregistre"]'), '(\d+( - \d+)?)')(self.doc) + transfer.id = Regexp( + CleanText('//span[@id="MM_VIREMENT_AR_VIREMENT_lblVirementEnregistre"]'), + r'(\d+( - \d+)?)' + )(self.doc) return transfer @@ -1960,7 +2133,9 @@ class ProTransferPage(TransferPage): RECIPIENT_XPATH = '//select[@id="MM_VIREMENT_SAISIE_VIREMENT_ddlCompteCrediterPro"]/option' def is_here(self): - return CleanText('//span[contains(text(), "Créer une liste de virements")] | //span[contains(text(), "Réalisez un virement")]')(self.doc) + return CleanText(''' + //span[contains(text(), "Créer une liste de virements")] | //span[contains(text(), "Réalisez un virement")] + ''')(self.doc) @method class iter_recipients(MyRecipients): @@ -2061,8 +2236,10 @@ def on_load(self): raise AddRecipientBankError(message=error) def is_here(self): - return bool(CleanText('//h2[contains(text(), "Ajouter un compte bénéficiaire")] |\ - //h2[contains(text(), "Confirmer l\'ajout d\'un compte bénéficiaire")]')(self.doc)) + return bool(CleanText(''' + //h2[contains(text(), "Ajouter un compte bénéficiaire")] + | //h2[contains(text(), "Confirmer l\'ajout d\'un compte bénéficiaire")] + ''')(self.doc)) def post_recipient(self, recipient): form = self.get_form(id='main') @@ -2102,14 +2279,18 @@ class ProAddRecipientPage(RecipientPage): FORM_FIELD_ADD = 'MM$WIZARD_AJOUT_COMPTE_TIERS$COMPTES_TIERS_ADD' def is_here(self): - return CleanText('//span[@id="MM_m_CH_lblTitle" and contains(text(), "Ajoutez un compte tiers")] |\ - //span[@id="MM_m_CH_lblTitle" and contains(text(), "Confirmez votre ajout")]')(self.doc) + return CleanText(''' + //span[@id="MM_m_CH_lblTitle" and contains(text(), "Ajoutez un compte tiers")] + | //span[@id="MM_m_CH_lblTitle" and contains(text(), "Confirmez votre ajout")] + ''')(self.doc) class TransactionsDetailsPage(LoggedPage, HTMLPage): def is_here(self): - return bool(CleanText('//h2[contains(text(), "Débits différés imputés")] | //span[@id="MM_m_CH_lblTitle" and contains(text(), "Débit différé imputé")]')(self.doc)) + return bool(CleanText( + '//h2[contains(text(), "Débits différés imputés")] | //span[@id="MM_m_CH_lblTitle" and contains(text(), "Débit différé imputé")]' + )(self.doc)) @pagination @method @@ -2124,8 +2305,10 @@ class get_detail(TableElement): def next_page(self): # only for new website, don't have any accounts with enough deferred card transactions on old webiste - if self.page.doc.xpath('//a[contains(@id, "lnkSuivante") and not(contains(@disabled,"disabled")) \ - and not(contains(@class, "aspNetDisabled"))]'): + if self.page.doc.xpath(''' + //a[contains(@id, "lnkSuivante") and not(contains(@disabled,"disabled")) + and not(contains(@class, "aspNetDisabled"))] + '''): form = self.page.get_form(id='main') form['__EVENTTARGET'] = "MM$ECRITURE_GLOBALE$lnkSuivante" form['__EVENTARGUMENT'] = '' @@ -2147,7 +2330,7 @@ def obj_amount(self): def go_form_to_summary(self): # return to first page to_history = Link(self.doc.xpath('//a[contains(text(), "Retour à l\'historique")]'))(self.doc) - n = re.match('.*\([\'\"](MM\$.*?)[\'\"],.*\)$', to_history) + n = re.match(r'.*\([\'\"](MM\$.*?)[\'\"],.*\)$', to_history) form = self.get_form(id='main') form['__EVENTTARGET'] = n.group(1) form.submit() @@ -2188,7 +2371,10 @@ class iter_documents(ListElement): @property def item_xpath(self): if Env('has_subscription')(self): - return '//h3[contains(text(), "%s")]//following-sibling::div[@class="panel"][1]/table/tbody/tr' % Env('sub_id')(self) + return ( + '//h3[contains(text(), "%s")]//following-sibling::div[@class="panel"][1]/table/tbody/tr' + % Env('sub_id')(self) + ) return '//div[@id="MM_CONSULTATION_RELEVES_COURRIERS_EDOCUMENTS_divRelevesCourriers"]/table/tbody/tr' class item(ItemElement): @@ -2196,7 +2382,12 @@ class item(ItemElement): obj_format = 'pdf' obj_url = Regexp(Link('.//td[@class="telecharger"]//a'), r'WebForm_PostBackOptions\("(\S*)"') - obj_id = Format('%s_%s_%s', Env('sub_id'), CleanText('./td[2]', symbols='/', replace=[(' ', '_')]), Regexp(CleanText('./td[3]'), r'([\wé]*)')) + obj_id = Format( + '%s_%s_%s', + Env('sub_id'), + CleanText('./td[2]', symbols='/', replace=[(' ', '_')]), + Regexp(CleanText('./td[3]'), r'([\wé]*)') + ) obj_label = Format('%s %s', CleanText('./td[3]'), CleanText('./td[2]')) obj_date = Date(CleanText('./td[2]'), dayfirst=True) diff --git a/modules/caissedepargne/transfer_pages.py b/modules/caissedepargne/transfer_pages.py index b1f69a7aeacaea7f71250475648c72dacc427c76..0afeb75dac6763e036aa1ac816023e35da36579a 100644 --- a/modules/caissedepargne/transfer_pages.py +++ b/modules/caissedepargne/transfer_pages.py @@ -17,6 +17,8 @@ # 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