Skip to content

Commit b981ba2

Browse files
authored
Merge pull request #67 from cstarner/master
Added Change Data Capture Support
2 parents 62d2852 + 308b784 commit b981ba2

File tree

11 files changed

+298
-5
lines changed

11 files changed

+298
-5
lines changed

README.rst

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -245,6 +245,38 @@ Review results for batch operation:
245245
for error in fault.Error:
246246
print "Error " + error.Message
247247

248+
Change Data Capture
249+
-----------------------
250+
Change Data Capture returns a list of objects that have changed since a given time (see `Change data capture`_ for more
251+
details):
252+
253+
::
254+
255+
from quickbooks.cdc import change_data_capture
256+
from quickbooks.objects import Invoice
257+
258+
cdc_response = change_data_capture([Invoice], "2017-01-01T00:00:00", qb=client)
259+
for invoice in cdc_response.Invoice:
260+
# Do something with the invoice
261+
262+
Querying muliple entity types at the same time:
263+
264+
::
265+
266+
from quickbooks.objects import Invoice, Customer
267+
268+
cdc_response = change_data_capture([Invoice, Customer], "2017-01-01T00:00:00", qb=client)
269+
270+
271+
If you use a ``datetime`` object for the timestamp, it is automatically converted to a string:
272+
273+
::
274+
275+
from datetime import datetime
276+
277+
cdc_response = change_data_capture([Invoice, Customer], datetime(2017, 1, 1, 0, 0, 0), qb=client)
278+
279+
248280
Attachments
249281
----------------
250282
See `Attachable documentation`_ for list of valid file types, file size limits and other restrictions.
@@ -279,6 +311,8 @@ Attaching a file to customer:
279311
attachment.ContentType = 'application/pdf'
280312
attachment.save(qb=client)
281313

314+
315+
282316
Working with JSON data
283317
----------------
284318
All objects include ``to_json`` and ``from_json`` methods.
@@ -330,6 +364,8 @@ on Python 2.
330364
.. _Minor versions: https://developer.intuit.com/docs/0100_quickbooks_online/0200_dev_guides/accounting/minor_versions
331365
.. _Attachable documentation: https://developer.intuit.com/docs/api/accounting/Attachable
332366
.. _Integration tests folder: https://github.com/sidecars/python-quickbooks/tree/master/tests/integration
367+
.. _Change data capture: https://developer.intuit.com/docs/api/accounting/changedatacapture
368+
333369

334370
.. |Build Status| image:: https://travis-ci.org/sidecars/python-quickbooks.svg?branch=master
335371
:target: https://travis-ci.org/sidecars/python-quickbooks

quickbooks/cdc.py

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
from datetime import datetime
2+
from .client import QuickBooks
3+
from .objects.changedatacapture import QueryResponse, CDCResponse
4+
from .helpers import qb_datetime_format
5+
6+
7+
def change_data_capture(qbo_class_list, timestamp, qb=None):
8+
if qb is None:
9+
qb = QuickBooks()
10+
11+
cdc_class_dict = dict((cls.qbo_object_name, cls) for cls in qbo_class_list)
12+
13+
cdc_class_names = list(cdc_class_dict.keys())
14+
entity_list_string = ','.join(cdc_class_names)
15+
16+
if isinstance(timestamp, datetime):
17+
timestamp_string = qb_datetime_format(timestamp)
18+
else:
19+
timestamp_string = timestamp
20+
21+
resp = qb.change_data_capture(entity_list_string, timestamp_string)
22+
23+
cdc_response_dict = resp.pop('CDCResponse')
24+
cdc_response = CDCResponse.from_json(resp)
25+
26+
query_response_list = cdc_response_dict[0]['QueryResponse']
27+
for query_response_dict in query_response_list:
28+
qb_object_name = [x for x in query_response_dict if x in cdc_class_names][0]
29+
qb_object_list = query_response_dict.pop(qb_object_name)
30+
qb_object_cls = cdc_class_dict[qb_object_name]
31+
32+
query_response = QueryResponse.from_json(query_response_dict)
33+
query_response._object_list = [qb_object_cls.from_json(obj) for obj in qb_object_list]
34+
35+
setattr(cdc_response, qb_object_name, query_response)
36+
37+
return cdc_response

quickbooks/client.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -203,7 +203,6 @@ def get_access_tokens(self, oauth_verifier):
203203

204204
self.access_token = session.access_token
205205
self.access_token_secret = session.access_token_secret
206-
207206
return session
208207

209208
def disconnect_account(self):
@@ -215,6 +214,14 @@ def disconnect_account(self):
215214
result = self.make_request("GET", url)
216215
return result
217216

