diff --git a/CHANGELOG.md b/CHANGELOG.md index c823906..c7ba46d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ nylas-python Changelog Unreleased ---------- * Added optional `tracking_options.domain_name` support for custom link and open tracking hostnames in message sends, scheduled sends, drafts, and Transactional Send +* Added Contact metadata request/response models and `metadata_pair` filtering, with contact webhook compatibility documentation v6.17.0 ---------- diff --git a/nylas/models/contacts.py b/nylas/models/contacts.py index 9ddcc1c..c9fa790 100644 --- a/nylas/models/contacts.py +++ b/nylas/models/contacts.py @@ -1,6 +1,6 @@ from dataclasses import dataclass from enum import Enum -from typing import Optional, List +from typing import Dict, List, Optional from typing_extensions import TypedDict, NotRequired from dataclasses_json import dataclass_json @@ -132,6 +132,9 @@ class Contact: given_name: The contact's given name. job_title: The contact's job title. manager_name: The contact's manager name. + metadata: Nylas-owned metadata associated with the contact. Metadata is + not written to the provider and does not follow a contact if its + public ID changes. middle_name: The contact's middle name. nickname: The contact's nickname. notes: The contact's notes. @@ -158,6 +161,7 @@ class Contact: given_name: Optional[str] = None job_title: Optional[str] = None manager_name: Optional[str] = None + metadata: Optional[Dict[str, str]] = None middle_name: Optional[str] = None nickname: Optional[str] = None notes: Optional[str] = None @@ -286,6 +290,9 @@ class CreateContactRequest(TypedDict): given_name: The contact's given name. job_title: The contact's job title. manager_name: The contact's manager name. + metadata: Nylas-owned metadata for the contact. On update, omission or + None preserves existing metadata, an object replaces it, and an + empty object clears it. middle_name: The contact's middle name. nickname: The contact's nickname. notes: The contact's notes. @@ -309,6 +316,7 @@ class CreateContactRequest(TypedDict): given_name: NotRequired[str] job_title: NotRequired[str] manager_name: NotRequired[str] + metadata: NotRequired[Optional[Dict[str, str]]] middle_name: NotRequired[str] nickname: NotRequired[str] notes: NotRequired[str] @@ -338,6 +346,9 @@ class ListContactsQueryParams(ListQueryParams): source: Return contacts from a specific source. group: Return contacts from a specific group. recurse: Return contacts from all sub-groups of the specified group. + metadata_pair: Filter by one indexed metadata key/value pair. Use one + of key1 through key5. This cannot be combined with provider-side + contact filters; pagination parameters are supported. select (NotRequired[str]): Comma-separated list of fields to return in the response. This allows you to receive only the portion of object data that you're interested in. limit (NotRequired[int]): The maximum number of objects to return. @@ -351,6 +362,7 @@ class ListContactsQueryParams(ListQueryParams): source: NotRequired[SourceType] group: NotRequired[str] recurse: NotRequired[bool] + metadata_pair: NotRequired[Dict[str, str]] class GroupType(str, Enum): diff --git a/nylas/models/webhooks.py b/nylas/models/webhooks.py index 7cbee4a..982e130 100644 --- a/nylas/models/webhooks.py +++ b/nylas/models/webhooks.py @@ -10,7 +10,13 @@ class WebhookTriggers(str, Enum): - """Enum representing the available webhook triggers.""" + """ + Enum representing the available webhook triggers. + + Native iCloud contacts support CONTACT_UPDATED and CONTACT_DELETED. Yahoo + contacts support neither trigger, including for changes made through the + Nylas API. There is no CONTACT_CREATED trigger. + """ BOOKING_CREATED = "booking.created" BOOKING_PENDING = "booking.pending" BOOKING_RESCHEDULED = "booking.rescheduled" diff --git a/tests/handler/test_http_client.py b/tests/handler/test_http_client.py index 416b357..7564e23 100644 --- a/tests/handler/test_http_client.py +++ b/tests/handler/test_http_client.py @@ -170,6 +170,17 @@ def test_build_query_params(self, patched_version_and_sys): == "https://test.nylas.com/foo?foo=bar&list=a&list=b&list=c&map=key1:value1&map=key2:value2" ) + def test_build_contact_metadata_pair_query_param(self): + url = _build_query_params( + base_url="https://test.nylas.com/v3/grants/abc-123/contacts", + query_params={"metadata_pair": {"key1": "sync_eligible"}}, + ) + + assert ( + url + == "https://test.nylas.com/v3/grants/abc-123/contacts?metadata_pair=key1:sync_eligible" + ) + def test_execute_download_request(self, http_client, patched_request): response = http_client._execute_download_request( path="/foo", diff --git a/tests/resources/test_contacts.py b/tests/resources/test_contacts.py index c55cff0..41b212b 100644 --- a/tests/resources/test_contacts.py +++ b/tests/resources/test_contacts.py @@ -24,6 +24,7 @@ def test_contact_deserialization(self): "im_addresses": [{"type": "other", "im_address": "myjabberaddress"}], "job_title": "Software Engineer", "manager_name": "Bill", + "metadata": {"key1": "sync_eligible"}, "middle_name": "Jacob", "nickname": "JD", "notes": "Loves ramen", @@ -64,6 +65,7 @@ def test_contact_deserialization(self): ] assert contact.job_title == "Software Engineer" assert contact.manager_name == "Bill" + assert contact.metadata == {"key1": "sync_eligible"} assert contact.middle_name == "Jacob" assert contact.nickname == "JD" assert contact.notes == "Loves ramen" @@ -112,6 +114,23 @@ def test_list_contacts_with_query_params(self, http_client_list_response): overrides=None, ) + def test_list_contacts_with_metadata_pair(self, http_client_list_response): + contacts = Contacts(http_client_list_response) + + contacts.list( + identifier="abc-123", + query_params={"metadata_pair": {"key1": "sync_eligible"}}, + ) + + http_client_list_response._execute.assert_called_once_with( + "GET", + "/v3/grants/abc-123/contacts", + None, + {"metadata_pair": {"key1": "sync_eligible"}}, + None, + overrides=None, + ) + def test_list_contacts_with_select_param(self, http_client_list_response): contacts = Contacts(http_client_list_response) @@ -219,6 +238,7 @@ def test_create_contact(self, http_client_response): "given_name": "John", "surname": "Doe", "company_name": "Nylas", + "metadata": {"key1": "sync_eligible"}, } contacts.create(identifier="abc-123", request_body=request_body) @@ -238,6 +258,7 @@ def test_update_contact(self, http_client_response): "given_name": "John", "surname": "Doe", "company_name": "Nylas", + "metadata": {}, } contacts.update( diff --git a/tests/resources/test_webhooks.py b/tests/resources/test_webhooks.py index 159a94d..bc5e810 100644 --- a/tests/resources/test_webhooks.py +++ b/tests/resources/test_webhooks.py @@ -5,6 +5,11 @@ class TestWebhooks: + def test_contact_webhook_contract(self): + assert WebhookTriggers.CONTACT_UPDATED.value == "contact.updated" + assert WebhookTriggers.CONTACT_DELETED.value == "contact.deleted" + assert "contact.created" not in {trigger.value for trigger in WebhookTriggers} + def test_webhook_deserialization(self, http_client): webhook_json = { "id": "UMWjAjMeWQ4D8gYF2moonK4486",