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
7 changes: 5 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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
Expand Down
3 changes: 3 additions & 0 deletions chartmogul/api/contact.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion chartmogul/version.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
__version__ = "4.8.0"
__version__ = "4.9.0"
56 changes: 56 additions & 0 deletions test/api/test_contact.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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,
Expand All @@ -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):
"""
Expand Down Expand Up @@ -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(
Expand Down Expand Up @@ -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(
Expand Down
Loading