From 1db006313c87473ac7d8426613cf43aeacf4d942 Mon Sep 17 00:00:00 2001 From: Muhammad Anas Date: Wed, 20 May 2026 17:06:45 +0500 Subject: [PATCH 1/2] fix: handle hubspot sync edge cases (409 conflicts, empty line items, duplicate hubspot_id mappings) --- hubspot_xpro/api.py | 9 +++++++++ hubspot_xpro/tasks.py | 13 ++++++++++--- hubspot_xpro/tasks_test.py | 14 +++++++++++--- 3 files changed, 30 insertions(+), 6 deletions(-) diff --git a/hubspot_xpro/api.py b/hubspot_xpro/api.py index 1311f7c194..8714c79918 100644 --- a/hubspot_xpro/api.py +++ b/hubspot_xpro/api.py @@ -210,6 +210,13 @@ def sync_contact_hubspot_ids_to_db(): alt_email_q |= Q(email__iexact=alt_email) user = User.objects.filter(alt_email_q).first() if user: + # Skip if this hubspot_id is already mapped to a different user + existing = HubspotObject.objects.filter( + content_type=content_type, + hubspot_id=contact.id, + ).exclude(object_id=user.id).exists() + if existing: + continue HubspotObject.objects.update_or_create( content_type=content_type, object_id=user.id, @@ -332,6 +339,8 @@ def sync_deal_line_hubspot_ids_to_db(order, hubspot_order_id) -> bool: matches = 0 expected_matches = 1 if is_b2b else order.lines.count() + if not line_items: + return False if is_b2b or len(line_items) == 1: HubspotObject.objects.update_or_create( content_type=ContentType.objects.get_for_model(order_line), diff --git a/hubspot_xpro/tasks.py b/hubspot_xpro/tasks.py index 8ba716c619..35930b6ecf 100644 --- a/hubspot_xpro/tasks.py +++ b/hubspot_xpro/tasks.py @@ -378,9 +378,16 @@ def batch_create_hubspot_objects_chunked( errored_chunks.append(still_failed) time.sleep(settings.HUBSPOT_TASK_DELAY / 1000) if errored_chunks: - raise ApiException( - status=last_error_status, - reason=f"Batch hubspot create failed for the following chunks: {errored_chunks}", + if last_error_status == 429: # noqa: PLR2004 + raise ApiException( + status=last_error_status, + reason=f"Batch hubspot create failed for the following chunks: {errored_chunks}", + ) + log.error( + "Batch hubspot create failed for type %s, chunks: %s (status %s)", + hubspot_type, + errored_chunks, + last_error_status, ) return created_ids diff --git a/hubspot_xpro/tasks_test.py b/hubspot_xpro/tasks_test.py index a28847e5bc..171eb2e012 100644 --- a/hubspot_xpro/tasks_test.py +++ b/hubspot_xpro/tasks_test.py @@ -301,7 +301,7 @@ def test_batch_create_hubspot_objects_chunked(mocker, id_count): @pytest.mark.parametrize( "status, expected_error", # noqa: PT006 - [[429, TooManyRequestsException], [500, ApiException]], # noqa: PT007 + [[429, TooManyRequestsException], [500, None]], # noqa: PT007 ) def test_batch_create_hubspot_objects_chunked_error(mocker, status, expected_error): """batch_create_hubspot_objects_chunked raise expected exception""" @@ -314,12 +314,20 @@ def test_batch_create_hubspot_objects_chunked_error(mocker, status, expected_err side_effect=(ApiException(status=status)), ) chunk = sorted([user.id for user in UserFactory.create_batch(3)]) - with pytest.raises(expected_error): - tasks.batch_create_hubspot_objects_chunked( + if expected_error: + with pytest.raises(expected_error): + tasks.batch_create_hubspot_objects_chunked( + HubspotObjectType.CONTACTS.value, + "user", + chunk, + ) + else: + result = tasks.batch_create_hubspot_objects_chunked( HubspotObjectType.CONTACTS.value, "user", chunk, ) + assert result == [] for item in chunk: mock_sync_contact.assert_any_call(item) From 7bc79b9279420a2520f2b88bc7c61948863ce57e Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 20 May 2026 13:07:21 +0000 Subject: [PATCH 2/2] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- hubspot_xpro/api.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/hubspot_xpro/api.py b/hubspot_xpro/api.py index 8714c79918..1ea5d3ba32 100644 --- a/hubspot_xpro/api.py +++ b/hubspot_xpro/api.py @@ -211,10 +211,14 @@ def sync_contact_hubspot_ids_to_db(): user = User.objects.filter(alt_email_q).first() if user: # Skip if this hubspot_id is already mapped to a different user - existing = HubspotObject.objects.filter( - content_type=content_type, - hubspot_id=contact.id, - ).exclude(object_id=user.id).exists() + existing = ( + HubspotObject.objects.filter( + content_type=content_type, + hubspot_id=contact.id, + ) + .exclude(object_id=user.id) + .exists() + ) if existing: continue HubspotObject.objects.update_or_create(