From 17b26d8cec5f7f4f6485db46da60320e7b80e119 Mon Sep 17 00:00:00 2001 From: Gavin Bauer Date: Tue, 20 Jun 2023 13:12:53 -0700 Subject: [PATCH 1/3] Adds logic to manage suppressions - Adds calls to list, add, and delete suppression support - Adds doc pages --- docs/index.rst | 1 + docs/supression.rst | 77 ++++++++++++++++++++++++++++++++++++++++++ src/postmarker/core.py | 25 ++++++++++++++ 3 files changed, 103 insertions(+) create mode 100644 docs/supression.rst diff --git a/docs/index.rst b/docs/index.rst index 8cc0ff2..ccc996e 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -28,6 +28,7 @@ Contents: django tornado spamcheck + suppression webhooks testing reference diff --git a/docs/supression.rst b/docs/supression.rst new file mode 100644 index 0000000..92d5df5 --- /dev/null +++ b/docs/supression.rst @@ -0,0 +1,77 @@ +.. _supression: + +Suppression API +============== + +You can manage Postmark's Suppression lists with a few simple calls: + +To view the current suppression list: +.. code-block:: python + + >>> response = postmark.get_suppressions(stream_id="test") + >>> print(response['Suppressions']) + { + "Suppressions":[ + { + "EmailAddress":"address@wildbit.com", + "SuppressionReason":"ManualSuppression", + "Origin": "Recipient", + "CreatedAt":"2019-12-17T08:58:33-05:00" + }, + { + "EmailAddress":"bounce.address@wildbit.com", + "SuppressionReason":"HardBounce", + "Origin": "Recipient", + "CreatedAt":"2019-12-17T08:58:33-05:00" + }, + { + "EmailAddress":"spam.complaint.address@wildbit.com", + "SuppressionReason":"SpamComplaint", + "Origin": "Recipient", + "CreatedAt":"2019-12-17T08:58:33-05:00" + } + ] + } +You can filter this with "SuppressionReason", "Origin", "todate", "fromdate", and "EmailAddress" lile: +.. code-block:: python + + >>> response = postmark.get_suppressions(stream_id="test", EmailAddress="address@wildbit.com") + >>> print(response['Suppressions']) + { + "Suppressions":[ + { + "EmailAddress":"address@wildbit.com", + "SuppressionReason":"ManualSuppression", + "Origin": "Recipient", + "CreatedAt":"2019-12-17T08:58:33-05:00" + } + ] + } +You can add a new suppression with: +.. code-block:: python + + >>> response = postmark.add_suppressions(stream_id="test", emails=["address@wildbit.com"]) + >>> print(response['Suppressions']) + { + "Suppressions":[ + { + "EmailAddress":"good.address@wildbit.com", + "Status":"Suppressed", + "Message": null + }, + ] + } +You can delete a suppression with: +.. code-block:: python + + >>> response = postmark.delete_suppressions(stream_id="test", emails=["address@wildbit.com"]) + >>> print(response['Suppressions']) + { + "Suppressions":[ + { + "EmailAddress":"address@wildbit.com", + "Status":"Deleted", + "Message": null + } + ] + } diff --git a/src/postmarker/core.py b/src/postmarker/core.py index 39cd7bd..ca1dbb6 100644 --- a/src/postmarker/core.py +++ b/src/postmarker/core.py @@ -116,6 +116,31 @@ def spamcheck(self, dump, options="long"): raise SpamAssassinError(response["message"]) return response + def get_suppressions(self, stream_id, **kwargs): + url = DEFAULT_API + f"/message-streams/{stream_id}/suppressions/dump" + response = self._call("GET", url, **kwargs) + return response + + def add_suppression(self, stream_id, emails): + url = DEFAULT_API + f"/message-streams/{stream_id}/suppressions" + if type(emails) != list: + raise ValueError("emails must be of type list") + data = {'Suppressions': []} + for email in emails: + data['Suppressions'].append({'EmailAddress': email}) + response = self._call("POST", url, "", data) + return response + + def delete_suppression(self, stream_id, emails): + url = DEFAULT_API + f"/message-streams/{stream_id}/suppressions/delete" + if type(emails) != list: + raise ValueError("emails must be of type list") + data = {'Suppressions': []} + for email in emails: + data['Suppressions'].append({'EmailAddress': email}) + response = self._call("POST", url, "", data) + return response + def _call(self, method, root, endpoint, data=None, headers=None, **kwargs): default_headers = {"Accept": "application/json", "User-Agent": USER_AGENT} if headers: From 3b4be64d5d75f8dce78c67f1e253969c830df3f3 Mon Sep 17 00:00:00 2001 From: Gavin Bauer Date: Thu, 18 Jan 2024 12:29:20 -0800 Subject: [PATCH 2/3] Add a suppression object - Add a suppression and suppression_response object to handle responses from the API - Updates _call calls to use endpoints - Dedupes code - Update docs to reflect new returns --- docs/supression.rst | 80 +++++++++------------------ src/postmarker/core.py | 49 +++++++++------- src/postmarker/models/suppressions.py | 33 +++++++++++ 3 files changed, 88 insertions(+), 74 deletions(-) create mode 100644 src/postmarker/models/suppressions.py diff --git a/docs/supression.rst b/docs/supression.rst index 92d5df5..c89f13d 100644 --- a/docs/supression.rst +++ b/docs/supression.rst @@ -9,69 +9,39 @@ To view the current suppression list: .. code-block:: python >>> response = postmark.get_suppressions(stream_id="test") - >>> print(response['Suppressions']) - { - "Suppressions":[ - { - "EmailAddress":"address@wildbit.com", - "SuppressionReason":"ManualSuppression", - "Origin": "Recipient", - "CreatedAt":"2019-12-17T08:58:33-05:00" - }, - { - "EmailAddress":"bounce.address@wildbit.com", - "SuppressionReason":"HardBounce", - "Origin": "Recipient", - "CreatedAt":"2019-12-17T08:58:33-05:00" - }, - { - "EmailAddress":"spam.complaint.address@wildbit.com", - "SuppressionReason":"SpamComplaint", - "Origin": "Recipient", - "CreatedAt":"2019-12-17T08:58:33-05:00" - } - ] - } -You can filter this with "SuppressionReason", "Origin", "todate", "fromdate", and "EmailAddress" lile: + >>> suppressions = (response['Suppressions']) + >>> for suppression in suppressions: + >>> print(suppression.email_address]) + >>> print(" " + suppression.suppression_reason]) + >>> print(" " + suppression.origin]) + >>> print(" " + suppression.created_at]) + address@wildbit.com + ManualSuppression + Recipient + 2019-12-17T08:58:33-05:00 + bounce.address@wilbit.com + HardBounce + Recipient + 2019-12-17T08:58:33-05:00 + spam.complaint.address@wildbit.com + SpamComplaint + Recipient + 2019-12-17T08:58:33-05:00 +You can search for a particular suppression with "emmails", "Origin", "todate", "fromdate", and "EmailAddress" lile: .. code-block:: python >>> response = postmark.get_suppressions(stream_id="test", EmailAddress="address@wildbit.com") - >>> print(response['Suppressions']) - { - "Suppressions":[ - { - "EmailAddress":"address@wildbit.com", - "SuppressionReason":"ManualSuppression", - "Origin": "Recipient", - "CreatedAt":"2019-12-17T08:58:33-05:00" - } - ] - } + >>> print(response[0].email_address + " " + response[0].suppression_reason) + address@wildbit.com ManualSuppression You can add a new suppression with: .. code-block:: python >>> response = postmark.add_suppressions(stream_id="test", emails=["address@wildbit.com"]) - >>> print(response['Suppressions']) - { - "Suppressions":[ - { - "EmailAddress":"good.address@wildbit.com", - "Status":"Suppressed", - "Message": null - }, - ] - } + >>> print(response[0].email_address + " " + response[0].status) + good.address@wildbit.com Suppressed You can delete a suppression with: .. code-block:: python >>> response = postmark.delete_suppressions(stream_id="test", emails=["address@wildbit.com"]) - >>> print(response['Suppressions']) - { - "Suppressions":[ - { - "EmailAddress":"address@wildbit.com", - "Status":"Deleted", - "Message": null - } - ] - } + >>> print(response[0].email_address + " " + response[0].status) + address@wildbit.com Deleted diff --git a/src/postmarker/core.py b/src/postmarker/core.py index ca1dbb6..cbc4df0 100644 --- a/src/postmarker/core.py +++ b/src/postmarker/core.py @@ -14,6 +14,7 @@ from .models.server import ServerManager from .models.stats import StatsManager from .models.status import StatusManager +from .models.suppressions import Suppression, SuppressionResponse from .models.templates import TemplateManager from .models.triggers import TriggersManager from .utils import get_args @@ -88,6 +89,20 @@ def _setup_managers(self): instance = manager_class(self) setattr(self, instance.name, instance) + def _manage_suppression(self, endpoint, emails): + if type(emails) != list: + emails = [emails] + data = {'Suppressions': [{'EmailAddress': email} for email in emails]} + response = self._call("POST", self.root_api_url, endpoint, data) + suppression_response_list = [] + for suppression in response['Suppressions']: + suppression_response_list.append(SuppressionResponse( + email_address=suppression['EmailAddress'], + status=suppression['Status'], + message=suppression['Message'] + )) + return suppression_response_list + @property def session(self): if not hasattr(self, "_session"): @@ -117,29 +132,25 @@ def spamcheck(self, dump, options="long"): return response def get_suppressions(self, stream_id, **kwargs): - url = DEFAULT_API + f"/message-streams/{stream_id}/suppressions/dump" - response = self._call("GET", url, **kwargs) - return response + endpoint = f"/message-streams/{stream_id}/suppressions" + response = self._call("GET", self.root_api_url, endpoint, **kwargs) + suppression_list = [] + for suppression in response['Suppressions']: + suppression_list.append(Suppression( + email_address=suppression['EmailAddress'], + suppression_reason=suppression['SuppressionReason'], + origin=suppression['Origin'], + created_at=suppression['CreatedAt'] + )) + return suppression_list def add_suppression(self, stream_id, emails): - url = DEFAULT_API + f"/message-streams/{stream_id}/suppressions" - if type(emails) != list: - raise ValueError("emails must be of type list") - data = {'Suppressions': []} - for email in emails: - data['Suppressions'].append({'EmailAddress': email}) - response = self._call("POST", url, "", data) - return response + endpoint = f"/message-streams/{stream_id}/suppressions" + return self._manage_suppression(endpoint, emails) def delete_suppression(self, stream_id, emails): - url = DEFAULT_API + f"/message-streams/{stream_id}/suppressions/delete" - if type(emails) != list: - raise ValueError("emails must be of type list") - data = {'Suppressions': []} - for email in emails: - data['Suppressions'].append({'EmailAddress': email}) - response = self._call("POST", url, "", data) - return response + endpoint = f"/message-streams/{stream_id}/suppressions/delete" + return self._manage_suppression(endpoint, emails) def _call(self, method, root, endpoint, data=None, headers=None, **kwargs): default_headers = {"Accept": "application/json", "User-Agent": USER_AGENT} diff --git a/src/postmarker/models/suppressions.py b/src/postmarker/models/suppressions.py new file mode 100644 index 0000000..8628ec6 --- /dev/null +++ b/src/postmarker/models/suppressions.py @@ -0,0 +1,33 @@ +"""Supressions + +Information on suppression lists""" + +from .base import ModelManager, MessageModel + + +class Suppression(MessageModel): + """Suppression model.""" + email_address = None + suppression_reason = None + origin = None + created_at = None + + def __init__(self, email_address=None, suppression_reason=None, origin=None, created_at=None, *args, **kwargs): + super().__init__(*args, **kwargs) + self.email_address = email_address + self.suppression_reason = suppression_reason + self.origin = origin + self.created_at = created_at + + +class SuppressionResponse(MessageModel): + """Suppression request model.""" + email_address = None + status = None + message = None + + def __init__(self, email_address=None, status=None, message=None, *args, **kwargs): + super().__init__(*args, **kwargs) + self.email_address = email_address + self.status = status + self.message = message From 2773f267c2c935374ae145a29f5cd11680da0145 Mon Sep 17 00:00:00 2001 From: Gavin Bauer Date: Thu, 18 Jan 2024 12:53:30 -0800 Subject: [PATCH 3/3] Switches to model manager, updates docs --- docs/supression.rst | 10 +++--- src/postmarker/core.py | 38 ++-------------------- src/postmarker/models/suppressions.py | 46 +++++++++++++++++++++++++++ 3 files changed, 53 insertions(+), 41 deletions(-) diff --git a/docs/supression.rst b/docs/supression.rst index c89f13d..b37dd75 100644 --- a/docs/supression.rst +++ b/docs/supression.rst @@ -8,7 +8,7 @@ You can manage Postmark's Suppression lists with a few simple calls: To view the current suppression list: .. code-block:: python - >>> response = postmark.get_suppressions(stream_id="test") + >>> response = postmark.suppressions.get_suppression(stream_id="test") >>> suppressions = (response['Suppressions']) >>> for suppression in suppressions: >>> print(suppression.email_address]) @@ -27,21 +27,21 @@ To view the current suppression list: SpamComplaint Recipient 2019-12-17T08:58:33-05:00 -You can search for a particular suppression with "emmails", "Origin", "todate", "fromdate", and "EmailAddress" lile: +You can search for a particular suppression with "SuppressionReason", "Origin", "todate", "fromdate", and "EmailAddress" like: .. code-block:: python - >>> response = postmark.get_suppressions(stream_id="test", EmailAddress="address@wildbit.com") + >>> response = postmark.suppressions.get_suppression(stream_id="test", EmailAddress="address@wildbit.com") >>> print(response[0].email_address + " " + response[0].suppression_reason) address@wildbit.com ManualSuppression You can add a new suppression with: .. code-block:: python - >>> response = postmark.add_suppressions(stream_id="test", emails=["address@wildbit.com"]) + >>> response = postmark.suppressions.add(stream_id="test", emails=["address@wildbit.com"]) >>> print(response[0].email_address + " " + response[0].status) good.address@wildbit.com Suppressed You can delete a suppression with: .. code-block:: python - >>> response = postmark.delete_suppressions(stream_id="test", emails=["address@wildbit.com"]) + >>> response = postmark.suppressions.delete(stream_id="test", emails=["address@wildbit.com"]) >>> print(response[0].email_address + " " + response[0].status) address@wildbit.com Deleted diff --git a/src/postmarker/core.py b/src/postmarker/core.py index cbc4df0..61cfbba 100644 --- a/src/postmarker/core.py +++ b/src/postmarker/core.py @@ -14,7 +14,7 @@ from .models.server import ServerManager from .models.stats import StatsManager from .models.status import StatusManager -from .models.suppressions import Suppression, SuppressionResponse +from .models.suppressions import SuppressionManager from .models.templates import TemplateManager from .models.triggers import TriggersManager from .utils import get_args @@ -38,6 +38,7 @@ class PostmarkClient: ServerManager, StatsManager, StatusManager, + SuppressionManager, TemplateManager, TriggersManager, ) @@ -89,20 +90,6 @@ def _setup_managers(self): instance = manager_class(self) setattr(self, instance.name, instance) - def _manage_suppression(self, endpoint, emails): - if type(emails) != list: - emails = [emails] - data = {'Suppressions': [{'EmailAddress': email} for email in emails]} - response = self._call("POST", self.root_api_url, endpoint, data) - suppression_response_list = [] - for suppression in response['Suppressions']: - suppression_response_list.append(SuppressionResponse( - email_address=suppression['EmailAddress'], - status=suppression['Status'], - message=suppression['Message'] - )) - return suppression_response_list - @property def session(self): if not hasattr(self, "_session"): @@ -131,27 +118,6 @@ def spamcheck(self, dump, options="long"): raise SpamAssassinError(response["message"]) return response - def get_suppressions(self, stream_id, **kwargs): - endpoint = f"/message-streams/{stream_id}/suppressions" - response = self._call("GET", self.root_api_url, endpoint, **kwargs) - suppression_list = [] - for suppression in response['Suppressions']: - suppression_list.append(Suppression( - email_address=suppression['EmailAddress'], - suppression_reason=suppression['SuppressionReason'], - origin=suppression['Origin'], - created_at=suppression['CreatedAt'] - )) - return suppression_list - - def add_suppression(self, stream_id, emails): - endpoint = f"/message-streams/{stream_id}/suppressions" - return self._manage_suppression(endpoint, emails) - - def delete_suppression(self, stream_id, emails): - endpoint = f"/message-streams/{stream_id}/suppressions/delete" - return self._manage_suppression(endpoint, emails) - def _call(self, method, root, endpoint, data=None, headers=None, **kwargs): default_headers = {"Accept": "application/json", "User-Agent": USER_AGENT} if headers: diff --git a/src/postmarker/models/suppressions.py b/src/postmarker/models/suppressions.py index 8628ec6..1c41f85 100644 --- a/src/postmarker/models/suppressions.py +++ b/src/postmarker/models/suppressions.py @@ -31,3 +31,49 @@ def __init__(self, email_address=None, status=None, message=None, *args, **kwarg self.email_address = email_address self.status = status self.message = message + + +class SuppressionManager(ModelManager): + name = "suppressions" + model = Suppression + + def _manage_suppression(self, endpoint, emails): + if type(emails) != list: + emails = [emails] + data = {'Suppressions': [{'EmailAddress': email} for email in emails]} + response = self.call("POST", endpoint=endpoint, data=data) + suppression_response_list = [] + for suppression in response['Suppressions']: + suppression_response_list.append(SuppressionResponse( + email_address=suppression['EmailAddress'], + status=suppression['Status'], + message=suppression['Message'] + )) + return suppression_response_list + + def get_suppression(self, stream_id, **kwargs): + params = {} + for key, value in kwargs.items(): + if key in ['EmailAddress', 'SuppressionReason', 'Origin', 'todate', 'fromdate']: + params[key] = value + else: + raise ValueError(f"Invalid parameter: {key}") + endpoint = f"/message-streams/{stream_id}/suppressions" + response = self.call("GET", endpoint=endpoint, params=params) + suppression_list = [] + for suppression in response['Suppressions']: + suppression_list.append(Suppression( + email_address=suppression['EmailAddress'], + suppression_reason=suppression['SuppressionReason'], + origin=suppression['Origin'], + created_at=suppression['CreatedAt'] + )) + return suppression_list + + def add(self, stream_id, emails): + endpoint = f"/message-streams/{stream_id}/suppressions" + return self._manage_suppression(endpoint, emails) + + def delete(self, stream_id, emails): + endpoint = f"/message-streams/{stream_id}/suppressions/delete" + return self._manage_suppression(endpoint, emails)