Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion Pipfile
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,4 @@ pytest-cov = "*"
urllib3 = ">=2.1.0"
intuit-oauth = "==1.2.5"
requests = ">=2.31.0"
simplejson = ">=3.19.1"
requests_oauthlib = ">=1.3.1"
457 changes: 181 additions & 276 deletions Pipfile.lock

Large diffs are not rendered by default.

13 changes: 11 additions & 2 deletions quickbooks/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,13 @@
import base64
import hashlib
import hmac
import decimal

from . import exceptions
from requests_oauthlib import OAuth2Session

to_bytes = lambda value, *args, **kwargs: bytes(value, "utf-8", *args, **kwargs)
def to_bytes(value, *args, **kwargs):
return bytes(value, "utf-8", *args, **kwargs)


class Environments(object):
Expand All @@ -24,6 +26,7 @@ class QuickBooks(object):
minorversion = None
verifier_token = None
invoice_link = False
use_decimal = False

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

if 'use_decimal' in kwargs:
instance.use_decimal = kwargs.get('use_decimal')

return instance

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

try:
result = req.json()
if (self.use_decimal):
result = json.loads(req.text, parse_float=decimal.Decimal)
else:
result = json.loads(req.text)
except:
raise exceptions.QuickbooksException("Error reading json response: {0}".format(req.text), 10000)

Expand Down
18 changes: 11 additions & 7 deletions quickbooks/mixins.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,20 @@
import decimal
import json
from urllib.parse import quote

try: import simplejson as json
except ImportError: import json

from .utils import build_where_clause, build_choose_clause
from .client import QuickBooks
from .exceptions import QuickbooksException
from .utils import build_choose_clause, build_where_clause

class DecimalEncoder(json.JSONEncoder):
def default(self, obj):
if isinstance(obj, decimal.Decimal):
return str(obj)
return super(DecimalEncoder, self).default(obj)

class ToJsonMixin(object):
def to_json(self):
return json.dumps(self, default=self.json_filter(), sort_keys=True, indent=4)
return json.dumps(self, cls=DecimalEncoder, default=self.json_filter(), sort_keys=True, indent=4)

def json_filter(self):
"""
Expand Down Expand Up @@ -178,7 +182,7 @@ def void(self, qb=None):

data = self.get_void_data()
params = self.get_void_params()
results = qb.post(url, json.dumps(data), params=params)
results = qb.post(url, json.dumps(data, cls=DecimalEncoder), params=params)

return results

Expand Down Expand Up @@ -232,7 +236,7 @@ def delete(self, qb=None, request_id=None):
'Id': self.Id,
'SyncToken': self.SyncToken,
}
return qb.delete_object(self.qbo_object_name, json.dumps(data), request_id=request_id)
return qb.delete_object(self.qbo_object_name, json.dumps(data, cls=DecimalEncoder), request_id=request_id)


class DeleteNoIdMixin(object):
Expand Down
1 change: 0 additions & 1 deletion quickbooks/objects/payment.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
from ..client import QuickBooks
from .creditcardpayment import CreditCardPayment
from ..mixins import DeleteMixin, VoidMixin
import json
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

unused import



class PaymentLine(QuickbooksBaseObject):
Expand Down
3 changes: 1 addition & 2 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
intuit-oauth==1.2.4
requests_oauthlib>=1.3.1
requests>=2.31.0
simplejson>=3.19.1
requests>=2.31.0
1 change: 0 additions & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,6 @@ def read(*parts):
'intuit-oauth==1.2.5',
'requests_oauthlib>=1.3.1',
'requests>=2.31.0',
'simplejson>=3.19.1',
'python-dateutil',
],

Expand Down
19 changes: 16 additions & 3 deletions tests/unit/test_client.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import json
from tests.integration.test_base import QuickbooksUnitTestCase

try:
Expand All @@ -6,7 +7,7 @@
from unittest.mock import patch, mock_open

from quickbooks.exceptions import QuickbooksException, SevereException, AuthorizationException
from quickbooks import client
from quickbooks import client, mixins
from quickbooks.objects.salesreceipt import SalesReceipt


Expand Down Expand Up @@ -141,7 +142,7 @@ def test_get_single_object(self, make_req):

@patch('quickbooks.client.QuickBooks.process_request')
def test_make_request(self, process_request):
process_request.return_value = MockResponse()
process_request.return_value = MockResponseJson()

qb_client = client.QuickBooks()
qb_client.company_id = "1234"
Expand Down Expand Up @@ -220,7 +221,7 @@ def test_download_pdf_not_authorized(self, process_request):
@patch('quickbooks.client.QuickBooks.process_request')
def test_make_request_file_closed(self, process_request):
file_path = '/path/to/file.txt'
process_request.return_value = MockResponse()
process_request.return_value = MockResponseJson()
with patch('builtins.open', mock_open(read_data=b'file content')) as mock_file:
qb_client = client.QuickBooks(auth_client=self.auth_client)
qb_client.make_request('POST',
Expand Down Expand Up @@ -253,6 +254,18 @@ def json(self):
def content(self):
return ''

class MockResponseJson:
def __init__(self, json_data=None, status_code=200):
self.json_data = json_data or {}
self.status_code = status_code

@property
def text(self):
return json.dumps(self.json_data, cls=mixins.DecimalEncoder)

def json(self):
return self.json_data


class MockUnauthorizedResponse(object):
@property
Expand Down