1
0
Fork 0

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 attached
master
Dustin 2023-05-11 22:52:35 -05:00
parent 7683ff5760
commit 7cab766c38
1 changed files with 74 additions and 51 deletions

View File

@ -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
r = requests.post(
f'{NTFY_URL}/{topic}',
headers=headers,
data=message,
)
url = f'{NTFY_URL}/{topic}'
if message:
r = requests.post(
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',
)
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_commerce(page, end_date, token):
failed = True
if not download_chase(page, end_date, token):
failed = True
raise SystemExit(1 if failed else 0)