csingley / ofxtools

Python OFX Library

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

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.