217+
def change_data_capture(self, entity_string, changed_since):
218+
url = self.api_url + "/company/{0}/cdc".format(self.company_id)
219+
220+
params = {"entities": entity_string, "changedSince": changed_since}
221+
222+
result = self.make_request("GET", url, params=params)
223+
return result
224+
218225
def reconnect_account(self):
219226
"""
220227
Reconnect current account by refreshing OAuth access tokens

quickbooks/mixins.py

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -223,3 +223,36 @@ def download_pdf(self, qb=None):
223223
raise QuickbooksException(
224224
"Cannot download {0} when no Id is assigned or if no quickbooks client is passed in".format(
225225
self.qbo_object_name))
226+
227+
228+
class ObjectListMixin(object):
229+
qbo_object_name = ""
230+
_object_list = []
231+
232+
def __iter__(self):
233+
return self._object_list.__iter__()
234+
235+
def __len__(self):
236+
return self._object_list.__len__()
237+
238+
def __contains__(self, item):
239+
return self._object_list.__contains__(item)
240+
241+
def __getitem__(self, key):
242+
# if key is of invalid type or value, the list values will raise the error
243+
return self._object_list.__getitem__(key)
244+
245+
def __setitem__(self, key, value):
246+
self._object_list.__setitem__(key, value)
247+
248+
def __delitem__(self, key):
249+
self._object_list.__delitem__(key)
250+
251+
def __reversed__(self):
252+
return self._object_list.__reversed__()
253+
254+
def append(self, value):
255+
self._object_list.append(value)
256+
257+
def pop(self, *args, **kwargs):
258+
return self._object_list.pop(*args, **kwargs)
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
from ..mixins import FromJsonMixin, ObjectListMixin
2+
3+
4+
class CDCResponse(FromJsonMixin):
5+
qbo_object_name = "CDCResponse"
6+
7+
def __init__(self):
8+
super(CDCResponse, self).__init__()
9+
10+
11+
class QueryResponse(FromJsonMixin, ObjectListMixin):
12+
qbo_object_name = "QueryResponse"
13+
14+
def __init__(self):
15+
super(QueryResponse, self).__init__()

requirements.txt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
rauth>=0.7.1
2+
requests>=2.7.0
3+
simplejson>=2.2.0
4+
six>=1.4.0

tests/integration/test_purchaseorder.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ def test_create(self):
4646
purchaseorder.APAccountRef = account.to_ref()
4747
purchaseorder.TotalAmt = 100
4848

49-
print purchaseorder.to_json()
49+
#print purchaseorder.to_json()
5050
purchaseorder.save(qb=self.qb_client)
5151

5252
query_purchaseorder = PurchaseOrder.get(purchaseorder.Id, qb=self.qb_client)

tests/unit/test_batch.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
import unittest
2-
from mock import patch
2+
try:
3+
from mock import patch
4+
except ImportError:
5+
from unittest.mock import patch
36
from quickbooks import batch, client
47
from quickbooks.objects.customer import Customer
58
from quickbooks.exceptions import QuickbooksException

tests/unit/test_cdc.py

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
import unittest
2+
try:
3+
from mock import patch
4+
except ImportError:
5+
from unittest.mock import patch
6+
from quickbooks.cdc import change_data_capture
7+
from quickbooks.objects import Invoice, Customer
8+
from quickbooks import QuickBooks
9+
from datetime import datetime
10+
11+
class ChangeDataCaptureTest(unittest.TestCase):
12+
13+
def setUp(self):
14+
self.qb_client = QuickBooks(
15+
sandbox=True,
16+
consumer_key="update_consumer_key",
17+
consumer_secret="update_consumer_secret",
18+
access_token="update_access_token",
19+
access_token_secret="update_access_token_secret",
20+
company_id="update_company_id",
21+
callback_url="update_callback_url"
22+
)
23+
24+
self.cdc_json_response = {
25+
"CDCResponse": [
26+
{
27+
"QueryResponse": [
28+
{
29+
"Customer": [
30+
{
31+
"Id": 1,
32+
"DisplayName": "TestCustomer",
33+
"Job": False,
34+
"Balance": 0
35+
}
36+
],
37+
"startPosition": 1,
38+
"maxResults": 1
39+
},
40+
{
41+
"Invoice": [
42+
{
43+
"DocNumber": "12344",
44+
"TxnDate": "2017-01-01",
45+
"Line": [
46+
{
47+
"Id": 1
48+
},
49+
{
50+
"Id": 2
51+
}
52+
]
53+
},
54+
{
55+
"DocNumber": "12345",
56+
"TxnDate": "2017-01-01",
57+
"Line": [
58+
{
59+
"Id": 1
60+
},
61+
{
62+
"Id": 2
63+
}
64+
]
65+
},
66+
],
67+
"startPosition": 1,
68+
"maxResults": 2
69+
}
70+
]
71+
}
72+
],
73+
"time": "2016-01-01T00:00:00"
74+
}
75+
76+
77+
@patch('quickbooks.client.QuickBooks.make_request')
78+
def test_change_data_capture(self, make_request):
79+
make_request.return_value = self.cdc_json_response.copy()
80+
cdc_response = change_data_capture([Invoice, Customer], "2017-01-01T00:00:00")
81+
self.assertEquals(1, len(cdc_response.Customer))
82+
self.assertEquals(2, len(cdc_response.Invoice))
83+
84+
85+
@patch('quickbooks.client.QuickBooks.make_request')
86+
def test_change_data_capture_with_timestamp(self, make_request):
87+
make_request.return_value = self.cdc_json_response.copy()
88+
cdc_response_with_datetime = change_data_capture([Invoice, Customer], datetime(2017, 1, 1, 0, 0, 0))
89+
self.assertEquals(1, len(cdc_response_with_datetime.Customer))
90+
self.assertEquals(2, len(cdc_response_with_datetime.Invoice))

tests/unit/test_client.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
import unittest
22

3-
from mock import patch
3+
try:
4+
from mock import patch
5+
except ImportError:
6+
from unittest.mock import patch
47

58
from quickbooks.exceptions import QuickbooksException, SevereException
69
from quickbooks import client

0 commit comments

Comments
 (0)