\d{2})'),
+ FrenchTransaction.TYPE_BANK,
+ ),
+ (re.compile(r'^\*.*'), FrenchTransaction.TYPE_BANK),
+ ]
class VirtKeyboardPage(HTMLPage):
@@ -180,19 +226,26 @@ def get_string_code(self, string):
class PasswordPage(LoginPage, HTMLPage):
- TO_DIGIT = {'2': ['a', 'b', 'c'],
- '3': ['d', 'e', 'f'],
- '4': ['g', 'h', 'i'],
- '5': ['j', 'k', 'l'],
- '6': ['m', 'n', 'o'],
- '7': ['p', 'q', 'r', 's'],
- '8': ['t', 'u', 'v'],
- '9': ['w', 'x', 'y', 'z']
- }
+ TO_DIGIT = {
+ 'a': '2', 'b': '2', 'c': '2',
+ 'd': '3', 'e': '3', 'f': '3',
+ 'g': '4', 'h': '4', 'i': '4',
+ 'j': '5', 'k': '5', 'l': '5',
+ 'm': '6', 'n': '6', 'o': '6',
+ 'p': '7', 'q': '7', 'r': '7', 's': '7',
+ 't': '8', 'u': '8', 'v': '8',
+ 'w': '9', 'x': '9', 'y': '9', 'z': '9',
+ }
def enter_password(self, username, password):
if not password.isdigit():
- password = ''.join([c if c.isdigit() else [k for k, v in self.TO_DIGIT.items() if c in v][0] for c in password.lower()])
+ old_password = password
+ password = ''
+ for c in old_password.lower():
+ if c.isdigit():
+ password += c
+ else:
+ password += self.TO_DIGIT[c]
keyboard_page = self.browser.keyboard.open()
vk = BoursoramaVirtKeyboard(keyboard_page)
@@ -262,12 +315,24 @@ class item(ItemElement):
def condition(self):
# Ignore externally aggregated accounts and insurances:
- return not self.is_external() and not any(x in Field('url')(self) for x in ('automobile', 'assurance/protection', 'assurance/comptes', 'assurance/famille'))
+ return (
+ not self.is_external()
+ and not re.search(
+ 'automobile|assurance/protection|assurance/comptes|assurance/famille',
+ Field('url')(self)
+ )
+ )
obj_label = CleanText('.//a[has-class("account--name")] | .//div[has-class("account--name")]')
obj_currency = FrenchTransaction.Currency('.//a[has-class("account--balance")]')
- obj_valuation_diff = Async('details') & CleanDecimal('//li[h4[text()="Total des +/- values"]]/h3 |\
- //li[span[text()="Total des +/- values latentes"]]/span[has-class("overview__value")]', replace_dots=True, default=NotAvailable)
+ obj_valuation_diff = (
+ Async('details')
+ & CleanDecimal(
+ '''//li[h4[text()="Total des +/- values"]]/h3 |
+ //li[span[text()="Total des +/- values latentes"]]/span[has-class("overview__value")]''',
+ replace_dots=True, default=NotAvailable
+ )
+ )
obj__holder = None
obj__amount = CleanDecimal.French('.//a[has-class("account--balance")]')
@@ -288,7 +353,12 @@ def obj_coming(self):
# report deferred expenses in the coming attribute
if Field('type')(self) == Account.TYPE_CARD:
return Field('_amount')(self)
- return Async('details', CleanDecimal(u'//li[h4[text()="Mouvements à venir"]]/h3', replace_dots=True, default=NotAvailable))(self)
+ return Async(
+ 'details',
+ CleanDecimal(
+ u'//li[h4[text()="Mouvements à venir"]]/h3', replace_dots=True, default=NotAvailable
+ )
+ )(self)
def obj_id(self):
type = Field('type')(self)
@@ -299,7 +369,14 @@ def obj_id(self):
return self.obj__idparts()[1]
# sometimes it's sometimes it's
- id = Async('details', Regexp(CleanText('//*[has-class("account-number")]', transliterate=True), r'Reference du compte : (\d+)', default=NotAvailable))(self)
+ id = Async(
+ 'details',
+ Regexp(
+ CleanText('//*[has-class("account-number")]', transliterate=True),
+ r'Reference du compte : (\d+)',
+ default=NotAvailable
+ )
+ )(self)
if not id:
raise SkipItem()
return id
@@ -354,7 +431,7 @@ def is_external(self):
return '/budget/' in Field('url')(self)
def obj__idparts(self):
- return re.findall('[a-z\d]{32}', Field('url')(self))
+ return re.findall(r'[a-z\d]{32}', Field('url')(self))
def obj__webid(self):
parts = self.obj__idparts()
@@ -363,8 +440,10 @@ def obj__webid(self):
# We do not yield other banks accounts for the moment.
def validate(self, obj):
- return not Async('details', CleanText(u'//h4[contains(text(), "Établissement bancaire")]'))(self) and not \
- Async('details', CleanText(u'//h4/div[contains(text(), "Établissement bancaire")]'))(self)
+ return (
+ not Async('details', CleanText(u'//h4[contains(text(), "Établissement bancaire")]'))(self)
+ and not Async('details', CleanText(u'//h4/div[contains(text(), "Établissement bancaire")]'))(self)
+ )
class LoanPage(LoggedPage, HTMLPage):
@@ -380,14 +459,26 @@ class get_loan(ItemElement):
klass = Loan
obj_id = CleanText('//h3[contains(@class, "account-number")]/strong')
- obj_label = CleanText('//h2[contains(@class, "page-title__account")]//*[@class="account-edit-label"]/span[1]')
+ obj_label = CleanText(r'//h2[contains(@class, "page-title__account")]//*[@class="account-edit-label"]/span[1]')
obj_currency = CleanCurrency('//p[contains(text(), "Solde impayé")]/span')
obj_duration = CleanDecimal.French('//p[contains(text(), "échéances restantes")]/span', default=NotAvailable)
- obj_rate = CleanDecimal.French('//p[contains(text(), "Taux nominal en vigueur du prêt")]/span', default=NotAvailable)
- obj_nb_payments_left = CleanDecimal.French('//p[contains(text(), "échéances restantes")]/span', default=NotAvailable)
- obj_next_payment_amount = CleanDecimal.French('//p[contains(text(), "Montant de la prochaine échéance")]/span', default=NotAvailable)
+ obj_rate = CleanDecimal.French(
+ '//p[contains(text(), "Taux nominal en vigueur du prêt")]/span',
+ default=NotAvailable
+ )
+ obj_nb_payments_left = CleanDecimal.French(
+ '//p[contains(text(), "échéances restantes")]/span',
+ default=NotAvailable
+ )
+ obj_next_payment_amount = CleanDecimal.French(
+ '//p[contains(text(), "Montant de la prochaine échéance")]/span',
+ default=NotAvailable
+ )
obj_nb_payments_total = CleanDecimal.French('//p[contains(text(), "écheances totales") or contains(text(), "Nombre total")]/span')
- obj_subscription_date = Date(CleanText('//p[contains(text(), "Date de départ du prêt")]/span'), parse_func=parse_french_date)
+ obj_subscription_date = Date(
+ CleanText('//p[contains(text(), "Date de départ du prêt")]/span'),
+ parse_func=parse_french_date
+ )
def obj_total_amount(self):
total_amount = CleanText('//p[contains(text(), "Montant emprunt")]/span')(self)
@@ -401,8 +492,15 @@ def obj_maturity_date(self):
# Sometimes there is no maturity date, so instead there is just a dash
if maturity_date == '-':
return NotAvailable
- return Date(CleanText('//p[contains(text(), "échéance finale")]/span'), parse_func=parse_french_date)(self)
- return Date(Regexp(CleanText('//p[contains(text(), "date de votre dernière échéance")]'), r'(\d.*)'), parse_func=parse_french_date, default=NotAvailable)(self)
+ return Date(
+ CleanText('//p[contains(text(), "échéance finale")]/span'),
+ parse_func=parse_french_date
+ )(self)
+
+ return Date(
+ Regexp(CleanText('//p[contains(text(), "date de votre dernière échéance")]'), r'(\d.*)'),
+ parse_func=parse_french_date, default=NotAvailable
+ )(self)
def obj_balance(self):
balance = CleanDecimal.French('//div[contains(text(), "Capital restant dû")]/following-sibling::div')(self)
@@ -437,22 +535,30 @@ def on_load(self):
# handle ics calendar
dates = page_content.split('BEGIN:VEVENT')[1:]
- assert len(dates)%2 == 0, 'List lenght should be even-numbered'
+ assert len(dates) % 2 == 0, 'List length should be even-numbered'
# get all dates
dates = [re.search(r'(?<=VALUE\=DATE:)(\d{8})', el).group(1) for el in dates]
dates.sort()
for i in range(0, len(dates), 2):
- if len(dates[i:i+2]) == 2:
+ if len(dates[i:i + 2]) == 2:
# list contains tuple like (vdate, date)
- self.browser.deferred_card_calendar.append((Date().filter(dates[i]), Date().filter(dates[i+1])))
+ self.browser.deferred_card_calendar.append(
+ (
+ Date().filter(dates[i]),
+ Date().filter(dates[i + 1]),
+ )
+ )
class CalendarPage(LoggedPage, HTMLPage):
def on_load(self):
# redirect
- calendar_ics_url = urljoin(self.browser.BASEURL, CleanText('//a[contains(@href, "calendrier.ics")]/@href')(self.doc))
+ calendar_ics_url = urljoin(
+ self.browser.BASEURL,
+ CleanText('//a[contains(@href, "calendrier.ics")]/@href')(self.doc)
+ )
self.browser.location(calendar_ics_url)
@@ -460,10 +566,12 @@ class HistoryPage(LoggedPage, HTMLPage):
@pagination
@method
class iter_history(ListElement):
- item_xpath = '//ul[has-class("list__movement")]/li[div and not(contains(@class, "summary")) \
- and not(contains(@class, "graph")) \
- and not(contains(@class, "separator")) \
- and not(contains(@class, "list__movement__line--deffered"))]'
+ item_xpath = '''
+ //ul[has-class("list__movement")]/li[div and not(contains(@class, "summary"))
+ and not(contains(@class, "graph"))
+ and not(contains(@class, "separator"))
+ and not(contains(@class, "list__movement__line--deffered"))]
+ '''
def next_page(self):
next_page = self.el.xpath('//li[a[contains(text(), "Mouvements")]]')
@@ -471,11 +579,10 @@ def next_page(self):
next_page_token = Attr('.', 'data-operations-next-pagination')(next_page[0])
params = {
'rumroute': 'accounts.bank.movements',
- 'continuationToken': next_page_token
+ 'continuationToken': next_page_token,
}
parsed = urlparse(self.page.url)
- return '%s://%s%s?%s' %(parsed.scheme, parsed.netloc, parsed.path, urlencode(params))
-
+ return '%s://%s%s?%s' % (parsed.scheme, parsed.netloc, parsed.path, urlencode(params))
class item(ItemElement):
klass = Transaction
@@ -491,7 +598,10 @@ class item(ItemElement):
)
def obj_id(self):
- return Attr('.', 'data-id', default=NotAvailable)(self) or Attr('.', 'data-custom-id', default=NotAvailable)(self)
+ return (
+ Attr('.', 'data-id', default=NotAvailable)(self)
+ or Attr('.', 'data-custom-id', default=NotAvailable)(self)
+ )
def obj_type(self):
# In order to set TYPE_DEFERRED_CARD transactions correctly,
@@ -514,7 +624,7 @@ def obj_rdate(self):
# Transaction.Raw may have already set it
return self.obj.rdate
- s = Regexp(Field('raw'), ' (\d{2}/\d{2}/\d{2}) | (?!NUM) (\d{6}) ', default=NotAvailable)(self)
+ s = Regexp(Field('raw'), r' (\d{2}/\d{2}/\d{2}) | (?!NUM) (\d{6}) ', default=NotAvailable)(self)
if not s:
return Field('date')(self)
s = s.replace('/', '')
@@ -522,7 +632,11 @@ def obj_rdate(self):
return Date(dayfirst=True, default=NotAvailable).filter('%s-%s-%s' % (s[:2], s[2:4], s[4:]))
def obj__is_coming(self):
- return Env('coming', default=False)(self) or len(self.xpath('.//span[@title="Mouvement à débit différé"]')) or self.obj_date() > date.today()
+ return (
+ Env('coming', default=False)(self)
+ or len(self.xpath('.//span[@title="Mouvement à débit différé"]'))
+ or self.obj_date() > date.today()
+ )
def obj_date(self):
# Months with accents are retrieved like that: f\xe9vrier
@@ -542,9 +656,11 @@ def validate(self, obj):
# TYPE_DEFERRED_CARD transactions are already present in the card history
# so we only return TYPE_DEFERRED_CARD for the coming:
if not Env('coming', default=False)(self):
- return not len(self.xpath('.//span[has-class("icon-carte-bancaire")]')) \
- and not len(self.xpath('.//a[contains(@href, "/carte")]')) \
- and obj.type != Transaction.TYPE_DEFERRED_CARD
+ return (
+ not len(self.xpath('.//span[has-class("icon-carte-bancaire")]'))
+ and not len(self.xpath('.//a[contains(@href, "/carte")]'))
+ and obj.type != Transaction.TYPE_DEFERRED_CARD
+ )
elif Env('coming', default=False)(self):
# Do not return coming from deferred cards if their
# summary does not have a fixed amount yet:
@@ -602,7 +718,7 @@ def obj_rdate(self):
# Transaction.Raw may have already set it
return self.obj.rdate
- s = Regexp(Field('raw'), ' (\d{2}/\d{2}/\d{2}) | (?!NUM) (\d{6}) ', default=NotAvailable)(self)
+ s = Regexp(Field('raw'), r' (\d{2}/\d{2}/\d{2}) | (?!NUM) (\d{6}) ', default=NotAvailable)(self)
if not s:
return Field('date')(self)
s = s.replace('/', '')
@@ -680,10 +796,16 @@ def inner(page, *args, **kwargs):
class MarketPage(LoggedPage, HTMLPage):
def get_balance(self, account_type):
- txt = u"Solde au" if account_type is Account.TYPE_LIFE_INSURANCE else u"Total Portefeuille"
+ if account_type == Account.TYPE_LIFE_INSURANCE:
+ txt = "Solde au"
+ else:
+ txt = "Total Portefeuille"
# HTML tags are usually h4-h3 but may also be span-span
h_balance = CleanDecimal('//li[h4[contains(text(), "%s")]]/h3' % txt, replace_dots=True, default=None)(self.doc)
- span_balance = CleanDecimal('//li/span[contains(text(), "%s")]/following-sibling::span' % txt, replace_dots=True, default=None)(self.doc)
+ span_balance = CleanDecimal(
+ '//li/span[contains(text(), "%s")]/following-sibling::span' % txt,
+ replace_dots=True, default=None
+ )(self.doc)
return h_balance or span_balance or None
def get_market_order_link(self):
@@ -716,22 +838,26 @@ def obj_date(self):
def parse(self, el):
if el.xpath('./td[2]/a'):
- m = re.search('(\d+)', el.xpath('./td[2]/a')[0].get('data-modal-alert-behavior', ''))
+ m = re.search(r'(\d+)', el.xpath('./td[2]/a')[0].get('data-modal-alert-behavior', ''))
if m:
- self.env['account']._history_pages.append((Field('raw')(self),\
- self.page.browser.open('%s%s%s' % (self.page.url.split('mouvements')[0], 'mouvement/', m.group(1))).page))
+ url = '%s%s%s' % (self.page.url.split('mouvements')[0], 'mouvement/', m.group(1))
+ page = self.page.browser.open(url).page
+ self.env['account']._history_pages.append((Field('raw')(self), page))
raise SkipItem()
@method
class get_investment(Myiter_investment):
class item (Myitem):
def obj_unitvalue(self):
- return CleanDecimal(replace_dots=True, default=NotAvailable).filter((TableCell('unitvalue')(self)[0]).xpath('./span[not(@class)]'))
+ el = TableCell('unitvalue')(self)[0].xpath('./span[not(@class)]')
+ return CleanDecimal(replace_dots=True, default=NotAvailable).filter(el)
def iter_investment(self):
# Xpath can be h3/h4 or div/span; in both cases
# the first node contains "Solde Espèces":
- valuation = CleanDecimal('//li/*[contains(text(), "Solde Espèces")]/following-sibling::*', replace_dots=True, default=None)(self.doc)
+ valuation = CleanDecimal(
+ '//li/*[contains(text(), "Solde Espèces")]/following-sibling::*', replace_dots=True, default=None
+ )(self.doc)
if not empty(valuation):
yield create_french_liquidity(valuation)
@@ -746,7 +872,10 @@ def get_transactions_from_detail(self, account):
for table in page.doc.xpath('//table'):
t = Transaction()
- t.date = Date(CleanText(page.doc.xpath('//span[contains(text(), "Date d\'effet")]/following-sibling::span')), dayfirst=True)(page)
+ t.date = Date(
+ CleanText(page.doc.xpath('//span[contains(text(), "Date d\'effet")]/following-sibling::span')),
+ dayfirst=True
+ )(page)
t.label = label
t.amount = CleanDecimal(replace_dots=True).filter(amounts[0])
amounts.pop(0)
@@ -873,8 +1002,10 @@ def obj_label(self):
class ErrorPage(HTMLPage):
def on_load(self):
- error = (Attr('//input[@required][@id="profile_lei_type_identifier"]', 'data-message', default=None)(self.doc) or
- CleanText('//h2[@class="page-title"][contains(text(), "Actualisation")]', default=None)(self.doc))
+ error = (
+ Attr('//input[@required][@id="profile_lei_type_identifier"]', 'data-message', default=None)(self.doc)
+ or CleanText('//h2[@class="page-title"][contains(text(), "Actualisation")]', default=None)(self.doc)
+ )
if error:
raise ActionNeeded(error)
@@ -938,8 +1069,8 @@ def populate_cards_number(self, cards):
for _hash in self.doc.xpath('//div[contains(@class, "credit-card-carousel")]/@data-card-key'):
# We get the card number associate to the cards_hash
card_number = CleanText(
- '//div[@data-card-key="%s" and contains(@class, "credit-card-carousel")]'
- '//*[local-name()="svg"]//*[local-name()="tspan"]' % _hash,
+ '//div[@data-card-key="%s" and contains(@class, "credit-card-carousel")]' % _hash
+ + '//*[local-name()="svg"]//*[local-name()="tspan"]',
replace=[(' ', '')]
)(self.doc)
@@ -1011,10 +1142,16 @@ class item(ItemElement):
klass = Recipient
obj_id = CleanText('.//div[@class="c-card-ghost__sub-label"]')
- obj_bank_name = Regexp(CleanText('.//div[@class="transfer__account-name"]'), pattern=r'- ([^-]*)$', default=NotAvailable)
+ obj_bank_name = Regexp(
+ CleanText('.//div[@class="transfer__account-name"]'), pattern=r'- ([^-]*)$',
+ default=NotAvailable
+ )
def obj_label(self):
- label = Regexp(CleanText('.//div[@class="c-card-ghost__top-label"]'), pattern=r'^(.*?)(?: -[^-]*)?$')(self)
+ label = Regexp(
+ CleanText('.//div[@class="c-card-ghost__top-label"]'),
+ pattern=r'^(.*?)(?: -[^-]*)?$'
+ )(self)
return label.rstrip('-').rstrip()
def obj_category(self):
@@ -1099,7 +1236,10 @@ def obj_exec_date(self):
if type_ == 'Ponctuel':
return datetime.date.today()
elif type_ == 'Différé':
- return Date(CleanText('//span[@id="transfer-date"]/span[@class="transfer__account-value"]'), dayfirst=True)(self)
+ return Date(
+ CleanText('//span[@id="transfer-date"]/span[@class="transfer__account-value"]'),
+ dayfirst=True
+ )(self)
def submit(self):
form = self.get_form(name='Confirm')
@@ -1202,7 +1342,7 @@ def get_currency_list(self):
class CurrencyConvertPage(JsonPage):
def get_rate(self):
- if not 'error' in self.doc:
+ if 'error' not in self.doc:
return Decimal(str(self.doc['rate']))
diff --git a/modules/boursorama/transfer_pages.py b/modules/boursorama/transfer_pages.py
index 8cf2619df741cb5a4e349759935710509896a1a1..4a2f057ddd679aaaa1eae4e820fab6ce7c1ccc5d 100644
--- a/modules/boursorama/transfer_pages.py
+++ b/modules/boursorama/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
from weboob.browser.pages import HTMLPage, LoggedPage, pagination