diff --git a/modules/lcl/browser.py b/modules/lcl/browser.py
index de496b1c85ff3cc30d9bc695263293c5e9e0156f..9d16ef5e44f8cc651728cb33f89ee298c181bfc4 100644
--- a/modules/lcl/browser.py
+++ b/modules/lcl/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
@@ -26,7 +27,6 @@
from functools import wraps
from dateutil.relativedelta import relativedelta
-
from weboob.exceptions import (
BrowserIncorrectPassword, BrowserUnavailable,
AuthMethodNotImplemented, ActionNeeded,
@@ -137,7 +137,10 @@ class LCLBrowser(LoginBrowser, StatesMixin):
av_list = URL(r'https://assurance-vie-et-prevoyance.secure.lcl.fr/rest/assurance/synthesePartenaire', AVListPage)
avdetail = URL(r'https://assurance-vie-et-prevoyance.secure.lcl.fr/consultation/epargne', AVDetailPage)
av_history = URL(r'https://assurance-vie-et-prevoyance.secure.lcl.fr/rest/assurance/historique', AVHistoryPage)
- av_investments = URL(r'https://assurance-vie-et-prevoyance.secure.lcl.fr/rest/detailEpargne/contrat/(?P\w+)', AVInvestmentsPage)
+ av_investments = URL(
+ r'https://assurance-vie-et-prevoyance.secure.lcl.fr/rest/detailEpargne/contrat/(?P\w+)',
+ AVInvestmentsPage
+ )
loans = URL(r'/outil/UWCR/SynthesePar/', LoansPage)
loans_pro = URL(r'/outil/UWCR/SynthesePro/', LoansProPage)
@@ -168,8 +171,11 @@ class LCLBrowser(LoginBrowser, StatesMixin):
profile = URL(r'/outil/UWIP/Accueil/rafraichir', ProfilePage)
- deposit = URL(r'/outil/UWPL/CompteATerme/accesSynthese',
- r'/outil/UWPL/DetailCompteATerme/accesDetail', DepositPage)
+ deposit = URL(
+ r'/outil/UWPL/CompteATerme/accesSynthese',
+ r'/outil/UWPL/DetailCompteATerme/accesDetail',
+ DepositPage
+ )
__states__ = ('contracts', 'current_contract', 'parsed_contracts')
@@ -371,16 +377,26 @@ def get_accounts(self):
continue
self.location('/outil/UWRI/Accueil/')
+
if self.no_perm.is_here():
self.logger.warning('RIB is unavailable.')
+
elif self.page.has_iban_choice():
self.rib.go(data={'compte': '%s/%s/%s' % (a.id[0:5], a.id[5:11], a.id[11:])})
if self.rib.is_here():
iban = self.page.get_iban()
- a.iban = iban if iban and a.id[11:] in iban else NotAvailable
+ if iban and a.id[11:] in iban:
+ a.iban = iban
+ else:
+ a.iban = NotAvailable
+
else:
iban = self.page.check_iban_by_account(a.id)
- a.iban = iban if iban is not None else NotAvailable
+ if iban:
+ a.iban = iban
+ else:
+ a.iban = NotAvailable
+
self.update_accounts(a)
# retrieve loans accounts
@@ -460,7 +476,11 @@ def set_ownership(self, account, owner_name):
if not account.ownership:
if account.parent and account.parent.ownership:
account.ownership = account.parent.ownership
- elif re.search(r'(m|mr|me|mme|mlle|mle|ml)\.? (.*)\bou (m|mr|me|mme|mlle|mle|ml)\b(.*)', account.label, re.IGNORECASE):
+ elif re.search(
+ r'(m|mr|me|mme|mlle|mle|ml)\.? (.*)\bou (m|mr|me|mme|mlle|mle|ml)\b(.*)',
+ account.label,
+ re.IGNORECASE
+ ):
account.ownership = AccountOwnership.CO_OWNER
elif all(n in account.label for n in owner_name.split()):
account.ownership = AccountOwnership.OWNER
@@ -479,10 +499,13 @@ def get_bourse_accounts_ids(self):
def get_history(self, account):
if hasattr(account, '_market_link') and account._market_link:
self.connexion_bourse()
- self.location(account._link_id, params={
- 'nump': account._market_id,
- })
+ self.location(
+ account._link_id, params={
+ 'nump': account._market_id,
+ }
+ )
self.page.get_fullhistory()
+
for tr in self.page.iter_history():
yield tr
self.deconnexion_bourse()
@@ -682,7 +705,7 @@ def init_new_recipient(self, recipient, **params):
# Send sms to user.
data = [
('telChoisi', 'MOBILE'),
- ('_', int(round(time.time() * 1000)))
+ ('_', int(round(time.time() * 1000))),
]
self.location('/outil/UWAF/Otp/envoiCodeOtp', params=data)
self.page.check_error()
diff --git a/modules/lcl/enterprise/browser.py b/modules/lcl/enterprise/browser.py
index 8bc424a40d56eb9a94a161ef7099861a93233d09..0b50f7a361530946e8bb376e052001acc9f5b08d 100644
--- a/modules/lcl/enterprise/browser.py
+++ b/modules/lcl/enterprise/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 weboob.browser import LoginBrowser, URL, need_login
from weboob.exceptions import BrowserIncorrectPassword
@@ -29,10 +30,16 @@ class LCLEnterpriseBrowser(LoginBrowser):
BASEURL = 'https://entreprises.secure.lcl.fr'
pass_expired = URL('/outil/IQEN/Authentication/forcerChangePassword', PassExpiredPage)
- login = URL('/outil/IQEN/Authentication/indexRedirect',
- '/outil/IQEN/Authentication/(?P.*)', LoginPage)
- movements = URL('/outil/IQMT/mvt.Synthese/syntheseMouvementPerso',
- '/outil/IQMT/mvt.Synthese', MovementsPage)
+ login = URL(
+ '/outil/IQEN/Authentication/indexRedirect',
+ '/outil/IQEN/Authentication/(?P.*)',
+ LoginPage
+ )
+ movements = URL(
+ '/outil/IQMT/mvt.Synthese/syntheseMouvementPerso',
+ '/outil/IQMT/mvt.Synthese',
+ MovementsPage
+ )
profile = URL('/outil/IQGA/FicheUtilisateur/maFicheUtilisateur', ProfilePage)
def __init__(self, *args, **kwargs):
@@ -40,7 +47,6 @@ def __init__(self, *args, **kwargs):
self.accounts = None
self.owner_type = AccountOwnerType.ORGANIZATION
-
def deinit(self):
if self.page and self.page.logged:
self.login.go(page="logout")
@@ -51,10 +57,11 @@ def deinit(self):
def do_login(self):
self.login.go().login(self.username, self.password)
- error = self.page.get_error() if self.login.is_here() else False
+ if self.login.is_here():
+ error = self.page.get_error()
- if error:
- raise BrowserIncorrectPassword(error)
+ if error:
+ raise BrowserIncorrectPassword(error)
@need_login
def get_accounts_list(self):
diff --git a/modules/lcl/enterprise/pages.py b/modules/lcl/enterprise/pages.py
index 147ada6e5cce7559d5f505e09a9c7a9890d7a39e..a368a74395025b48d53f3d176fc2baf6e2928cc6 100644
--- a/modules/lcl/enterprise/pages.py
+++ b/modules/lcl/enterprise/pages.py
@@ -18,8 +18,9 @@
# along with this weboob module. If not, see .
-import re, requests
+import re
+import requests
from weboob.browser.pages import HTMLPage, LoggedPage, pagination
from weboob.browser.elements import ListElement, ItemElement, TableElement, method
from weboob.browser.filters.standard import CleanText, Date, CleanDecimal, Env
@@ -52,7 +53,7 @@ def login(self, login, password):
class MovementsPage(LoggedPage, HTMLPage):
def get_changecompte(self, link):
form = self.get_form('//form[contains(@action, "changeCompte")]')
- m = re.search('\'(\d+).*\'(\d+)', link)
+ m = re.search(r"'(\d+).*'(\d+)", link)
form['perimetreMandatParentData'] = m.group(1)
form['perimetreMandatEnfantData'] = m.group(2)
# Can't do multi with async because of inconsistency...
@@ -93,14 +94,16 @@ def obj_label(self):
obj__data = Env('data')
def parse(self, el):
- page, url, data = self.page.get_changecompte(Link('.')(self)) if self.env['multi'] else (self.page, None, None)
+ page, url, data = (self.page, None, None)
+ if self.env['multi']:
+ page, url, data = self.page.get_changecompte(Link('.')(self))
+
balance_xpath = '//div[contains(text(),"Solde")]/strong'
self.env['balance'] = MyDecimal().filter(page.doc.xpath(balance_xpath))
self.env['currency'] = Account.get_currency(CleanText().filter(page.doc.xpath(balance_xpath)))
self.env['url'] = url
self.env['data'] = data
-
@pagination
@method
class iter_history(TableElement):
@@ -114,7 +117,7 @@ class iter_history(TableElement):
def next_page(self):
url = Link('//a[contains(text(), "Page suivante")]', default=None)(self)
if url:
- m = re.search('\s+\'([^\']+).*\'(\d+)', url)
+ m = re.search(r"\s+'([^']+).*'(\d+)", url)
return requests.Request("POST", m.group(1), data={'numPage': m.group(2)})
class item(ItemElement):
@@ -127,7 +130,9 @@ class item(ItemElement):
def obj_amount(self):
credit = MyDecimal(TableCell('credit'))(self)
debit = MyDecimal(TableCell('debit'))(self)
- return credit if credit else -debit
+ if credit:
+ return credit
+ return -debit
class ProfilePage(LoggedPage, HTMLPage):
diff --git a/modules/lcl/module.py b/modules/lcl/module.py
index c91d9bce554a1d87c9fd6da3469f35c25f822141..4fbad1da98f8e446484c07257d4eaf64c65b3aec 100644
--- a/modules/lcl/module.py
+++ b/modules/lcl/module.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 decimal import Decimal
from functools import wraps
@@ -65,31 +66,45 @@ class LCLModule(Module, CapBankWealth, CapBankTransferAddRecipient, CapContact,
VERSION = '2.1'
DESCRIPTION = u'LCL'
LICENSE = 'LGPLv3+'
- CONFIG = BackendConfig(ValueBackendPassword('login', label='Identifiant', masked=False),
- ValueBackendPassword('password', label='Code personnel'),
- Value('website', label='Type de compte', default='par',
- choices={'par': 'Particuliers',
- 'pro': 'Professionnels',
- 'ent': 'Entreprises',
- 'esp': 'Espace Pro'},
- aliases={'elcl': 'par'}))
+ CONFIG = BackendConfig(
+ ValueBackendPassword('login', label='Identifiant', masked=False),
+ ValueBackendPassword('password', label='Code personnel'),
+ Value(
+ 'website',
+ label='Type de compte',
+ default='par',
+ choices={
+ 'par': 'Particuliers',
+ 'pro': 'Professionnels',
+ 'ent': 'Entreprises',
+ 'esp': 'Espace Pro',
+ },
+ aliases={'elcl': 'par'}
+ )
+ )
BROWSER = LCLBrowser
accepted_document_types = (DocumentTypes.STATEMENT, DocumentTypes.NOTICE, DocumentTypes.REPORT, DocumentTypes.OTHER)
def create_default_browser(self):
# assume all `website` option choices are defined here
- browsers = {'par': LCLBrowser,
- 'pro': LCLProBrowser,
- 'ent': LCLEnterpriseBrowser,
- 'esp': LCLEspaceProBrowser}
+ browsers = {
+ 'par': LCLBrowser,
+ 'pro': LCLProBrowser,
+ 'ent': LCLEnterpriseBrowser,
+ 'esp': LCLEspaceProBrowser,
+ }
website_value = self.config['website']
- self.BROWSER = browsers.get(website_value.get(),
- browsers[website_value.default])
-
- return self.create_browser(self.config['login'].get(),
- self.config['password'].get())
+ self.BROWSER = browsers.get(
+ website_value.get(),
+ browsers[website_value.default]
+ )
+
+ return self.create_browser(
+ self.config['login'].get(),
+ self.config['password'].get()
+ )
def iter_accounts(self):
return self.browser.get_accounts_list()
diff --git a/modules/lcl/pages.py b/modules/lcl/pages.py
index 84f1dc0c4866714d848abfdc8059ab9e7d4c0a6d..7736e7a03b74387461aca40356aacf389e75199b 100644
--- a/modules/lcl/pages.py
+++ b/modules/lcl/pages.py
@@ -16,6 +16,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, division
import re
@@ -112,7 +114,7 @@ class LCLVirtKeyboard(MappedVirtKeyboard):
'6': 'aba912172f21f78cd6da437cfc4cdbd0',
'7': 'f710190d6b947869879ec02d8e851dfa',
'8': 'b42cc25e1539a15f767aa7a641f3bfec',
- '9': 'cc60e5894a9d8e12ee0c2c104c1d5490'
+ '9': 'cc60e5894a9d8e12ee0c2c104c1d5490',
}
url = "/outil/UAUT/Clavier/creationClavier?random="
@@ -344,13 +346,25 @@ class get_advisor(ItemElement):
obj_name = CleanText('//div[@id="contacterMaBqMenu"]//p[@id="itemNomContactMaBq"]/span')
obj_email = obj_mobile = obj_fax = NotAvailable
- obj_phone = Regexp(CleanText('//div[@id="contacterMaBqMenu"]//p[contains(text(), "Tel")]', replace=[(' ', '')]), '([\s\d]+)', default=NotAvailable)
+ obj_phone = Regexp(
+ CleanText('//div[@id="contacterMaBqMenu"]//p[contains(text(), "Tel")]', replace=[(' ', '')]),
+ r'([\s\d]+)',
+ default=NotAvailable
+ )
obj_agency = CleanText('//div[@id="sousContentAgence"]//p[@class="itemSousTitreMenuMaBq"][1]')
def obj_address(self):
- address = CleanText('//div[@id="sousContentAgence"]//p[@class="itemSousTitreMenuMaBq"][2]', default=None)(self)
- city = CleanText('//div[@id="sousContentAgence"]//p[@class="itemSousTitreMenuMaBq"][3]', default=None)(self)
- return "%s %s" % (address, city) if address and city else NotAvailable
+ address = CleanText(
+ '//div[@id="sousContentAgence"]//p[@class="itemSousTitreMenuMaBq"][2]',
+ default=None
+ )(self)
+ city = CleanText(
+ '//div[@id="sousContentAgence"]//p[@class="itemSousTitreMenuMaBq"][3]',
+ default=None
+ )(self)
+ if not (address and city):
+ return NotAvailable
+ return "%s %s" % (address, city)
class LoansPage(LoggedPage, HTMLPage):
@@ -375,20 +389,37 @@ class account(ItemElement):
def obj_label(self):
has_type = CleanText('./ancestor::table[.//th[contains(text(), "Type")]]', default=None)(self)
- return CleanText('./td[2]')(self) if has_type else CleanText('./ancestor::table/preceding-sibling::div[1]')(self).split(' - ')[0]
+ if has_type:
+ return CleanText('./td[2]')(self)
+ else:
+ return CleanText('./ancestor::table/preceding-sibling::div[1]')(self).split(' - ')[0]
def obj_ownership(self):
- if re.search(r'(m|mr|me|mme|mlle|mle|ml)\.? (.*)\b(ou)? (m|mr|me|mme|mlle|mle|ml)\b(.*)', CleanText(TableCell('id'))(self), re.IGNORECASE):
+ pattern = re.compile(
+ r'(m|mr|me|mme|mlle|mle|ml)\.? (.*)\b(ou)? (m|mr|me|mme|mlle|mle|ml)\b(.*)',
+ re.IGNORECASE
+ )
+ if pattern.search(CleanText(TableCell('id'))(self)):
return AccountOwnership.CO_OWNER
return AccountOwnership.OWNER
def parse(self, el):
label = Field('label')(self)
- trs = self.xpath('//td[contains(text(), $label)]/ancestor::tr[1] | ./ancestor::table[1]/tbody/tr', label=label)
+ trs = self.xpath(
+ '//td[contains(text(), $label)]/ancestor::tr[1] | ./ancestor::table[1]/tbody/tr',
+ label=label
+ )
i = [i for i in range(len(trs)) if el == trs[i]]
- i = i[0] if i else 0
+ if i:
+ i = i[0]
+ else:
+ i = 0
label = label.replace(' ', '')
- self.env['id'] = "%s%s%s" % (Regexp(CleanText(TableCell('id')), r'(\w+)\s-\s(\w+)', r'\1\2')(self), label.replace(' ', ''), i)
+ self.env['id'] = "%s%s%s" % (
+ Regexp(CleanText(TableCell('id')), r'(\w+)\s-\s(\w+)', r'\1\2')(self),
+ label.replace(' ', ''),
+ i,
+ )
class LoansProPage(LoggedPage, HTMLPage):
@@ -413,43 +444,65 @@ class account(ItemElement):
def obj_label(self):
has_type = CleanText('./ancestor::table[.//th[contains(text(), "Nature libell")]]', default=None)(self)
- return CleanText('./td[3]')(self) if has_type else CleanText('./ancestor::table/preceding-sibling::div[1]')(self).split(' - ')[0]
+ if has_type:
+ return CleanText('./td[3]')(self)
+ else:
+ return CleanText('./ancestor::table/preceding-sibling::div[1]')(self).split(' - ')[0]
def parse(self, el):
label = Field('label')(self)
- trs = self.xpath('//td[contains(text(), $label)]/ancestor::tr[1] | ./ancestor::table[1]/tbody/tr', label=label)
+ trs = self.xpath(
+ '//td[contains(text(), $label)]/ancestor::tr[1] | ./ancestor::table[1]/tbody/tr',
+ label=label
+ )
i = [i for i in range(len(trs)) if el == trs[i]]
- i = i[0] if i else 0
+ if i:
+ i = i[0]
+ else:
+ i = 0
label = label.replace(' ', '')
- self.env['id'] = "%s%s%s" % (Regexp(CleanText(TableCell('id')), r'(\w+)\s-\s(\w+)', r'\1\2')(self), label.replace(' ', ''), i)
+ self.env['id'] = "%s%s%s" % (
+ Regexp(CleanText(TableCell('id')), r'(\w+)\s-\s(\w+)', r'\1\2')(self),
+ label.replace(' ', ''),
+ i,
+ )
class Transaction(FrenchTransaction):
PATTERNS = [
- (re.compile('^(?PCB) (?PRETRAIT) DU (?P\d+)/(?P\d+)'), FrenchTransaction.TYPE_WITHDRAWAL),
- (re.compile('^(?P(PRLV|PE)( SEPA)?) (?P.*)'), FrenchTransaction.TYPE_ORDER),
- (re.compile('^(?PCHQ\.) (?P.*)'), FrenchTransaction.TYPE_CHECK),
- (re.compile('^(?PRELEVE CB) AU (\d+)/(\d+)/(\d+)'), FrenchTransaction.TYPE_CARD),
- (re.compile('^(?PCB) (?P.*) (?P\d+)/(?P\d+)/(?P\d+)'), FrenchTransaction.TYPE_CARD),
- (re.compile('^(?P(PRELEVEMENT|TELEREGLEMENT|TIP)) (?P.*)'), FrenchTransaction.TYPE_ORDER),
- (re.compile('^(?P(ECHEANCE\s*)?PRET)(?P.*)'), FrenchTransaction.TYPE_LOAN_PAYMENT),
- (re.compile('^(TP-\d+-)?(?P(EVI|VIR(EM(EN)?)?T?)(.PERMANENT)? ((RECU|FAVEUR) TIERS|SEPA RECU)?)( /FRM)?(?P.*)'), FrenchTransaction.TYPE_TRANSFER),
- (re.compile('^(?PREMBOURST)(?P.*)'), FrenchTransaction.TYPE_PAYBACK),
- (re.compile('^(?PCOM(MISSIONS?)?)(?P.*)'), FrenchTransaction.TYPE_BANK),
- (re.compile('^(?P(?PREMUNERATION).*)'), FrenchTransaction.TYPE_BANK),
- (re.compile('^(?P(?PABON.*?)\s*.*)'), FrenchTransaction.TYPE_BANK),
- (re.compile('^(?P(?PRESULTAT .*?)\s*.*)'), FrenchTransaction.TYPE_BANK),
- (re.compile('^(?P(?PTRAIT\..*?)\s*.*)'), FrenchTransaction.TYPE_BANK),
+ (
+ re.compile(r'^(?PCB) (?PRETRAIT) DU (?P\d+)/(?P\d+)'),
+ FrenchTransaction.TYPE_WITHDRAWAL,
+ ),
+ (re.compile(r'^(?P(PRLV|PE)( SEPA)?) (?P.*)'), FrenchTransaction.TYPE_ORDER),
+ (re.compile(r'^(?PCHQ\.) (?P.*)'), FrenchTransaction.TYPE_CHECK),
+ (re.compile(r'^(?PRELEVE CB) AU (\d+)/(\d+)/(\d+)'), FrenchTransaction.TYPE_CARD),
+ (
+ re.compile(r'^(?PCB) (?P.*) (?P\d+)/(?P\d+)/(?P\d+)'),
+ FrenchTransaction.TYPE_CARD,
+ ),
+ (re.compile(r'^(?P(PRELEVEMENT|TELEREGLEMENT|TIP)) (?P.*)'), FrenchTransaction.TYPE_ORDER),
+ (re.compile(r'^(?P(ECHEANCE\s*)?PRET)(?P.*)'), FrenchTransaction.TYPE_LOAN_PAYMENT),
+ (
+ re.compile(r'^(TP-\d+-)?(?P(EVI|VIR(EM(EN)?)?T?)(.PERMANENT)? ((RECU|FAVEUR) TIERS|SEPA RECU)?)( /FRM)?(?P.*)'),
+ FrenchTransaction.TYPE_TRANSFER,
+ ),
+ (re.compile(r'^(?PREMBOURST)(?P.*)'), FrenchTransaction.TYPE_PAYBACK),
+ (re.compile(r'^(?PCOM(MISSIONS?)?)(?P.*)'), FrenchTransaction.TYPE_BANK),
+ (re.compile(r'^(?P(?PREMUNERATION).*)'), FrenchTransaction.TYPE_BANK),
+ (re.compile(r'^(?P(?PABON.*?)\s*.*)'), FrenchTransaction.TYPE_BANK),
+ (re.compile(r'^(?P(?PRESULTAT .*?)\s*.*)'), FrenchTransaction.TYPE_BANK),
+ (re.compile(r'^(?P(?PTRAIT\..*?)\s*.*)'), FrenchTransaction.TYPE_BANK),
(re.compile(r'(?P(?PCOTISATION).*)'), FrenchTransaction.TYPE_BANK),
(re.compile(r'(?P(?PINTERETS).*)'), FrenchTransaction.TYPE_BANK),
- (re.compile('^(?PREM CHQ) (?P.*)'), FrenchTransaction.TYPE_DEPOSIT),
- (re.compile('^VIREMENT.*'), FrenchTransaction.TYPE_TRANSFER),
- (re.compile('.*(PRELEVEMENTS|PRELVT|TIP).*'), FrenchTransaction.TYPE_ORDER),
- (re.compile('.*CHEQUE.*'), FrenchTransaction.TYPE_CHECK),
- (re.compile('.*ESPECES.*'), FrenchTransaction.TYPE_DEPOSIT),
- (re.compile('.*(CARTE|CB).*'), FrenchTransaction.TYPE_CARD),
- (re.compile('.*(AGIOS|ANNULATIONS|IMPAYES|CREDIT).*'), FrenchTransaction.TYPE_BANK),
- (re.compile('.*(FRAIS DE TENUE DE COMPTE).*'), FrenchTransaction.TYPE_BANK),
+ (re.compile(r'^(?PREM CHQ) (?P.*)'), FrenchTransaction.TYPE_DEPOSIT),
+ (re.compile(r'^VIREMENT.*'), FrenchTransaction.TYPE_TRANSFER),
+ (re.compile(r'.*(PRELEVEMENTS|PRELVT|TIP).*'), FrenchTransaction.TYPE_ORDER),
+ (re.compile(r'.*CHEQUE.*'), FrenchTransaction.TYPE_CHECK),
+ (re.compile(r'.*ESPECES.*'), FrenchTransaction.TYPE_DEPOSIT),
+ (re.compile(r'.*(CARTE|CB).*'), FrenchTransaction.TYPE_CARD),
+ (re.compile(r'.*(AGIOS|ANNULATIONS|IMPAYES|CREDIT).*'), FrenchTransaction.TYPE_BANK),
+ (re.compile(r'.*(FRAIS DE TENUE DE COMPTE).*'), FrenchTransaction.TYPE_BANK),
(re.compile(r'.*\b(RETRAIT)\b.*'), FrenchTransaction.TYPE_WITHDRAWAL),
]
@@ -501,58 +554,124 @@ def load_details(self):
def obj_rdate(self):
rdate = self.obj.rdate
date = Field('date')(self)
+
if rdate > date:
date_guesser = Env('date_guesser')(self)
return date_guesser.guess_date(rdate.day, rdate.month)
+
return rdate
def obj_type(self):
- type = Async('details', CleanText(u'//td[contains(text(), "Nature de l\'opération")]/following-sibling::*[1]'))(self)
+ type = Async(
+ 'details',
+ CleanText("""//td[contains(text(), "Nature de l'opération")]/following-sibling::*[1]""")
+ )(self)
if not type:
return Transaction.TYPE_UNKNOWN
+
for pattern, _type in Transaction.PATTERNS:
match = pattern.match(type)
if match:
return _type
+
return Transaction.TYPE_UNKNOWN
def condition(self):
- return (self.parent.get_colnum('date') is not None
- and len(self.el.findall('td')) >= 3
- and self.el.get('class')
- and 'tableTr' not in self.el.get('class'))
+ return (
+ self.parent.get_colnum('date') is not None
+ and len(self.el.findall('td')) >= 3
+ and self.el.get('class')
+ and 'tableTr' not in self.el.get('class')
+ )
def validate(self, obj):
if obj.category == 'RELEVE CB':
obj.type = Transaction.TYPE_CARD_SUMMARY
- raw = Async('details', CleanText(u'//td[contains(text(), "Libellé")]/following-sibling::*[1]|//td[contains(text(), "Nom du donneur")]/following-sibling::*[1]', default=obj.raw))(self)
+ raw = Async(
+ 'details',
+ CleanText(
+ '//td[contains(text(), "Libellé")]/following-sibling::*[1]|//td[contains(text(), "Nom du donneur")]/following-sibling::*[1]',
+ default=obj.raw
+ )
+ )(self)
+
if raw:
- if obj.raw in raw or raw in obj.raw or ' ' not in obj.raw:
+ if (
+ obj.raw in raw
+ or raw in obj.raw
+ or ' ' not in obj.raw
+ ):
obj.raw = raw
obj.label = raw
else:
obj.label = '%s %s' % (obj.raw, raw)
obj.raw = '%s %s' % (obj.raw, raw)
+
m = re.search(r'\d+,\d+COM (\d+,\d+)', raw)
if m:
obj.commission = -CleanDecimal(replace_dots=True).filter(m.group(1))
+
elif not obj.raw:
# Empty transaction label
- obj.raw = obj.label = Async('details', CleanText(u'//td[contains(text(), "Nature de l\'opération")]/following-sibling::*[1]'))(self)
+ obj.raw = obj.label = Async(
+ 'details',
+ CleanText("""//td[contains(text(), "Nature de l'opération")]/following-sibling::*[1]""")
+ )(self)
+
# Some transactions have no details, but we can find the type of the transaction,
# the label and the category from the raw label.
if obj.type == Transaction.TYPE_UNKNOWN:
parse_with_patterns(obj.raw, obj, self.klass.PATTERNS)
+
if not obj.date:
- obj.date = Async('details', Date(CleanText(u'//td[contains(text(), "Date de l\'opération")]/following-sibling::*[1]', default=u''), dayfirst=True, default=NotAvailable))(self)
+ obj.date = Async(
+ 'details',
+ Date(
+ CleanText(
+ """//td[contains(text(), "Date de l'opération")]/following-sibling::*[1]""",
+ default=''
+ ),
+ dayfirst=True,
+ default=NotAvailable
+ )
+ )(self)
+
obj.rdate = obj.date
- obj.vdate = Async('details', Date(CleanText(u'//td[contains(text(), "Date de valeur")]/following-sibling::*[1]', default=u''), dayfirst=True, default=NotAvailable))(self)
- obj.amount = Async('details', CleanDecimal(u'//td[contains(text(), "Montant")]/following-sibling::*[1]', replace_dots=True, default=NotAvailable))(self)
+
+ obj.vdate = Async(
+ 'details',
+ Date(
+ CleanText(
+ '//td[contains(text(), "Date de valeur")]/following-sibling::*[1]',
+ default=''
+ ),
+ dayfirst=True,
+ default=NotAvailable
+ )
+ )(self)
+
+ obj.amount = Async(
+ 'details',
+ CleanDecimal(
+ '//td[contains(text(), "Montant")]/following-sibling::*[1]',
+ replace_dots=True,
+ default=NotAvailable
+ )
+ )(self)
+
# ugly hack to fix broken html
# sometimes transactions have really an amount of 0...
if not obj.amount and CleanDecimal(TableCell('credit'), default=None)(self) is None:
- obj.amount = Async('details', CleanDecimal(u'//td[contains(text(), "Montant")]/following-sibling::*[1]', replace_dots=True, default=NotAvailable))(self)
+ obj.amount = Async(
+ 'details',
+ CleanDecimal(
+ u'//td[contains(text(), "Montant")]/following-sibling::*[1]',
+ replace_dots=True,
+ default=NotAvailable
+ )
+ )(self)
+
return True
@pagination
@@ -565,10 +684,15 @@ def get_balance_without_comings(self):
default=NotAvailable
)(self.doc)
-class CardsPage(LoggedPage, HTMLPage):
+class CardsPage(LoggedPage, HTMLPage):
def deferred_date(self):
- deferred_date = Regexp(CleanText('//div[@class="date"][contains(text(), "Carte")]'), r'le ([^:]+)', default=None)(self.doc)
+ deferred_date = Regexp(
+ CleanText('//div[@class="date"][contains(text(), "Carte")]'),
+ r'le ([^:]+)',
+ default=None
+ )(self.doc)
+
assert deferred_date, 'Cannot find deferred_date'
return parse_french_date(deferred_date).date()
@@ -726,7 +850,9 @@ def open_iframe(self):
break
def password_required(self):
- return CleanText(u'//b[contains(text(), "Afin de sécuriser vos transactions, nous vous invitons à créer un mot de passe trading")]')(self.doc)
+ return CleanText(
+ '//b[contains(text(), "Afin de sécuriser vos transactions, nous vous invitons à créer un mot de passe trading")]'
+ )(self.doc)
def get_next(self):
if 'onload' in self.doc.xpath('.//body')[0].attrib:
@@ -802,7 +928,10 @@ class item(ItemElement):
klass = Investment
obj_label = CleanText('.//td[2]/div/a')
- obj_code = CleanText('.//td[2]/div/br/following-sibling::text()') & Regexp(pattern='^([^ ]+).*', default=NotAvailable)
+ obj_code = (
+ CleanText('.//td[2]/div/br/following-sibling::text()')
+ & Regexp(pattern='^([^ ]+).*', default=NotAvailable)
+ )
obj_quantity = MyDecimal('.//td[3]/span')
obj_diff = MyDecimal('.//td[7]/span')
obj_valuation = MyDecimal('.//td[5]')
@@ -838,8 +967,8 @@ class iter_history(TableElement):
def next_page(self):
form = self.page.get_form(id="historyFilter")
form['PAGE'] = int(form['PAGE']) + 1
- return requests.Request("POST", form.url, data=dict(form)) \
- if self.page.doc.xpath('//*[@data-page = $page]', page=form['PAGE']) else None
+ if self.page.doc.xpath('//*[@data-page = $page]', page=form['PAGE']):
+ return requests.Request("POST", form.url, data=dict(form))
class item(ItemElement):
klass = Transaction
@@ -854,6 +983,8 @@ def obj_label(self):
def parse(self, el):
i = None
+ self.env['investments'] = []
+
if CleanText(TableCell('code'))(self):
i = Investment()
i.label = Field('label')(self)
@@ -861,7 +992,8 @@ def parse(self, el):
i.quantity = MyDecimal(TableCell('quantity'))(self)
i.valuation = Field('amount')(self)
i.vdate = Field('date')(self)
- self.env['investments'] = [i] if i else []
+
+ self.env['investments'] = [i]
MARKET_ORDER_DIRECTIONS = {
@@ -1002,7 +1134,10 @@ def get_calie_life_insurances_first_index(self):
# so we stop at the first index
for account in self.doc.xpath('//table[@class]/tbody/tr'):
if account.xpath('.//td[has-class("nomContrat")]//a[contains(@class, "redirect")][@href="#"]'):
- index = Attr(account.xpath('.//td[has-class("nomContrat")]//a[contains(@class, "redirect")][@href="#"]'), 'id')(self)
+ index = Attr(
+ account.xpath('.//td[has-class("nomContrat")]//a[contains(@class, "redirect")][@href="#"]'), # wtf
+ 'id'
+ )(self)
return index
@method
@@ -1141,7 +1276,9 @@ def get_colnum(self, name):
class CaliePage(LoggedPage, HTMLPage):
def check_error(self):
- message = CleanText('//div[contains(@class, "disclaimer-div")]//text()[contains(., "utilisation vaut acceptation")]')(self.doc)
+ message = CleanText(
+ '//div[contains(@class, "disclaimer-div")]//text()[contains(., "utilisation vaut acceptation")]'
+ )(self.doc)
if self.doc.xpath('//button[@id="acceptDisclaimerButton"]') and message:
raise ActionNeeded(message)
@@ -1213,7 +1350,10 @@ def come_back(self):
params['typeaction'] = 'reroutage_retour'
params['site'] = 'LCLI'
params['stbzn'] = 'bnc'
- return self.browser.location('https://assurance-vie-et-prevoyance.secure.lcl.fr/filiale/entreeBam', params=params)
+ return self.browser.location(
+ 'https://assurance-vie-et-prevoyance.secure.lcl.fr/filiale/entreeBam',
+ params=params
+ )
class AVListPage(LoggedPage, JsonPage):
@@ -1267,11 +1407,11 @@ class item(ItemElement):
# 70= N° transaction, 6660666= N° account, 2018-03-18= date and 2018-03-16=rdate.
# We thus use "70_ABC666ABC" for the transaction ID.
- obj_id = Regexp(CleanText(Dict('idope')), '(\d+_[\dA-Z]+)')
+ obj_id = Regexp(CleanText(Dict('idope')), r'(\d+_[\dA-Z]+)')
def obj__dates(self):
raw = CleanText(Dict('idope'))(self)
- m = re.findall('\d{4}-\d{2}-\d{2}', raw)
+ m = re.findall(r'\d{4}-\d{2}-\d{2}', raw)
# We must verify that the two dates are correctly fetched
assert len(m) == 2
return m
@@ -1290,8 +1430,14 @@ def update_life_insurance_account(self, life_insurance):
Dict('situationAdministrativeEpargne/lppeoscp'),
Dict('situationAdministrativeEpargne/lnpeoscp'),
)(self.doc)
- life_insurance.label = '%s %s' % (Dict('situationAdministrativeEpargne/lcofc')(self.doc), life_insurance._owner)
- life_insurance.valuation_diff = CleanDecimal(Dict('situationFinanciereEpargne/mtpmvcnt'), default=NotAvailable)(self.doc)
+ life_insurance.label = '%s %s' % (
+ Dict('situationAdministrativeEpargne/lcofc')(self.doc),
+ life_insurance._owner,
+ )
+ life_insurance.valuation_diff = CleanDecimal(
+ Dict('situationFinanciereEpargne/mtpmvcnt'),
+ default=NotAvailable
+ )(self.doc)
return life_insurance
@method
@@ -1322,8 +1468,13 @@ def obj_code_type(self):
class RibPage(LoggedPage, LCLBasePage):
def get_iban(self):
- if (self.doc.xpath('//div[contains(@class, "rib_cadre")]//div[contains(@class, "rib_internat")]')):
- return CleanText('//div[contains(@class, "rib_cadre") and not(contains(@class, "hidden"))]//div[contains(@class, "rib_internat")]//p//strong/text()[1]', replace=[(' ', '')])(self.doc)
+ if self.doc.xpath(
+ '//div[contains(@class, "rib_cadre")]//div[contains(@class, "rib_internat")]'
+ ):
+ return CleanText(
+ '//div[contains(@class, "rib_cadre") and not(contains(@class, "hidden"))]//div[contains(@class, "rib_internat")]//p//strong/text()[1]',
+ replace=[(' ', '')]
+ )(self.doc)
def check_iban_by_account(self, account_id):
iban_account_id = CleanText().filter(self.doc.xpath('(//td[@class[contains(., "guichet-")]]/following-sibling::*)[1]/strong'))
@@ -1331,11 +1482,17 @@ def check_iban_by_account(self, account_id):
iban_account = "%s%s" % (iban_guichet_id, iban_account_id[4:])
if account_id == iban_account:
- return CleanText('//div[contains(@class, "rib_cadre") and not(contains(@class, "hidden"))]//div[contains(@class, "rib_internat")]//p//strong/text()[1]', replace=[(' ', '')])(self.doc)
+ return CleanText(
+ '//div[contains(@class, "rib_cadre") and not(contains(@class, "hidden"))]//div[contains(@class, "rib_internat")]//p//strong/text()[1]',
+ replace=[(' ', '')]
+ )(self.doc)
+
return None
def has_iban_choice(self):
- return False if self.doc.xpath('(//strong[contains(., "RELEVE D\'IDENTITE BANCAIRE")])[1]') else True
+ return not bool(
+ self.doc.xpath('(//strong[contains(., "RELEVE D\'IDENTITE BANCAIRE")])[1]')
+ )
class HomePage(LoggedPage, HTMLPage):
@@ -1347,7 +1504,9 @@ def on_load(self):
# This aims to track input errors.
script_error = CleanText(u"//script[contains(text(), 'if (\"true\"===\"true\")')]")(self.doc)
if script_error:
- raise TransferBankError(message=CleanText().filter(html2text(re.search(u'\.html\("(.*?)"\)', script_error).group(1))))
+ html = re.search(r'\.html\("(.*?)"\)', script_error).group(1)
+ message = CleanText().filter(html2text(html)) # wtf?
+ raise TransferBankError(message=message)
def can_transfer(self, account_transfer_id):
for div in self.doc.xpath('//div[input[@id="indexCompteEmetteur"]]//div[@class="infoCompte" and not(@title)]'):
@@ -1408,19 +1567,23 @@ def handle_response(self, account, recipient):
transfer.account_iban = account.iban
transfer.account_label = account.label
transfer.account_balance = account.balance
- assert account._transfer_id in CleanText(
- u'//div[div[@class="libelleChoix" and contains(text(), "Compte émetteur")]] \
- //div[@class="infoCompte" and not(@title)]', replace=[(' ', '')]
- )(self.doc)
+ assert (
+ account._transfer_id in CleanText(
+ '//div[div[@class="libelleChoix" and contains(text(), "Compte émetteur")]]//div[@class="infoCompte" and not(@title)]',
+ replace=[(' ', '')]
+ )(self.doc)
+ )
transfer._recipient = recipient
transfer.recipient_id = self.get_id_from_response('recipient')
transfer.recipient_iban = recipient.iban
transfer.recipient_label = recipient.label
- assert recipient._transfer_id in CleanText(
- u'//div[div[@class="libelleChoix" and contains(text(), "Compte destinataire")]] \
- //div[@class="infoCompte" and not(@title)]', replace=[(' ', '')]
- )(self.doc)
+ assert (
+ recipient._transfer_id in CleanText(
+ '//div[div[@class="libelleChoix" and contains(text(), "Compte destinataire")]]//div[@class="infoCompte" and not(@title)]',
+ replace=[(' ', '')]
+ )(self.doc)
+ )
transfer.currency = FrenchTransaction.Currency('//div[@class="topBox"]/div[@class="montant"]')(self.doc)
transfer.amount = CleanDecimal('//div[@class="topBox"]/div[@class="montant"]', replace_dots=True)(self.doc)
@@ -1429,8 +1592,10 @@ def handle_response(self, account, recipient):
dayfirst=True
)(self.doc)
# skip html comment with filtering on text() content
- transfer.label = CleanText('//div[@class="motif"]/text()[contains(., "Motif : ")]',
- replace=[('Motif : ', '')])(self.doc)
+ transfer.label = CleanText(
+ '//div[@class="motif"]/text()[contains(., "Motif : ")]',
+ replace=[('Motif : ', '')]
+ )(self.doc)
return transfer
@@ -1441,7 +1606,7 @@ def confirm(self):
def get_value(self, _id, value_type):
for div in self.doc.xpath('//div[@onclick]'):
if _id in CleanText('.//div[not(@title)]', replace=[(' ', '')])(div):
- return Regexp(Attr('.', 'onclick'), '(\d+)')(div)
+ return Regexp(Attr('.', 'onclick'), r'(\d+)')(div)
raise TransferError('Could not find %s account.' % value_type)
def choose_origin(self, account_transfer_id):
@@ -1476,7 +1641,11 @@ def parse(self, el):
if bool(CleanText('./div[@id="soldeEurosCompte"]')(self)):
self.env['category'] = u'Interne'
account = find_object(self.page.browser.get_accounts_list(), id=self.obj_id(self))
- self.env['iban'] = account.iban if account else NotAvailable
+
+ self.env['iban'] = NotAvailable
+ if account:
+ self.env['iban'] = account.iban
+
self.env['bank_name'] = u'LCL'
else:
self.env['category'] = u'Externe'