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
18 changes: 17 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ A Python 3 library for accessing the Quickbooks API. Complete rework of
[quickbooks-python](https://github.com/troolee/quickbooks-python).

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

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

Attaching file bytes to customer:

attachment = Attachable()

attachable_ref = AttachableRef()
attachable_ref.EntityRef = customer.to_ref()

attachment.AttachableRef.append(attachable_ref)

attachment.FileName = 'Filename'
attachment._FileBytes = pdf_bytes # bytes object containing the file content
attachment.ContentType = 'application/pdf'
attachment.save(qb=client)

**Note:** You can use either `_FilePath` or `_FileBytes` to attach a file, but not both at the same time.

Passing in optional params
----------------
Some QBO objects have options that need to be set on the query string of an API call.
Expand Down
19 changes: 11 additions & 8 deletions quickbooks/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,7 @@ def change_data_capture(self, entity_string, changed_since):
return result

def make_request(self, request_type, url, request_body=None, content_type='application/json',
params=None, file_path=None, request_id=None):
params=None, file_path=None, file_bytes=None, request_id=None):

if not params:
params = {}
Expand All @@ -172,7 +172,7 @@ def make_request(self, request_type, url, request_body=None, content_type='appli
'User-Agent': 'python-quickbooks V3 library'
}

if file_path:
if file_path or file_bytes:
url = url.replace('attachable', 'upload')
boundary = '-------------PythonMultipartPost'
headers.update({
Expand All @@ -183,8 +183,11 @@ def make_request(self, request_type, url, request_body=None, content_type='appli
'Connection': 'close'
})

with open(file_path, 'rb') as attachment:
binary_data = str(base64.b64encode(attachment.read()).decode('ascii'))
if file_path:
with open(file_path, 'rb') as attachment:
binary_data = str(base64.b64encode(attachment.read()).decode('ascii'))
else:
binary_data = str(base64.b64encode(file_bytes).decode('ascii'))

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

Expand Down Expand Up @@ -287,11 +290,11 @@ def handle_exceptions(results):
else:
raise exceptions.QuickbooksException(message, code, detail)

def create_object(self, qbbo, request_body, _file_path=None, request_id=None, params=None):
def create_object(self, qbbo, request_body, _file_path=None, _file_bytes=None, request_id=None, params=None):
self.isvalid_object_name(qbbo)

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

return results

Expand All @@ -307,9 +310,9 @@ def isvalid_object_name(self, object_name):

return True

def update_object(self, qbbo, request_body, _file_path=None, request_id=None, params=None):
def update_object(self, qbbo, request_body, _file_path=None, _file_bytes=None, request_id=None, params=None):
url = "{0}/company/{1}/{2}".format(self.api_url, self.company_id, qbbo.lower())
result = self.post(url, request_body, file_path=_file_path, request_id=request_id, params=params)
result = self.post(url, request_body, file_path=_file_path, file_bytes=_file_bytes, request_id=request_id, params=params)

return result

Expand Down
13 changes: 11 additions & 2 deletions quickbooks/objects/attachable.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ def __init__(self):
self.AttachableRef = []
self.FileName = None
self._FilePath = ''
self._FileBytes = None
self.Note = ""
self.FileAccessUri = None
self.TempDownloadUri = None
Expand All @@ -53,10 +54,18 @@ def save(self, qb=None):
if not qb:
qb = QuickBooks()

# Validate that we have either file path or bytes, but not both
if self._FilePath and self._FileBytes:
raise ValueError("Cannot specify both _FilePath and _FileBytes")

if self.Id and int(self.Id) > 0:
json_data = qb.update_object(self.qbo_object_name, self.to_json(), _file_path=self._FilePath)
json_data = qb.update_object(self.qbo_object_name, self.to_json(),
_file_path=self._FilePath,
_file_bytes=self._FileBytes)
else:
json_data = qb.create_object(self.qbo_object_name, self.to_json(), _file_path=self._FilePath)
json_data = qb.create_object(self.qbo_object_name, self.to_json(),
_file_path=self._FilePath,
_file_bytes=self._FileBytes)

if self.Id is None and self.FileName:
obj = type(self).from_json(json_data['AttachableResponse'][0]['Attachable'])
Expand Down
22 changes: 22 additions & 0 deletions tests/integration/test_attachable.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,4 +68,26 @@ def test_create_file(self):
self.assertEqual(query_attachable.AttachableRef[0].EntityRef.value, vendor.Id)
self.assertEqual(query_attachable.Note, "Sample note")

def test_create_file_from_bytes(self):
attachable = Attachable()
file_content = b"File contents in bytes"

vendor = Vendor.all(max_results=1, qb=self.qb_client)[0]

attachable_ref = AttachableRef()
attachable_ref.EntityRef = vendor.to_ref()
attachable.AttachableRef.append(attachable_ref)

attachable.Note = "Sample note with bytes"
attachable.FileName = "test.txt"
attachable._FileBytes = file_content
attachable.ContentType = 'text/plain'

attachable.save(qb=self.qb_client)

query_attachable = Attachable.get(attachable.Id, qb=self.qb_client)

self.assertEqual(query_attachable.AttachableRef[0].EntityRef.value, vendor.Id)
self.assertEqual(query_attachable.Note, "Sample note with bytes")


2 changes: 1 addition & 1 deletion tests/unit/test_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ def test_update_object_with_request_id(self, make_req):
qb_client.update_object("Customer", "request_body", request_id="123")

url = "https://sandbox-quickbooks.api.intuit.com/v3/company/1234/customer"
make_req.assert_called_with("POST", url, "request_body", file_path=None, params=None, request_id="123")
make_req.assert_called_with("POST", url, "request_body", file_path=None, file_bytes=None, request_id="123", params=None)

@patch('quickbooks.client.QuickBooks.get')
def test_get_current_user(self, get):
Expand Down