Generating OFX
Creating your own OFX requests or responses - as would be needed for, say,
a Python-powered OFX server - is fairly straightforward. However, you will
need to be familiar with the OFX spec. ofxtools validates individual
nodes in the hierarchy, but does not verify compliant sequence order and does
not validate against a DTD.
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)
Now wrap the statement in the required OFX transaction and message-set containers.
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)
That completes the 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.