Skip to content

Commit

Permalink
feat: add support for batchWithTemplates
Browse files Browse the repository at this point in the history
Fixes #156
  • Loading branch information
sileht authored and Stranger6667 committed May 11, 2021
1 parent f46d0eb commit 33bf3b6
Show file tree
Hide file tree
Showing 4 changed files with 120 additions and 0 deletions.
9 changes: 9 additions & 0 deletions docs/emails.rst
Original file line number Diff line number Diff line change
Expand Up @@ -202,3 +202,12 @@ Batches are available via the :py:meth:`~postmarker.models.emails.EmailManager.E
For now, batches expose a very limited interface - only :py:meth:`~postmarker.models.emails.EmailBatch.send` method and
length information via the ``len`` function.

Template batches are available via the :py:meth:`~postmarker.models.emails.EmailManager.EmailTemplateBatch` constructor.

.. code-block:: python
>>> batch = postmark.emails.EmailTemplateBatch(template)
>>> len(batch)
1
>>> batch.send()
57 changes: 57 additions & 0 deletions src/postmarker/models/emails.py
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,45 @@ def send(self):
return self._manager._send_with_template(**self.as_dict())


class EmailTemplateBatch(Model):
"""Gathers multiple email templates in a single batch."""

MAX_SIZE = 500

def __init__(self, *emails, **kwargs):
self.emails = emails
super().__init__(**kwargs)

def __len__(self):
return len(self.emails)

def as_dict(self, **extra):
"""Converts all available emails to dictionaries.
:return: List of dictionaries.
"""
return [self._construct_email(email, **extra) for email in self.emails]

def _construct_email(self, email, **extra):
"""Converts incoming data to properly structured dictionary."""
if isinstance(email, dict):
email = EmailTemplate(manager=self._manager, **email)
elif not isinstance(email, EmailTemplate):
raise ValueError
email._update(extra)
return email.as_dict()

def send(self, **extra):
"""Sends email batch.
:return: Information about sent emails.
:rtype: `list`
"""
emails = self.as_dict(**extra)
responses = [self._manager._send_batch_with_template(*batch) for batch in chunks(emails, self.MAX_SIZE)]
return sum(responses, [])


class EmailBatch(Model):
"""Gathers multiple emails in a single batch."""

Expand Down Expand Up @@ -281,6 +320,9 @@ def _send(self, **kwargs):
def _send_with_template(self, **kwargs):
return self.call("POST", "/email/withTemplate/", data=kwargs)

def _send_batch_with_template(self, *email_templates):
return self.call("POST", "/email/batchWithTemplates/", data={"Messages": email_templates})

def _send_batch(self, *emails):
"""Low-level batch send call."""
return self.call("POST", "/email/batch", data=emails)
Expand Down Expand Up @@ -404,6 +446,14 @@ def send_batch(self, *emails, **extra):
"""
return self.EmailBatch(*emails).send(**extra)

def send_template_batch(self, *emails, **extra):
"""Sends an email batch.
:param emails: :py:class:`TemplateEmail` instances or dictionaries
:param extra: dictionary with extra arguments for every message in the batch.
"""
return self.EmailTemplateBatch(*emails).send(**extra)

# NOTE. The following methods are included here to expose better interface without need to import relevant classes.

def Email(
Expand Down Expand Up @@ -499,3 +549,10 @@ def EmailBatch(self, *emails):
:return: :py:class:`EmailBatch`
"""
return EmailBatch(*emails, manager=self)

def EmailTemplateBatch(self, *emails):
"""Constructs :py:class:`EmailTemplateBatch` instance.
:return: :py:class:`EmailTemplateBatch`
"""
return EmailTemplateBatch(*emails, manager=self)
12 changes: 12 additions & 0 deletions test/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -87,11 +87,23 @@ def email(postmark):
return postmark.emails.Email(From="[email protected]", To="[email protected]", TextBody="text")


@pytest.fixture()
def email_template(postmark):
return postmark.emails.EmailTemplate(
From="[email protected]", To="[email protected]", TemplateId=983381, TemplateModel={}
)


@pytest.fixture
def email_batch(postmark, email):
return postmark.emails.EmailBatch(email)


@pytest.fixture
def email_template_batch(postmark, email):
return postmark.emails.EmailTemplateBatch(email)


@pytest.fixture(scope="session")
def template(postmark):
return postmark.templates.get(983381)
Expand Down
42 changes: 42 additions & 0 deletions test/models/test_emails.py
Original file line number Diff line number Diff line change
Expand Up @@ -377,3 +377,45 @@ def test_inline_image(self, email, postmark_request, image, expected):
class TestDelivery:
def test_str(self, delivery_webhook):
assert str(delivery_webhook) == "Delivery to [email protected]"


class TestTemplateBatchSend:
def test_template_email_instance(self, postmark, email_template, postmark_request):
postmark.emails.send_template_batch(email_template)
assert postmark_request.call_args[1]["json"] == {"Messages": (email_template.as_dict(),)}

def test_dict(self, postmark, postmark_request):
template_dict = {
"TemplateId": 983381,
"TemplateModel": {},
"From": "[email protected]",
"To": "[email protected]",
}
expected = {
"TemplateId": 983381,
"TemplateModel": {},
"From": "[email protected]",
"To": "[email protected]",
"Headers": [],
"Attachments": [],
}
postmark.emails.send_template_batch(template_dict)
assert postmark_request.call_args[1]["json"] == {"Messages": (expected,)}

def test_multiple(self, postmark, email_template, postmark_request):
postmark.emails.send_template_batch(email_template, email_template)
assert postmark_request.call_args[1]["json"] == {
"Messages": (
email_template.as_dict(),
email_template.as_dict(),
)
}

def test_invalid(self, postmark):
with pytest.raises(ValueError):
postmark.emails.send_template_batch(object())


class TestEmailTemplateBatch:
def test_len(self, email_template_batch):
assert len(email_template_batch) == 1

0 comments on commit 33bf3b6

Please sign in to comment.