diff --git a/CHANGELOG.md b/CHANGELOG.md index 0ad9dba..20b8b1b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,9 +8,12 @@ and this project adheres to [Semantic Versioning]. [Keep a Changelog]: https://keepachangelog.com/en/1.0.0/ [Semantic Versioning]: https://semver.org/spec/v2.0.0.html +## [4.9.0] - 2026-03-16 +- Add `external_id` key to Contact model + ## [4.8.0] - 2026-01-06 - Mention filters (CFL) param for metrics api -- Add Customer.subscriptions +- Add Customer.subscriptions - Update CustomerSubscription.list_imported to deprecated - Add disabled, disabled_at, edit_history_summary and errors fields to Invoice @@ -24,7 +27,7 @@ and this project adheres to [Semantic Versioning]. - Remove future dependency to resolve vulnerability issues ## [4.6.2] - 2025-07-09 -- Update Marshmallow dependency to use >=3.24.0 +- Update Marshmallow dependency to use >=3.24.0 ## [4.6.1] - 2025-05-19 - Fixed Tasks API schema issue diff --git a/chartmogul/api/contact.py b/chartmogul/api/contact.py index 6c1b8d1..92b0e24 100644 --- a/chartmogul/api/contact.py +++ b/chartmogul/api/contact.py @@ -26,6 +26,9 @@ class _Schema(Schema): phone = fields.String(allow_none=True) linked_in = fields.String(allow_none=True) twitter = fields.String(allow_none=True) + # load_default=None ensures this attribute is always present on the Contact + # object even when the API omits the field (e.g. older responses) + external_id = fields.String(allow_none=True, load_default=None) custom = fields.Dict(allow_none=True) @post_load diff --git a/chartmogul/version.py b/chartmogul/version.py index ea674c5..454a2ed 100644 --- a/chartmogul/version.py +++ b/chartmogul/version.py @@ -1 +1 @@ -__version__ = "4.8.0" +__version__ = "4.9.0" diff --git a/test/api/test_contact.py b/test/api/test_contact.py index 3cd041c..9295414 100644 --- a/test/api/test_contact.py +++ b/test/api/test_contact.py @@ -9,6 +9,7 @@ "customer_uuid": "cus_00000000-0000-0000-0000-000000000000", "data_source_uuid": "ds_00000000-0000-0000-0000-000000000000", "customer_external_id": "external_001", + "external_id": "contact_external_id_001", "first_name": "First name", "last_name": "Last name", "position": 9, @@ -25,6 +26,7 @@ "uuid": "con_00000000-0000-0000-0000-000000000000", "customer_uuid": "cus_00000000-0000-0000-0000-000000000000", "data_source_uuid": "ds_00000000-0000-0000-0000-000000000000", + "external_id": "contact_external_id_001", "first_name": "First name", "last_name": "Last name", "position": 9, @@ -42,6 +44,18 @@ allContacts = {"entries": [contact], "cursor": "cursor==", "has_more": False} +contactWithoutExternalId = {k: v for k, v in contact.items() if k != "external_id"} + +contactWithNullExternalId = { + **contact, + "external_id": None, +} + +createContactWithNullExternalId = { + **createContact, + "external_id": None, +} + class ContactTestCase(unittest.TestCase): """ @@ -93,6 +107,30 @@ def test_create(self, mock_requests): self.assertEqual(mock_requests.last_request.qs, {}) self.assertEqual(mock_requests.last_request.json(), createContact) + @requests_mock.mock() + def test_create_with_null_external_id(self, mock_requests): + mock_requests.register_uri( + "POST", "https://api.chartmogul.com/v1/contacts", status_code=200, json=contactWithNullExternalId + ) + + config = Config("token") + result = Contact.create(config, data=createContactWithNullExternalId).get() + self.assertEqual(mock_requests.call_count, 1, "expected call") + self.assertEqual(mock_requests.last_request.json(), createContactWithNullExternalId) + self.assertIsNone(result.external_id) + + @requests_mock.mock() + def test_create_without_external_id(self, mock_requests): + mock_requests.register_uri( + "POST", "https://api.chartmogul.com/v1/contacts", status_code=200, json=contactWithoutExternalId + ) + + config = Config("token") + result = Contact.create(config, data=createContactWithNullExternalId).get() + self.assertEqual(mock_requests.call_count, 1, "expected call") + self.assertEqual(mock_requests.last_request.json(), createContactWithNullExternalId) + self.assertIsNone(result.external_id) + @requests_mock.mock() def test_merge(self, mock_requests): mock_requests.register_uri( @@ -133,6 +171,24 @@ def test_modify(self, mock_requests): self.assertEqual(mock_requests.last_request.json(), jsonRequest) self.assertTrue(isinstance(expected, Contact)) + @requests_mock.mock() + def test_modify_with_null_external_id(self, mock_requests): + mock_requests.register_uri( + "PATCH", + "https://api.chartmogul.com/v1/contacts/con_00000000-0000-0000-0000-000000000000", + status_code=200, + json=contactWithNullExternalId, + ) + + jsonRequest = {"external_id": None} + config = Config("token") + result = Contact.modify( + config, uuid="con_00000000-0000-0000-0000-000000000000", data=jsonRequest + ).get() + self.assertEqual(mock_requests.call_count, 1, "expected call") + self.assertEqual(mock_requests.last_request.json(), jsonRequest) + self.assertIsNone(result.external_id) + @requests_mock.mock() def test_retrieve(self, mock_requests): mock_requests.register_uri(