Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Handle chknum in transaction field #173

Open
wants to merge 18 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
language: python
python:
- '2.7'
- '3.4'
- '3.5'
- '3.6'
- '3.7'
- '3.8'
install:
- if [[ $TRAVIS_PYTHON_VERSION == 2* ]]; then pip install BeautifulSoup six nose coverage
python-coveralls; fi
Expand All @@ -15,10 +16,12 @@ after_success:
- coveralls
deploy:
provider: pypi
edge: true
user: jseutter
password:
secure: buE5iS5WhggpFcqR7iIEfcnDNHGeZ4zcYlgy3p9mJKEP8s7NMVeYJc+0FnnNs2fOEVR1QUX/URFtAZegtW9Bi/hVSc2bECZxM75uH342vqtea2rNJ7wQLSugUO+w9Q7HvC2KqeVl3s5Qa4Y3+mwv3Ej4tPI/WfASaNZG3XkwX4c=
on:
tags: true
distributions: sdist bdist_wheel
repo: jseutter/ofxparse
skip_existing: true
3 changes: 2 additions & 1 deletion README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -73,6 +73,7 @@ Here's a sample program
transaction.payee
transaction.type
transaction.date
transaction.user_date
transaction.amount
transaction.id
transaction.memo
Expand Down
2 changes: 1 addition & 1 deletion ofxparse/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
Statement, Transaction)
from .ofxprinter import OfxPrinter

__version__ = '0.18'
__version__ = '0.21'
__all__ = [
'OfxParser',
'OfxParserException',
Expand Down
51 changes: 42 additions & 9 deletions ofxparse/ofxparse.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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
Expand Down Expand Up @@ -117,7 +122,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"
Expand Down Expand Up @@ -304,6 +312,7 @@ def __init__(self):
self.payee = ''
self.type = ''
self.date = None
self.user_date = None
self.amount = None
self.id = ''
self.memo = ''
Expand Down Expand Up @@ -411,6 +420,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:
Expand All @@ -426,6 +438,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:
Expand Down Expand Up @@ -1019,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:
Expand Down Expand Up @@ -1051,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

Expand All @@ -1073,4 +1102,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)
1 change: 1 addition & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
python-coveralls
beautifulsoup4
29 changes: 29 additions & 0 deletions tests/fixtures/error_message.ofx
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<?OFX OFXHEADER="200" VERSION="211" SECURITY="NONE" OLDFILEUID="NONE" NEWFILEUID="NONE"?>
<OFX>
<SIGNONMSGSRSV1>
<SONRS>
<STATUS>
<CODE>0</CODE>
<SEVERITY>INFO</SEVERITY>
<MESSAGE>SUCCESS</MESSAGE>
</STATUS>
<DTSERVER>20180521052952.749[-7:PDT]</DTSERVER>
<LANGUAGE>ENG</LANGUAGE>
<FI>
<ORG>svb.com</ORG>
<FID>944</FID>
</FI>
</SONRS>
</SIGNONMSGSRSV1>
<BANKMSGSRSV1>
<STMTTRNRS>
<TRNUID>ae91f50f-f16d-4bc1-b88f-2a7fa04b6de1</TRNUID>
<STATUS>
<CODE>2000</CODE>
<SEVERITY>ERROR</SEVERITY>
<MESSAGE>General Server Error</MESSAGE>
</STATUS>
</STMTTRNRS>
</BANKMSGSRSV1>
</OFX>
58 changes: 57 additions & 1 deletion tests/test_parse.py
Original file line number Diff line number Diff line change
Expand Up @@ -415,6 +415,7 @@ def testThatParseTransactionReturnsATransaction(self):
input = '''
<STMTTRN>
<TRNTYPE>POS
<DTUSER>20090131
<DTPOSTED>20090401122017.000[-5:EST]
<TRNAMT>-6.60
<FITID>0000123456782009040100001
Expand All @@ -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)
Expand All @@ -447,6 +449,20 @@ def testThatParseTransactionWithFieldCheckNum(self):
transaction = OfxParser.parseTransaction(txn.find('stmttrn'))
self.assertEqual('700', transaction.checknum)

def testThatParseTransactionWithFieldChkNum(self):
input = '''
<STMTTRN>
<TRNTYPE>CHECK
<DTPOSTED>20231121
<TRNAMT>-113.71
<FITID>0000489
<CHKNUM>1932
</STMTTRN>
'''
txn = soup_maker(input)
transaction = OfxParser.parseTransaction(txn.find('stmttrn'))
self.assertEqual('1932', transaction.checknum)

def testThatParseTransactionWithCommaAsDecimalPoint(self):
input = '''
<STMTTRN>
Expand Down Expand Up @@ -493,6 +509,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 = '''
<STMTTRN>
<TRNTYPE>POS
<DTPOSTED>20090401122017.000[-5:EST]
<TRNAMT>+1,006.60
<FITID>0000123456782009040100001
<NAME>MCDONALD'S #112
<MEMO>POS MERCHANDISE;MCDONALD'S #112
</STMTTRN>
'''
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 = '''
<STMTTRN>
<TRNTYPE>POS
<DTPOSTED>20090401122017.000[-5:EST]
<TRNAMT>+1 006,60
<FITID>0000123456782009040100001
<NAME>MCDONALD'S #112
<MEMO>POS MERCHANDISE;MCDONALD'S #112
</STMTTRN>
'''
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.

Expand Down Expand Up @@ -852,7 +900,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 = [
{
Expand Down Expand Up @@ -981,6 +1029,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):

Expand Down