class INTERXFERMSGSRQV1(Aggregate): """OFX section 11.13.1.3.1""" intertrnrq = ListAggregate(INTERTRNRQ) recintertrnrq = ListAggregate(RECINTERTRNRQ) intersyncrq = ListAggregate(INTERSYNCRQ) recintersyncrq = ListAggregate(RECINTERSYNCRQ)
class BANKMSGSRSV1(Aggregate): """OFX section 11.13.1.1.2""" stmttrnrs = ListAggregate(STMTTRNRS) stmtendtrnrs = ListAggregate(STMTENDTRNRS) stpchktrnrs = ListAggregate(STPCHKTRNRS) intratrnrs = ListAggregate(INTRATRNRS) recintratrnrs = ListAggregate(RECINTRATRNRS) bankmailtrnrs = ListAggregate(BANKMAILTRNRS) stpchksyncrs = ListAggregate(STPCHKSYNCRS) intrasyncrs = ListAggregate(INTRASYNCRS) recintrasyncrs = ListAggregate(RECINTRASYNCRS) bankmailsyncrs = ListAggregate(BANKMAILSYNCRS) @property def statements(self): stmts = [] for trnrs in self: stmtrs = None if isinstance(trnrs, STMTTRNRS): stmtrs = trnrs.stmtrs elif isinstance(trnrs, STMTENDTRNRS): stmtrs = trnrs.stmtendrs if stmtrs is not None: # Staple wrapper TRNUID, CLTCOOKIE onto STMTRS for convenience stmtrs.trnuid = trnrs.trnuid stmtrs.cltcookie = trnrs.cltcookie stmts.append(stmtrs) return stmts
class INTERXFERMSGSRSV1(Aggregate): """OFX section 11.13.1.3.2""" intertrnrs = ListAggregate(INTERTRNRS) recintertrnrs = ListAggregate(RECINTERTRNRS) intersyncrs = ListAggregate(INTERSYNCRS) recintersyncrs = ListAggregate(RECINTERSYNCRS)
class BANKMSGSRQV1(Aggregate): """OFX section 11.13.1.1.1""" stmttrnrq = ListAggregate(STMTTRNRQ) stmtendtrnrq = ListAggregate(STMTENDTRNRQ) stpchktrnrq = ListAggregate(STPCHKTRNRQ) intratrnrq = ListAggregate(INTRATRNRQ) recintratrnrq = ListAggregate(RECINTRATRNRQ) bankmailtrnrq = ListAggregate(BANKMAILTRNRQ) stpchksyncrq = ListAggregate(STPCHKSYNCRQ) intrasyncrq = ListAggregate(INTRASYNCRQ) recintrasyncrq = ListAggregate(RECINTRASYNCRQ) bankmailsyncrq = ListAggregate(BANKMAILSYNCRQ) @property def statements(self): stmts = [] for trnrq in self: stmtrq = None if isinstance(trnrq, STMTTRNRQ): stmtrq = trnrq.stmtrq elif isinstance(trnrq, STMTTRNRQ): stmtrq = trnrq.stmtendrq if stmtrq is not None: stmts.append(stmtrq) return stmts
class SIGNUPMSGSRSV1(Aggregate): """OFX section 8.1""" enrolltrnrs = ListAggregate(ENROLLTRNRS) acctinfotrnrs = ListAggregate(ACCTINFOTRNRS) accttrnrs = ListAggregate(ACCTTRNRS) chguserinfotrnrs = ListAggregate(CHGUSERINFOTRNRS)
class INVPOSLIST(Aggregate): """OFX section 13.9.2.2""" posdebt = ListAggregate(POSDEBT) posmf = ListAggregate(POSMF) posopt = ListAggregate(POSOPT) posother = ListAggregate(POSOTHER) posstock = ListAggregate(POSSTOCK)
class SECLIST(Aggregate): """OFX section 13.8.4.4""" debtinfo = ListAggregate(DEBTINFO) mfinfo = ListAggregate(MFINFO) optinfo = ListAggregate(OPTINFO) otherinfo = ListAggregate(OTHERINFO) stockinfo = ListAggregate(STOCKINFO)
class ACCTINFO(Aggregate): """ OFX section 8.5.3 The text description is a little ambiguous. Here's what the schema says: <xsd:sequence> <xsd:element name="DESC" type="ofx:ShortMessageType" minOccurs="0"/> <xsd:element name="PHONE" type="ofx:PhoneType" minOccurs="0"/> <xsd:sequence maxOccurs="unbounded"> <xsd:choice> <xsd:element name="BANKACCTINFO" type="ofx:BankAccountInfo"/> <xsd:element name="CCACCTINFO" type="ofx:CreditCardAccountInfo"/> <xsd:element name="BPACCTINFO" type="ofx:BillPaymentAccountInfo"/> <xsd:element name="INVACCTINFO" type="ofx:InvestmentAccountInfo"/> <xsd:element name="PRESACCTINFO" type="ofx:PresentmentAccountInfo"/> </xsd:choice> </xsd:sequence> </xsd:sequence> """ desc = String(80) phone = String(32) bankacctinfo = ListAggregate(BANKACCTINFO) ccacctinfo = ListAggregate(CCACCTINFO) bpacctinfo = ListAggregate(BPACCTINFO) invacctinfo = ListAggregate(INVACCTINFO) # presacctinfo = ListAggregate(PRESACCTINFO) @classmethod def validate_args(cls, *args, **kwargs): # Must contain at least one <xxxACCTINFO> if len(args) == 0: msg = "{} must contain at least one of {}" raise ValueError(msg.format(cls.__name__, cls.listaggregates.keys())) # For a given service xxx, there can be at most one <xxxACCTINFO> # returned. For example, you cannot return two <BANKACCTINFO> # aggregates. sortKey = operator.attrgetter("__class__.__name__") args_copy = sorted(args, key=sortKey) for tag, group in itertools.groupby(args_copy, key=sortKey): if len(list(group)) > 1: msg = "{} contains multiple {} aggregates" raise ValueError(msg.format(cls.__name__, tag)) super().validate_args(*args, **kwargs) def __repr__(self): return "<{} desc='{}' phone='{}' len={}>".format( self.__class__.__name__, self.desc, self.phone, len(self) )
class BILLPAYMSGSRSV1(Aggregate): """ OFX section 12.11.1.2 """ pmttrnrs = ListAggregate(PMTTRNRS) recpmttrnrs = ListAggregate(RECPMTTRNRS) payeetrnrs = ListAggregate(PAYEETRNRS) pmtinqtrnrs = ListAggregate(PMTINQTRNRS) pmtmailtrns = ListAggregate(PMTMAILTRNRS) pmtsyncrs = ListAggregate(PMTSYNCRS) recpmtsyncrs = ListAggregate(RECPMTSYNCRS) payeesyncrs = ListAggregate(PAYEESYNCRS) pmtmailsyncrs = ListAggregate(PMTMAILSYNCRS)
class INVSTMTMSGSRQV1(Aggregate): """OFX section 13.7.1.2.1""" invstmttrnrq = ListAggregate(INVSTMTTRNRQ) invmailtrnrq = ListAggregate(INVMAILTRNRQ) invmailsyncrq = ListAggregate(INVMAILSYNCRQ) @property def statements(self): stmts = [] for trnrq in self: if isinstance(trnrq, INVSTMTTRNRQ): stmtrq = trnrq.invstmtrq if stmtrq is not None: stmts.append(stmtrq) return stmts
class INV401K(Aggregate): """ OFX section 13.9.3 """ employername = NagString(32, required=True) planid = String(32) planjoindate = DateTime() employercontactinfo = String(255) brokercontactinfo = String(255) deferpctpretax = Decimal() deferpctaftertax = Decimal() matchinfo = SubAggregate(MATCHINFO) contribinfo = SubAggregate(CONTRIBINFO) currentvestpct = Decimal() vestinfo = ListAggregate(VESTINFO) loaninfo = ListAggregate(LOANINFO) inv401ksummary = SubAggregate(INV401KSUMMARY)
class INVMAILSYNCRQ(SyncRqList): """ OFX Section 13.10.2.1 """ incimages = Bool(required=True) usehtml = Bool(required=True) invacctfrom = SubAggregate(INVACCTFROM) invmailtrnrq = ListAggregate(INVMAILTRNRQ)
class TAX1099R_V100(Aggregate): """OFX tax extensions section 2.2.10""" srvrtid = String(10, required=True) taxyear = Integer(4, required=True) void = Bool() grossdist = Bool() taxamt = Decimal() taxamtnd = Decimal() totaldist = Bool() capgain = Decimal() fedtaxwh = Decimal() empcontins = Decimal() netunapmp = Decimal() nonempcomp = Decimal() distcode = String(1, required=True) irasepsimp = Bool() annctrctdist = Decimal() annctrctper = Decimal() pertodist = Decimal() totempcont = Decimal() amtallocableirr = Decimal() firstyeardesigroth = Integer(4) sttaxwhagg = ListAggregate(STTAXWHAGG) lcltaxwhagg = ListAggregate(LCLTAXWHAGG) payeraddr = SubAggregate(PAYERADDR, required=True) payerid = String(32, required=True) recaddr = SubAggregate(RECADDR) recid = String(32) recacct = String(32) fatca = Bool() dtbenefitpmt = DateTime() @classmethod def validate_args(cls, *args, **kwargs): # "[IRASEPSIMP] is required if any of the following tags are present in # the 1099R aggregate: GROSSDIST, TAXAMT, FEDTAXWH, STTAXWH, # or LCLTAXWH" has_irasepsimp = "irasepsimp" in kwargs for tag in ("grossdist", "taxamt", "fedtaxwh", "sttaxwh", "lcltaxwh"): if tag in kwargs and not has_irasepsimp: msg = ( "{}.__init__(): irasepsimp must also be provided if {} is provided" ) raise ValueError(msg.format(cls.__name__, tag)) super().validate_args(*args, **kwargs)
class INTERSYNCRQ(SyncRqList): """ OFX section 11.12.3.1 """ bankacctfrom = SubAggregate(BANKACCTFROM) ccacctfrom = SubAggregate(CCACCTFROM) intertrnrq = ListAggregate(INTERTRNRQ) requiredMutexes = SyncRqList.requiredMutexes + [["bankacctfrom", "ccacctfrom"]]
class RECINTRASYNCRQ(SyncRqList): """ OFX section 11.12.5.1 """ bankacctfrom = SubAggregate(BANKACCTFROM) ccacctfrom = SubAggregate(CCACCTFROM) recintratrnrq = ListAggregate(RECINTRATRNRQ) requiredMutexes = SyncRqList.requiredMutexes + [["bankacctfrom", "ccacctfrom"]]
class BANKMAILSYNCRS(SyncRsList): """OFX section 11.12.7.2""" bankacctfrom = SubAggregate(BANKACCTFROM) ccacctfrom = SubAggregate(CCACCTFROM) bankmailtrnrs = ListAggregate(BANKMAILTRNRS) requiredMutexes = [["bankacctfrom", "ccacctfrom"]]
class INTERSYNCRS(SyncRsList): """OFX section 11.12.3.2""" bankacctfrom = SubAggregate(BANKACCTFROM) ccacctfrom = SubAggregate(CCACCTFROM) intertrnrs = ListAggregate(INTERTRNRS) requiredMutexes = [["bankacctfrom", "ccacctfrom"]]
class RECINTRASYNCRS(SyncRsList): """OFX section 11.12.5.2""" bankacctfrom = SubAggregate(BANKACCTFROM) ccacctfrom = SubAggregate(CCACCTFROM) recintratrnrs = ListAggregate(RECINTRATRNRS) requiredMutexes = [["bankacctfrom", "ccacctfrom"]]
class STPCHKRS(Aggregate): """OFX section 11.6.1.1""" curdef = OneOf(*CURRENCY_CODES, required=True) bankacctfrom = SubAggregate(BANKACCTFROM, required=True) stpchknum = ListAggregate(STPCHKNUM) fee = Decimal(required=True) feemsg = String(80, required=True)
class SECLISTMSGSRSV1(Aggregate): """OFX section 13.7.2.2.2""" # N.B. this part of the spec is unusual in that SECLIST is a direct # child of SECLISTMSGSRSV1, unwrapped. SECLISTRS, wrapped in SECLISTTRNS, # is an empty aggregate; including SECLISTTRNRS/SECLISTRS under # SECLISTMSGSTSV1 merely indicates that the accompanying SECLIST was # generated in response to a client SECLISTRQ. seclisttrnrs = ListAggregate(SECLISTTRNRS) seclist = ListAggregate(SECLIST) @property def securities(self): securities = [] for child in self: if isinstance(child, SECLIST): securities.extend(child) return securities
class ACCTINFORS(Aggregate): """ OFX section 8.5.2 """ dtacctup = DateTime(required=True) acctinfo = ListAggregate(ACCTINFO) def __repr__(self): return "<{} dtacctup='{}' len={}>".format(self.__class__.__name__, self.dtacctup, len(self))
class INVSTMTMSGSRSV1(Aggregate): """OFX section 13.7.1.2.2""" invstmttrnrs = ListAggregate(INVSTMTTRNRS) invmailtrnrs = ListAggregate(INVMAILTRNRS) invmailsyncrs = ListAggregate(INVMAILSYNCRS) @property def statements(self): stmts = [] for trnrs in self: if isinstance(trnrs, INVSTMTTRNRS): stmtrs = trnrs.invstmtrs if stmtrs is not None: # Staple wrapper TRNUID, CLTCOOKIE onto STMTRS for convenience stmtrs.trnuid = trnrs.trnuid stmtrs.cltcookie = trnrs.cltcookie stmts.append(stmtrs) return stmts
class CREDITCARDMSGSRQV1(Aggregate): """OFX section 11.13.1.1.1""" ccstmttrnrq = ListAggregate(CCSTMTTRNRQ) ccstmtendtrnrq = ListAggregate(CCSTMTENDTRNRQ) @property def statements(self): stmts = [] for trnrq in self: stmtrq = None if isinstance(trnrq, CCSTMTTRNRQ): stmtrq = trnrq.ccstmtrq elif isinstance(trnrq, CCSTMTENDTRNRQ): stmtrq = trnrq.ccstmtendrq if stmtrq is not None: stmts.append(stmtrq) return stmts
class BANKMAILSYNCRQ(SyncRqList): """ OFX section 11.12.7.1 """ incimages = Bool(required=True) usehtml = Bool(required=True) bankacctfrom = SubAggregate(BANKACCTFROM) ccacctfrom = SubAggregate(CCACCTFROM) bankmailtrnrq = ListAggregate(BANKMAILTRNRQ) requiredMutexes = SyncRqList.requiredMutexes + [["bankacctfrom", "ccacctfrom"]]
class TAX1099INT_V100(Aggregate): """OFX tax extensions section 2.2.12""" srvrtid = String(10, required=True) taxyear = Integer(4, required=True) void = Bool() corrected = Bool() payerrtn = String(32) intincome = Decimal() intusbndtrs = Decimal() fedtaxwh = Decimal() investexp = Decimal() fortaxpd = Decimal() forincomeamt = Decimal() forcnt = String(32) forincome = ListAggregate(FORINCOME) taxexemptint = Decimal() origstate = ListAggregate(ORIGSTATE) specifiedpabint = Decimal() marketdiscount = Decimal() bondpremium = Decimal() bondpremusobligations = Decimal() tebondpremium = Decimal() cusipnum = String(32) statecode = String(2) stateidnum = String(32) statetaxwheld = Decimal() addlstatetaxwhagg = SubAggregate(ADDLSTATETAXWHAGG) payeraddr = SubAggregate(PAYERADDR, required=True) payerid = String(32, required=True) recaddr = SubAggregate(RECADDR) recid = String(32) recacct = String(32) tinnot = Bool() fatca = Bool() optionalMutexes = [ ["forcnt", "forincome"], ["statecode", "addlstatetaxwhagg"], ["stateidnum", "addlstatetaxwhagg"], ["statetaxwheld", "addlstatetaxwhagg"], ]
class INVOICE(Aggregate): """OFX Section 12.5.2.3""" invno = String(32, required=True) invtotalamt = Decimal(required=True) invpaidamt = Decimal(required=True) invdate = DateTime(required=True) invdesc = String(80, required=True) discount = SubAggregate(DISCOUNT) adjustment = SubAggregate(ADJUSTMENT) lineitem = ListAggregate(LINEITEM)
class TAX1099MSGSRSV1(Aggregate): """ OFX tax extensions section 2.2.2 """ tax1099trnrs = ListAggregate(TAX1099TRNRS) @classmethod def validate_args(cls, *args, **kwargs): # Must contain at least one TAX1099TRNRS if len(args) == 0: msg = "{} must contain at least one of {}" raise ValueError(msg.format(cls.__name__, list(cls.listaggregates.keys()))) super().validate_args(*args, **kwargs)
class MFACHALLENGERS(Aggregate): """ OFX Section 2.5.4.2 """ mfachallenge = ListAggregate(MFACHALLENGE) @classmethod def validate_args(cls, *args, **kwargs): # "Challenge question aggregate (1 or more)" if len(args) == 0: msg = "{} must contain at least one item" raise ValueError(msg.format(cls.__name__)) super().validate_args(*args, **kwargs)
class CONTRIBINFO(Aggregate): """ OFX section 13.9.3 """ contribsecurity = ListAggregate(CONTRIBSECURITY) @classmethod def validate_args(cls, *args, **kwargs): # "current contribution allocation for a security (1 or more)" if len(args) == 0: msg = "{} must contain at least one item" raise ValueError(msg.format(cls.__name__)) super().validate_args(*args, **kwargs)
class CREDITCARDMSGSRSV1(Aggregate): """OFX section 11.13.1.1.2""" ccstmttrnrs = ListAggregate(CCSTMTTRNRS) ccstmtendtrnrs = ListAggregate(CCSTMTENDTRNRS) @property def statements(self): stmts = [] for trnrs in self: stmtrs = None if isinstance(trnrs, CCSTMTTRNRS): stmtrs = trnrs.ccstmtrs else: assert isinstance(trnrs, CCSTMTENDTRNRS) stmtrs = trnrs.ccstmtendrs if stmtrs is not None: # Staple wrapper TRNUID, CLTCOOKIE onto STMTRS for convenience stmtrs.trnuid = trnrs.trnuid stmtrs.cltcookie = trnrs.cltcookie stmts.append(stmtrs) return stmts