Regression using 'hasattr' on STMTRS
PhracturedBlue opened this issue · comments
As of version 0.8.20 this worked, but it stopped working in 0.8.21
from ofxtools.models import *
stmt = STMTRS(
curdef='USD',
bankacctfrom=BANKACCTFROM(bankid="bank", acctid="12345678", accttype='CHECKING'),
banktranlist=BANKTRANLIST(dtstart=datetime.datetime.now().astimezone(), dtend=datetime.datetime.now().astimezone()),
ledgerbal=LEDGERBAL(balamt=1000, dtasof=datetime.datetime.now().astimezone()),
ballist=BALLIST())
hasattr(stmt, 'invposlist')
Throws:
Traceback (most recent call last):
File "t1.py", line 10, in <module>
hasattr(stmt, 'invposlist')
File ".venv/lib/python3.9/site-packages/ofxtools/models/base.py", line 519, in __getattr__
return getattr(subagg, attr)
File ".venv/lib/python3.9/site-packages/ofxtools/models/base.py", line 517, in __getattr__
subagg = getattr(self, subaggregate)
File ".venv/lib/python3.9/site-packages/ofxtools/Types.py", line 117, in __get__
return self.data[parent]
KeyError: <BANKTRANLIST dtstart='2021-10-20 09:06:13.141100-07:00' dtend='2021-10-20 09:06:13.141121-07:00' len=0>
I do not believe hasattr
should ever throw an exception (since that is the whole purpose of using it.
hasattr
basically does this:
def hasattr(obj, attrname):
try:
getattr(obj, attrname)
return True
except AttributeError:
return False
However you're getting a KeyError instead, which it doesn't handle.
This is clearly an implementation flaw in the descriptor protocol for Types.SubAggregate
and/or models.base.Aggregate
.
I think you're just running into problems because Aggregate
offers proxy access to attributes of its SubAggregates
, so that e.g. you can just refer to stmt.accttype
instead of saying stmt.bankacctfrom.accttype
.
This is indeed a bug that I'll fix... but whatever it is you're doing, may I suggest that you instead avail yourself of isinstance
and friends? The ofxtools
library is well-suited to type dispatch. You're not really subclassing the models and types that implement the OFX specification, are you?
I have some code (from a few years ago) that looks like:
for statement in statements:
if hasattr(statement, 'ballist') and statement.ballist:
for balance in statement.ballist:
txns.append((balance, statement))
if hasattr(statement, 'invposlist') and statement.invposlist:
txns.extend([(_x, statement) for _x in statement.invposlist])
I could use isinstance here (and likely I'll convert the code), but there are 5 (I think) responses that can contain a BALLIST entry, and since I don't really care what the response type is for this code, when I wrote it the above seemed more extensible.
Ahh, it's fine. I have no intention of shipping methods so weak they can't stand up to hasattr
, that's just embarrassing.
Try 97006ad, I think that ought to the trick, as long as you're not directly instantiating SubAggregate
for some reason.
That did indeed fix the issue. Thanks
No problem, thanks for the bug report.
Just for reference, you can peep some sample type dispatch of OFX transactions:
https://github.com/csingley/capgains/blob/master/capgains/ofx/reader.py#L251
That usage of itertools.groupby()
worked out really nicely.