feat: sync sdk contact book support (#373)

This commit is contained in:
KM Koushik
2026-03-08 00:59:40 +11:00
committed by GitHub
parent 33acd09d77
commit 83cb0b24f7
11 changed files with 3577 additions and 2945 deletions
+4
View File
@@ -1,6 +1,8 @@
"""Python client for the UseSend API."""
from .usesend import UseSend, UseSendHTTPError
from .contacts import Contacts # type: ignore
from .contact_books import ContactBooks # type: ignore
from .domains import Domains # type: ignore
from .campaigns import Campaigns # type: ignore
from .webhooks import (
@@ -17,6 +19,8 @@ __all__ = [
"UseSend",
"UseSendHTTPError",
"types",
"Contacts",
"ContactBooks",
"Domains",
"Campaigns",
"Webhooks",
@@ -0,0 +1,50 @@
"""Contact book resource client using TypedDict shapes (no Pydantic)."""
from __future__ import annotations
from typing import Optional, Tuple, List
from .types import (
APIError,
ContactBook,
ContactBookCreate,
ContactBookCreateResponse,
ContactBookDeleteResponse,
ContactBookUpdate,
ContactBookUpdateResponse,
)
class ContactBooks:
"""Client for `/contactBooks` endpoints."""
def __init__(self, usesend: "UseSend") -> None:
self.usesend = usesend
def list(self) -> Tuple[Optional[List[ContactBook]], Optional[APIError]]:
data, err = self.usesend.get("/contactBooks")
return (data, err) # type: ignore[return-value]
def create(
self, payload: ContactBookCreate
) -> Tuple[Optional[ContactBookCreateResponse], Optional[APIError]]:
data, err = self.usesend.post("/contactBooks", payload)
return (data, err) # type: ignore[return-value]
def get(self, contact_book_id: str) -> Tuple[Optional[ContactBook], Optional[APIError]]:
data, err = self.usesend.get(f"/contactBooks/{contact_book_id}")
return (data, err) # type: ignore[return-value]
def update(
self, contact_book_id: str, payload: ContactBookUpdate
) -> Tuple[Optional[ContactBookUpdateResponse], Optional[APIError]]:
data, err = self.usesend.patch(f"/contactBooks/{contact_book_id}", payload)
return (data, err) # type: ignore[return-value]
def delete(
self, contact_book_id: str
) -> Tuple[Optional[ContactBookDeleteResponse], Optional[APIError]]:
data, err = self.usesend.delete(f"/contactBooks/{contact_book_id}")
return (data, err) # type: ignore[return-value]
from .usesend import UseSend # noqa: E402 pylint: disable=wrong-import-position
+50
View File
@@ -2,11 +2,17 @@
from __future__ import annotations
from typing import Any, Dict, Optional, Tuple
from urllib.parse import urlencode
from .types import (
APIError,
ContactDeleteResponse,
Contact,
ContactBulkCreate,
ContactBulkCreateResponse,
ContactBulkDelete,
ContactBulkDeleteResponse,
ContactList,
ContactUpdate,
ContactUpdateResponse,
ContactUpsert,
@@ -31,6 +37,32 @@ class Contacts:
)
return (data, err) # type: ignore[return-value]
def list(
self,
book_id: str,
*,
emails: Optional[str] = None,
page: Optional[int] = None,
limit: Optional[int] = None,
ids: Optional[str] = None,
) -> Tuple[Optional[ContactList], Optional[APIError]]:
query: Dict[str, Any] = {}
if emails is not None:
query["emails"] = emails
if page is not None:
query["page"] = page
if limit is not None:
query["limit"] = limit
if ids is not None:
query["ids"] = ids
path = f"/contactBooks/{book_id}/contacts"
if query:
path = f"{path}?{urlencode(query)}"
data, err = self.usesend.get(path)
return (data, err) # type: ignore[return-value]
def get(
self, book_id: str, contact_id: str
) -> Tuple[Optional[Contact], Optional[APIError]]:
@@ -57,6 +89,24 @@ class Contacts:
)
return (data, err) # type: ignore[return-value]
def bulk_create(
self, book_id: str, payload: ContactBulkCreate
) -> Tuple[Optional[ContactBulkCreateResponse], Optional[APIError]]:
data, err = self.usesend.post(
f"/contactBooks/{book_id}/contacts/bulk",
payload,
)
return (data, err) # type: ignore[return-value]
def bulk_delete(
self, book_id: str, payload: ContactBulkDelete
) -> Tuple[Optional[ContactBulkDeleteResponse], Optional[APIError]]:
data, err = self.usesend.delete(
f"/contactBooks/{book_id}/contacts/bulk",
payload,
)
return (data, err) # type: ignore[return-value]
def delete(
self, *, book_id: str, contact_id: str
) -> Tuple[Optional[ContactDeleteResponse], Optional[APIError]]:
+76
View File
@@ -271,6 +271,65 @@ class EmailCancelResponse(TypedDict, total=False):
# ---------------------------------------------------------------------------
class ContactBookCounts(TypedDict, total=False):
contacts: int
class ContactBook(TypedDict, total=False):
id: str
name: str
teamId: float
properties: Dict[str, str]
variables: List[str]
emoji: str
doubleOptInEnabled: Optional[bool]
doubleOptInFrom: Optional[str]
doubleOptInSubject: Optional[str]
doubleOptInContent: Optional[str]
createdAt: str
updatedAt: str
_count: ContactBookCounts
ContactBookList = List[ContactBook]
class ContactBookCreate(TypedDict, total=False):
name: str
emoji: Optional[str]
properties: Optional[Dict[str, str]]
doubleOptInEnabled: Optional[bool]
doubleOptInFrom: Optional[str]
doubleOptInSubject: Optional[str]
doubleOptInContent: Optional[str]
variables: Optional[List[str]]
class ContactBookCreateResponse(ContactBook, total=False):
pass
class ContactBookUpdate(TypedDict, total=False):
name: Optional[str]
emoji: Optional[str]
properties: Optional[Dict[str, str]]
doubleOptInEnabled: Optional[bool]
doubleOptInFrom: Optional[str]
doubleOptInSubject: Optional[str]
doubleOptInContent: Optional[str]
variables: Optional[List[str]]
class ContactBookUpdateResponse(ContactBook, total=False):
pass
class ContactBookDeleteResponse(TypedDict):
id: str
success: bool
message: str
class ContactCreate(TypedDict, total=False):
email: str
firstName: Optional[str]
@@ -298,6 +357,23 @@ class ContactListItem(TypedDict, total=False):
ContactList = List[ContactListItem]
ContactBulkCreate = List[ContactCreate]
class ContactBulkCreateResponse(TypedDict):
message: str
count: float
class ContactBulkDelete(TypedDict):
contactIds: List[str]
class ContactBulkDeleteResponse(TypedDict):
success: bool
count: float
class ContactUpdate(TypedDict, total=False):
firstName: Optional[str]
lastName: Optional[str]
+3
View File
@@ -71,6 +71,8 @@ class UseSend:
# Lazily initialise resource clients.
self.emails = Emails(self)
self.contacts = Contacts(self)
self.contact_books = ContactBooks(self)
self.contactBooks = self.contact_books
self.domains = Domains(self)
self.campaigns = Campaigns(self)
@@ -186,6 +188,7 @@ class UseSend:
# Import here to avoid circular dependency during type checking
from .emails import Emails # noqa: E402 pylint: disable=wrong-import-position
from .contacts import Contacts # noqa: E402 pylint: disable=wrong-import-position
from .contact_books import ContactBooks # noqa: E402 pylint: disable=wrong-import-position
from .domains import Domains # type: ignore # noqa: E402
from .campaigns import Campaigns # type: ignore # noqa: E402
from .webhooks import Webhooks # noqa: E402 pylint: disable=wrong-import-position