comparison src/ofxstatement/plugins/us_hsbc/plugin.py @ 9:28548158a325 default tip

Some minor improvements/fixes This will eventually evolve to something more generic, since I have re-used the same design, and some of the parts here, to write a plugin for Charles Schwab.
author Louis Opter <louis@opter.org>
date Thu, 09 Mar 2017 22:55:02 -0800
parents 164da24a2997
children
comparison
equal deleted inserted replaced
8:164da24a2997 9:28548158a325
16 # along with this program. If not, see <http://www.gnu.org/licenses/>. 16 # along with this program. If not, see <http://www.gnu.org/licenses/>.
17 17
18 import contextlib 18 import contextlib
19 import csv 19 import csv
20 import datetime 20 import datetime
21 import enum
22 import locale 21 import locale
23 import logging 22 import logging
24 import pdb 23 import pdb
25 import sys 24 import sys
26 25
27 from decimal import Decimal, Decimal as D 26 from decimal import Decimal, Decimal as D
28 from ofxstatement.parser import CsvStatementParser 27 from ofxstatement.parser import CsvStatementParser
29 from ofxstatement.plugin import Plugin 28 from ofxstatement.plugin import Plugin
30 from ofxstatement.statement import StatementLine, generate_transaction_id 29 from ofxstatement.statement import StatementLine, generate_transaction_id
31 from typing import Any, Dict, Iterable, List 30 from typing import Dict, Generator, Iterable, List
32 from typing.io import TextIO 31 from typing.io import TextIO
33 32
34 from .record import CsvIndexes, Record 33 from .record import CsvIndexes, Record
35 from .transactions import enrich 34 from .transactions import enrich
36 35
37 36
38 logger = logging.getLogger("ofxstatement.plugins.us_hsbc") 37 logger = logging.getLogger("ofxstatement.plugins.us_hsbc")
39 38
40 39
41 @contextlib.contextmanager 40 @contextlib.contextmanager
42 def _override_locale(category, value): 41 def _override_locale(category: int, value: str) -> Generator[None, None, None]:
43 save = locale.getlocale(category) 42 save = locale.getlocale(category)
44 locale.setlocale(category, value) 43 locale.setlocale(category, value)
45 yield 44 yield
46 locale.setlocale(category, save) 45 locale.setlocale(category, save)
46
47
48 class _spawn_debugger_on_exception:
49
50 def __init__(self, errmsg: str) -> None:
51 self._errmsg = errmsg
52
53 def __enter__(self) -> None:
54 pass
55
56 def __exit__(self, exc_type, exc_value, exc_tb) -> bool:
57 if exc_value is not None:
58 logger.exception(self._errmsg)
59 logger.info("Press {} to exit the debugger".format(
60 "^Z" if sys.platform.startswith("win32") else "^D"
61 ))
62 pdb.post_mortem()
63 sys.exit(1)
47 64
48 65
49 class Parser(CsvStatementParser): 66 class Parser(CsvStatementParser):
50 67
51 date_format = "%m/%d/%Y" 68 date_format = "%m/%d/%Y"
63 "CREDIT RECEIVED ON": "CREDIT", 80 "CREDIT RECEIVED ON": "CREDIT",
64 "MISCELLANEOUS CREDIT": "CREDIT", 81 "MISCELLANEOUS CREDIT": "CREDIT",
65 "CASH CONCENTRATION VENMO": "XFER", 82 "CASH CONCENTRATION VENMO": "XFER",
66 "DEPOSIT FROM": "DIRECTDEP", 83 "DEPOSIT FROM": "DIRECTDEP",
67 "DEPOSIT": "DEP", 84 "DEPOSIT": "DEP",
85 "HSBC SECURITIES": "XFER",
68 "INTEREST EARNED AND PAID": "INT", 86 "INTEREST EARNED AND PAID": "INT",
69 "ONLINE PAYMENT TO": "PAYMENT", 87 "ONLINE PAYMENT TO": "PAYMENT",
70 "PAY TO": "PAYMENT", 88 "PAY TO": "PAYMENT",
71 "PAYMENT -": "PAYMENT", # receiving a payment (e.g: on your CC account) 89 "PAYMENT -": "PAYMENT", # receiving a payment (e.g: on your CC account)
72 "PAYMENT TO": "PAYMENT", 90 "PAYMENT TO": "PAYMENT",
104 def parse_record(self, row: List[str]) -> StatementLine: 122 def parse_record(self, row: List[str]) -> StatementLine:
105 if self.cur_record < 4: # starts at 1 123 if self.cur_record < 4: # starts at 1
106 logger.info("Skipping row: {}".format(row)) 124 logger.info("Skipping row: {}".format(row))
107 return None # skip (all) the csv headers 125 return None # skip (all) the csv headers
108 126
109 try: 127 with _spawn_debugger_on_exception("Parsing failed:"):
110 sl = super(Parser, self).parse_record(row) 128 sl = super(Parser, self).parse_record(row)
111 record = Record(row) 129 record = Record(row)
112 130
113 if record.currency != "$": 131 if record.currency != "$":
114 logger.warning( 132 logger.warning(
121 enrich(sl, record) 139 enrich(sl, record)
122 sl.id = generate_transaction_id(sl) 140 sl.id = generate_transaction_id(sl)
123 141
124 self.statement.start_date = min(sl.date, self.statement.start_date) 142 self.statement.start_date = min(sl.date, self.statement.start_date)
125 self.statement.end_date = max(sl.date, self.statement.end_date) 143 self.statement.end_date = max(sl.date, self.statement.end_date)
126 except Exception:
127 logger.exception("Parsing failed:")
128 logger.info("Press {} to exit the debugger".format(
129 "^Z" if sys.platform.startswith("win32") else "^D"
130 ))
131 pdb.post_mortem()
132 sys.exit(1)
133 144
134 return sl 145 return sl
135 146
136 @_override_locale(locale.LC_NUMERIC, "en_US") 147 @_override_locale(locale.LC_NUMERIC, "en_US")
137 def parse_decimal(self, value: str) -> Decimal: 148 def parse_decimal(self, value: str) -> Decimal:
146 encoding = self.settings.get("charset", HSBCUSAPlugin.DEFAULT_CHARSET) 157 encoding = self.settings.get("charset", HSBCUSAPlugin.DEFAULT_CHARSET)
147 158
148 # XXX: how does this gets closed? 159 # XXX: how does this gets closed?
149 fin = open(filename, "r", encoding=encoding) 160 fin = open(filename, "r", encoding=encoding)
150 161
151 parser = Parser(fin) 162 p = Parser(fin)
152 parser.statement.bank_id = self.settings.get("routing_number") 163 p.statement.bank_id = self.settings.get("routing_number")
153 parser.statement.account_id = self.settings.get("account_number") 164 p.statement.account_id = self.settings.get("account_number")
154 parser.statement.account_type = self.settings.get("account_type", "CHECKING") 165 p.statement.account_type = self.settings.get("account_type", "CHECKING")
155 166
156 return parser 167 return p