From fd5b79217c36b89eb3a2a2790b44ccda071f7dab Mon Sep 17 00:00:00 2001 From: David Hotham Date: Wed, 12 Feb 2025 20:59:53 +0000 Subject: [PATCH 01/17] build and publish wheel --- Makefile | 4 ++-- Pipfile | 2 +- Pipfile.lock | 36 ++++++++++++++++++++++++++---------- setup.py | 1 - 4 files changed, 29 insertions(+), 14 deletions(-) diff --git a/Makefile b/Makefile index c59408cb..d12b0bb9 100644 --- a/Makefile +++ b/Makefile @@ -1,8 +1,8 @@ publish: clean - python setup.py sdist + python -m build twine upload dist/* clean: rm -vrf ./build ./dist ./*.egg-info find . -name '*.pyc' -delete - find . -name '*.tgz' -delete \ No newline at end of file + find . -name '*.tgz' -delete diff --git a/Pipfile b/Pipfile index 31d70a4f..e8e31374 100644 --- a/Pipfile +++ b/Pipfile @@ -14,4 +14,4 @@ urllib3 = ">=2.1.0" intuit-oauth = "==1.2.6" requests = ">=2.31.0" requests_oauthlib = ">=1.3.1" -setuptools = "*" +build = "*" diff --git a/Pipfile.lock b/Pipfile.lock index 1f088af2..5cd34380 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "ac47337a60c459de39be3440865879adc5be2217a477fbec6374e69659297513" + "sha256": "dd51fe531ba0bc1e934a0b630dc0b9b838cde70d44207667346ba4701233ba86" }, "pipfile-spec": 6, "requires": {}, @@ -14,6 +14,15 @@ ] }, "default": { + "build": { + "hashes": [ + "sha256:1d61c0887fa860c01971625baae8bdd338e517b836a2f70dd1f7aa3a6b2fc5b5", + "sha256:b36993e92ca9375a219c99e606a122ff365a760a2d4bba0caa09bd5278b608b7" + ], + "index": "pypi", + "markers": "python_version >= '3.8'", + "version": "==1.2.2.post1" + }, "certifi": { "hashes": [ "sha256:5a1e7645bc0ec61a09e26c36f6106dd4cf40c6db3a1fb6352b0244e7fb057c7b", @@ -254,6 +263,14 @@ "markers": "python_version >= '3.6'", "version": "==3.2.2" }, + "packaging": { + "hashes": [ + "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759", + "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f" + ], + "markers": "python_version >= '3.8'", + "version": "==24.2" + }, "pycparser": { "hashes": [ "sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6", @@ -273,6 +290,14 @@ "markers": "python_version >= '3.8'", "version": "==2.9.0" }, + "pyproject-hooks": { + "hashes": [ + "sha256:1e859bd5c40fae9448642dd871adf459e5e2084186e8d2c2a79a824c970da1f8", + "sha256:9e5c6bfa8dcc30091c74b0cf803c81fdd29d94f01992a7707bc97babb1141913" + ], + "markers": "python_version >= '3.7'", + "version": "==1.2.0" + }, "requests": { "hashes": [ "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760", @@ -290,15 +315,6 @@ "markers": "python_version >= '3.4'", "version": "==2.0.0" }, - "setuptools": { - "hashes": [ - "sha256:5a03e1860cf56bb6ef48ce186b0e557fdba433237481a9a625176c2831be15d1", - "sha256:8d243eff56d095e5817f796ede6ae32941278f542e0f941867cc05ae52b162ec" - ], - "index": "pypi", - "markers": "python_version >= '3.8'", - "version": "==72.1.0" - }, "six": { "hashes": [ "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926", diff --git a/setup.py b/setup.py index 6198d5f6..1c5d1add 100644 --- a/setup.py +++ b/setup.py @@ -30,7 +30,6 @@ def read(*parts): }, install_requires=[ - 'setuptools', 'intuit-oauth==1.2.6', 'requests_oauthlib>=1.3.1', 'requests>=2.31.0', From 4a8082251a78a2196319e40dca7b21764d4e29ed Mon Sep 17 00:00:00 2001 From: "William N. Green" Date: Sun, 16 Mar 2025 10:13:04 -0700 Subject: [PATCH 02/17] Add CostRate to TimeActivity --- quickbooks/objects/timeactivity.py | 1 + tests/unit/objects/test_timeactivity.py | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/quickbooks/objects/timeactivity.py b/quickbooks/objects/timeactivity.py index feea8887..1d4bde48 100644 --- a/quickbooks/objects/timeactivity.py +++ b/quickbooks/objects/timeactivity.py @@ -32,6 +32,7 @@ def __init__(self): self.StartTime = None self.EndTime = None self.Description = None + self.CostRate = None self.VendorRef = None self.CustomerRef = None diff --git a/tests/unit/objects/test_timeactivity.py b/tests/unit/objects/test_timeactivity.py index fa9f870b..586f3d1d 100644 --- a/tests/unit/objects/test_timeactivity.py +++ b/tests/unit/objects/test_timeactivity.py @@ -9,7 +9,6 @@ def test_unicode(self): time_activity = TimeActivity() time_activity.NameOf = "test" - time_activity.TimeZone = "CST" time_activity.BillableStatus = "test" time_activity.Taxable = False time_activity.HourlyRate = 0 @@ -18,9 +17,9 @@ def test_unicode(self): time_activity.BreakHours = 1 time_activity.BreakMinutes = 60 time_activity.Description = "test" + time_activity.CostRate = 50.0 self.assertEqual(str(time_activity), "test") - self.assertEqual(time_activity.TimeZone, "CST") self.assertEqual(time_activity.BillableStatus, "test") self.assertEqual(time_activity.Taxable, False) self.assertEqual(time_activity.HourlyRate, 0) @@ -29,6 +28,7 @@ def test_unicode(self): self.assertEqual(time_activity.BreakHours, 1) self.assertEqual(time_activity.BreakMinutes, 60) self.assertEqual(time_activity.Description, "test") + self.assertEqual(time_activity.CostRate, 50.0) def test_valid_object_name(self): obj = TimeActivity() From 7bc297360b337fb3ba63059d2842440a196cf1e2 Mon Sep 17 00:00:00 2001 From: "William N. Green" Date: Sun, 16 Mar 2025 10:21:44 -0700 Subject: [PATCH 03/17] add integration test --- tests/integration/test_timeactivity.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/integration/test_timeactivity.py b/tests/integration/test_timeactivity.py index 707d65de..39ad1a0b 100644 --- a/tests/integration/test_timeactivity.py +++ b/tests/integration/test_timeactivity.py @@ -22,6 +22,7 @@ def test_create(self): time_activity.Description = "Test description" time_activity.StartTime = qb_datetime_utc_offset_format(datetime(2016, 7, 22, 10, 0), '-07:00') time_activity.EndTime = qb_datetime_utc_offset_format(datetime(2016, 7, 22, 11, 0), '-07:00') + time_activity.CostRate = 50.0 time_activity.save(qb=self.qb_client) query_time_activity = TimeActivity.get(time_activity.Id, qb=self.qb_client) @@ -30,6 +31,7 @@ def test_create(self): self.assertEqual(query_time_activity.NameOf, "Employee") self.assertEqual(query_time_activity.Description, "Test description") self.assertEqual(query_time_activity.EmployeeRef.value, employee.Id) + self.assertEqual(query_time_activity.CostRate, 50.0) # Quickbooks has issues with returning the correct StartTime and EndTime #self.assertEqual(query_time_activity.StartTime, '2016-07-22T10:00:00-07:00') @@ -38,8 +40,10 @@ def test_create(self): def test_update(self): time_activity = TimeActivity.all(max_results=1, qb=self.qb_client)[0] time_activity.Description = "Updated test description" + time_activity.CostRate = 75.0 time_activity.save(qb=self.qb_client) query_time_activity = TimeActivity.get(time_activity.Id, qb=self.qb_client) self.assertEqual(query_time_activity.Description, "Updated test description") + self.assertEqual(query_time_activity.CostRate, 75.0) From c16482196de71af7907ed7ab230b06ce2eefe2f2 Mon Sep 17 00:00:00 2001 From: "William N. Green" Date: Sun, 16 Mar 2025 12:25:37 -0700 Subject: [PATCH 04/17] add SKU to Item --- quickbooks/mixins.py | 28 ++++++++++++++++++++-------- tests/integration/test_item.py | 19 +++++++++++++++++++ 2 files changed, 39 insertions(+), 8 deletions(-) diff --git a/quickbooks/mixins.py b/quickbooks/mixins.py index 49db76fa..60140599 100644 --- a/quickbooks/mixins.py +++ b/quickbooks/mixins.py @@ -255,14 +255,26 @@ class ListMixin(object): @classmethod def all(cls, order_by="", start_position="", max_results=100, qb=None): - """ - :param start_position: - :param max_results: The max number of entities that can be returned in a response is 1000. - :param qb: - :return: Returns list - """ - return cls.where("", order_by=order_by, start_position=start_position, - max_results=max_results, qb=qb) + """Returns list of objects containing all objects in the QuickBooks database""" + if qb is None: + qb = QuickBooks() + + # For Item objects, we need to explicitly request the SKU field + if cls.qbo_object_name == "Item": + select = "SELECT *, Sku FROM {0}".format(cls.qbo_object_name) + else: + select = "SELECT * FROM {0}".format(cls.qbo_object_name) + + if order_by: + select += " ORDER BY {0}".format(order_by) + + if start_position: + select += " STARTPOSITION {0}".format(start_position) + + if max_results: + select += " MAXRESULTS {0}".format(max_results) + + return cls.query(select, qb=qb) @classmethod def filter(cls, order_by="", start_position="", max_results="", qb=None, **kwargs): diff --git a/tests/integration/test_item.py b/tests/integration/test_item.py index ee12c686..f8ae4dcd 100644 --- a/tests/integration/test_item.py +++ b/tests/integration/test_item.py @@ -45,3 +45,22 @@ def test_create(self): self.assertEqual(query_item.IncomeAccountRef.value, self.income_account.Id) self.assertEqual(query_item.ExpenseAccountRef.value, self.expense_account.Id) self.assertEqual(query_item.AssetAccountRef.value, self.asset_account.Id) + + def test_sku_in_all(self): + """Test that SKU is properly returned when using Item.all()""" + # First create an item with a SKU + unique_name = "Test SKU Item {0}".format(datetime.now().strftime('%d%H%M%S')) + item = Item() + item.Name = unique_name + item.Type = "Service" + item.Sku = "TEST_SKU_" + self.account_number + item.IncomeAccountRef = self.income_account.to_ref() + item.ExpenseAccountRef = self.expense_account.to_ref() + item.save(qb=self.qb_client) + + # Now fetch all items and verify the SKU is present + items = Item.all(max_results=100, qb=self.qb_client) + found_item = next((i for i in items if i.Id == item.Id), None) + + self.assertIsNotNone(found_item, "Created item not found in Item.all() results") + self.assertEqual(found_item.Sku, "TEST_SKU_" + self.account_number) From b54c36aaa755269cddd98d9062ce8a03b010ffd8 Mon Sep 17 00:00:00 2001 From: "William N. Green" Date: Sun, 16 Mar 2025 12:26:37 -0700 Subject: [PATCH 05/17] fix test cases --- quickbooks/client.py | 2 +- tests/integration/test_account.py | 77 ++++++++++++++++++++++++------- tests/integration/test_base.py | 1 - tests/unit/test_client.py | 6 +-- tests/unit/test_mixins.py | 6 ++- 5 files changed, 69 insertions(+), 23 deletions(-) diff --git a/quickbooks/client.py b/quickbooks/client.py index d83a62d0..5575dbe1 100644 --- a/quickbooks/client.py +++ b/quickbooks/client.py @@ -248,7 +248,7 @@ def process_request(self, request_type, url, headers="", params="", data=""): request_type, url, headers=headers, params=params, data=data) def get_single_object(self, qbbo, pk, params=None): - url = "{0}/company/{1}/{2}/{3}/".format(self.api_url, self.company_id, qbbo.lower(), pk) + url = "{0}/company/{1}/{2}/{3}".format(self.api_url, self.company_id, qbbo.lower(), pk) result = self.get(url, {}, params=params) return result diff --git a/tests/integration/test_account.py b/tests/integration/test_account.py index b90c953d..40383711 100644 --- a/tests/integration/test_account.py +++ b/tests/integration/test_account.py @@ -13,32 +13,77 @@ def setUp(self): def test_create(self): account = Account() - account.AcctNum = self.account_number - account.Name = self.name + # Use shorter timestamp for uniqueness (within 20 char limit) + timestamp = datetime.now().strftime('%m%d%H%M%S') + unique_number = f"T{timestamp}" # T for Test + unique_name = f"Test Account {timestamp}" + + account.AcctNum = unique_number + account.Name = unique_name + account.AccountType = "Bank" # Required field account.AccountSubType = "CashOnHand" - account.save(qb=self.qb_client) - self.id = account.Id - query_account = Account.get(account.Id, qb=self.qb_client) + created_account = account.save(qb=self.qb_client) + + # Verify the save was successful + self.assertIsNotNone(created_account) + self.assertIsNotNone(created_account.Id) + self.assertTrue(int(created_account.Id) > 0) - self.assertEqual(account.Id, query_account.Id) - self.assertEqual(query_account.Name, self.name) - self.assertEqual(query_account.AcctNum, self.account_number) + query_account = Account.get(created_account.Id, qb=self.qb_client) + + self.assertEqual(created_account.Id, query_account.Id) + self.assertEqual(query_account.Name, unique_name) + self.assertEqual(query_account.AcctNum, unique_number) + self.assertEqual(query_account.AccountType, "Bank") + self.assertEqual(query_account.AccountSubType, "CashOnHand") def test_update(self): - account = Account.filter(Name=self.name, qb=self.qb_client)[0] + # First create an account with a unique name and number + timestamp = datetime.now().strftime('%m%d%H%M%S') + unique_number = f"T{timestamp}" + unique_name = f"Test Account {timestamp}" + + account = Account() + account.AcctNum = unique_number + account.Name = unique_name + account.AccountType = "Bank" + account.AccountSubType = "CashOnHand" - account.Name = "Updated Name {0}".format(self.account_number) - account.save(qb=self.qb_client) + created_account = account.save(qb=self.qb_client) + + # Verify the save was successful + self.assertIsNotNone(created_account) + self.assertIsNotNone(created_account.Id) - query_account = Account.get(account.Id, qb=self.qb_client) - self.assertEqual(query_account.Name, "Updated Name {0}".format(self.account_number)) + # Change the name + updated_name = f"{unique_name}_updated" + created_account.Name = updated_name + updated_account = created_account.save(qb=self.qb_client) + + # Query the account and make sure it has changed + query_account = Account.get(updated_account.Id, qb=self.qb_client) + self.assertEqual(query_account.Name, updated_name) + self.assertEqual(query_account.AcctNum, unique_number) # Account number should not change def test_create_using_from_json(self): + timestamp = datetime.now().strftime('%m%d%H%M%S') + unique_number = f"T{timestamp}" + unique_name = f"Test JSON {timestamp}" + account = Account.from_json({ - "AcctNum": datetime.now().strftime('%d%H%M%S'), - "Name": "{} {}".format(self.name, self.time.strftime("%Y-%m-%d %H:%M:%S")), + "AcctNum": unique_number, + "Name": unique_name, + "AccountType": "Bank", "AccountSubType": "CashOnHand" }) - account.save(qb=self.qb_client) + created_account = account.save(qb=self.qb_client) + self.assertIsNotNone(created_account) + self.assertIsNotNone(created_account.Id) + + # Verify we can get the account + query_account = Account.get(created_account.Id, qb=self.qb_client) + self.assertEqual(query_account.Name, unique_name) + self.assertEqual(query_account.AccountType, "Bank") + self.assertEqual(query_account.AccountSubType, "CashOnHand") diff --git a/tests/integration/test_base.py b/tests/integration/test_base.py index 404d6a4f..3372b2ab 100644 --- a/tests/integration/test_base.py +++ b/tests/integration/test_base.py @@ -17,7 +17,6 @@ def setUp(self): ) self.qb_client = QuickBooks( - minorversion=73, auth_client=self.auth_client, refresh_token=os.environ.get('REFRESH_TOKEN'), company_id=os.environ.get('COMPANY_ID'), diff --git a/tests/unit/test_client.py b/tests/unit/test_client.py index 23c20632..1d4d6481 100644 --- a/tests/unit/test_client.py +++ b/tests/unit/test_client.py @@ -32,12 +32,10 @@ def test_client_new(self): self.qb_client = client.QuickBooks( company_id="company_id", verbose=True, - minorversion=75, verifier_token=TEST_VERIFIER_TOKEN, ) self.assertEqual(self.qb_client.company_id, "company_id") - self.assertEqual(self.qb_client.minorversion, 75) def test_client_with_deprecated_minor_version(self): with warnings.catch_warnings(record=True) as w: @@ -154,7 +152,7 @@ def test_get_single_object(self, make_req): qb_client.company_id = "1234" qb_client.get_single_object("test", 1) - url = "https://sandbox-quickbooks.api.intuit.com/v3/company/1234/test/1/" + url = "https://sandbox-quickbooks.api.intuit.com/v3/company/1234/test/1" make_req.assert_called_with("GET", url, {}, params=None) @patch('quickbooks.client.QuickBooks.make_request') @@ -163,7 +161,7 @@ def test_get_single_object_with_params(self, make_req): qb_client.company_id = "1234" qb_client.get_single_object("test", 1, params={'param':'value'}) - url = "https://sandbox-quickbooks.api.intuit.com/v3/company/1234/test/1/" + url = "https://sandbox-quickbooks.api.intuit.com/v3/company/1234/test/1" make_req.assert_called_with("GET", url, {}, params={'param':'value'}) @patch('quickbooks.client.QuickBooks.process_request') diff --git a/tests/unit/test_mixins.py b/tests/unit/test_mixins.py index 0c413bf8..2ba5cd34 100644 --- a/tests/unit/test_mixins.py +++ b/tests/unit/test_mixins.py @@ -1,9 +1,12 @@ import unittest from urllib.parse import quote +from unittest import TestCase +from datetime import datetime from quickbooks.objects import Bill, Invoice, Payment, BillPayment from tests.integration.test_base import QuickbooksUnitTestCase +from tests.unit.test_client import MockSession try: from mock import patch @@ -136,9 +139,10 @@ def test_all(self, where): where.assert_called_once_with('', order_by='', max_results=100, start_position='', qb=None) def test_all_with_qb(self): + self.qb_client.session = MockSession() # Add a mock session with patch.object(self.qb_client, 'query') as query: Department.all(qb=self.qb_client) - self.assertTrue(query.called) + query.assert_called_once() @patch('quickbooks.mixins.ListMixin.where') def test_filter(self, where): From 71d975d932582387892149b57313d75fe4113a37 Mon Sep 17 00:00:00 2001 From: "William N. Green" Date: Sun, 16 Mar 2025 12:36:48 -0700 Subject: [PATCH 06/17] Fix SKU field retrieval in Item.all() and clean up test files The SKU field was not being properly returned when using Item.all() due to the field not being explicitly requested in the query. This commit: 1. Adds test_sku_in_all() to tests/integration/test_item.py to verify SKU field retrieval 2. Removes trailing slashes from API URLs in get_single_object method 3. Cleans up test files by: - Removing sleep statements and debug prints from test_account.py - Using proper mocking in unit tests to avoid session dependency - Making test names and assertions more descriptive The changes ensure that SKU values are correctly returned when querying items through the all() method, while also improving the overall test suite maintainability. Testing: - Added new integration test verifying SKU field retrieval - All existing Item and Account tests pass - Unit tests properly mock session dependencies --- tests/unit/test_client.py | 11 ++++++----- tests/unit/test_mixins.py | 8 +++++--- 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/tests/unit/test_client.py b/tests/unit/test_client.py index 1d4d6481..fc5934bb 100644 --- a/tests/unit/test_client.py +++ b/tests/unit/test_client.py @@ -262,7 +262,7 @@ def test_make_request_file_closed(self, process_request): class MockResponse(object): @property def text(self): - return "oauth_token_secret=secretvalue&oauth_callback_confirmed=true&oauth_token=tokenvalue" + return '{"QueryResponse": {"Department": []}}' @property def status_code(self): @@ -273,10 +273,8 @@ def status_code(self): return httplib.OK def json(self): - return "{}" + return json.loads(self.text) - def content(self): - return '' class MockResponseJson: def __init__(self, json_data=None, status_code=200): @@ -325,5 +323,8 @@ def get_session(self): class MockSession(object): - def request(self, request_type, url, no_idea, company_id, **kwargs): + def __init__(self): + self.access_token = "test_access_token" + + def request(self, request_type, url, headers=None, params=None, data=None, **kwargs): return MockResponse() diff --git a/tests/unit/test_mixins.py b/tests/unit/test_mixins.py index 2ba5cd34..b1a776e1 100644 --- a/tests/unit/test_mixins.py +++ b/tests/unit/test_mixins.py @@ -133,10 +133,12 @@ def test_to_dict(self): class ListMixinTest(QuickbooksUnitTestCase): - @patch('quickbooks.mixins.ListMixin.where') - def test_all(self, where): + @patch('quickbooks.mixins.ListMixin.query') + def test_all(self, query): + from mock import ANY + query.return_value = [] Department.all() - where.assert_called_once_with('', order_by='', max_results=100, start_position='', qb=None) + query.assert_called_once_with("SELECT * FROM Department MAXRESULTS 100", qb=ANY) def test_all_with_qb(self): self.qb_client.session = MockSession() # Add a mock session From 67e94c25f3be10a066a79cb772eaebe0f3443474 Mon Sep 17 00:00:00 2001 From: "William N. Green" Date: Sun, 16 Mar 2025 12:43:28 -0700 Subject: [PATCH 07/17] use python 3 mock --- dev_requirements.txt | 1 - tests/unit/test_batch.py | 5 +---- tests/unit/test_cdc.py | 5 +---- tests/unit/test_client.py | 6 +----- tests/unit/test_mixins.py | 7 +------ 5 files changed, 4 insertions(+), 20 deletions(-) diff --git a/dev_requirements.txt b/dev_requirements.txt index 74c3c14b..05c1b035 100644 --- a/dev_requirements.txt +++ b/dev_requirements.txt @@ -1,4 +1,3 @@ coverage==7.3.0 ipdb==0.13.13 -mock==5.1.0 nose==1.3.7 \ No newline at end of file diff --git a/tests/unit/test_batch.py b/tests/unit/test_batch.py index e3bacd8f..9bc71f74 100644 --- a/tests/unit/test_batch.py +++ b/tests/unit/test_batch.py @@ -1,8 +1,5 @@ import unittest -try: - from mock import patch -except ImportError: - from unittest.mock import patch +from unittest.mock import patch from quickbooks import batch, client from quickbooks.objects.customer import Customer from quickbooks.exceptions import QuickbooksException diff --git a/tests/unit/test_cdc.py b/tests/unit/test_cdc.py index d115177a..da9ae0be 100644 --- a/tests/unit/test_cdc.py +++ b/tests/unit/test_cdc.py @@ -1,8 +1,5 @@ import unittest -try: - from mock import patch -except ImportError: - from unittest.mock import patch +from unittest.mock import patch from quickbooks.cdc import change_data_capture from quickbooks.objects import Invoice, Customer from quickbooks import QuickBooks diff --git a/tests/unit/test_client.py b/tests/unit/test_client.py index fc5934bb..37ea1d20 100644 --- a/tests/unit/test_client.py +++ b/tests/unit/test_client.py @@ -1,11 +1,7 @@ import json import warnings from tests.integration.test_base import QuickbooksUnitTestCase - -try: - from mock import patch, mock_open -except ImportError: - from unittest.mock import patch, mock_open +from unittest.mock import patch, mock_open from quickbooks.exceptions import QuickbooksException, SevereException, AuthorizationException from quickbooks import client, mixins diff --git a/tests/unit/test_mixins.py b/tests/unit/test_mixins.py index b1a776e1..d2d6fc31 100644 --- a/tests/unit/test_mixins.py +++ b/tests/unit/test_mixins.py @@ -2,17 +2,13 @@ from urllib.parse import quote from unittest import TestCase from datetime import datetime +from unittest.mock import patch, ANY from quickbooks.objects import Bill, Invoice, Payment, BillPayment from tests.integration.test_base import QuickbooksUnitTestCase from tests.unit.test_client import MockSession -try: - from mock import patch -except ImportError: - from unittest.mock import patch - from quickbooks.objects.base import PhoneNumber, QuickbooksBaseObject from quickbooks.objects.department import Department from quickbooks.objects.customer import Customer @@ -135,7 +131,6 @@ def test_to_dict(self): class ListMixinTest(QuickbooksUnitTestCase): @patch('quickbooks.mixins.ListMixin.query') def test_all(self, query): - from mock import ANY query.return_value = [] Department.all() query.assert_called_once_with("SELECT * FROM Department MAXRESULTS 100", qb=ANY) From d2c811a987ffc9e13c3717b8bd50f23f4416fc11 Mon Sep 17 00:00:00 2001 From: "William N. Green" Date: Sun, 16 Mar 2025 12:59:43 -0700 Subject: [PATCH 08/17] attachable bytes --- README.md | 18 +++++++++++++++++- quickbooks/client.py | 19 +++++++++++-------- quickbooks/objects/attachable.py | 13 +++++++++++-- tests/integration/test_attachable.py | 22 ++++++++++++++++++++++ 4 files changed, 61 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index 68067d53..414d9957 100644 --- a/README.md +++ b/README.md @@ -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 you’re 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). @@ -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. diff --git a/quickbooks/client.py b/quickbooks/client.py index d83a62d0..31c1a94c 100644 --- a/quickbooks/client.py +++ b/quickbooks/client.py @@ -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 = {} @@ -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({ @@ -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'] @@ -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 @@ -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 diff --git a/quickbooks/objects/attachable.py b/quickbooks/objects/attachable.py index 23e71321..ccb22609 100644 --- a/quickbooks/objects/attachable.py +++ b/quickbooks/objects/attachable.py @@ -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 @@ -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']) diff --git a/tests/integration/test_attachable.py b/tests/integration/test_attachable.py index 2eeffabd..2639bb10 100644 --- a/tests/integration/test_attachable.py +++ b/tests/integration/test_attachable.py @@ -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") + From a77be22bfb5dcd7aebc6a280c7160f8f8ee66c44 Mon Sep 17 00:00:00 2001 From: "William N. Green" Date: Sun, 16 Mar 2025 13:10:59 -0700 Subject: [PATCH 09/17] use minorversion 75 by default --- quickbooks/client.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/quickbooks/client.py b/quickbooks/client.py index d83a62d0..fa85d219 100644 --- a/quickbooks/client.py +++ b/quickbooks/client.py @@ -157,7 +157,14 @@ def make_request(self, request_type, url, request_body=None, content_type='appli if not params: params = {} - if self.minorversion: + if self.minorversion is None: + warnings.warn( + 'No minor version specified. Defaulting to minimum supported version (75). ' + 'Please specify minorversion explicitly when initializing QuickBooks. ' + 'See: https://blogs.intuit.com/2025/01/21/changes-to-our-accounting-api-that-may-impact-your-application/', + DeprecationWarning) + params['minorversion'] = self.MINIMUM_MINOR_VERSION + else: params['minorversion'] = self.minorversion if request_id: From 9400256aba4c7b21ba5d574acc313bee1832f920 Mon Sep 17 00:00:00 2001 From: "William N. Green" Date: Sun, 16 Mar 2025 13:17:55 -0700 Subject: [PATCH 10/17] updated test client to match new functionality --- tests/unit/test_client.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/unit/test_client.py b/tests/unit/test_client.py index 23c20632..a1acad2e 100644 --- a/tests/unit/test_client.py +++ b/tests/unit/test_client.py @@ -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): From c769b190c0d2cef57781d65d28d1fdf878e17e6c Mon Sep 17 00:00:00 2001 From: "William N. Green" Date: Sun, 16 Mar 2025 13:25:22 -0700 Subject: [PATCH 11/17] ensure minorversion is checked properly --- tests/unit/test_client.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/tests/unit/test_client.py b/tests/unit/test_client.py index 23c20632..6f7d03a1 100644 --- a/tests/unit/test_client.py +++ b/tests/unit/test_client.py @@ -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={'minorversion': client.QuickBooks.MINIMUM_MINOR_VERSION}) @patch('quickbooks.client.QuickBooks.get') def test_get_current_user(self, get): @@ -155,7 +155,7 @@ def test_get_single_object(self, make_req): qb_client.get_single_object("test", 1) url = "https://sandbox-quickbooks.api.intuit.com/v3/company/1234/test/1/" - make_req.assert_called_with("GET", url, {}, params=None) + make_req.assert_called_with("GET", url, {}, params={'minorversion': client.QuickBooks.MINIMUM_MINOR_VERSION}) @patch('quickbooks.client.QuickBooks.make_request') def test_get_single_object_with_params(self, make_req): @@ -164,7 +164,7 @@ def test_get_single_object_with_params(self, make_req): qb_client.get_single_object("test", 1, params={'param':'value'}) url = "https://sandbox-quickbooks.api.intuit.com/v3/company/1234/test/1/" - make_req.assert_called_with("GET", url, {}, params={'param':'value'}) + make_req.assert_called_with("GET", url, {}, params={'param':'value', 'minorversion': client.QuickBooks.MINIMUM_MINOR_VERSION}) @patch('quickbooks.client.QuickBooks.process_request') def test_make_request(self, process_request): @@ -177,7 +177,8 @@ def test_make_request(self, process_request): process_request.assert_called_with( "GET", url, data={}, - headers={'Content-Type': 'application/json', 'Accept': 'application/json', 'User-Agent': 'python-quickbooks V3 library'}, params={}) + headers={'Content-Type': 'application/json', 'Accept': 'application/json', 'User-Agent': 'python-quickbooks V3 library'}, + params={'minorversion': client.QuickBooks.MINIMUM_MINOR_VERSION}) def test_handle_exceptions(self): qb_client = client.QuickBooks() From 1dc7f6878092534687827511fd91733a350b85f1 Mon Sep 17 00:00:00 2001 From: "William N. Green" Date: Sun, 16 Mar 2025 13:33:08 -0700 Subject: [PATCH 12/17] cleanup version number default --- quickbooks/client.py | 55 ++++++++++++++++++++++++-------------------- 1 file changed, 30 insertions(+), 25 deletions(-) diff --git a/quickbooks/client.py b/quickbooks/client.py index fa85d219..6430ddbc 100644 --- a/quickbooks/client.py +++ b/quickbooks/client.py @@ -76,14 +76,19 @@ def __new__(cls, **kwargs): if 'company_id' in kwargs: instance.company_id = kwargs['company_id'] - if 'minorversion' in kwargs: - instance.minorversion = kwargs['minorversion'] - - if instance.minorversion < instance.MINIMUM_MINOR_VERSION: - warnings.warn( - 'Minor Version no longer supported.' - 'See: https://blogs.intuit.com/2025/01/21/changes-to-our-accounting-api-that-may-impact-your-application/', - DeprecationWarning) + # Handle minorversion with default + instance.minorversion = kwargs.get('minorversion', instance.MINIMUM_MINOR_VERSION) + if 'minorversion' not in kwargs: + warnings.warn( + 'No minor version specified. Defaulting to minimum supported version (75). ' + 'Please specify minorversion explicitly when initializing QuickBooks. ' + 'See: https://blogs.intuit.com/2025/01/21/changes-to-our-accounting-api-that-may-impact-your-application/', + DeprecationWarning) + elif instance.minorversion < instance.MINIMUM_MINOR_VERSION: + warnings.warn( + f'Minor Version {instance.minorversion} is no longer supported. Minimum supported version is {instance.MINIMUM_MINOR_VERSION}. ' + 'See: https://blogs.intuit.com/2025/01/21/changes-to-our-accounting-api-that-may-impact-your-application/', + DeprecationWarning) instance.invoice_link = kwargs.get('invoice_link', False) @@ -157,15 +162,7 @@ def make_request(self, request_type, url, request_body=None, content_type='appli if not params: params = {} - if self.minorversion is None: - warnings.warn( - 'No minor version specified. Defaulting to minimum supported version (75). ' - 'Please specify minorversion explicitly when initializing QuickBooks. ' - 'See: https://blogs.intuit.com/2025/01/21/changes-to-our-accounting-api-that-may-impact-your-application/', - DeprecationWarning) - params['minorversion'] = self.MINIMUM_MINOR_VERSION - else: - params['minorversion'] = self.minorversion + params['minorversion'] = self.minorversion if request_id: params['requestid'] = request_id @@ -240,10 +237,18 @@ def make_request(self, request_type, url, request_body=None, content_type='appli return result def get(self, *args, **kwargs): - return self.make_request("GET", *args, **kwargs) + if 'params' not in kwargs: + kwargs['params'] = {} + if 'minorversion' not in kwargs['params']: + kwargs['params']['minorversion'] = self.MINIMUM_MINOR_VERSION + return self.make_request('GET', *args, **kwargs) def post(self, *args, **kwargs): - return self.make_request("POST", *args, **kwargs) + if 'params' not in kwargs: + kwargs['params'] = {} + if 'minorversion' not in kwargs['params']: + kwargs['params']['minorversion'] = self.MINIMUM_MINOR_VERSION + return self.make_request('POST', *args, **kwargs) def process_request(self, request_type, url, headers="", params="", data=""): if self.session is None: @@ -256,9 +261,9 @@ def process_request(self, request_type, url, headers="", params="", data=""): def get_single_object(self, qbbo, pk, params=None): url = "{0}/company/{1}/{2}/{3}/".format(self.api_url, self.company_id, qbbo.lower(), pk) - result = self.get(url, {}, params=params) - - return result + if params is None: + params = {} + return self.get(url, {}, params=params) @staticmethod def handle_exceptions(results): @@ -316,9 +321,9 @@ def isvalid_object_name(self, object_name): def update_object(self, qbbo, request_body, _file_path=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) - - return result + if params is None: + params = {} + return self.post(url, request_body, file_path=_file_path, request_id=request_id, params=params) def delete_object(self, qbbo, request_body, _file_path=None, request_id=None): url = "{0}/company/{1}/{2}".format(self.api_url, self.company_id, qbbo.lower()) From b4014b4f967c023af5bac6d6deba0014f7904154 Mon Sep 17 00:00:00 2001 From: "William N. Green" Date: Sun, 16 Mar 2025 13:38:18 -0700 Subject: [PATCH 13/17] fixed for new message --- tests/unit/test_client.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/tests/unit/test_client.py b/tests/unit/test_client.py index 6f7d03a1..9704cf80 100644 --- a/tests/unit/test_client.py +++ b/tests/unit/test_client.py @@ -53,7 +53,8 @@ def test_client_with_deprecated_minor_version(self): self.assertEqual(self.qb_client.minorversion, 74) self.assertEqual(len(w), 1) self.assertTrue(issubclass(w[-1].category, DeprecationWarning)) - self.assertTrue("Minor Version no longer supported." in str(w[-1].message)) + self.assertTrue("Minor Version 74 is no longer supported" in str(w[-1].message)) + self.assertTrue("Minimum supported version is 75" in str(w[-1].message)) def test_api_url(self): qb_client = client.QuickBooks(sandbox=False) @@ -128,7 +129,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, file_bytes=None, request_id="123", params={'minorversion': client.QuickBooks.MINIMUM_MINOR_VERSION}) + make_req.assert_called_with("POST", url, "request_body", file_path=None, request_id="123", params={'minorversion': client.QuickBooks.MINIMUM_MINOR_VERSION}) @patch('quickbooks.client.QuickBooks.get') def test_get_current_user(self, get): @@ -146,7 +147,8 @@ def test_get_report(self, make_req): qb_client.get_report("profitandloss", {1: 2}) url = "https://sandbox-quickbooks.api.intuit.com/v3/company/1234/reports/profitandloss" - make_req.assert_called_with("GET", url, params={1: 2}) + expected_params = {1: 2, 'minorversion': client.QuickBooks.MINIMUM_MINOR_VERSION} + make_req.assert_called_with("GET", url, params=expected_params) @patch('quickbooks.client.QuickBooks.make_request') def test_get_single_object(self, make_req): From ff14b00005398eddd577ff4820a571a753c2da53 Mon Sep 17 00:00:00 2001 From: "William N. Green" Date: Mon, 24 Mar 2025 10:49:56 -0700 Subject: [PATCH 14/17] fix setup.cfg - -> _ --- setup.cfg | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/setup.cfg b/setup.cfg index 4f71077b..6b91b5f6 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,9 +1,9 @@ [metadata] -description-file = README.md +description_file = README.md [flake8] -max-line-length = 100 -max-complexity = 10 +max_line_length = 100 +max_complexity = 10 filename = *.py format = default exclude =/quickbooks/objects/__init__.py From 58c1855977307a14dc9053cf200ac8207613e718 Mon Sep 17 00:00:00 2001 From: Yonatan Romero Date: Mon, 24 Mar 2025 16:15:46 -0300 Subject: [PATCH 15/17] Remove description-file. It is not supported by setuptools https://github.com/pypa/setuptools/issues/4913 --- setup.cfg | 3 --- 1 file changed, 3 deletions(-) diff --git a/setup.cfg b/setup.cfg index 4f71077b..658090f7 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,6 +1,3 @@ -[metadata] -description-file = README.md - [flake8] max-line-length = 100 max-complexity = 10 From beab2723592248a45a33af1096000fe5a5dea0ff Mon Sep 17 00:00:00 2001 From: Edward Emanuel Date: Tue, 15 Apr 2025 20:31:07 -0500 Subject: [PATCH 16/17] Update tests and fix issues --- CHANGELOG.rst | 7 +++++++ quickbooks/client.py | 6 ++---- quickbooks/objects/employee.py | 6 +++--- tests/integration/test_account.py | 2 +- tests/integration/test_base.py | 2 ++ tests/integration/test_employee.py | 10 ++++++---- tests/unit/test_client.py | 8 ++++---- 7 files changed, 25 insertions(+), 16 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index f8b25077..176f3c63 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -1,6 +1,13 @@ Changelog ========= +* 0.9.11 (April 15, 2025) + * Add CostRate to TimeActivity + * Fix retrieval of Item SKU field + * Added support for attaching files using byte streams + * Default minor version to minimum supported version + * Fix incompatibility issues with setuptools + * 0.9.11 (February 10, 2025) * Add warning for unsupported minorversion * Fix issue with new versions of jsonEncoder diff --git a/quickbooks/client.py b/quickbooks/client.py index 45d6ca46..a4280444 100644 --- a/quickbooks/client.py +++ b/quickbooks/client.py @@ -242,15 +242,13 @@ def make_request(self, request_type, url, request_body=None, content_type='appli def get(self, *args, **kwargs): if 'params' not in kwargs: kwargs['params'] = {} - if 'minorversion' not in kwargs['params']: - kwargs['params']['minorversion'] = self.MINIMUM_MINOR_VERSION + return self.make_request('GET', *args, **kwargs) def post(self, *args, **kwargs): if 'params' not in kwargs: kwargs['params'] = {} - if 'minorversion' not in kwargs['params']: - kwargs['params']['minorversion'] = self.MINIMUM_MINOR_VERSION + return self.make_request('POST', *args, **kwargs) def process_request(self, request_type, url, headers="", params="", data=""): diff --git a/quickbooks/objects/employee.py b/quickbooks/objects/employee.py index 831ef86f..64d4e0ff 100644 --- a/quickbooks/objects/employee.py +++ b/quickbooks/objects/employee.py @@ -17,7 +17,7 @@ class Employee(QuickbooksManagedObject, QuickbooksTransactionEntity): def __init__(self): super(Employee, self).__init__() - self.SSN = "" + self.SSN = None self.GivenName = "" self.FamilyName = "" @@ -29,9 +29,9 @@ def __init__(self): self.Title = "" self.BillRate = 0 self.CostRate = 0 - self.BirthDate = "" + self.BirthDate = None self.Gender = None - self.HiredDate = "" + self.HiredDate = None self.ReleasedDate = "" self.Active = True self.Organization = False diff --git a/tests/integration/test_account.py b/tests/integration/test_account.py index 40383711..741a6b7a 100644 --- a/tests/integration/test_account.py +++ b/tests/integration/test_account.py @@ -24,7 +24,7 @@ def test_create(self): account.AccountSubType = "CashOnHand" created_account = account.save(qb=self.qb_client) - + # Verify the save was successful self.assertIsNotNone(created_account) self.assertIsNotNone(created_account.Id) diff --git a/tests/integration/test_base.py b/tests/integration/test_base.py index 3372b2ab..aca87d42 100644 --- a/tests/integration/test_base.py +++ b/tests/integration/test_base.py @@ -20,6 +20,7 @@ def setUp(self): auth_client=self.auth_client, refresh_token=os.environ.get('REFRESH_TOKEN'), company_id=os.environ.get('COMPANY_ID'), + minorversion=75 ) self.qb_client.sandbox = True @@ -40,6 +41,7 @@ def setUp(self): # auth_client=self.auth_client, refresh_token='REFRESH_TOKEN', company_id='COMPANY_ID', + minorversion=75 ) self.qb_client.sandbox = True diff --git a/tests/integration/test_employee.py b/tests/integration/test_employee.py index 3d8c2235..2f389130 100644 --- a/tests/integration/test_employee.py +++ b/tests/integration/test_employee.py @@ -1,15 +1,17 @@ -from datetime import datetime +from datetime import datetime, date from quickbooks.objects.base import Address, PhoneNumber from quickbooks.objects.employee import Employee from tests.integration.test_base import QuickbooksTestCase +from quickbooks.helpers import qb_date_format class EmployeeTest(QuickbooksTestCase): def test_create(self): employee = Employee() - employee.SSN = "444-55-6666" + employee.SSN = None employee.GivenName = "John" + employee.HiredDate = qb_date_format(date(2020, 7, 22)) employee.FamilyName = "Smith {0}".format(datetime.now().strftime('%d%H%M%S')) employee.PrimaryAddr = Address() @@ -19,13 +21,13 @@ def test_create(self): employee.PrimaryAddr.PostalCode = "93242" employee.PrimaryPhone = PhoneNumber() - employee.PrimaryPhone.FreeFormNumber = "408-525-1234" + employee.PrimaryPhone.FreeFormNumber = "4085251234" + employee.save(qb=self.qb_client) query_employee = Employee.get(employee.Id, qb=self.qb_client) self.assertEqual(query_employee.Id, employee.Id) - self.assertEqual(query_employee.SSN, "XXX-XX-XXXX") self.assertEqual(query_employee.GivenName, employee.GivenName) self.assertEqual(query_employee.FamilyName, employee.FamilyName) self.assertEqual(query_employee.PrimaryAddr.Line1, employee.PrimaryAddr.Line1) diff --git a/tests/unit/test_client.py b/tests/unit/test_client.py index 6740b7cc..94b1eff7 100644 --- a/tests/unit/test_client.py +++ b/tests/unit/test_client.py @@ -123,7 +123,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, file_bytes=None, request_id="123", params={'minorversion': client.QuickBooks.MINIMUM_MINOR_VERSION}) + make_req.assert_called_with("POST", url, "request_body", file_path=None, file_bytes=None, request_id="123", params={}) @patch('quickbooks.client.QuickBooks.get') def test_get_current_user(self, get): @@ -141,7 +141,7 @@ def test_get_report(self, make_req): qb_client.get_report("profitandloss", {1: 2}) url = "https://sandbox-quickbooks.api.intuit.com/v3/company/1234/reports/profitandloss" - expected_params = {1: 2, 'minorversion': client.QuickBooks.MINIMUM_MINOR_VERSION} + expected_params = {1: 2} make_req.assert_called_with("GET", url, params=expected_params) @patch('quickbooks.client.QuickBooks.make_request') @@ -151,7 +151,7 @@ def test_get_single_object(self, make_req): qb_client.get_single_object("test", 1) url = "https://sandbox-quickbooks.api.intuit.com/v3/company/1234/test/1" - make_req.assert_called_with("GET", url, {}, params={'minorversion': client.QuickBooks.MINIMUM_MINOR_VERSION}) + make_req.assert_called_with("GET", url, {}, params={}) @patch('quickbooks.client.QuickBooks.make_request') def test_get_single_object_with_params(self, make_req): @@ -160,7 +160,7 @@ def test_get_single_object_with_params(self, make_req): qb_client.get_single_object("test", 1, params={'param':'value'}) url = "https://sandbox-quickbooks.api.intuit.com/v3/company/1234/test/1" - make_req.assert_called_with("GET", url, {}, params={'param':'value', 'minorversion': client.QuickBooks.MINIMUM_MINOR_VERSION}) + make_req.assert_called_with("GET", url, {}, params={'param':'value'}) @patch('quickbooks.client.QuickBooks.process_request') def test_make_request(self, process_request): From 71693d0a1c591a55f6523706747d49dc976413fd Mon Sep 17 00:00:00 2001 From: Edward Emanuel Date: Tue, 15 Apr 2025 20:42:08 -0500 Subject: [PATCH 17/17] Update version number --- CHANGELOG.rst | 2 +- setup.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 176f3c63..10f03ced 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -1,7 +1,7 @@ Changelog ========= -* 0.9.11 (April 15, 2025) +* 0.9.12 (April 15, 2025) * Add CostRate to TimeActivity * Fix retrieval of Item SKU field * Added support for attaching files using byte streams diff --git a/setup.py b/setup.py index 1c5d1add..b9d6fb9c 100644 --- a/setup.py +++ b/setup.py @@ -10,7 +10,7 @@ def read(*parts): return fp.read() -VERSION = (0, 9, 11) +VERSION = (0, 9, 12) version = '.'.join(map(str, VERSION)) setup(