Skip to content

Commit 874bf73

Browse files
committed
Updates for fault bug
1 parent 31eca80 commit 874bf73

File tree

4 files changed

+125
-66
lines changed

4 files changed

+125
-66
lines changed

quickbooks/client.py

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -214,7 +214,6 @@ def make_request(
214214
}
215215

216216
if file_path:
217-
attachment = open(file_path, "rb")
218217
url = url.replace("attachable", "upload")
219218
boundary = "-------------PythonMultipartPost"
220219
headers.update(
@@ -227,7 +226,8 @@ def make_request(
227226
}
228227
)
229228

230-
binary_data = str(base64.b64encode(attachment.read()).decode("ascii"))
229+
with open(file_path, "rb") as attachment:
230+
binary_data = str(base64.b64encode(attachment.read()).decode("ascii"))
231231

232232
content_type = json.loads(request_body)["ContentType"]
233233

@@ -281,8 +281,12 @@ def make_request(
281281
"Error reading json response: {0}".format(req.text), 10000
282282
)
283283

284-
if "Fault" in result or "Fault" in str(result):
285-
self.handle_exceptions(result["Fault"])
284+
if "Fault" in result:
285+
self.handle_exceptions(
286+
result.get(
287+
"Fault", {"code": 0, "Message": "Unknown error", "Detail": result}
288+
)
289+
)
286290
elif not req.status_code == httplib.OK:
287291
raise exceptions.QuickbooksException(
288292
"Error returned with status code '{0}': {1}".format(

quickbooks/mixins.py

Lines changed: 99 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,47 @@
11
from urllib.parse import quote
2+
import decimal
23

3-
try: import simplejson as json
4-
except ImportError: import json
4+
try:
5+
import simplejson as json
6+
except ImportError:
7+
import json
58

69
from .utils import build_where_clause, build_choose_clause
710
from .client import QuickBooks
811
from .exceptions import QuickbooksException
912

1013

14+
class DecimalEncoder(json.JSONEncoder):
15+
def default(self, o):
16+
if isinstance(o, decimal.Decimal):
17+
return str(o)
18+
return super(DecimalEncoder, self).default(o)
19+
20+
1121
class ToJsonMixin(object):
1222
def to_json(self):
13-
return json.dumps(self, default=self.json_filter(), sort_keys=True, indent=4)
23+
return json.dumps(
24+
self,
25+
cls=DecimalEncoder,
26+
default=self.json_filter(),
27+
sort_keys=True,
28+
indent=4,
29+
)
1430

1531
def json_filter(self):
1632
"""
1733
filter out properties that have names starting with _
1834
or properties that have a value of None
1935
"""
20-
return lambda obj: dict((k, v) for k, v in obj.__dict__.items()
21-
if not k.startswith('_') and getattr(obj, k) is not None)
36+
return lambda obj: (
37+
str(obj)
38+
if isinstance(obj, decimal.Decimal)
39+
else dict(
40+
(k, v)
41+
for k, v in obj.__dict__.items()
42+
if not k.startswith("_") and getattr(obj, k) is not None
43+
)
44+
)
2245

2346

2447
class FromJsonMixin(object):
@@ -39,8 +62,8 @@ def from_json(cls, json_data):
3962

4063
for data in json_data[key]:
4164

42-
if 'DetailType' in data and data['DetailType'] in obj.detail_dict:
43-
sub_obj = obj.detail_dict[data['DetailType']]()
65+
if "DetailType" in data and data["DetailType"] in obj.detail_dict:
66+
sub_obj = obj.detail_dict[data["DetailType"]]()
4467
else:
4568
sub_obj = obj.list_dict[key]()
4669

@@ -61,17 +84,21 @@ def to_dict(obj, classkey=None):
6184
"""
6285
if isinstance(obj, dict):
6386
data = {}
64-
for (k, v) in obj.items():
87+
for k, v in obj.items():
6588
data[k] = to_dict(v, classkey)
6689
return data
6790
elif hasattr(obj, "_ast"):
6891
return to_dict(obj._ast())
6992
elif hasattr(obj, "__iter__") and not isinstance(obj, str):
7093
return [to_dict(v, classkey) for v in obj]
7194
elif hasattr(obj, "__dict__"):
72-
data = dict([(key, to_dict(value, classkey))
73-
for key, value in obj.__dict__.items()
74-
if not callable(value) and not key.startswith('_')])
95+
data = dict(
96+
[
97+
(key, to_dict(value, classkey))
98+
for key, value in obj.__dict__.items()
99+
if not callable(value) and not key.startswith("_")
100+
]
101+
)
75102

76103
if classkey is not None and hasattr(obj, "__class__"):
77104
data[classkey] = obj.__class__.__name__
@@ -96,7 +123,7 @@ def get(cls, id, qb=None):
96123

97124
json_data = qb.get_single_object(cls.qbo_object_name, pk=id)
98125

99-
if cls.qbo_json_object_name != '':
126+
if cls.qbo_json_object_name != "":
100127
return cls.from_json(json_data[cls.qbo_json_object_name])
101128
else:
102129
return cls.from_json(json_data[cls.qbo_object_name])
@@ -110,10 +137,10 @@ def send(self, qb=None, send_to=None):
110137
end_point = "{0}/{1}/send".format(self.qbo_object_name.lower(), self.Id)
111138

112139
if send_to:
113-
send_to = quote(send_to, safe='')
140+
send_to = quote(send_to, safe="")
114141
end_point = "{0}?sendTo={1}".format(end_point, send_to)
115142

116-
results = qb.misc_operation(end_point, None, 'application/octet-stream')
143+
results = qb.misc_operation(end_point, None, "application/octet-stream")
117144

118145
return results
119146

@@ -122,18 +149,9 @@ class VoidMixin(object):
122149

123150
def get_void_params(self):
124151
qb_object_params_map = {
125-
"Payment": {
126-
"operation": "update",
127-
"include": "void"
128-
},
129-
"SalesReceipt": {
130-
"operation": "update",
131-
"include": "void"
132-
},
133-
"BillPayment": {
134-
"operation": "update",
135-
"include": "void"
136-
},
152+
"Payment": {"operation": "update", "include": "void"},
153+
"SalesReceipt": {"operation": "update", "include": "void"},
154+
"BillPayment": {"operation": "update", "include": "void"},
137155
"Invoice": {
138156
"operation": "void",
139157
},
@@ -143,21 +161,13 @@ def get_void_params(self):
143161

144162
def get_void_data(self):
145163
qb_object_params_map = {
146-
"Payment": {
147-
"Id": self.Id,
148-
"SyncToken": self.SyncToken,
149-
"sparse": True
150-
},
164+
"Payment": {"Id": self.Id, "SyncToken": self.SyncToken, "sparse": True},
151165
"SalesReceipt": {
152166
"Id": self.Id,
153167
"SyncToken": self.SyncToken,
154-
"sparse": True
155-
},
156-
"BillPayment": {
157-
"Id": self.Id,
158-
"SyncToken": self.SyncToken,
159-
"sparse": True
168+
"sparse": True,
160169
},
170+
"BillPayment": {"Id": self.Id, "SyncToken": self.SyncToken, "sparse": True},
161171
"Invoice": {
162172
"Id": self.Id,
163173
"SyncToken": self.SyncToken,
@@ -171,7 +181,7 @@ def void(self, qb=None):
171181
qb = QuickBooks()
172182

173183
if not self.Id:
174-
raise QuickbooksException('Cannot void unsaved object')
184+
raise QuickbooksException("Cannot void unsaved object")
175185

176186
endpoint = self.qbo_object_name.lower()
177187
url = "{0}/company/{1}/{2}".format(qb.api_url, qb.company_id, endpoint)
@@ -192,11 +202,21 @@ def save(self, qb=None, request_id=None, params=None):
192202
qb = QuickBooks()
193203

194204
if self.Id and int(self.Id) > 0:
195-
json_data = qb.update_object(self.qbo_object_name, self.to_json(), request_id=request_id, params=params)
205+
json_data = qb.update_object(
206+
self.qbo_object_name,
207+
self.to_json(),
208+
request_id=request_id,
209+
params=params,
210+
)
196211
else:
197-
json_data = qb.create_object(self.qbo_object_name, self.to_json(), request_id=request_id, params=params)
198-
199-
if self.qbo_json_object_name != '':
212+
json_data = qb.create_object(
213+
self.qbo_object_name,
214+
self.to_json(),
215+
request_id=request_id,
216+
params=params,
217+
)
218+
219+
if self.qbo_json_object_name != "":
200220
obj = type(self).from_json(json_data[self.qbo_json_object_name])
201221
else:
202222
obj = type(self).from_json(json_data[self.qbo_object_name])
@@ -213,7 +233,9 @@ def save(self, qb=None, request_id=None):
213233
if not qb:
214234
qb = QuickBooks()
215235

216-
json_data = qb.update_object(self.qbo_object_name, self.to_json(), request_id=request_id)
236+
json_data = qb.update_object(
237+
self.qbo_object_name, self.to_json(), request_id=request_id
238+
)
217239
obj = type(self).from_json(json_data[self.qbo_object_name])
218240
return obj
219241

@@ -226,13 +248,15 @@ def delete(self, qb=None, request_id=None):
226248
qb = QuickBooks()
227249

228250
if not self.Id:
229-
raise QuickbooksException('Cannot delete unsaved object')
251+
raise QuickbooksException("Cannot delete unsaved object")
230252

231253
data = {
232-
'Id': self.Id,
233-
'SyncToken': self.SyncToken,
254+
"Id": self.Id,
255+
"SyncToken": self.SyncToken,
234256
}
235-
return qb.delete_object(self.qbo_object_name, json.dumps(data), request_id=request_id)
257+
return qb.delete_object(
258+
self.qbo_object_name, json.dumps(data), request_id=request_id
259+
)
236260

237261

238262
class DeleteNoIdMixin(object):
@@ -242,7 +266,9 @@ def delete(self, qb=None, request_id=None):
242266
if not qb:
243267
qb = QuickBooks()
244268

245-
return qb.delete_object(self.qbo_object_name, self.to_json(), request_id=request_id)
269+
return qb.delete_object(
270+
self.qbo_object_name, self.to_json(), request_id=request_id
271+
)
246272

247273

248274
class ListMixin(object):
@@ -257,8 +283,13 @@ def all(cls, order_by="", start_position="", max_results=100, qb=None):
257283
:param qb:
258284
:return: Returns list
259285
"""
260-
return cls.where("", order_by=order_by, start_position=start_position,
261-
max_results=max_results, qb=qb)
286+
return cls.where(
287+
"",
288+
order_by=order_by,
289+
start_position=start_position,
290+
max_results=max_results,
291+
qb=qb,
292+
)
262293

263294
@classmethod
264295
def filter(cls, order_by="", start_position="", max_results="", qb=None, **kwargs):
@@ -270,9 +301,13 @@ def filter(cls, order_by="", start_position="", max_results="", qb=None, **kwarg
270301
:param kwargs: field names and values to filter the query
271302
:return: Filtered list
272303
"""
273-
return cls.where(build_where_clause(**kwargs),
274-
start_position=start_position, max_results=max_results, order_by=order_by,
275-
qb=qb)
304+
return cls.where(
305+
build_where_clause(**kwargs),
306+
start_position=start_position,
307+
max_results=max_results,
308+
order_by=order_by,
309+
qb=qb,
310+
)
276311

277312
@classmethod
278313
def choose(cls, choices, field="Id", qb=None):
@@ -285,7 +320,9 @@ def choose(cls, choices, field="Id", qb=None):
285320
return cls.where(build_choose_clause(choices, field), qb=qb)
286321

287322
@classmethod
288-
def where(cls, where_clause="", order_by="", start_position="", max_results="", qb=None):
323+
def where(
324+
cls, where_clause="", order_by="", start_position="", max_results="", qb=None
325+
):
289326
"""
290327
:param where_clause: QBO SQL where clause (DO NOT include 'WHERE')
291328
:param order_by:
@@ -307,7 +344,8 @@ def where(cls, where_clause="", order_by="", start_position="", max_results="",
307344
max_results = " MAXRESULTS " + str(max_results)
308345

309346
select = "SELECT * FROM {0} {1}{2}{3}{4}".format(
310-
cls.qbo_object_name, where_clause, order_by, start_position, max_results)
347+
cls.qbo_object_name, where_clause, order_by, start_position, max_results
348+
)
311349

312350
return cls.query(select, qb=qb)
313351

@@ -325,7 +363,7 @@ def query(cls, select, qb=None):
325363

326364
obj_list = []
327365

328-
if cls.qbo_json_object_name != '':
366+
if cls.qbo_json_object_name != "":
329367
object_name = cls.qbo_json_object_name
330368
else:
331369
object_name = cls.qbo_object_name
@@ -350,7 +388,8 @@ def count(cls, where_clause="", qb=None):
350388
where_clause = "WHERE " + where_clause
351389

352390
select = "SELECT COUNT(*) FROM {0} {1}".format(
353-
cls.qbo_object_name, where_clause)
391+
cls.qbo_object_name, where_clause
392+
)
354393

355394
json_data = qb.query(select)
356395

@@ -369,7 +408,9 @@ def download_pdf(self, qb=None):
369408
else:
370409
raise QuickbooksException(
371410
"Cannot download {0} when no Id is assigned or if no quickbooks client is passed in".format(
372-
self.qbo_object_name))
411+
self.qbo_object_name
412+
)
413+
)
373414

