Skip to content

Commit

Permalink
feat: Impl contacts API (#60)
Browse files Browse the repository at this point in the history
  • Loading branch information
drish authored Dec 28, 2023
1 parent 869d0b9 commit fccc64a
Show file tree
Hide file tree
Showing 7 changed files with 282 additions and 3 deletions.
2 changes: 1 addition & 1 deletion examples/batch_email_send.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,4 @@
]

emails = resend.Batch.send(params)
print(emails)
print(emails)
41 changes: 41 additions & 0 deletions examples/contacts.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import os

import resend

if not os.environ["RESEND_API_KEY"]:
raise EnvironmentError("RESEND_API_KEY is missing")


audience_id = "78b8d3bc-a55a-45a3-aee6-6ec0a5e13d7e"

contact = resend.Contacts.create(
{
"audience_id": audience_id,
"email": "[email protected]",
"first_name": "Steve",
"last_name": "Wozniak",
"unsubscribed": True,
}
)
print("created contact !")
print(contact)

update_params = {
"audience_id": audience_id,
"id": contact["id"],
"last_name": "Updated",
"unsubscribed": False,
}

updated = resend.Contacts.update(update_params)
print("updated contact !")
print(updated)

cont = resend.Contacts.get(audience_id=audience_id, id=contact["id"])
print(cont)

contacts = resend.Contacts.list(audience_id=audience_id)
print(contacts)

rmed = resend.Contacts.remove(audience_id=audience_id, id=contact["id"])
print(rmed)
2 changes: 2 additions & 0 deletions resend/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from .api_keys import ApiKeys
from .audiences import Audiences
from .batch import Batch
from .contacts import Contacts
from .domains import Domains
from .emails import Emails
from .request import Request
Expand All @@ -25,4 +26,5 @@
"Domains",
"Batch",
"Audiences",
"Contacts"
]
37 changes: 37 additions & 0 deletions resend/contacts.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
from typing import Dict

from resend import request


class Contacts:
"""Contacts API Wrapper"""

@classmethod
# https://resend.com/docs/api-reference/contacts/create-contact
def create(cls, params={}) -> Dict:
path = f"/audiences/{params['audience_id']}/contacts"
return request.Request(path=path, params=params, verb="post").perform()

@classmethod
# https://resend.com/docs/api-reference/contacts/update-contact
def update(cls, params={}) -> Dict:
path = f"/audiences/{params['audience_id']}/contacts/{params['id']}"
return request.Request(path=path, params=params, verb="patch").perform()

@classmethod
# https://resend.com/docs/api-reference/audiences/list-audiences
def list(cls, audience_id) -> Dict:
path = f"/audiences/#{audience_id}/contacts"
return request.Request(path=path, params={}, verb="get").perform()

@classmethod
# https://resend.com/docs/api-reference/audiences/get-audience
def get(cls, audience_id, id) -> Dict:
path = f"/audiences/{audience_id}/contacts/{id}"
return request.Request(path=path, params={}, verb="get").perform()

