From 9a2adf9de0fd8273a9be4be760fee8d7bc514513 Mon Sep 17 00:00:00 2001 From: Jerry Seutter Date: Wed, 8 Aug 2018 07:54:37 -0500 Subject: [PATCH 01/18] Version bump --- ofxparse/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ofxparse/__init__.py b/ofxparse/__init__.py index b12d53b..e939657 100644 --- a/ofxparse/__init__.py +++ b/ofxparse/__init__.py @@ -4,7 +4,7 @@ Statement, Transaction) from .ofxprinter import OfxPrinter -__version__ = '0.18' +__version__ = '0.19' __all__ = [ 'OfxParser', 'OfxParserException', From 603ed805f44eb45274afb665c87e5409ac1c6de7 Mon Sep 17 00:00:00 2001 From: David Euresti Date: Tue, 25 Sep 2018 11:31:28 -0700 Subject: [PATCH 02/18] Include message in ofx.status if it exists. --- ofxparse/ofxparse.py | 6 ++++++ tests/fixtures/error_message.ofx | 29 +++++++++++++++++++++++++++++ tests/test_parse.py | 8 ++++++++ 3 files changed, 43 insertions(+) create mode 100644 tests/fixtures/error_message.ofx diff --git a/ofxparse/ofxparse.py b/ofxparse/ofxparse.py index 2621286..3398858 100644 --- a/ofxparse/ofxparse.py +++ b/ofxparse/ofxparse.py @@ -411,6 +411,9 @@ def parse(cls, file_handle, fail_fast=True, custom_date_format=None): ) ofx_obj.status['severity'] = \ stmttrnrs_status.find('severity').contents[0].strip() + message = stmttrnrs_status.find('message') + ofx_obj.status['message'] = \ + message.contents[0].strip() if message else None ccstmttrnrs = ofx.find('ccstmttrnrs') if ccstmttrnrs: @@ -426,6 +429,9 @@ def parse(cls, file_handle, fail_fast=True, custom_date_format=None): ) ofx_obj.status['severity'] = \ ccstmttrnrs_status.find('severity').contents[0].strip() + message = ccstmttrnrs_status.find('message') + ofx_obj.status['message'] = \ + message.contents[0].strip() if message else None stmtrs_ofx = ofx.findAll('stmtrs') if stmtrs_ofx: diff --git a/tests/fixtures/error_message.ofx b/tests/fixtures/error_message.ofx new file mode 100644 index 0000000..6996ab1 --- /dev/null +++ b/tests/fixtures/error_message.ofx @@ -0,0 +1,29 @@ + + + + + + + 0 + INFO + SUCCESS + + 20180521052952.749[-7:PDT] + ENG + + svb.com + 944 + + + + + + ae91f50f-f16d-4bc1-b88f-2a7fa04b6de1 + + 2000 + ERROR + General Server Error + + + + diff --git a/tests/test_parse.py b/tests/test_parse.py index 46ad680..b5c67d3 100644 --- a/tests/test_parse.py +++ b/tests/test_parse.py @@ -981,6 +981,14 @@ def testEmptyBalance(self): with open_file('fail_nice/empty_balance.ofx') as f: self.assertRaises(OfxParserException, OfxParser.parse, f) + def testErrorInTransactionList(self): + """There is an error in the transaction list.""" + with open_file('error_message.ofx') as f: + ofx = OfxParser.parse(f, False) + self.assertEqual(ofx.status['code'], 2000) + self.assertEqual(ofx.status['severity'], 'ERROR') + self.assertEqual(ofx.status['message'], 'General Server Error') + class TestParseSonrs(TestCase): From cc7b3ff3a402b2e59b46d0621000e0935f3e423d Mon Sep 17 00:00:00 2001 From: Jerry Seutter Date: Fri, 30 Nov 2018 22:25:40 -0600 Subject: [PATCH 03/18] Version 0.20 bump --- ofxparse/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ofxparse/__init__.py b/ofxparse/__init__.py index e939657..023b916 100644 --- a/ofxparse/__init__.py +++ b/ofxparse/__init__.py @@ -4,7 +4,7 @@ Statement, Transaction) from .ofxprinter import OfxPrinter -__version__ = '0.19' +__version__ = '0.20' __all__ = [ 'OfxParser', 'OfxParserException', From 71da1eb56aef23d53adb962a77d50bd839bf3f97 Mon Sep 17 00:00:00 2001 From: Jerry Seutter Date: Tue, 4 Feb 2020 08:16:18 -0800 Subject: [PATCH 04/18] Add beautifulsoup4 to requirements.txt --- requirements.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/requirements.txt b/requirements.txt index ae10411..e076f7c 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1 +1,2 @@ python-coveralls +beautifulsoup4 From 0d5a4c2da74e85773b7e20b4fcd8b483215f78a0 Mon Sep 17 00:00:00 2001 From: Jerry Seutter Date: Tue, 4 Feb 2020 08:16:35 -0800 Subject: [PATCH 05/18] Fix deprecation warning --- tests/test_parse.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_parse.py b/tests/test_parse.py index b5c67d3..98a5f1b 100644 --- a/tests/test_parse.py +++ b/tests/test_parse.py @@ -852,7 +852,7 @@ def testPositions(self): account = ofx.accounts[0] statement = account.statement positions = statement.positions - self.assertEquals(len(positions), 2) + self.assertEqual(len(positions), 2) expected_positions = [ { From 0ff8b1320a15222d28ee9bfe9ce1be95948ecaed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Faure-Lacroix?= Date: Thu, 7 Nov 2019 14:06:30 -0500 Subject: [PATCH 06/18] Add Support for iso-8859-1 (latin-1) as encoding when described as USASCII with charset 8859-1 --- ofxparse/ofxparse.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/ofxparse/ofxparse.py b/ofxparse/ofxparse.py index 3398858..9180c71 100644 --- a/ofxparse/ofxparse.py +++ b/ofxparse/ofxparse.py @@ -117,7 +117,10 @@ def handle_encoding(self): if enc_type == "USASCII": cp = ascii_headers.get("CHARSET", "1252") - encoding = "cp%s" % (cp, ) + if cp == "8859-1": + encoding = "iso-8859-1" + else: + encoding = "cp%s" % (cp, ) elif enc_type in ("UNICODE", "UTF-8"): encoding = "utf-8" From a0905be39e245ec752f766e6be0d9b741fd9aa89 Mon Sep 17 00:00:00 2001 From: tony chang Date: Thu, 7 Feb 2019 10:56:34 -0800 Subject: [PATCH 07/18] Fix typo in example code. --- README.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.rst b/README.rst index de44e9a..52303d1 100644 --- a/README.rst +++ b/README.rst @@ -32,7 +32,7 @@ Here's a sample program # Account - account = ofx.occount + account = ofx.account account.account_id # The account number account.number # The account number (deprecated -- returns account_id) account.routing_number # The bank routing number From fb8464ee9460b5542360a613547de2078acaf98d Mon Sep 17 00:00:00 2001 From: Jerry Seutter Date: Tue, 4 Feb 2020 08:20:51 -0800 Subject: [PATCH 08/18] Drop Python 3.4 testing --- .travis.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 282ae7d..edbf51e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,7 +1,6 @@ language: python python: - '2.7' -- '3.4' - '3.5' - '3.6' install: From 7e78000ad9daa04beb575355d47e9f7892cb48a1 Mon Sep 17 00:00:00 2001 From: Jerry Seutter Date: Tue, 4 Feb 2020 08:24:29 -0800 Subject: [PATCH 09/18] Add python 3.7 testing --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index edbf51e..f06611e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,6 +3,7 @@ python: - '2.7' - '3.5' - '3.6' +- '3.7' install: - if [[ $TRAVIS_PYTHON_VERSION == 2* ]]; then pip install BeautifulSoup six nose coverage python-coveralls; fi From a4ed32b15361d563af32d917a365c4780387c94f Mon Sep 17 00:00:00 2001 From: Jerry Seutter Date: Tue, 4 Feb 2020 08:30:38 -0800 Subject: [PATCH 10/18] Add python 3.8 testing --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index f06611e..0ff2903 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,6 +4,7 @@ python: - '3.5' - '3.6' - '3.7' +- '3.8' install: - if [[ $TRAVIS_PYTHON_VERSION == 2* ]]; then pip install BeautifulSoup six nose coverage python-coveralls; fi From 6645a7937efcd7894f65893b8b454dfe9776fae2 Mon Sep 17 00:00:00 2001 From: Karthikeyan Singaravelan Date: Wed, 22 Jan 2020 19:46:22 +0530 Subject: [PATCH 11/18] Import ABC from collections.abc instead of collections for Python 3.9 compatibility. --- ofxparse/ofxparse.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/ofxparse/ofxparse.py b/ofxparse/ofxparse.py index 9180c71..17d55b5 100644 --- a/ofxparse/ofxparse.py +++ b/ofxparse/ofxparse.py @@ -13,6 +13,11 @@ except ImportError: from io import StringIO +try: + from collections.abc import Iterable +except ImportError: + from collections import Iterable + import six from . import mcc @@ -37,7 +42,7 @@ def try_decode(string, encoding): def is_iterable(candidate): if sys.version_info < (2, 6): return hasattr(candidate, 'next') - return isinstance(candidate, collections.Iterable) + return isinstance(candidate, Iterable) @contextlib.contextmanager From fa0d61dc7351546c0debd4da162c8b4d503f2b68 Mon Sep 17 00:00:00 2001 From: LoudMurmur Date: Wed, 31 Jul 2019 19:13:06 +0200 Subject: [PATCH 12/18] Handle new formattings for decimal numbers Allow parsing of number containing spaces or startting with a '+' sign. Such as '1 025,58' or '+1205'. --- ofxparse/ofxparse.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/ofxparse/ofxparse.py b/ofxparse/ofxparse.py index 17d55b5..a1c2229 100644 --- a/ofxparse/ofxparse.py +++ b/ofxparse/ofxparse.py @@ -1087,4 +1087,8 @@ def toDecimal(cls, tag): # Handle 10000,50 formatted numbers if '.' not in d and ',' in d: d = d.replace(',', '.') + # Handle 1 025,53 formatted numbers + d = d.replace(' ', '') + # Handle +1058,53 formatted numbers + d = d.replace('+', '') return decimal.Decimal(d) From 48fe667c6cb6a235f238e756a7bc14b792b19e0c Mon Sep 17 00:00:00 2001 From: Jerry Seutter Date: Tue, 4 Feb 2020 09:16:26 -0800 Subject: [PATCH 13/18] Add tests for handling leading plus symbols as well as spaces in numbers --- tests/test_parse.py | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/tests/test_parse.py b/tests/test_parse.py index 98a5f1b..c521405 100644 --- a/tests/test_parse.py +++ b/tests/test_parse.py @@ -493,6 +493,38 @@ def testThatParseTransactionWithDotAsDecimalPointAndCommaAsSeparator(self): transaction = OfxParser.parseTransaction(txn.find('stmttrn')) self.assertEqual(Decimal('-1006.60'), transaction.amount) + def testThatParseTransactionWithLeadingPlusSign(self): + " Parse numbers with a leading '+' sign. " + input = ''' + + POS + 20090401122017.000[-5:EST] + +1,006.60 + 0000123456782009040100001 + MCDONALD'S #112 + POS MERCHANDISE;MCDONALD'S #112 + +''' + txn = soup_maker(input) + transaction = OfxParser.parseTransaction(txn.find('stmttrn')) + self.assertEqual(Decimal('1006.60'), transaction.amount) + + def testThatParseTransactionWithSpaces(self): + " Parse numbers with a space separating the thousands. " + input = ''' + + POS + 20090401122017.000[-5:EST] + +1 006,60 + 0000123456782009040100001 + MCDONALD'S #112 + POS MERCHANDISE;MCDONALD'S #112 + +''' + txn = soup_maker(input) + transaction = OfxParser.parseTransaction(txn.find('stmttrn')) + self.assertEqual(Decimal('1006.60'), transaction.amount) + def testThatParseTransactionWithNullAmountIgnored(self): """A null transaction value is converted to 0. From 47a9d2f4221a3276b4479287a2c9e4627709af9e Mon Sep 17 00:00:00 2001 From: Chemtov Date: Thu, 28 Mar 2019 00:51:25 +0100 Subject: [PATCH 14/18] Add user_date to the transaction object from the DTUSER field. In a transaction dtuser reflects the date at which the transaction is actually done. dtposted (the current date property of the transaction object) reflects the date at which the transaction is effective. This patch adds the dtuser to the transaction object as user_date. Closes: #150 --- README.rst | 1 + ofxparse/ofxparse.py | 14 ++++++++++++++ tests/test_parse.py | 2 ++ 3 files changed, 17 insertions(+) diff --git a/README.rst b/README.rst index 52303d1..cf42fdb 100644 --- a/README.rst +++ b/README.rst @@ -73,6 +73,7 @@ Here's a sample program transaction.payee transaction.type transaction.date + transaction.user_date transaction.amount transaction.id transaction.memo diff --git a/ofxparse/ofxparse.py b/ofxparse/ofxparse.py index a1c2229..8af6c60 100644 --- a/ofxparse/ofxparse.py +++ b/ofxparse/ofxparse.py @@ -312,6 +312,7 @@ def __init__(self): self.payee = '' self.type = '' self.date = None + self.user_date = None self.amount = None self.id = '' self.memo = '' @@ -1033,6 +1034,19 @@ def parseTransaction(cls, txn_ofx): raise OfxParserException( six.u("Missing Transaction Date (a required field)")) + user_date_tag = txn_ofx.find('dtuser') + if hasattr(user_date_tag, "contents"): + try: + transaction.user_date = cls.parseOfxDateTime( + user_date_tag.contents[0].strip()) + except IndexError: + raise OfxParserException("Invalid Transaction User Date") + except ValueError: + ve = sys.exc_info()[1] + raise OfxParserException(str(ve)) + except TypeError: + pass + id_tag = txn_ofx.find('fitid') if hasattr(id_tag, "contents"): try: diff --git a/tests/test_parse.py b/tests/test_parse.py index c521405..78bd779 100644 --- a/tests/test_parse.py +++ b/tests/test_parse.py @@ -415,6 +415,7 @@ def testThatParseTransactionReturnsATransaction(self): input = ''' POS + 20090131 20090401122017.000[-5:EST] -6.60 0000123456782009040100001 @@ -427,6 +428,7 @@ def testThatParseTransactionReturnsATransaction(self): self.assertEqual('pos', transaction.type) self.assertEqual(datetime( 2009, 4, 1, 12, 20, 17) - timedelta(hours=-5), transaction.date) + self.assertEqual(datetime(2009, 1, 31, 0, 0), transaction.user_date) self.assertEqual(Decimal('-6.60'), transaction.amount) self.assertEqual('0000123456782009040100001', transaction.id) self.assertEqual("MCDONALD'S #112", transaction.payee) From ad2a11a2241594f60150fa706bd2906e51aa8dd8 Mon Sep 17 00:00:00 2001 From: Jerry Seutter Date: Sun, 30 May 2021 22:37:33 -0500 Subject: [PATCH 15/18] Version bump 0.21 --- ofxparse/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ofxparse/__init__.py b/ofxparse/__init__.py index 023b916..a2983cf 100644 --- a/ofxparse/__init__.py +++ b/ofxparse/__init__.py @@ -4,7 +4,7 @@ Statement, Transaction) from .ofxprinter import OfxPrinter -__version__ = '0.20' +__version__ = '0.21' __all__ = [ 'OfxParser', 'OfxParserException', From 76a24bdf351010460f0d184889045430892871b8 Mon Sep 17 00:00:00 2001 From: Jerry Seutter Date: Sun, 30 May 2021 22:50:17 -0500 Subject: [PATCH 16/18] fix travis --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index 0ff2903..f0128f5 100644 --- a/.travis.yml +++ b/.travis.yml @@ -23,3 +23,4 @@ deploy: tags: true distributions: sdist bdist_wheel repo: jseutter/ofxparse + skip_existing: true From e62a8be545134bf0ed2c43b17b3a21ee80d49bf2 Mon Sep 17 00:00:00 2001 From: Jerry Seutter Date: Sun, 30 May 2021 22:54:22 -0500 Subject: [PATCH 17/18] fix travis --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index f0128f5..8cad73c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -16,6 +16,7 @@ after_success: - coveralls deploy: provider: pypi + edge: true user: jseutter password: secure: buE5iS5WhggpFcqR7iIEfcnDNHGeZ4zcYlgy3p9mJKEP8s7NMVeYJc+0FnnNs2fOEVR1QUX/URFtAZegtW9Bi/hVSc2bECZxM75uH342vqtea2rNJ7wQLSugUO+w9Q7HvC2KqeVl3s5Qa4Y3+mwv3Ej4tPI/WfASaNZG3XkwX4c= From bed9baac10377f36717f5ff69f562dff384e45a4 Mon Sep 17 00:00:00 2001 From: Ludovic Chopin Date: Mon, 27 Nov 2023 08:07:59 +0100 Subject: [PATCH 18/18] Handle chknum in transaction field --- ofxparse/ofxparse.py | 15 ++++++++------- tests/test_parse.py | 14 ++++++++++++++ 2 files changed, 22 insertions(+), 7 deletions(-) diff --git a/ofxparse/ofxparse.py b/ofxparse/ofxparse.py index 8af6c60..fa3a791 100644 --- a/ofxparse/ofxparse.py +++ b/ofxparse/ofxparse.py @@ -1079,13 +1079,14 @@ def parseTransaction(cls, txn_ofx): if cls.fail_fast: raise - checknum_tag = txn_ofx.find('checknum') - if hasattr(checknum_tag, 'contents'): - try: - transaction.checknum = checknum_tag.contents[0].strip() - except IndexError: - raise OfxParserException(six.u("Empty Check (or other reference) \ - number")) + for check_field in ('checknum', 'chknum'): + checknum_tag = txn_ofx.find(check_field) + if hasattr(checknum_tag, 'contents'): + try: + transaction.checknum = checknum_tag.contents[0].strip() + except IndexError: + raise OfxParserException(six.u("Empty Check (or other reference) \ + number")) return transaction diff --git a/tests/test_parse.py b/tests/test_parse.py index 78bd779..cb5753c 100644 --- a/tests/test_parse.py +++ b/tests/test_parse.py @@ -449,6 +449,20 @@ def testThatParseTransactionWithFieldCheckNum(self): transaction = OfxParser.parseTransaction(txn.find('stmttrn')) self.assertEqual('700', transaction.checknum) + def testThatParseTransactionWithFieldChkNum(self): + input = ''' + + CHECK + 20231121 + -113.71 + 0000489 + 1932 + +''' + txn = soup_maker(input) + transaction = OfxParser.parseTransaction(txn.find('stmttrn')) + self.assertEqual('1932', transaction.checknum) + def testThatParseTransactionWithCommaAsDecimalPoint(self): input = '''