374415

375416
class ObjectListMixin(object):

quickbooks/objects/companycurrency.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,10 @@
1-
from .base import QuickbooksManagedObject, QuickbooksTransactionEntity, Ref, CustomField, MetaData
1+
from .base import (
2+
QuickbooksManagedObject,
3+
QuickbooksTransactionEntity,
4+
Ref,
5+
CustomField,
6+
MetaData,
7+
)
28

39

410
class CompanyCurrency(QuickbooksManagedObject, QuickbooksTransactionEntity):
@@ -35,6 +41,6 @@ def to_ref(self):
3541

3642
ref.name = self.Name
3743
ref.type = self.qbo_object_name
38-
ref.value = self.Id
44+
ref.value = self.Code or self.Id
3945

4046
return ref

quickbooks/objects/customer.py

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,12 @@
1-
from .base import Address, PhoneNumber, EmailAddress, WebAddress, Ref, QuickbooksManagedObject, \
2-
QuickbooksTransactionEntity
1+
from .base import (
2+
Address,
3+
PhoneNumber,
4+
EmailAddress,
5+
WebAddress,
6+
Ref,
7+
QuickbooksManagedObject,
8+
QuickbooksTransactionEntity,
9+
)
310

411

512
class Customer(QuickbooksManagedObject, QuickbooksTransactionEntity):
@@ -71,6 +78,7 @@ def __init__(self):
7178
self.PaymentMethodRef = None
7279
self.ParentRef = None
7380
self.ARAccountRef = None
81+
self.CurrencyRef = None
7482

7583
def __str__(self):
7684
return self.DisplayName

0 commit comments

Comments
 (0)