@classmethod
# https://resend.com/docs/api-reference/audiences/delete-audience
def remove(cls, audience_id, id) -> Dict:
path = f"/audiences/{id}/contacts/{id}"
return request.Request(path=path, params={}, verb="delete").perform()
30 changes: 29 additions & 1 deletion resend/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ def __init__(
suggested_action = """Check the error message
to see the list of missing fields."""

if message != "":
if message == "":
message = default_message

ResendError.__init__(
Expand All @@ -132,11 +132,38 @@ def __init__(
)


class ApplicationError(ResendError):
"""see https://resend.com/docs/errors"""

def __init__(
self,
message,
error_type,
code,
):
default_message = """
Something went wrong."""

suggested_action = """Contact Resend support."""

if message == "":
message = default_message

ResendError.__init__(
self,
code=code or 500,
message=message,
suggested_action=suggested_action,
error_type=error_type,
)


ERRORS: Dict[str, Dict[str, ResendError]] = {
"400": {"validation_error": ValidationError},
"422": {"missing_required_fields": MissingRequiredFieldsError},
"401": {"missing_api_key": MissingApiKeyError},
"403": {"invalid_api_key": InvalidApiKeyError},
"500": {"application_error": ApplicationError},
}


Expand All @@ -149,4 +176,5 @@ def raise_for_code_and_type(code, error_type, message: str) -> ResendError:

# Raise error from errors list
error: ResendError = error.get(error_type)

raise error(code=code, message=message, error_type=error_type)
1 change: 0 additions & 1 deletion resend/request.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@ def perform(self):
message=error.get("message"),
error_type=error.get("name"),
)

return resp.json()

def __get_headers(self) -> Dict:
Expand Down
172 changes: 172 additions & 0 deletions tests/contacts_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
import unittest
from unittest.mock import MagicMock, patch

import resend

# flake8: noqa


class TestResendContacts(unittest.TestCase):
def test_contacts_create(self):
resend.api_key = "re_123"

patcher = patch("resend.Request.make_request")
mock = patcher.start()
mock.status_code = 200
m = MagicMock()
m.status_code = 200

def mock_json():
return {"object": "contact", "id": "479e3145-dd38-476b-932c-529ceb705947"}

m.json = mock_json
mock.return_value = m

params = {
"audience_id": "48c269ed-9873-4d60-bdd9-cd7e6fc0b9b8",
"email": "[email protected]",
"first_name": "Steve",
"last_name": "Wozniak",
"unsubscribed": True,
}
contact = resend.Contacts.create(params)
assert contact["id"] == "479e3145-dd38-476b-932c-529ceb705947"
assert contact["object"] == "contact"

patcher.stop()

def test_contacts_update(self):
resend.api_key = "re_123"

patcher = patch("resend.Request.make_request")
mock = patcher.start()
mock.status_code = 200
m = MagicMock()
m.status_code = 200

def mock_json():
return {
"object": "contact",
"id": "479e3145-dd38-476b-932c-529ceb705947",
}

m.json = mock_json
mock.return_value = m

params = {
"audience_id": "48c269ed-9873-4d60-bdd9-cd7e6fc0b9b8",
"id": "479e3145-dd38-476b-932c-529ceb705947",
"first_name": "Updated",
"unsubscribed": True,
}
contact = resend.Contacts.update(params)
assert contact["id"] == "479e3145-dd38-476b-932c-529ceb705947"
assert contact["object"] == "contact"

patcher.stop()

def test_contacts_get(self):
resend.api_key = "re_123"

patcher = patch("resend.Request.make_request")
mock = patcher.start()
mock.status_code = 200
m = MagicMock()
m.status_code = 200

def mock_json():
return {
"object": "contact",
"id": "e169aa45-1ecf-4183-9955-b1499d5701d3",
"email": "[email protected]",
"first_name": "Steve",
"last_name": "Wozniak",
"created_at": "2023-10-06T23:47:56.678Z",
"unsubscribed": False,
}

m.json = mock_json
mock.return_value = m

contact = resend.Contacts.get(
audience_id="48c269ed-9873-4d60-bdd9-cd7e6fc0b9b8",
id="e169aa45-1ecf-4183-9955-b1499d5701d3",
)
assert contact["object"] == "contact"
assert contact["id"] == "e169aa45-1ecf-4183-9955-b1499d5701d3"
assert contact["email"] == "[email protected]"
assert contact["first_name"] == "Steve"
assert contact["last_name"] == "Wozniak"
assert contact["created_at"] == "2023-10-06T23:47:56.678Z"
assert contact["unsubscribed"] is False

patcher.stop()

def test_contacts_remove(self):
resend.api_key = "re_123"

patcher = patch("resend.Request.make_request")
mock = patcher.start()
mock.status_code = 200
m = MagicMock()
m.status_code = 200

def mock_json():
return {
"object": "contact",
"id": "520784e2-887d-4c25-b53c-4ad46ad38100",
"deleted": True,
}

m.json = mock_json
mock.return_value = m

rmed = resend.Contacts.remove(
audience_id="48c269ed-9873-4d60-bdd9-cd7e6fc0b9b8",
id="78261eea-8f8b-4381-83c6-79fa7120f1cf",
)
assert rmed["object"] == "contact"
assert rmed["id"] == "520784e2-887d-4c25-b53c-4ad46ad38100"
assert rmed["deleted"] is True

patcher.stop()

def test_contacts_list(self):
resend.api_key = "re_123"

patcher = patch("resend.Request.make_request")
mock = patcher.start()
mock.status_code = 200
m = MagicMock()
m.status_code = 200

def mock_json():
return {
"object": "list",
"data": [
{
"id": "e169aa45-1ecf-4183-9955-b1499d5701d3",
"email": "[email protected]",
"first_name": "Steve",
"last_name": "Wozniak",
"created_at": "2023-10-06T23:47:56.678Z",
"unsubscribed": False,
}
],
}

m.json = mock_json
mock.return_value = m

contacts = resend.Contacts.list(
audience_id="48c269ed-9873-4d60-bdd9-cd7e6fc0b9b8"
)
assert contacts["object"] == "list"
assert contacts["data"][0]["id"] == "e169aa45-1ecf-4183-9955-b1499d5701d3"
assert contacts["data"][0]["email"] == "[email protected]"
assert contacts["data"][0]["first_name"] == "Steve"
assert contacts["data"][0]["last_name"] == "Wozniak"
assert contacts["data"][0]["created_at"] == "2023-10-06T23:47:56.678Z"
assert contacts["data"][0]["unsubscribed"] is False

patcher.stop()

0 comments on commit fccc64a

Please sign in to comment.