Skip to content

Commit da51fbc

Browse files
authored
Merge branch 'master' into pablogamboa/remove-simplejson
2 parents ddbbd3d + ea194e8 commit da51fbc

8 files changed

Lines changed: 48 additions & 21 deletions

File tree

Pipfile.lock

Lines changed: 7 additions & 7 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

contributing.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,12 +27,12 @@ I use [pytest](https://docs.pytest.org/en/7.4.x/contents.html), [Coverage](https
2727

2828
6. Run all tests: ```pytest --cov```
2929
Run only unit tests: ```pytest tests/unit --cov```
30-
Run only integration tests: ```pytest tests/intergration --cov```
30+
Run only integration tests: ```pytest tests/integration --cov```
3131

3232

3333

3434
## Creating new tests
35-
Normal Unit tests that do not connect to the QBO API should be located under `test/unit` Test that connect to QBO API should go under `tests/integration`. Inheriting from `QuickbooksTestCase` will automatically setup `self.qb_client` to use when connecting to QBO.
35+
Normal Unit tests that do not connect to the QBO API should be located under `test/unit`. Tests that connect to QBO API should go under `tests/integration`. Inheriting from `QuickbooksTestCase` will automatically setup `self.qb_client` to use when connecting to QBO.
3636

3737
Example:
3838
```

quickbooks/client.py

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,13 @@
44
import base64
55
import hashlib
66
import hmac
7+
import decimal
78

89
from . import exceptions
910
from requests_oauthlib import OAuth2Session
1011

11-
to_bytes = lambda value, *args, **kwargs: bytes(value, "utf-8", *args, **kwargs)
12+
def to_bytes(value, *args, **kwargs):
13+
return bytes(value, "utf-8", *args, **kwargs)
1214

1315

1416
class Environments(object):
@@ -24,6 +26,7 @@ class QuickBooks(object):
2426
minorversion = None
2527
verifier_token = None
2628
invoice_link = False
29+
use_decimal = False
2730

2831
sandbox_api_url_v3 = "https://sandbox-quickbooks.api.intuit.com/v3"
2932
api_url_v3 = "https://quickbooks.api.intuit.com/v3"
@@ -79,6 +82,9 @@ def __new__(cls, **kwargs):
7982
if 'verifier_token' in kwargs:
8083
instance.verifier_token = kwargs.get('verifier_token')
8184

85+
if 'use_decimal' in kwargs:
86+
instance.use_decimal = kwargs.get('use_decimal')
87+
8288
return instance
8389

8490
def _start_session(self):
@@ -206,7 +212,10 @@ def make_request(self, request_type, url, request_body=None, content_type='appli
206212
"Application authentication failed", error_code=req.status_code, detail=req.text)
207213

208214
try:
209-
result = req.json()
215+
if (self.use_decimal):
216+
result = json.loads(req.text, parse_float=decimal.Decimal)
217+
else:
218+
result = json.loads(req.text)
210219
except:
211220
raise exceptions.QuickbooksException("Error reading json response: {0}".format(req.text), 10000)
212221

quickbooks/mixins.py

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,20 @@
1+
import decimal
12
import json
23
from urllib.parse import quote
34

4-
5-
from .utils import build_where_clause, build_choose_clause
65
from .client import QuickBooks
76
from .exceptions import QuickbooksException
7+
from .utils import build_choose_clause, build_where_clause
88

9+
class DecimalEncoder(json.JSONEncoder):
10+
def default(self, obj):
11+
if isinstance(obj, decimal.Decimal):
12+
return str(obj)
13+
return super(DecimalEncoder, self).default(obj)
914

1015
class ToJsonMixin(object):
1116
def to_json(self):
12-
return json.dumps(self, default=self.json_filter(), sort_keys=True, indent=4)
17+
return json.dumps(self, cls=DecimalEncoder, default=self.json_filter(), sort_keys=True, indent=4)
1318

1419
def json_filter(self):
1520
"""
@@ -177,7 +182,7 @@ def void(self, qb=None):
177182

178183
data = self.get_void_data()
179184
params = self.get_void_params()
180-
results = qb.post(url, json.dumps(data), params=params)
185+
results = qb.post(url, json.dumps(data, cls=DecimalEncoder), params=params)
181186

182187
return results
183188

@@ -231,7 +236,7 @@ def delete(self, qb=None, request_id=None):
231236
'Id': self.Id,
232237
'SyncToken': self.SyncToken,
233238
}
234-
return qb.delete_object(self.qbo_object_name, json.dumps(data), request_id=request_id)
239+
return qb.delete_object(self.qbo_object_name, json.dumps(data, cls=DecimalEncoder), request_id=request_id)
235240

236241

237242
class DeleteNoIdMixin(object):

quickbooks/objects/payment.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
from ..client import QuickBooks
55
from .creditcardpayment import CreditCardPayment
66
from ..mixins import DeleteMixin, VoidMixin
7-
import json
87

98

109
class PaymentLine(QuickbooksBaseObject):

requirements.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
intuit-oauth==1.2.4
22
requests_oauthlib>=1.3.1
3-
requests>=2.31.0
3+
requests>=2.31.0

setup.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ def read(*parts):
5050
'Programming Language :: Python :: 3.9',
5151
'Programming Language :: Python :: 3.10',
5252
'Programming Language :: Python :: 3.11',
53+
'Programming Language :: Python :: 3.12',
5354
],
5455
packages=find_packages(exclude=("tests",)),
5556
)

tests/unit/test_client.py

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import json
12
from tests.integration.test_base import QuickbooksUnitTestCase
23

34
try:
@@ -6,7 +7,7 @@
67
from unittest.mock import patch, mock_open
78

89
from quickbooks.exceptions import QuickbooksException, SevereException, AuthorizationException
9-
from quickbooks import client
10+
from quickbooks import client, mixins
1011
from quickbooks.objects.salesreceipt import SalesReceipt
1112

1213

@@ -141,7 +142,7 @@ def test_get_single_object(self, make_req):
141142

142143
@patch('quickbooks.client.QuickBooks.process_request')
143144
def test_make_request(self, process_request):
144-
process_request.return_value = MockResponse()
145+
process_request.return_value = MockResponseJson()
145146

146147
qb_client = client.QuickBooks()
147148
qb_client.company_id = "1234"
@@ -220,7 +221,7 @@ def test_download_pdf_not_authorized(self, process_request):
220221
@patch('quickbooks.client.QuickBooks.process_request')
221222
def test_make_request_file_closed(self, process_request):
222223
file_path = '/path/to/file.txt'
223-
process_request.return_value = MockResponse()
224+
process_request.return_value = MockResponseJson()
224225
with patch('builtins.open', mock_open(read_data=b'file content')) as mock_file:
225226
qb_client = client.QuickBooks(auth_client=self.auth_client)
226227
qb_client.make_request('POST',
@@ -253,6 +254,18 @@ def json(self):
253254
def content(self):
254255
return ''
255256

257+
class MockResponseJson:
258+
def __init__(self, json_data=None, status_code=200):
259+
self.json_data = json_data or {}
260+
self.status_code = status_code
261+
262+
@property
263+
def text(self):
264+
return json.dumps(self.json_data, cls=mixins.DecimalEncoder)
265+
266+
def json(self):
267+
return self.json_data
268+
256269

257270
class MockUnauthorizedResponse(object):
258271
@property

0 commit comments

Comments
 (0)