Generating OFX¶
Creating your own OFX requests or responses - as would be neeeded for, say,
a Python-powered OFX server - is fairly straightforward. However, you will
need to be pretty familiar with the OFX spec. ofxtools
validates
individual nodes in the hierarchy, but doesn’t really do anything to verify
compliant sequence order, for example. It doesn’t validate against a DTD.
That is on you, friend.
Don’t forget to make datetimes timezone-aware.
As an example, we’ll create a trivial bank statement response. You can follow along in section 11.4.2.2 of the OFX spec.
In [1]: from ofxtools.models import *
In [2]: from ofxtools.utils import UTC
In [3]: from decimal import Decimal
In [4]: from datetime import datetime
In [5]: ledgerbal = LEDGERBAL(balamt=Decimal('150.65'),
...: dtasof=datetime(2015, 1, 1, tzinfo=UTC))
In [6]: acctfrom = BANKACCTFROM(bankid='123456789',
...: acctid='23456', accttype='CHECKING') # OFX Section 11.3.1
In [7]: stmtrs = STMTRS(curdef='USD', bankacctfrom=acctfrom,
...: ledgerbal=ledgerbal)
So far so good. Now to slather it in wrapper cruft and garnish with metadata.
In [8]: status = STATUS(code=0, severity='INFO')
In [9]: stmttrnrs = STMTTRNRS(trnuid='5678', status=status, stmtrs=stmtrs)
In [10]: bankmsgsrs = BANKMSGSRSV1(stmttrnrs)
In [11]: fi = FI(org='Illuminati', fid='666') # Required for Quicken compatibility
In [12]: sonrs = SONRS(status=status,
...: dtserver=datetime(2015, 1, 2, 17, tzinfo=UTC),
...: language='ENG', fi=fi)
In [13]: signonmsgs = SIGNONMSGSRSV1(sonrs=sonrs)
In [14]: ofx = OFX(signonmsgsrsv1=signonmsgs, bankmsgsrsv1=bankmsgsrs)
OK, that’s the complete OFX message body. To serialize it, we transform the
ofxtools.models
structure back into an instance of
xml.etree.ElementTree.ElementTree
.
In [15]: import xml.etree.ElementTree as ET
In [16]: root = ofx.to_etree()
In [17]: message = ET.tostring(root).decode()
In [18]: message
Out[18]: '<OFX><SIGNONMSGSRSV1><SONRS><STATUS><CODE>0</CODE><SEVERITY>INFO</SEVERITY></STATUS><DTSERVER>20150102170000</DTSERVER><LANGUAGE>ENG</LANGUAGE><FI><ORG>Illuminati</ORG><FID>666</FID></FI></SONRS></SIGNONMSGSRSV1><BANKMSGSRSV1><STMTTRNRS><TRNUID>5678</TRNUID><STATUS><CODE>0</CODE><SEVERITY>INFO</SEVERITY></STATUS><STMTRS><CURDEF>USD</CURDEF><BANKACCTFROM><BANKID>123456789</BANKID><ACCTID>23456</ACCTID><ACCTTYPE>CHECKING</ACCTTYPE></BANKACCTFROM><LEDGERBAL><BALAMT>150.65</BALAMT><DTASOF>20150101000000</DTASOF></LEDGERBAL></STMTRS></STMTTRNRS></BANKMSGSRSV1></OFX>'
One last step - we need to prepend an OFX header.
In [19]: from ofxtools.header import make_header
In [20]: header = str(make_header(version=220))
In [21]: header
Out[21]: '<?xml version="1.0" encoding="UTF-8" standalone="no"?>\r\n<?OFX OFXHEADER="200" VERSION="220" SECURITY="NONE" OLDFILEUID="NONE" NEWFILEUID="NONE"?>\r\n'
In [22]: response = header + message
In [23]: response
Out[23]: '<?xml version="1.0" encoding="UTF-8" standalone="no"?>\r\n<?OFX OFXHEADER="200" VERSION="220" SECURITY="NONE" OLDFILEUID="NONE" NEWFILEUID="NONE"?>\r\n<OFX><SIGNONMSGSRSV1><SONRS><STATUS><CODE>0</CODE><SEVERITY>INFO</SEVERITY></STATUS><DTSERVER>20150102170000</DTSERVER><LANGUAGE>ENG</LANGUAGE><FI><ORG>Illuminati</ORG><FID>666</FID></FI></SONRS></SIGNONMSGSRSV1><BANKMSGSRSV1><STMTTRNRS><TRNUID>5678</TRNUID><STATUS><CODE>0</CODE><SEVERITY>INFO</SEVERITY></STATUS><STMTRS><CURDEF>USD</CURDEF><BANKACCTFROM><BANKID>123456789</BANKID><ACCTID>23456</ACCTID><ACCTTYPE>CHECKING</ACCTTYPE></BANKACCTFROM><LEDGERBAL><BALAMT>150.65</BALAMT><DTASOF>20150101000000</DTASOF></LEDGERBAL></STMTRS></STMTTRNRS></BANKMSGSRSV1></OFX>'
Hand that to your HTTP server, and off you go.