class MSGSETLIST(Aggregate): """OFX section 7.2""" signonmsgset = ListAggregate(SIGNONMSGSET) signupmsgset = ListAggregate(SIGNUPMSGSET) bankmsgset = ListAggregate(BANKMSGSET) creditcardmsgset = ListAggregate(CREDITCARDMSGSET) invstmtmsgset = ListAggregate(INVSTMTMSGSET) interxfermsgset = ListAggregate(INTERXFERMSGSET) wirexfermsgset = ListAggregate(WIREXFERMSGSET) billpaymsgset = ListAggregate(BILLPAYMSGSET) emailmsgset = ListAggregate(EMAILMSGSET) seclistmsgset = ListAggregate(SECLISTMSGSET) # presdirmsgset = ListAggregate(PRESDIRMSGSET) presdirmsgset = Unsupported() # presdlvmsgset = ListAggregate(PRESDLVMSGSET) presdlvmsgset = Unsupported() profmsgset = ListAggregate(PROFMSGSET) tax1099msgset = ListAggregate(TAX1099MSGSET) @classmethod def validate_args(cls, *args, **kwargs): # "[MSGSETLIST contents] One or more message set aggregates" if len(args) == 0: msg = "{} must contain at least one item" raise ValueError(msg.format(cls.__name__)) super().validate_args(*args, **kwargs)
class CREDITCARDMSGSETV1(Aggregate): """OFX section 11.13.3""" msgsetcore = SubAggregate(MSGSETCORE, required=True) closingavail = Bool(required=True) pendingavail = Bool() imageprof = Unsupported()
class STMTTRN(Aggregate, Origcurrency): """OFX section 11.4.3""" trntype = OneOf(*TRNTYPES, required=True) dtposted = DateTime(required=True) dtuser = DateTime() dtavail = DateTime() trnamt = Decimal(required=True) fitid = String(255, required=True) correctfitid = String(255) correctaction = OneOf("REPLACE", "DELETE") srvrtid = String(10) checknum = String(12) refnum = String(32) sic = Integer() payeeid = String(12) name = NagString(32) payee = SubAggregate(PAYEE) extdname = String(100) bankacctto = SubAggregate(BANKACCTTO) ccacctto = SubAggregate(CCACCTTO) memo = String(255) imagedata = Unsupported() currency = SubAggregate(CURRENCY) origcurrency = SubAggregate(ORIGCURRENCY) inv401ksource = OneOf(*INV401KSOURCES) optionalMutexes = [ ["name", "payee"], ["ccacctto", "bankacctto"], ["currency", "origcurrency"], ]
class CCSTMTRS(Aggregate): """OFX section 11.4.3.2""" curdef = OneOf(*CURRENCY_CODES, required=True) ccacctfrom = SubAggregate(CCACCTFROM, required=True) banktranlist = SubAggregate(BANKTRANLIST) banktranlistp = Unsupported() ledgerbal = SubAggregate(LEDGERBAL, required=True) availbal = SubAggregate(AVAILBAL) cashadvbalamt = Decimal() intratepurch = Decimal() intratecash = Decimal() intratexfer = Decimal() rewardinfo = SubAggregate(REWARDINFO) ballist = SubAggregate(BALLIST) mktginfo = String(360) @property def account(self): return self.ccacctfrom @property def transactions(self): return self.banktranlist @property def balance(self): return self.ledgerbal
class CCCLOSING(Aggregate): """OFX Section 11.5.4.2""" fitid = String(255, required=True) dtopen = DateTime() dtclose = DateTime(required=True) dtnext = DateTime() balopen = Decimal() balclose = Decimal(required=True) intytd = Decimal() dtpmtdue = DateTime() minpmtdue = Decimal() pastdueamt = Decimal() latefeeamt = Decimal() finchg = Decimal() intratepurch = Decimal() intratecash = Decimal() intratexfer = Decimal() payandcredit = Decimal() purandadv = Decimal() debadj = Decimal() creditlimit = Decimal() cashadvcreditlimit = Decimal() dtpoststart = DateTime(required=True) dtpostend = DateTime(required=True) autopay = Bool() lastpmtinfo = SubAggregate(LASTPMTINFO) rewardinfo = SubAggregate(REWARDINFO) mktginfo = String(360) imagedata = Unsupported() currency = SubAggregate(CURRENCY) origcurrency = SubAggregate(ORIGCURRENCY)
class BANKMSGSETV1(ElementList): """OFX section 11.13.2.1""" msgsetcore = SubAggregate(MSGSETCORE, required=True) invalidaccttype = ListElement(OneOf(*ACCTTYPES)) closingavail = Bool(required=True) pendingavail = Bool() xferprof = SubAggregate(XFERPROF) stpchkprof = SubAggregate(STPCHKPROF) emailprof = SubAggregate(EMAILPROF, required=True) imageprof = Unsupported()
class INVSTMTMSGSETV1(Aggregate): """OFX section 13.7.1.1""" msgsetcore = SubAggregate(MSGSETCORE, required=True) trandnld = Bool(required=True) oodnld = Bool(required=True) posdnld = Bool(required=True) baldnld = Bool(required=True) canemail = Bool(required=True) inv401kdnld = Bool() closingavail = Bool() imageprof = Unsupported()
class TESTAGGREGATE(Aggregate): metadata = String(32, required=True) option00 = Bool() option01 = Bool() option10 = Bool() option11 = Bool() req00 = Bool() req01 = Bool() req10 = Bool() req11 = Bool() testsubaggregate = SubAggregate(TESTSUBAGGREGATE) dontuse = Unsupported() optionalMutexes = [["option00", "option01"], ["option10", "option11"]] requiredMutexes = [["req00", "req01"], ["req10", "req11"]]
class SONRQ(Aggregate): """ OFX section 2.5.1.2 """ dtclient = DateTime(required=True) userid = String(32) userpass = String(171) userkey = String(64) accesstoken = String(1000) genuserkey = Bool() language = OneOf(*LANG_CODES, required=True) fi = SubAggregate(FI) sesscookie = String(1000) appid = String(5, required=True) appver = String(4, required=True) appkey = String(10000) clientuid = String(36) usercred1 = String(171) usercred2 = String(171) authtoken = String(171) accesskey = String(1000) mfachallengea = ListAggregate(MFACHALLENGEA) ofxextension = Unsupported() @classmethod def validate_args(cls, *args, **kwargs): # "Either <USERID> and <USERPASS> or <USERKEY>, but not both" userid = kwargs.get("userid", None) userpass = kwargs.get("userpass", None) userkey = kwargs.get("userkey", None) try: assert (userid and userpass) or userkey assert not ((userid or userpass) and userkey) except AssertionError: msg = ("{} must contain either <USERID> and <USERPASS> " "or <USERKEY>, but not both") raise ValueError(msg.format(cls.__name__)) super().validate_args(*args, **kwargs)
class SONRS(Aggregate): """ OFX section 2.5.1.3 """ status = SubAggregate(STATUS, required=True) dtserver = DateTime(required=True) userkey = String(64) tskeyexpire = DateTime() language = OneOf(*LANG_CODES, required=True) dtprofup = DateTime() dtacctup = DateTime() fi = SubAggregate(FI) sesscookie = String(1000) accesskey = String(1000) ofxextension = Unsupported() # Human-friendly attribute aliases @property def org(self): return self.fi.org @property def fid(self): return self.fi.fid
def instance(self): return Unsupported()
class OFX(Aggregate): """OFX Section 2.4.3""" signonmsgsrqv1 = SubAggregate(SIGNONMSGSRQV1) signonmsgsrsv1 = SubAggregate(SIGNONMSGSRSV1) signupmsgsrqv1 = SubAggregate(SIGNUPMSGSRQV1) signupmsgsrsv1 = SubAggregate(SIGNUPMSGSRSV1) bankmsgsrqv1 = SubAggregate(BANKMSGSRQV1) bankmsgsrsv1 = SubAggregate(BANKMSGSRSV1) creditcardmsgsrqv1 = SubAggregate(CREDITCARDMSGSRQV1) creditcardmsgsrsv1 = SubAggregate(CREDITCARDMSGSRSV1) invstmtmsgsrqv1 = SubAggregate(INVSTMTMSGSRQV1) invstmtmsgsrsv1 = SubAggregate(INVSTMTMSGSRSV1) interxfermsgsrqv1 = SubAggregate(INTERXFERMSGSRQV1) interxfermsgsrsv1 = SubAggregate(INTERXFERMSGSRSV1) wirexfermsgsrqv1 = SubAggregate(WIREXFERMSGSRQV1) wirexfermsgsrsv1 = SubAggregate(WIREXFERMSGSRSV1) billpaymsgsrqv1 = SubAggregate(BILLPAYMSGSRQV1) billpaymsgsrsv1 = SubAggregate(BILLPAYMSGSRSV1) emailmsgsrqv1 = SubAggregate(EMAILMSGSRQV1) emailmsgsrsv1 = SubAggregate(EMAILMSGSRSV1) seclistmsgsrqv1 = SubAggregate(SECLISTMSGSRQV1) seclistmsgsrsv1 = SubAggregate(SECLISTMSGSRSV1) presdirmsgsrqv1 = Unsupported() presdirmsgsrsv1 = Unsupported() presdlvmsgsrqv1 = Unsupported() presdlvmsgsrsv1 = Unsupported() profmsgsrqv1 = SubAggregate(PROFMSGSRQV1) profmsgsrsv1 = SubAggregate(PROFMSGSRSV1) loanmsgsrqv1 = Unsupported() loanmsgsrsv1 = Unsupported() tax1098msgsrqv1 = Unsupported() tax1098msgsrsv1 = Unsupported() tax1099msgsrqv1 = SubAggregate(TAX1099MSGSRQV1) tax1099msgsrsv1 = SubAggregate(TAX1099MSGSRSV1) taxw2msgsrqv1 = Unsupported() taxw2msgsrsv1 = Unsupported() tax1095msgsrqv1 = Unsupported() tax1095msgsrsv1 = Unsupported() requiredMutexes = [["signonmsgsrqv1", "signonmsgsrsv1"]] @classmethod def validate_args(cls, *args, **kwargs): # Don't allow mixed *RQ and *RS in the same OFX if not all_equal(key[-7:] for key in kwargs): msg = f"{cls.__name__}: mixed *MSGRQV1 and *MSGSRSV1 are invalid" raise ValueError(msg) super().validate_args(*args, **kwargs) def __repr__(self): s = "<{} ".format(self.__class__.__name__) signon = self.signon if signon.fi is not None: s += f"fid='{signon.fi.fid}' org='{signon.fi.org}' " s += f"len(statements)={len(self.statements)} " s += f"len(securities)={len(self.securities)}>" return s # Human-friendly attribute aliases @property def signon(self): if self.signonmsgsrqv1 is not None: return self.signonmsgsrqv1.sonrq else: assert self.signonmsgsrsv1 is not None return self.signonmsgsrsv1.sonrs @property def securities(self): seclist = [] msgs = getattr(self, "seclistmsgsrsv1", None) if msgs: seclist = msgs.securities return seclist @property def statements(self): stmts = [] for msgs in ( "bankmsgsrqv1", "creditcardmsgsrqv1", "invstmtmsgsrqv1", "bankmsgsrsv1", "creditcardmsgsrsv1", "invstmtmsgsrsv1", ): msg = getattr(self, msgs, None) if msg: stmts.extend(msg.statements) return stmts