Skip to content

Commit 60ba6c1

Browse files
Add External ID Field to Contact Model (#128)
* [del-1612] Added support for 'external_id' field in Contact model requests * Responded to PR feedback * Reverted some changes from previous commit and bumped library version to 4.9.0
1 parent cbf8ffa commit 60ba6c1

File tree

4 files changed

+65
-3
lines changed

4 files changed

+65
-3
lines changed

CHANGELOG.md

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,12 @@ and this project adheres to [Semantic Versioning].
88
[Keep a Changelog]: https://keepachangelog.com/en/1.0.0/
99
[Semantic Versioning]: https://semver.org/spec/v2.0.0.html
1010

11+
## [4.9.0] - 2026-03-16
12+
- Add `external_id` key to Contact model
13+
1114
## [4.8.0] - 2026-01-06
1215
- Mention filters (CFL) param for metrics api
13-
- Add Customer.subscriptions
16+
- Add Customer.subscriptions
1417
- Update CustomerSubscription.list_imported to deprecated
1518
- Add disabled, disabled_at, edit_history_summary and errors fields to Invoice
1619

@@ -24,7 +27,7 @@ and this project adheres to [Semantic Versioning].
2427
- Remove future dependency to resolve vulnerability issues
2528

2629
## [4.6.2] - 2025-07-09
27-
- Update Marshmallow dependency to use >=3.24.0
30+
- Update Marshmallow dependency to use >=3.24.0
2831

2932
## [4.6.1] - 2025-05-19
3033
- Fixed Tasks API schema issue

chartmogul/api/contact.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,9 @@ class _Schema(Schema):
2626
phone = fields.String(allow_none=True)
2727
linked_in = fields.String(allow_none=True)
2828
twitter = fields.String(allow_none=True)
29+
# load_default=None ensures this attribute is always present on the Contact
30+
# object even when the API omits the field (e.g. older responses)
31+
external_id = fields.String(allow_none=True, load_default=None)
2932
custom = fields.Dict(allow_none=True)
3033

3134
@post_load

chartmogul/version.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
__version__ = "4.8.0"
1+
__version__ = "4.9.0"

test/api/test_contact.py

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
"customer_uuid": "cus_00000000-0000-0000-0000-000000000000",
1010
"data_source_uuid": "ds_00000000-0000-0000-0000-000000000000",
1111
"customer_external_id": "external_001",
12+
"external_id": "contact_external_id_001",
1213
"first_name": "First name",
1314
"last_name": "Last name",
1415
"position": 9,
@@ -25,6 +26,7 @@
2526
"uuid": "con_00000000-0000-0000-0000-000000000000",
2627
"customer_uuid": "cus_00000000-0000-0000-0000-000000000000",
2728
"data_source_uuid": "ds_00000000-0000-0000-0000-000000000000",
29+
"external_id": "contact_external_id_001",
2830
"first_name": "First name",
2931
"last_name": "Last name",
3032
"position": 9,
@@ -42,6 +44,18 @@
4244

4345
allContacts = {"entries": [contact], "cursor": "cursor==", "has_more": False}
4446

47+
contactWithoutExternalId = {k: v for k, v in contact.items() if k != "external_id"}
48+
49+
contactWithNullExternalId = {
50+
**contact,
51+
"external_id": None,
52+
}
53+
54+
createContactWithNullExternalId = {
55+
**createContact,
56+
"external_id": None,
57+
}
58+
4559

4660
class ContactTestCase(unittest.TestCase):
4761
"""
@@ -93,6 +107,30 @@ def test_create(self, mock_requests):
93107
self.assertEqual(mock_requests.last_request.qs, {})
94108
self.assertEqual(mock_requests.last_request.json(), createContact)
95109

110+
@requests_mock.mock()
111+
def test_create_with_null_external_id(self, mock_requests):
112+
mock_requests.register_uri(
113+
"POST", "https://api.chartmogul.com/v1/contacts", status_code=200, json=contactWithNullExternalId
114+
)
115+
116+
config = Config("token")
117+
result = Contact.create(config, data=createContactWithNullExternalId).get()
118+
self.assertEqual(mock_requests.call_count, 1, "expected call")
119+
self.assertEqual(mock_requests.last_request.json(), createContactWithNullExternalId)
120+
self.assertIsNone(result.external_id)
121+
122+
@requests_mock.mock()
123+
def test_create_without_external_id(self, mock_requests):
124+
mock_requests.register_uri(
125+
"POST", "https://api.chartmogul.com/v1/contacts", status_code=200, json=contactWithoutExternalId
126+
)
127+
128+
config = Config("token")
129+
result = Contact.create(config, data=createContactWithNullExternalId).get()
130+
self.assertEqual(mock_requests.call_count, 1, "expected call")
131+
self.assertEqual(mock_requests.last_request.json(), createContactWithNullExternalId)
132+
self.assertIsNone(result.external_id)
133+
96134
@requests_mock.mock()
97135
def test_merge(self, mock_requests):
98136
mock_requests.register_uri(
@@ -133,6 +171,24 @@ def test_modify(self, mock_requests):
133171
self.assertEqual(mock_requests.last_request.json(), jsonRequest)
134172
self.assertTrue(isinstance(expected, Contact))
135173

174+
@requests_mock.mock()
175+
def test_modify_with_null_external_id(self, mock_requests):
176+
mock_requests.register_uri(
177+
"PATCH",
178+
"https://api.chartmogul.com/v1/contacts/con_00000000-0000-0000-0000-000000000000",
179+
status_code=200,
180+
json=contactWithNullExternalId,
181+
)
182+
183+
jsonRequest = {"external_id": None}
184+
config = Config("token")
185+
result = Contact.modify(
186+
config, uuid="con_00000000-0000-0000-0000-000000000000", data=jsonRequest
187+
).get()
188+
self.assertEqual(mock_requests.call_count, 1, "expected call")
189+
self.assertEqual(mock_requests.last_request.json(), jsonRequest)
190+
self.assertIsNone(result.external_id)
191+
136192
@requests_mock.mock()
137193
def test_retrieve(self, mock_requests):
138194
mock_requests.register_uri(

0 commit comments

Comments
 (0)