Skip to content

Commit 24c0dc5

Browse files
authored
Merge branch '0.9.12' into fix-setuptools
2 parents ff14b00 + 66996ba commit 24c0dc5

18 files changed

+251
-106
lines changed

Makefile

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
publish: clean
2-
python setup.py sdist
2+
python -m build
33
twine upload dist/*
44

55
clean:
66
rm -vrf ./build ./dist ./*.egg-info
77
find . -name '*.pyc' -delete
8-
find . -name '*.tgz' -delete
8+
find . -name '*.tgz' -delete

Pipfile

+1-1
Original file line numberDiff line numberDiff line change
@@ -14,4 +14,4 @@ urllib3 = ">=2.1.0"
1414
intuit-oauth = "==1.2.6"
1515
requests = ">=2.31.0"
1616
requests_oauthlib = ">=1.3.1"
17-
setuptools = "*"
17+
build = "*"

Pipfile.lock

+26-10
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

README.md

+17-1
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ A Python 3 library for accessing the Quickbooks API. Complete rework of
1010
[quickbooks-python](https://github.com/troolee/quickbooks-python).
1111

1212
These instructions were written for a Django application. Make sure to
13-
change it to whatever framework/method youre using.
13+
change it to whatever framework/method you're using.
1414
You can find additional examples of usage in [Integration tests folder](https://github.com/ej2/python-quickbooks/tree/master/tests/integration).
1515

1616
For information about contributing, see the [Contributing Page](https://github.com/ej2/python-quickbooks/blob/master/contributing.md).
@@ -247,6 +247,22 @@ Attaching a file to customer:
247247
attachment.ContentType = 'application/pdf'
248248
attachment.save(qb=client)
249249

250+
Attaching file bytes to customer:
251+
252+
attachment = Attachable()
253+
254+
attachable_ref = AttachableRef()
255+
attachable_ref.EntityRef = customer.to_ref()
256+
257+
attachment.AttachableRef.append(attachable_ref)
258+
259+
attachment.FileName = 'Filename'
260+
attachment._FileBytes = pdf_bytes # bytes object containing the file content
261+
attachment.ContentType = 'application/pdf'
262+
attachment.save(qb=client)
263+
264+
**Note:** You can use either `_FilePath` or `_FileBytes` to attach a file, but not both at the same time.
265+
250266
Passing in optional params
251267
----------------
252268
Some QBO objects have options that need to be set on the query string of an API call.

dev_requirements.txt

-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
11
coverage==7.3.0
22
ipdb==0.13.13
3-
mock==5.1.0
43
nose==1.3.7

quickbooks/client.py

+42-23
Original file line numberDiff line numberDiff line change
@@ -76,14 +76,19 @@ def __new__(cls, **kwargs):
7676
if 'company_id' in kwargs:
7777
instance.company_id = kwargs['company_id']
7878

79-
if 'minorversion' in kwargs:
80-
instance.minorversion = kwargs['minorversion']
81-
82-
if instance.minorversion < instance.MINIMUM_MINOR_VERSION:
83-
warnings.warn(
84-
'Minor Version no longer supported.'
85-
'See: https://blogs.intuit.com/2025/01/21/changes-to-our-accounting-api-that-may-impact-your-application/',
86-
DeprecationWarning)
79+
# Handle minorversion with default
80+
instance.minorversion = kwargs.get('minorversion', instance.MINIMUM_MINOR_VERSION)
81+
if 'minorversion' not in kwargs:
82+
warnings.warn(
83+
'No minor version specified. Defaulting to minimum supported version (75). '
84+
'Please specify minorversion explicitly when initializing QuickBooks. '
85+
'See: https://blogs.intuit.com/2025/01/21/changes-to-our-accounting-api-that-may-impact-your-application/',
86+
DeprecationWarning)
87+
elif instance.minorversion < instance.MINIMUM_MINOR_VERSION:
88+
warnings.warn(
89+
f'Minor Version {instance.minorversion} is no longer supported. Minimum supported version is {instance.MINIMUM_MINOR_VERSION}. '
90+
'See: https://blogs.intuit.com/2025/01/21/changes-to-our-accounting-api-that-may-impact-your-application/',
91+
DeprecationWarning)
8792

8893
instance.invoice_link = kwargs.get('invoice_link', False)
8994

@@ -152,13 +157,12 @@ def change_data_capture(self, entity_string, changed_since):
152157
return result
153158

154159
def make_request(self, request_type, url, request_body=None, content_type='application/json',
155-
params=None, file_path=None, request_id=None):
160+
params=None, file_path=None, file_bytes=None, request_id=None):
156161

157162
if not params:
158163
params = {}
159164

160-
if self.minorversion:
161-
params['minorversion'] = self.minorversion
165+
params['minorversion'] = self.minorversion
162166

163167
if request_id:
164168
params['requestid'] = request_id
@@ -172,7 +176,7 @@ def make_request(self, request_type, url, request_body=None, content_type='appli
172176
'User-Agent': 'python-quickbooks V3 library'
173177
}
174178

175-
if file_path:
179+
if file_path or file_bytes:
176180
url = url.replace('attachable', 'upload')
177181
boundary = '-------------PythonMultipartPost'
178182
headers.update({
@@ -183,8 +187,11 @@ def make_request(self, request_type, url, request_body=None, content_type='appli
183187
'Connection': 'close'
184188
})
185189

186-
with open(file_path, 'rb') as attachment:
187-
binary_data = str(base64.b64encode(attachment.read()).decode('ascii'))
190+
if file_path:
191+
with open(file_path, 'rb') as attachment:
192+
binary_data = str(base64.b64encode(attachment.read()).decode('ascii'))
193+
else:
194+
binary_data = str(base64.b64encode(file_bytes).decode('ascii'))
188195

189196
content_type = json.loads(request_body)['ContentType']
190197

@@ -233,10 +240,18 @@ def make_request(self, request_type, url, request_body=None, content_type='appli
233240
return result
234241

235242
def get(self, *args, **kwargs):
236-
return self.make_request("GET", *args, **kwargs)
243+
if 'params' not in kwargs:
244+
kwargs['params'] = {}
245+
if 'minorversion' not in kwargs['params']:
246+
kwargs['params']['minorversion'] = self.MINIMUM_MINOR_VERSION
247+
return self.make_request('GET', *args, **kwargs)
237248

238249
def post(self, *args, **kwargs):
239-
return self.make_request("POST", *args, **kwargs)
250+
if 'params' not in kwargs:
251+
kwargs['params'] = {}
252+
if 'minorversion' not in kwargs['params']:
253+
kwargs['params']['minorversion'] = self.MINIMUM_MINOR_VERSION
254+
return self.make_request('POST', *args, **kwargs)
240255

241256
def process_request(self, request_type, url, headers="", params="", data=""):
242257
if self.session is None:
@@ -248,10 +263,11 @@ def process_request(self, request_type, url, headers="", params="", data=""):
248263
request_type, url, headers=headers, params=params, data=data)
249264

250265
def get_single_object(self, qbbo, pk, params=None):
251-
url = "{0}/company/{1}/{2}/{3}/".format(self.api_url, self.company_id, qbbo.lower(), pk)
252-
result = self.get(url, {}, params=params)
266+
url = "{0}/company/{1}/{2}/{3}".format(self.api_url, self.company_id, qbbo.lower(), pk)
267+
if params is None:
268+
params = {}
253269

254-
return result
270+
return self.get(url, {}, params=params)
255271

256272
@staticmethod
257273
def handle_exceptions(results):
@@ -287,11 +303,11 @@ def handle_exceptions(results):
287303
else:
288304
raise exceptions.QuickbooksException(message, code, detail)
289305

290-
def create_object(self, qbbo, request_body, _file_path=None, request_id=None, params=None):
306+
def create_object(self, qbbo, request_body, _file_path=None, _file_bytes=None, request_id=None, params=None):
291307
self.isvalid_object_name(qbbo)
292308

293309
url = "{0}/company/{1}/{2}".format(self.api_url, self.company_id, qbbo.lower())
294-
results = self.post(url, request_body, file_path=_file_path, request_id=request_id, params=params)
310+
results = self.post(url, request_body, file_path=_file_path, file_bytes=_file_bytes, request_id=request_id, params=params)
295311

296312
return results
297313

@@ -307,9 +323,12 @@ def isvalid_object_name(self, object_name):
307323

308324
return True
309325

310-
def update_object(self, qbbo, request_body, _file_path=None, request_id=None, params=None):
326+
def update_object(self, qbbo, request_body, _file_path=None, _file_bytes=None, request_id=None, params=None):
311327
url = "{0}/company/{1}/{2}".format(self.api_url, self.company_id, qbbo.lower())
312-
result = self.post(url, request_body, file_path=_file_path, request_id=request_id, params=params)
328+
if params is None:
329+
params = {}
330+
331+
result = self.post(url, request_body, file_path=_file_path, file_bytes=_file_bytes, request_id=request_id, params=params)
313332

314333
return result
315334

quickbooks/mixins.py

+20-8
Original file line numberDiff line numberDiff line change
@@ -255,14 +255,26 @@ class ListMixin(object):
255255

256256
@classmethod
257257
def all(cls, order_by="", start_position="", max_results=100, qb=None):
258-
"""
259-
:param start_position:
260-
:param max_results: The max number of entities that can be returned in a response is 1000.
261-
:param qb:
262-
:return: Returns list
263-
"""
264-
return cls.where("", order_by=order_by, start_position=start_position,
265-
max_results=max_results, qb=qb)
258+
"""Returns list of objects containing all objects in the QuickBooks database"""
259+
if qb is None:
260+
qb = QuickBooks()
261+
262+
# For Item objects, we need to explicitly request the SKU field
263+
if cls.qbo_object_name == "Item":
264+
select = "SELECT *, Sku FROM {0}".format(cls.qbo_object_name)
265+
else:
266+
select = "SELECT * FROM {0}".format(cls.qbo_object_name)
267+
268+
if order_by:
269+
select += " ORDER BY {0}".format(order_by)
270+
271+
if start_position:
272+
select += " STARTPOSITION {0}".format(start_position)
273+
274+
if max_results:
275+
select += " MAXRESULTS {0}".format(max_results)
276+
277+
return cls.query(select, qb=qb)
266278

267279
@classmethod
268280
def filter(cls, order_by="", start_position="", max_results="", qb=None, **kwargs):

quickbooks/objects/attachable.py

+11-2
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ def __init__(self):
2727
self.AttachableRef = []
2828
self.FileName = None
2929
self._FilePath = ''
30+
self._FileBytes = None
3031
self.Note = ""
3132
self.FileAccessUri = None
3233
self.TempDownloadUri = None
@@ -53,10 +54,18 @@ def save(self, qb=None):
5354
if not qb:
5455
qb = QuickBooks()
5556

57+
# Validate that we have either file path or bytes, but not both
58+
if self._FilePath and self._FileBytes:
59+
raise ValueError("Cannot specify both _FilePath and _FileBytes")
60+
5661
if self.Id and int(self.Id) > 0:
57-
json_data = qb.update_object(self.qbo_object_name, self.to_json(), _file_path=self._FilePath)
62+
json_data = qb.update_object(self.qbo_object_name, self.to_json(),
63+
_file_path=self._FilePath,
64+
_file_bytes=self._FileBytes)
5865
else:
59-
json_data = qb.create_object(self.qbo_object_name, self.to_json(), _file_path=self._FilePath)
66+
json_data = qb.create_object(self.qbo_object_name, self.to_json(),
67+
_file_path=self._FilePath,
68+
_file_bytes=self._FileBytes)
6069

6170
if self.Id is None and self.FileName:
6271
obj = type(self).from_json(json_data['AttachableResponse'][0]['Attachable'])

setup.cfg

-3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,3 @@
1-
[metadata]
2-
description_file = README.md
3-
41
[flake8]
52
max_line_length = 100
63
max_complexity = 10

setup.py

-1
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,6 @@ def read(*parts):
3030
},
3131

3232
install_requires=[
33-
'setuptools',
3433
'intuit-oauth==1.2.6',
3534
'requests_oauthlib>=1.3.1',
3635
'requests>=2.31.0',

0 commit comments

Comments
 (0)