From 82a18763a8cdac636ffa78617f2271f5c0d52f80 Mon Sep 17 00:00:00 2001 From: Dylan Donkersgoed Date: Mon, 28 Jan 2019 18:43:54 -0500 Subject: [PATCH] Added functionality for specifying end date when downloading transactions, making it possible to collect transactions between Y days before X and X --- ofxclient/account.py | 34 ++++++++++++++++++---------------- ofxclient/client.py | 7 ++++--- 2 files changed, 22 insertions(+), 19 deletions(-) diff --git a/ofxclient/account.py b/ofxclient/account.py index 686a440..2103c0a 100644 --- a/ofxclient/account.py +++ b/ofxclient/account.py @@ -90,7 +90,7 @@ def long_description(self): def _default_description(self): return self.number_masked() - def download(self, days=60): + def download(self, days=60, end_date=datetime.datetime.now()): """Downloaded OFX response for the given time range :param days: Number of days to look back at @@ -98,13 +98,14 @@ def download(self, days=60): :rtype: :py:class:`StringIO` """ - days_ago = datetime.datetime.now() - datetime.timedelta(days=days) + days_ago = end_date - datetime.timedelta(days=days) as_of = time.strftime("%Y%m%d", days_ago.timetuple()) - query = self._download_query(as_of=as_of) + until = time.strftime('%Y%m%d', end_date.timetuple()) + query = self._download_query(as_of=as_of, until=until) response = self.institution.client().post(query) return StringIO(response) - def download_parsed(self, days=60): + def download_parsed(self, days=60, end_date=datetime.datetime.now()): """Downloaded OFX response parsed by :py:meth:`OfxParser.parse` :param days: Number of days to look back at @@ -113,31 +114,31 @@ def download_parsed(self, days=60): """ if IS_PYTHON_2: return OfxParser.parse( - self.download(days=days) + self.download(days=days, end_date=datetime.datetime.now()) ) else: return OfxParser.parse( - BytesIO(self.download(days=days).read().encode()) + BytesIO(self.download(days=days, end_date=end_date).read().encode()) ) - def statement(self, days=60): + def statement(self, days=60, end_date=datetime.datetime.now()): """Download the :py:class:`ofxparse.Statement` given the time range :param days: Number of days to look back at :type days: integer :rtype: :py:class:`ofxparser.Statement` """ - parsed = self.download_parsed(days=days) + parsed = self.download_parsed(days=days, end_date=end_date) return parsed.account.statement - def transactions(self, days=60): + def transactions(self, days=60, end_date=datetime.datetime.now()): """Download a a list of :py:class:`ofxparse.Transaction` objects :param days: Number of days to look back at :type days: integer :rtype: list of :py:class:`ofxparser.Transaction` objects """ - return self.statement(days=days).transactions + return self.statement(days=days, end_date=end_date).transactions def serialize(self): """Serialize predictably for use in configuration storage. @@ -244,7 +245,7 @@ def __init__(self, broker_id, **kwargs): super(BrokerageAccount, self).__init__(**kwargs) self.broker_id = broker_id - def _download_query(self, as_of): + def _download_query(self, as_of, until): """Formulate the specific query needed for download Not intended to be called by developers directly. @@ -254,7 +255,7 @@ def _download_query(self, as_of): """ c = self.institution.client() q = c.brokerage_account_query( - number=self.number, date=as_of, broker_id=self.broker_id) + number=self.number, date=as_of, date_end=until, broker_id=self.broker_id) return q @@ -278,7 +279,7 @@ def __init__(self, routing_number, account_type, **kwargs): self.routing_number = routing_number self.account_type = account_type - def _download_query(self, as_of): + def _download_query(self, as_of, until): """Formulate the specific query needed for download Not intended to be called by developers directly. @@ -289,7 +290,8 @@ def _download_query(self, as_of): c = self.institution.client() q = c.bank_account_query( number=self.number, - date=as_of, + date_start=as_of, + date_end=until, account_type=self.account_type, bank_id=self.routing_number) return q @@ -307,7 +309,7 @@ class CreditCardAccount(Account): def __init__(self, **kwargs): super(CreditCardAccount, self).__init__(**kwargs) - def _download_query(self, as_of): + def _download_query(self, as_of, until): """Formulate the specific query needed for download Not intended to be called by developers directly. @@ -316,5 +318,5 @@ def _download_query(self, as_of): :type as_of: string """ c = self.institution.client() - q = c.credit_card_account_query(number=self.number, date=as_of) + q = c.credit_card_account_query(number=self.number, date=as_of, date_end=until) return q diff --git a/ofxclient/client.py b/ofxclient/client.py index 036dfaf..0e120f2 100644 --- a/ofxclient/client.py +++ b/ofxclient/client.py @@ -108,10 +108,10 @@ def authenticated_query( contents.append(with_message) return LINE_ENDING.join([self.header(), _tag(*contents)]) - def bank_account_query(self, number, date, account_type, bank_id): + def bank_account_query(self, number, date_start, date_end, account_type, bank_id): """Bank account statement request""" return self.authenticated_query( - self._bareq(number, date, account_type, bank_id) + self._bareq(number, date_start, date_end, account_type, bank_id) ) def credit_card_account_query(self, number, date): @@ -236,7 +236,7 @@ def _acctreq(self, dtstart): return self._message("SIGNUP", "ACCTINFO", req) # this is from _ccreq below and reading page 176 of the latest OFX doc. - def _bareq(self, acctid, dtstart, accttype, bankid): + def _bareq(self, acctid, dtstart, dtend, accttype, bankid): req = _tag("STMTRQ", _tag("BANKACCTFROM", _field("BANKID", bankid), @@ -244,6 +244,7 @@ def _bareq(self, acctid, dtstart, accttype, bankid): _field("ACCTTYPE", accttype)), _tag("INCTRAN", _field("DTSTART", dtstart), + _field("DTEND", dtend), _field("INCLUDE", "Y"))) return self._message("BANK", "STMT", req)