Refactor error handling
The `ntfyerror` context manager replaces `screenshot_failure` for handling online banking interaction failures. It has several advantages, notably: * takes a screenshot of the browser page *before* logging out * cleaner suppression of exceptions, with success tracking * sends an `ntfy` message, with the screenshot attachedmaster
parent
7683ff5760
commit
7cab766c38
115
xactfetch.py
115
xactfetch.py
|
@ -1,4 +1,3 @@
|
|||
import contextlib
|
||||
import copy
|
||||
import datetime
|
||||
import json
|
||||
|
@ -15,7 +14,6 @@ from typing import Any, Optional, Type
|
|||
|
||||
import requests
|
||||
from playwright.sync_api import Page
|
||||
from playwright.sync_api import TimeoutError as PlaywrightTimeout
|
||||
from playwright.sync_api import sync_playwright
|
||||
|
||||
|
||||
|
@ -35,21 +33,34 @@ ACCOUNTS = {
|
|||
|
||||
|
||||
def ntfy(
|
||||
message: str,
|
||||
message: Optional[str] = None,
|
||||
topic: str = NTFY_TOPIC,
|
||||
title: Optional[str] = None,
|
||||
tags: Optional[str] = None,
|
||||
attach: Optional[bytes] = None,
|
||||
filename: Optional[str] = None,
|
||||
) -> None:
|
||||
assert message or attach
|
||||
headers = {
|
||||
'Title': title or 'xactfetch',
|
||||
}
|
||||
if tags:
|
||||
headers['Tags'] = tags
|
||||
url = f'{NTFY_URL}/{topic}'
|
||||
if message:
|
||||
r = requests.post(
|
||||
f'{NTFY_URL}/{topic}',
|
||||
url,
|
||||
headers=headers,
|
||||
data=message,
|
||||
)
|
||||
else:
|
||||
if filename:
|
||||
headers['Filename'] = filename
|
||||
r = requests.put(
|
||||
url,
|
||||
headers=headers,
|
||||
data=attach,
|
||||
)
|
||||
r.raise_for_status()
|
||||
|
||||
|
||||
|
@ -176,8 +187,8 @@ def get_last_transaction_date(key: int, token: str) -> datetime.date:
|
|||
return last_date.date() + datetime.timedelta(days=1)
|
||||
|
||||
|
||||
def download_chase(page: Page, end_date: datetime.date, token: str) -> None:
|
||||
with Chase(page) as c:
|
||||
def download_chase(page: Page, end_date: datetime.date, token: str) -> bool:
|
||||
with Chase(page) as c, ntfyerror('Chase', page) as r:
|
||||
c.login()
|
||||
key = ACCOUNTS['chase']
|
||||
try:
|
||||
|
@ -187,22 +198,23 @@ def download_chase(page: Page, end_date: datetime.date, token: str) -> None:
|
|||
'Skipping Chase account: could not get last transaction: %s',
|
||||
e,
|
||||
)
|
||||
return
|
||||
return False
|
||||
if start_date >= end_date:
|
||||
log.info(
|
||||
'Skipping Chase account: last transaction was %s',
|
||||
start_date,
|
||||
)
|
||||
return
|
||||
return True
|
||||
csv = c.download_transactions(start_date, end_date)
|
||||
log.info('Importing transactions from Chase into Firefly III')
|
||||
c.firefly_import(csv, key, token)
|
||||
return r.success
|
||||
|
||||
|
||||
def download_commerce(page: Page, end_date: datetime.date, token: str) -> None:
|
||||
def download_commerce(page: Page, end_date: datetime.date, token: str) -> bool:
|
||||
log.info('Downloading transaction lists from Commerce Bank')
|
||||
csvs = []
|
||||
with CommerceBank(page) as c:
|
||||
with CommerceBank(page) as c, ntfyerror('Commerce Bank', page) as r:
|
||||
c.login()
|
||||
for name, key in ACCOUNTS['commerce'].items():
|
||||
try:
|
||||
|
@ -231,6 +243,51 @@ def download_commerce(page: Page, end_date: datetime.date, token: str) -> None:
|
|||
log.info('Importing transactions from Commerce Bank into Firefly III')
|
||||
for key, csv in csvs:
|
||||
c.firefly_import(csv, key, token)
|
||||
return r.success
|
||||
|
||||
|
||||
class ntfyerror:
|
||||
def __init__(self, bank: str, page: Page) -> None:
|
||||
self.bank = bank
|
||||
self.page = page
|
||||
self.success = True
|
||||
|
||||
def __enter__(self) -> 'ntfyerror':
|
||||
return self
|
||||
|
||||
def __exit__(
|
||||
self,
|
||||
exc_type: Optional[Type[Exception]],
|
||||
exc_value: Optional[Exception],
|
||||
tb: Optional[TracebackType],
|
||||
) -> bool:
|
||||
if exc_type and exc_value and tb:
|
||||
self.success = False
|
||||
log.exception(
|
||||
'Swallowed exception:', exc_info=(exc_type, exc_value, tb)
|
||||
)
|
||||
if ss := self.page.screenshot():
|
||||
save_screenshot(ss)
|
||||
ntfy(
|
||||
title=f'xactfetch failed for {self.bank}',
|
||||
tags='warning',
|
||||
attach=ss,
|
||||
filename='screenshot.png',
|
||||
)
|
||||
return True
|
||||
|
||||
|
||||
def save_screenshot(screenshot: bytes):
|
||||
now = datetime.datetime.now()
|
||||
filename = now.strftime('screenshot_%Y%m%d%H%M%S.png')
|
||||
log.debug('Saving browser screenshot to %s', filename)
|
||||
try:
|
||||
with open(filename, 'wb') as f:
|
||||
f.write(screenshot)
|
||||
except Exception as e:
|
||||
log.error('Failed to save browser screenshot: %s', e)
|
||||
else:
|
||||
log.info('Browser screenshot saved as %s', filename)
|
||||
|
||||
|
||||
class CommerceBank:
|
||||
|
@ -543,26 +600,6 @@ class Chase:
|
|||
firefly_import(csv, config, token)
|
||||
|
||||
|
||||
@contextlib.contextmanager
|
||||
def screenshot_failure(page: Page):
|
||||
try:
|
||||
yield
|
||||
except Exception:
|
||||
log.exception('Failed to download transactions:')
|
||||
now = datetime.datetime.now()
|
||||
filename = now.strftime('screenshot_%Y%m%d%H%M%S.png')
|
||||
log.debug('Saving browser screenshot to %s', filename)
|
||||
try:
|
||||
screenshot = page.screenshot()
|
||||
with open(filename, 'wb') as f:
|
||||
f.write(screenshot)
|
||||
except Exception as e:
|
||||
log.error('Failed to save browser screenshot: %s', e)
|
||||
else:
|
||||
log.error('Browser screenshot saved as %s', filename)
|
||||
raise
|
||||
|
||||
|
||||
def main() -> None:
|
||||
logging.basicConfig(level=logging.DEBUG)
|
||||
if not rbw_unlocked():
|
||||
|
@ -578,23 +615,9 @@ def main() -> None:
|
|||
browser = pw.firefox.launch(headless=headless)
|
||||
page = browser.new_page()
|
||||
failed = False
|
||||
try:
|
||||
with screenshot_failure(page):
|
||||
download_commerce(page, end_date, token)
|
||||
except Exception:
|
||||
ntfy(
|
||||
'Downloading transactions from Commerce Bank failed',
|
||||
tags='warning',
|
||||
)
|
||||
if not download_commerce(page, end_date, token):
|
||||
failed = True
|
||||
try:
|
||||
with screenshot_failure(page):
|
||||
download_chase(page, end_date, token)
|
||||
except Exception:
|
||||
ntfy(
|
||||
'Downloading transactions from Chase failed',
|
||||
tags='warning',
|
||||
)
|
||||
if not download_chase(page, end_date, token):
|
||||
failed = True
|
||||
raise SystemExit(1 if failed else 0)
|
||||
|
||||
|
|
Loading…
Reference in New Issue