From 887bd3f39518559d484e28e6f7ab0d47daaa442f Mon Sep 17 00:00:00 2001 From: "KYLEPC\\Kyle" Date: Sun, 7 Oct 2018 09:36:50 -0600 Subject: [PATCH 01/16] Added campaign helper classes These helper classes help generate the correct get/post request information through the `get()` method --- sendgrid/helpers/campaigns/campaign.py | 230 ++++++++++++++++++++++++ sendgrid/helpers/campaigns/campaigns.py | 47 +++++ 2 files changed, 277 insertions(+) create mode 100644 sendgrid/helpers/campaigns/campaign.py create mode 100644 sendgrid/helpers/campaigns/campaigns.py diff --git a/sendgrid/helpers/campaigns/campaign.py b/sendgrid/helpers/campaigns/campaign.py new file mode 100644 index 000000000..39c86bbfa --- /dev/null +++ b/sendgrid/helpers/campaigns/campaign.py @@ -0,0 +1,230 @@ +class Campaign(object): + """Campaign class object for campaign API queries + + Required parameter: + title: str + + Optional parameters: + categories: list(str) + custom_unsubscribe_url: str + editor: str ['code' | 'design'] + html_content: str + ip_pool: str + list_ids: list(int) + plain_content: str + segment_ids: list(int) + sender_id: int + subject: str + suppression_group_id: int + + :param kwargs: List of inputs + """ + def __init__(self, **kwargs): + self._categories = [] + self._custom_unsubscribe_url = None + self._editor = None + self._html_content = None + self._id = None + self._ip_pool = None + self._list_ids = [] + self._plain_content = None + self._segment_ids = [] + self._sender_id = None + self._status = "" + self._subject = None + self._suppression_group_id = None + self._title = "" + + for key, val in kwargs.items(): + if hasattr(self, key): + setattr(self, key, val) + + def copy(self, new_title): + """Creates a copy of the campaign + + :param new_title: Title of new campaign + :type new_title: str + :return: Campaign + """ + new_camp = Campaign(**self.get()) + new_camp.title = new_title + return new_camp + + def delete(self): + return + + def get(self): + """Returns dict suitable for queries""" + body = {"title": self._title} + if self._categories: + body["categories"] = [str(i) for i in self._categories] + if self._custom_unsubscribe_url is not None: + body["custom_unsubscribe_url"] = self._custom_unsubscribe_url + if self._editor is not None: + body["editor"] = self._editor + if self._html_content is not None: + body["html_content"] = self._html_content + if self._ip_pool is not None: + body["ip_pool"] = self._ip_pool + if self._list_ids and all(isinstance(i, int) for i in self._list_ids): + body["list_ids"] = self._list_ids + if self._plain_content: + body["plain_content"] = self._plain_content + if self._segment_ids\ + and all(isinstance(i, int) for i in self._list_ids): + body["segment_ids"] = self._segment_ids + if self._sender_id is not None: + body["sender_id"] = self._sender_id + if self._subject is not None: + body["subject"] = self._subject + if self._suppression_group_id is not None: + body["suppression_group_id"] = self._suppression_group_id + return body + + def patch(self, **kwargs): + """Updates the campaign with + + Optional inputs: + title: str + subject: str + categories: list(str) + html_content: str + plain_content: str + + :param kwargs: Dictionary of optional inputs + :type kwargs: dict + :return: Updated get() dict + """ + if "title" in kwargs: + self.title = kwargs["title"] + if "subject" in kwargs: + self.subject = kwargs["subject"] + if "categories" in kwargs: + self.categories = kwargs["categories"] + if "html_content" in kwargs: + self.html_content = kwargs["html_content"] + if "plain_content" in kwargs: + self.plain_content = kwargs["plain_content"] + self.get() + + def send_test(self, to): + return + + def unschedule(self): + return + + @property + def categories(self): + return self._categories + + @categories.setter + def categories(self, value): + self._categories = value + + @property + def custom_unsubscribe_url(self): + return self._custom_unsubscribe_url + + @custom_unsubscribe_url.setter + def custom_unsubscribe_url(self, value): + self._custom_unsubscribe_url = value + + @property + def editor(self): + return self._editor + + @editor.setter + def editor(self, value): + value = str(value).lower() + if value in ["code", "design"]: + self._editor = value + + @property + def html_content(self): + return self._html_content + + @html_content.setter + def html_content(self, value): + self._html_content = value + + @property + def id(self): + return self._id + + @id.setter + def id(self, value): + self._id = value + + @property + def ip_pool(self): + return self._ip_pool + + @ip_pool.setter + def ip_pool(self, value): + self._ip_pool = value + + @property + def list_ids(self): + return self._list_ids + + @list_ids.setter + def list_ids(self, value): + self._list_ids = value + + @property + def plain_content(self): + return self._plain_content + + @plain_content.setter + def plain_content(self, value): + self._plain_content = value + + @property + def segment_ids(self): + return self._segment_ids + + @segment_ids.setter + def segment_ids(self, value): + self._segment_ids = value + + @property + def sender_id(self): + return self._sender_id + + @sender_id.setter + def sender_id(self, value): + self._sender_id = value + + @property + def status(self): + return self._status + + @status.setter + def status(self, value): + self._status = value + + @property + def subject(self): + return self._subject + + @subject.setter + def subject(self, value): + if isinstance(value, str) or isinstance(value, type(None)): + self._subject = value + + @property + def suppression_group_id(self): + return self._suppression_group_id + + @suppression_group_id.setter + def suppression_group_id(self, value): + self._suppression_group_id = value + + @property + def title(self): + return self._title + + @title.setter + def title(self, value): + if value: + self._title = str(value) diff --git a/sendgrid/helpers/campaigns/campaigns.py b/sendgrid/helpers/campaigns/campaigns.py new file mode 100644 index 000000000..2f76b83e4 --- /dev/null +++ b/sendgrid/helpers/campaigns/campaigns.py @@ -0,0 +1,47 @@ +class Campaigns(object): + def __init__(self, limit=10, offset=0): + self._limit = limit + self._offset = offset + + def get(self, limit=None, offset=None): + """Get all available campaigns + + Stores all Campaigns in 'campaigns' property and retrieves/populates + campaign IDs, which are stored in 'ids' property. + + :param limit: Number of results to receive + :type limit: int + :param offset: Index of first campaign to receive + :type offset: int + :return: List of Campaigns + :rtype: list(Campaign) + """ + params = {} + if limit is None: + limit = self._limit + if isinstance(limit, int): + params["limit"] = self._limit + + if offset is None: + offset = self._offset + if isinstance(offset, int): + params["offset"] = offset + + return params + + @property + def limit(self): + return self._limit + + @limit.setter + def limit(self, value): + self._limit = int(value) + + @property + def offset(self): + return self._offset + + @offset.setter + def offset(self, value): + self._offset = int(value) + From d4c8d6545731465976663213da3776bd237ba602 Mon Sep 17 00:00:00 2001 From: "KYLEPC\\Kyle" Date: Sun, 7 Oct 2018 09:37:36 -0600 Subject: [PATCH 02/16] Added schedule class to assist in scheduling campaigns A unix timestamp, datetime, or specified year/month/day/etc. are accepted and will all parse to a unix timestamp --- sendgrid/helpers/campaigns/schedule.py | 132 +++++++++++++++++++++++++ 1 file changed, 132 insertions(+) create mode 100644 sendgrid/helpers/campaigns/schedule.py diff --git a/sendgrid/helpers/campaigns/schedule.py b/sendgrid/helpers/campaigns/schedule.py new file mode 100644 index 000000000..0cc1ecfab --- /dev/null +++ b/sendgrid/helpers/campaigns/schedule.py @@ -0,0 +1,132 @@ +from datetime import datetime, timedelta + + +class Schedule(object): + """Get schedule-accepted Unix time stamp + + Python datetime.datetime or Unix timestamp are both accepted. + If send_at is None, the value will be created with other inputs + + :param send_at: Unix timestamp + :type send_at: int, datetime.datetime + :param year: Scheduled year (current+) + :type year: int + :param month: Scheduled year [1..12] + :type month: int + :param day: Scheduled year [1..31] + :type day: int + :param hour: Scheduled hour [0..23] + :type hour: int + :param minute: Scheduled minute [0..59] + :return: `send_at`=Unix timestamp dict + :rtype: dict + """ + def __init__(self, send_at=None, year=None, month=None, + day=None, hour=None, minute=None): + + def_dt = datetime.now() + timedelta(days=1) + self._minute = 0 + self._hour = 0 + self._day = def_dt.day + self._month = def_dt.month + self._year = def_dt.year + + if send_at is not None: + if isinstance(send_at, datetime): + self._timestamp = self.convert_datetime(send_at) + else: + self._timestamp = int(send_at) + self.parse_timestamp() + + if send_at is None: + if year is not None: + self._year = year + if month is not None: + self._month = month + if day is not None: + self._day = day + if hour is not None: + self._hour = hour + if minute is not None: + self._minute = minute + self._timestamp = self.convert_datetime( + datetime(self.year, self.month, self.day, self.hour, self.minute) + ) + + @staticmethod + def convert_datetime(dt): + """Converts a datetime to Unix time stamp + + :param dt: Datetime to parse + :type dt: datetime.datetime + :return: Unix timestamp + :rtype: int + """ + return int((dt - datetime(1970, 1, 1)). total_seconds()) + + def get(self): + return {"send_at": self._timestamp} + + def parse_timestamp(self, timestamp=None): + """Parses the Schedule object's timestamp into year, month, etc. + + :param timestamp: Unix timestamp to parse + :type timestamp: int + :return: Parsed datetime object + :rtype: datetime.datetime + """ + dt = datetime(1970, 1, 1) + timedelta(seconds=self._timestamp) + self._year = dt.year + self._month = dt.month + self._day = dt.day + self._hour = dt.hour + self._minute = dt.minute + return dt + + @property + def day(self): + return self._day + + @day.setter + def day(self, value): + self._day = value + + @property + def hour(self): + return self._hour + + @hour.setter + def hour(self, value): + self._hour = value + + @property + def minute(self): + return self._minute + + @minute.setter + def minute(self, value): + self._minute = value + + @property + def month(self): + return self._month + + @month.setter + def month(self, value): + self._month = value + + @property + def timestamp(self): + return self._timestamp + + @timestamp.setter + def timestamp(self, value): + self._timestamp = int(value) + + @property + def year(self): + return self._year + + @year.setter + def year(self, value): + self._year = value From 84cbaaa7fb7332482f7413759762cf466c25eea1 Mon Sep 17 00:00:00 2001 From: "KYLEPC\\Kyle" Date: Sun, 7 Oct 2018 09:38:34 -0600 Subject: [PATCH 03/16] Added assist functions to build, build and send, or build and schedule These methods will allow optional notify Mail objects to be send on completion --- sendgrid/helpers/campaigns/__init__.py | 6 ++++ sendgrid/helpers/campaigns/campaign_build.py | 23 +++++++++++++ .../campaigns/campaign_build_scheduled.py | 32 +++++++++++++++++++ .../helpers/campaigns/campaign_build_send.py | 23 +++++++++++++ 4 files changed, 84 insertions(+) create mode 100644 sendgrid/helpers/campaigns/__init__.py create mode 100644 sendgrid/helpers/campaigns/campaign_build.py create mode 100644 sendgrid/helpers/campaigns/campaign_build_scheduled.py create mode 100644 sendgrid/helpers/campaigns/campaign_build_send.py diff --git a/sendgrid/helpers/campaigns/__init__.py b/sendgrid/helpers/campaigns/__init__.py new file mode 100644 index 000000000..b1df7a8be --- /dev/null +++ b/sendgrid/helpers/campaigns/__init__.py @@ -0,0 +1,6 @@ +from .campaign import Campaign +from .campaigns import Campaigns +from .schedule import Schedule +from .campaign_build import campaign_build +from .campaign_build_send import campaign_build_send +from .campaign_build_scheduled import campaign_build_scheduled diff --git a/sendgrid/helpers/campaigns/campaign_build.py b/sendgrid/helpers/campaigns/campaign_build.py new file mode 100644 index 000000000..87d40e34a --- /dev/null +++ b/sendgrid/helpers/campaigns/campaign_build.py @@ -0,0 +1,23 @@ +import json + + +def campaign_build(api_client, campaign, notify=None): + """Build a Campaign via the SendGrid API + + :param api_client: SendGrid API Client + :type api_client: sendgrid.SendGridAPIClient + :param campaign: Campaign object to build + :type campaign: sendgrid.helpers.campaigns.Campaign + :param notify: Mail objects to send on completion + :type notify: list,sendgrid.helpers.mail.mail.Mail + :return: Campaid.id for newly created Campaign + """ + response = api_client.client.campaigns.post(request_body=campaign.get()) + camp_id = json.loads(response.body.decode())["id"] + if notify is not None: + if isinstance(notify, list): + for n in notify: + api_client.client.mail.send.post(request_body=n.get()) + else: + api_client.client.mail.send.post(request_body=notify.get()) + return camp_id diff --git a/sendgrid/helpers/campaigns/campaign_build_scheduled.py b/sendgrid/helpers/campaigns/campaign_build_scheduled.py new file mode 100644 index 000000000..971567cfa --- /dev/null +++ b/sendgrid/helpers/campaigns/campaign_build_scheduled.py @@ -0,0 +1,32 @@ +from .campaign_build import campaign_build + + +def campaign_build_scheduled(api_client, campaign, schedule, notify=None): + """Builds a campaign and schedules for release + + See sendgrid.helpers.campaigns.campaign.get_schedule for kwargs + + :param api_client: SendGrid API Client + :type api_client: sendgrid.SendGridAPIClient + :param campaign: Campaign object to build + :type campaign: sendgrid.helpers.campaigns.Campaign + :param notify: Mail objects to send on completion + :type notify: list,sendgrid.helpers.mail.mail.Mail + :param schedule: Schedule object to get schedule body + :type schedule: sendgrid.helpers.campaigns.schedule.Schedule + :param kwargs: sendgrid.helpers.campaigns.campaign.get_schedule args + :return: + """ + c_id = campaign_build(api_client, campaign) + print(schedule.get()) + x = getattr(api_client.client.campaigns, str(c_id)).schedules.post( + request_body=schedule.get() + ) + print(x) + if notify is not None: + if isinstance(notify, list): + for n in notify: + api_client.client.mail.send.post(request_body=n.get()) + else: + api_client.client.mail.send.post(request_body=notify.get()) + return diff --git a/sendgrid/helpers/campaigns/campaign_build_send.py b/sendgrid/helpers/campaigns/campaign_build_send.py new file mode 100644 index 000000000..6e965b5ed --- /dev/null +++ b/sendgrid/helpers/campaigns/campaign_build_send.py @@ -0,0 +1,23 @@ +from .campaign_build import campaign_build + + +def campaign_build_send(api_client, campaign, notify=None): + """Builds a campaign and sends immediately + + :param api_client: SendGrid API Client + :type api_client: sendgrid.SendGridAPIClient + :param campaign: Campaign object to build + :type campaign: sendgrid.helpers.campaigns.Campaign + :param notify: Mail objects to send on completion + :type notify: list,sendgrid.helpers.mail.mail.Mail + :return: + """ + c_id = campaign_build(api_client, campaign) + getattr(api_client.client.campaigns, str(c_id)).schedules.now.post() + if notify is not None: + if isinstance(notify, list): + for n in notify: + api_client.client.mail.send.post(request_body=n.get()) + else: + api_client.client.mail.send.post(request_body=notify.get()) + return From d4472a70b493fd9aa5e97f51844c07e37fa50619 Mon Sep 17 00:00:00 2001 From: "KYLEPC\\Kyle" Date: Sun, 7 Oct 2018 09:44:38 -0600 Subject: [PATCH 04/16] Added test cases for campaigns --- test/test_campaigns.py | 41 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 test/test_campaigns.py diff --git a/test/test_campaigns.py b/test/test_campaigns.py new file mode 100644 index 000000000..e9863abcf --- /dev/null +++ b/test/test_campaigns.py @@ -0,0 +1,41 @@ +# -*- coding: utf-8 -*- + +from datetime import datetime +from sendgrid.helpers.campaigns import * +try: + import unittest2 as unittest +except ImportError: + import unittest + + +class TestCampaignObject(unittest.TestCase): + + def test_set_title(self): + c_name = "Test Campaign" + camp = Campaign(title=c_name) + self.assertEqual(camp.title, "Test Campaign") + + +class TestScheduleObject(unittest.TestCase): + + def test_components_to_timestamp(self): + schedule = Schedule(year=2018, month=12, day=1, hour=8, minute=23) + unix_timestamp = (datetime(2018, 12, 1, 8, 23) - datetime(1970, 1, 1)).total_seconds() + self.assertEqual(schedule.timestamp, unix_timestamp) + + def test_timestamp_to_dt(self): + schedule = Schedule(send_at=(datetime(2018, 12, 1, 8, 23) - datetime(1970, 1, 1)).total_seconds()) + self.assertEqual(schedule.year, 2018) + self.assertEqual(schedule.month, 12) + self.assertEqual(schedule.day, 1) + self.assertEqual(schedule.hour, 8) + self.assertEqual(schedule.minute, 23) + + def test_dt_to_timestamp(self): + schedule = Schedule(datetime(2018, 12, 1, 8, 23)) + unix_timestamp = (datetime(2018, 12, 1, 8, 23) - datetime(1970, 1, 1)).total_seconds() + self.assertEqual(schedule.timestamp, unix_timestamp) + + +if __name__ == "__main__": + unittest.main() From 4dae80f61b3c4662ca2da48eac4b92b78256a5f7 Mon Sep 17 00:00:00 2001 From: "KYLEPC\\Kyle" Date: Sun, 7 Oct 2018 09:45:43 -0600 Subject: [PATCH 05/16] Added examples for campaigns --- examples/helpers/campaigns/campaigns.py | 74 +++++++++++++++++++++++++ 1 file changed, 74 insertions(+) create mode 100644 examples/helpers/campaigns/campaigns.py diff --git a/examples/helpers/campaigns/campaigns.py b/examples/helpers/campaigns/campaigns.py new file mode 100644 index 000000000..ac693da2a --- /dev/null +++ b/examples/helpers/campaigns/campaigns.py @@ -0,0 +1,74 @@ +import json +from sendgrid.helpers.campaigns import * +from sendgrid import * + +# NOTE: you will need move this file to the root directory of this project to execute properly. + +# Assumes you set your environment variable: +# https://github.com/sendgrid/sendgrid-python/blob/master/TROUBLESHOOTING.md#environment-variables-and-your-sendgrid-api-key +SG = SendGridAPIClient(apikey=os.environ.get('SENDGRID_API_KEY')) + +def pprint_json(json_raw): + print(json.dumps(json.loads(json_raw), indent=4, sort_keys=True)) + + +def print_campaigns(): + response = sg.client.categories.campaigns.get(query_params=stats_params) + print(response.status_code) + print(response.headers) + pprint_json(response.body) + + +def get_campaigns(): + """Get Campaign objects""" + campaigns = [] + response = SG.client.campaigns.get() + for camp in json.loads(response.body.decode())["result"]: + campaigns.append(Campaign(**camp)) + return campaigns + + +def create_campaign(call_func=False): + camp_settings = { + "title": "Test Campaign 1", + "categories": ["test", "example"], + "html_content": "New Edition!

New edition is now live!

", + } + camp = Campaign(**camp_settings) + + if call_func: + campaign_build(SG, camp) + else: + SG.client.campaigns.post(request_body=camp.get()) + + +def schedule_campaign(): + """Schedule the first campaign retrieved""" + # campaigns = [] + + campaigns = Campaigns(offset=0, limit=2) + response = SG.client.campaigns.get(query_params=campaigns.get()) + for camp in json.loads(response.body.decode())["result"]: + print(schedule.get()) + print(camp["title"]) + c_response = getattr(SG.client.campaigns, str(camp["id"])).schedules.get() + pprint_json(c_response.body.decode()) + + schedule = Schedule(year=2018, month=12, day=1, hour=8, minute=23) + c_response = getattr(SG.client.campaigns, str(camp["id"])).schedules.post( + request_body=schedule.get() + ) + pprint_json(c_response.body.decode()) + + c_response = getattr(SG.client.campaigns, str(camp["id"])).schedules.get() + pprint_json(c_response.body.decode()) + break + + +def main(): + get_campaigns() + create_campaign() + + +if __name__ == "__main__": + main() From 791f89e32c6469ff4ad78310b25d3841b5eaf4c3 Mon Sep 17 00:00:00 2001 From: "KYLEPC\\Kyle" Date: Sun, 7 Oct 2018 09:51:32 -0600 Subject: [PATCH 06/16] code cleanup --- examples/helpers/campaigns/campaigns.py | 2 ++ sendgrid/helpers/campaigns/campaign_build.py | 3 ++- sendgrid/helpers/campaigns/campaign_build_scheduled.py | 10 ++++------ sendgrid/helpers/campaigns/campaign_build_send.py | 5 +++-- sendgrid/helpers/campaigns/campaigns.py | 1 - sendgrid/helpers/campaigns/schedule.py | 4 +--- 6 files changed, 12 insertions(+), 13 deletions(-) diff --git a/examples/helpers/campaigns/campaigns.py b/examples/helpers/campaigns/campaigns.py index ac693da2a..41a408c86 100644 --- a/examples/helpers/campaigns/campaigns.py +++ b/examples/helpers/campaigns/campaigns.py @@ -1,4 +1,5 @@ import json +import os from sendgrid.helpers.campaigns import * from sendgrid import * @@ -8,6 +9,7 @@ # https://github.com/sendgrid/sendgrid-python/blob/master/TROUBLESHOOTING.md#environment-variables-and-your-sendgrid-api-key SG = SendGridAPIClient(apikey=os.environ.get('SENDGRID_API_KEY')) + def pprint_json(json_raw): print(json.dumps(json.loads(json_raw), indent=4, sort_keys=True)) diff --git a/sendgrid/helpers/campaigns/campaign_build.py b/sendgrid/helpers/campaigns/campaign_build.py index 87d40e34a..8f7b08fb6 100644 --- a/sendgrid/helpers/campaigns/campaign_build.py +++ b/sendgrid/helpers/campaigns/campaign_build.py @@ -10,7 +10,8 @@ def campaign_build(api_client, campaign, notify=None): :type campaign: sendgrid.helpers.campaigns.Campaign :param notify: Mail objects to send on completion :type notify: list,sendgrid.helpers.mail.mail.Mail - :return: Campaid.id for newly created Campaign + :return: Campaign.id for newly created Campaign + :rtype: int """ response = api_client.client.campaigns.post(request_body=campaign.get()) camp_id = json.loads(response.body.decode())["id"] diff --git a/sendgrid/helpers/campaigns/campaign_build_scheduled.py b/sendgrid/helpers/campaigns/campaign_build_scheduled.py index 971567cfa..86ecb71bd 100644 --- a/sendgrid/helpers/campaigns/campaign_build_scheduled.py +++ b/sendgrid/helpers/campaigns/campaign_build_scheduled.py @@ -14,19 +14,17 @@ def campaign_build_scheduled(api_client, campaign, schedule, notify=None): :type notify: list,sendgrid.helpers.mail.mail.Mail :param schedule: Schedule object to get schedule body :type schedule: sendgrid.helpers.campaigns.schedule.Schedule - :param kwargs: sendgrid.helpers.campaigns.campaign.get_schedule args - :return: + :return: ID of created campaign + :rtype: int """ c_id = campaign_build(api_client, campaign) - print(schedule.get()) - x = getattr(api_client.client.campaigns, str(c_id)).schedules.post( + getattr(api_client.client.campaigns, str(c_id)).schedules.post( request_body=schedule.get() ) - print(x) if notify is not None: if isinstance(notify, list): for n in notify: api_client.client.mail.send.post(request_body=n.get()) else: api_client.client.mail.send.post(request_body=notify.get()) - return + return c_id diff --git a/sendgrid/helpers/campaigns/campaign_build_send.py b/sendgrid/helpers/campaigns/campaign_build_send.py index 6e965b5ed..3a86764c0 100644 --- a/sendgrid/helpers/campaigns/campaign_build_send.py +++ b/sendgrid/helpers/campaigns/campaign_build_send.py @@ -10,7 +10,8 @@ def campaign_build_send(api_client, campaign, notify=None): :type campaign: sendgrid.helpers.campaigns.Campaign :param notify: Mail objects to send on completion :type notify: list,sendgrid.helpers.mail.mail.Mail - :return: + :return: ID of created campaign + :rtype: int """ c_id = campaign_build(api_client, campaign) getattr(api_client.client.campaigns, str(c_id)).schedules.now.post() @@ -20,4 +21,4 @@ def campaign_build_send(api_client, campaign, notify=None): api_client.client.mail.send.post(request_body=n.get()) else: api_client.client.mail.send.post(request_body=notify.get()) - return + return c_id diff --git a/sendgrid/helpers/campaigns/campaigns.py b/sendgrid/helpers/campaigns/campaigns.py index 2f76b83e4..0056473a1 100644 --- a/sendgrid/helpers/campaigns/campaigns.py +++ b/sendgrid/helpers/campaigns/campaigns.py @@ -44,4 +44,3 @@ def offset(self): @offset.setter def offset(self, value): self._offset = int(value) - diff --git a/sendgrid/helpers/campaigns/schedule.py b/sendgrid/helpers/campaigns/schedule.py index 0cc1ecfab..cb19aea0d 100644 --- a/sendgrid/helpers/campaigns/schedule.py +++ b/sendgrid/helpers/campaigns/schedule.py @@ -67,11 +67,9 @@ def convert_datetime(dt): def get(self): return {"send_at": self._timestamp} - def parse_timestamp(self, timestamp=None): + def parse_timestamp(self): """Parses the Schedule object's timestamp into year, month, etc. - :param timestamp: Unix timestamp to parse - :type timestamp: int :return: Parsed datetime object :rtype: datetime.datetime """ From 3e9e6295268fde6d5ab5ab18f646a78c0449d4dd Mon Sep 17 00:00:00 2001 From: "KYLEPC\\Kyle" Date: Sun, 7 Oct 2018 10:09:37 -0600 Subject: [PATCH 07/16] Added use case for campaign helpers --- use_cases/campaign_helpers.md | 39 +++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 use_cases/campaign_helpers.md diff --git a/use_cases/campaign_helpers.md b/use_cases/campaign_helpers.md new file mode 100644 index 000000000..fced8b236 --- /dev/null +++ b/use_cases/campaign_helpers.md @@ -0,0 +1,39 @@ +# Campaign Helper Usage + +Campaigns can be created using helper classes, then used to generate requests to the SendGrid API. + +```python +import os +from sendgrid import * +from sendgrind.helpers.campaigns import * + +SG = SendGridAPIClient(apikey=os.environ.get('SENDGRID_API_KEY')) + +data = { + "categories": [ + "summer line" + ], + "html_content": "

Check out our summer line!

", + "plain_content": "Check out our summer line!", + "subject": "New Products for Summer!", + "title": "May Newsletter" +} + +camp = Campaign(**data) + +# Use the build helper +campaign_id = campaign_build(SG, camp) + +# OR use the client +response = SG.client.campaigns.post(request_body=camp.get()) +print(response.status_code) +print(response.body) +print(response.headers) + + +# Schedule the campaign +schedule = Schedule(year=2018, month=12, day=1, hour=11, minute=59) +response = getattr(SG.client.campaigns, campaign_id).schedules.post( + request_body=schedule.get() +) +``` \ No newline at end of file From d977f532b1ec8e17375492694c4add1de9c8e3fd Mon Sep 17 00:00:00 2001 From: "KYLEPC\\Kyle" Date: Sun, 7 Oct 2018 10:24:56 -0600 Subject: [PATCH 08/16] Changed total_seconds to seconds for 2.6 functionality --- sendgrid/helpers/campaigns/schedule.py | 2 +- test/test_campaigns.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/sendgrid/helpers/campaigns/schedule.py b/sendgrid/helpers/campaigns/schedule.py index cb19aea0d..4548d8c6f 100644 --- a/sendgrid/helpers/campaigns/schedule.py +++ b/sendgrid/helpers/campaigns/schedule.py @@ -62,7 +62,7 @@ def convert_datetime(dt): :return: Unix timestamp :rtype: int """ - return int((dt - datetime(1970, 1, 1)). total_seconds()) + return int((dt - datetime(1970, 1, 1)).seconds) def get(self): return {"send_at": self._timestamp} diff --git a/test/test_campaigns.py b/test/test_campaigns.py index e9863abcf..ecaa30b52 100644 --- a/test/test_campaigns.py +++ b/test/test_campaigns.py @@ -20,11 +20,11 @@ class TestScheduleObject(unittest.TestCase): def test_components_to_timestamp(self): schedule = Schedule(year=2018, month=12, day=1, hour=8, minute=23) - unix_timestamp = (datetime(2018, 12, 1, 8, 23) - datetime(1970, 1, 1)).total_seconds() + unix_timestamp = int((datetime(2018, 12, 1, 8, 23) - datetime(1970, 1, 1)).seconds) self.assertEqual(schedule.timestamp, unix_timestamp) def test_timestamp_to_dt(self): - schedule = Schedule(send_at=(datetime(2018, 12, 1, 8, 23) - datetime(1970, 1, 1)).total_seconds()) + schedule = Schedule(send_at=int((datetime(2018, 12, 1, 8, 23) - datetime(1970, 1, 1)).seconds)) self.assertEqual(schedule.year, 2018) self.assertEqual(schedule.month, 12) self.assertEqual(schedule.day, 1) From d2f0e34821791813772fd81760d8425460194cad Mon Sep 17 00:00:00 2001 From: "KYLEPC\\Kyle" Date: Sun, 7 Oct 2018 10:33:29 -0600 Subject: [PATCH 09/16] Python 2.6 compatibility for unix timestamps datetime.timedelta does not have a "total_seconds" until python 2.7. By parsing seconds and days from the timedelta, the same can be achieved --- sendgrid/helpers/campaigns/schedule.py | 3 ++- test/test_campaigns.py | 7 +++++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/sendgrid/helpers/campaigns/schedule.py b/sendgrid/helpers/campaigns/schedule.py index 4548d8c6f..b4594e1c9 100644 --- a/sendgrid/helpers/campaigns/schedule.py +++ b/sendgrid/helpers/campaigns/schedule.py @@ -62,7 +62,8 @@ def convert_datetime(dt): :return: Unix timestamp :rtype: int """ - return int((dt - datetime(1970, 1, 1)).seconds) + t_delta = dt - datetime(1970, 1, 1) + return t_delta.seconds + t_delta.days * 86400 def get(self): return {"send_at": self._timestamp} diff --git a/test/test_campaigns.py b/test/test_campaigns.py index ecaa30b52..14a6b55cd 100644 --- a/test/test_campaigns.py +++ b/test/test_campaigns.py @@ -20,11 +20,14 @@ class TestScheduleObject(unittest.TestCase): def test_components_to_timestamp(self): schedule = Schedule(year=2018, month=12, day=1, hour=8, minute=23) - unix_timestamp = int((datetime(2018, 12, 1, 8, 23) - datetime(1970, 1, 1)).seconds) + t_delta = datetime(2018, 12, 1, 8, 23) - datetime(1970, 1, 1) + unix_timestamp = t_delta.seconds + t_delta.days * 86400 self.assertEqual(schedule.timestamp, unix_timestamp) def test_timestamp_to_dt(self): - schedule = Schedule(send_at=int((datetime(2018, 12, 1, 8, 23) - datetime(1970, 1, 1)).seconds)) + td = datetime(2018, 12, 1, 8, 23) - datetime(1970, 1, 1) + timestamp = td.seconds + td.days * 86400 + schedule = Schedule(send_at=timestamp) self.assertEqual(schedule.year, 2018) self.assertEqual(schedule.month, 12) self.assertEqual(schedule.day, 1) From 5475d765f319ac1e7397215c5dd6f10ee8b23ff5 Mon Sep 17 00:00:00 2001 From: "KYLEPC\\Kyle" Date: Sun, 7 Oct 2018 10:52:51 -0600 Subject: [PATCH 10/16] Properly return get() after patching a campaign --- sendgrid/helpers/campaigns/campaign.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sendgrid/helpers/campaigns/campaign.py b/sendgrid/helpers/campaigns/campaign.py index 39c86bbfa..cb23addba 100644 --- a/sendgrid/helpers/campaigns/campaign.py +++ b/sendgrid/helpers/campaigns/campaign.py @@ -105,7 +105,7 @@ def patch(self, **kwargs): self.html_content = kwargs["html_content"] if "plain_content" in kwargs: self.plain_content = kwargs["plain_content"] - self.get() + return self.get() def send_test(self, to): return From 1c0bde75a382d732ec1a09a73b2f6806a59ab037 Mon Sep 17 00:00:00 2001 From: "KYLEPC\\Kyle" Date: Sun, 7 Oct 2018 10:53:05 -0600 Subject: [PATCH 11/16] Expanded test cases --- test/test_campaigns.py | 44 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/test/test_campaigns.py b/test/test_campaigns.py index 14a6b55cd..2a96dc536 100644 --- a/test/test_campaigns.py +++ b/test/test_campaigns.py @@ -15,6 +15,50 @@ def test_set_title(self): camp = Campaign(title=c_name) self.assertEqual(camp.title, "Test Campaign") + def test_campaign_get(self): + data = { + "categories": [ + "summer line" + ], + "html_content": "

Check out our summer line!

", + "plain_content": "Check out our summer line!", + "subject": "New Products for Summer!", + "title": "May Newsletter" + } + camp = Campaign(**data) + self.assertEqual(camp.get(), data) + + def test_campaign_patch(self): + data = { + "categories": [ + "summer line" + ], + "html_content": "

Check out our summer line!

", + "plain_content": "Check out our summer line!", + "subject": "New Products for Summer!", + "title": "May Newsletter" + } + camp = Campaign(**data) + new_title = "New Title" + camp.patch(title=new_title) + self.assertEqual(camp.title, new_title) + + +class TestCampaignsObject(unittest.TestCase): + + def test_campaigns_get(self): + lim = 5 + offset = 20 + camps = Campaigns(offset=offset, limit=lim) + self.assertEqual(camps.get(), {"offset": offset, "limit": lim}) + + def test_campaigns_props(self): + lim = 5 + offset = 20 + camps = Campaigns(offset=offset, limit=lim) + self.assertEqual(camps.offset, offset) + self.assertEqual(camps.limit, lim) + class TestScheduleObject(unittest.TestCase): From 53b39e513575ef9bc02e2d598c65ff2adf7095c6 Mon Sep 17 00:00:00 2001 From: "KYLEPC\\Kyle" Date: Sun, 7 Oct 2018 11:00:24 -0600 Subject: [PATCH 12/16] Updated test for 2.6 compat --- test/test_campaigns.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/test_campaigns.py b/test/test_campaigns.py index 2a96dc536..c4258af54 100644 --- a/test/test_campaigns.py +++ b/test/test_campaigns.py @@ -80,7 +80,8 @@ def test_timestamp_to_dt(self): def test_dt_to_timestamp(self): schedule = Schedule(datetime(2018, 12, 1, 8, 23)) - unix_timestamp = (datetime(2018, 12, 1, 8, 23) - datetime(1970, 1, 1)).total_seconds() + t_delta = datetime(2018, 12, 1, 8, 23) - datetime(1970, 1, 1) + unix_timestamp = t_delta.seconds + t_delta.days * 86400 self.assertEqual(schedule.timestamp, unix_timestamp) From 39c854c4540c945082fe641a36e66806d0f822cf Mon Sep 17 00:00:00 2001 From: "KYLEPC\\Kyle" Date: Sun, 7 Oct 2018 19:49:10 -0600 Subject: [PATCH 13/16] Corrected patch dict suitable for API endpoint --- sendgrid/helpers/campaigns/campaign.py | 68 +++++++++++++------------- 1 file changed, 34 insertions(+), 34 deletions(-) diff --git a/sendgrid/helpers/campaigns/campaign.py b/sendgrid/helpers/campaigns/campaign.py index cb23addba..a8511ae4a 100644 --- a/sendgrid/helpers/campaigns/campaign.py +++ b/sendgrid/helpers/campaigns/campaign.py @@ -50,37 +50,43 @@ def copy(self, new_title): new_camp.title = new_title return new_camp - def delete(self): - return - def get(self): """Returns dict suitable for queries""" - body = {"title": self._title} - if self._categories: - body["categories"] = [str(i) for i in self._categories] - if self._custom_unsubscribe_url is not None: - body["custom_unsubscribe_url"] = self._custom_unsubscribe_url - if self._editor is not None: - body["editor"] = self._editor - if self._html_content is not None: - body["html_content"] = self._html_content - if self._ip_pool is not None: - body["ip_pool"] = self._ip_pool - if self._list_ids and all(isinstance(i, int) for i in self._list_ids): - body["list_ids"] = self._list_ids - if self._plain_content: - body["plain_content"] = self._plain_content - if self._segment_ids\ - and all(isinstance(i, int) for i in self._list_ids): - body["segment_ids"] = self._segment_ids - if self._sender_id is not None: - body["sender_id"] = self._sender_id - if self._subject is not None: - body["subject"] = self._subject - if self._suppression_group_id is not None: - body["suppression_group_id"] = self._suppression_group_id + body = {"title": self.title} + if self.categories: + body["categories"] = [str(i) for i in self.categories] + if self.custom_unsubscribe_url is not None: + body["custom_unsubscribe_url"] = self.custom_unsubscribe_url + if self.editor is not None: + body["editor"] = self.editor + if self.html_content is not None: + body["html_content"] = self.html_content + if self.ip_pool is not None: + body["ip_pool"] = self.ip_pool + if self.list_ids and all(isinstance(i, int) for i in self.list_ids): + body["list_ids"] = self.list_ids + if self.plain_content: + body["plain_content"] = self.plain_content + if self.segment_ids\ + and all(isinstance(i, int) for i in self.segment_ids): + body["segment_ids"] = self.segment_ids + if self.sender_id is not None: + body["sender_id"] = self.sender_id + if self.subject is not None: + body["subject"] = self.subject + if self.suppression_group_id is not None: + body["suppression_group_id"] = self.suppression_group_id return body + def get_patch(self): + return { + "title": self.title, + "subject": self.subject, + "categories": self.categories, + "html_content": self.html_content, + "plain_content": self.plain_content + } + def patch(self, **kwargs): """Updates the campaign with @@ -105,13 +111,7 @@ def patch(self, **kwargs): self.html_content = kwargs["html_content"] if "plain_content" in kwargs: self.plain_content = kwargs["plain_content"] - return self.get() - - def send_test(self, to): - return - - def unschedule(self): - return + return self.get_patch() @property def categories(self): From 57d8881871c95190ae293d9019b54d8269d11f15 Mon Sep 17 00:00:00 2001 From: "KYLEPC\\Kyle" Date: Sun, 7 Oct 2018 19:50:50 -0600 Subject: [PATCH 14/16] Expanded tests for more coverage --- test/test_campaigns.py | 37 ++++++++++++++++++++++++++++++++----- 1 file changed, 32 insertions(+), 5 deletions(-) diff --git a/test/test_campaigns.py b/test/test_campaigns.py index c4258af54..e0605354d 100644 --- a/test/test_campaigns.py +++ b/test/test_campaigns.py @@ -20,13 +20,20 @@ def test_campaign_get(self): "categories": [ "summer line" ], + "custom_unsubscribe_url": "test.url", + "editor": "code", "html_content": "

Check out our summer line!

", + "ip_pool": "test_ips", + "list_ids": [2, 3], "plain_content": "Check out our summer line!", + "segment_ids": [2, 3], + "sender_id": 4324, "subject": "New Products for Summer!", - "title": "May Newsletter" + "title": "May Newsletter", + "suppression_group_id": 5123 } camp = Campaign(**data) - self.assertEqual(camp.get(), data) + self.assertDictEqual(camp.get(), data) def test_campaign_patch(self): data = { @@ -39,9 +46,28 @@ def test_campaign_patch(self): "title": "May Newsletter" } camp = Campaign(**data) - new_title = "New Title" - camp.patch(title=new_title) - self.assertEqual(camp.title, new_title) + new_data = { + "categories": [ + "fall line" + ], + "html_content": "

Check out our summer line!

", + "plain_content": "Check out our summer line!", + "subject": "New Products for Fall!", + "title": "August Newsletter" + } + self.assertDictEqual(camp.patch(**new_data), new_data) + + def test_campaign_copy(self): + data = { + "plain_content": "Check out our summer line!", + "subject": "New Products for Summer!", + "title": "May Newsletter" + } + camp = Campaign(**data) + camp2 = camp.copy("June Newsletter") + self.assertEqual(camp.subject, camp2.subject) + self.assertEqual(camp.plain_content, camp2.plain_content) + self.assertNotEqual(camp.title, camp2.title) class TestCampaignsObject(unittest.TestCase): @@ -67,6 +93,7 @@ def test_components_to_timestamp(self): t_delta = datetime(2018, 12, 1, 8, 23) - datetime(1970, 1, 1) unix_timestamp = t_delta.seconds + t_delta.days * 86400 self.assertEqual(schedule.timestamp, unix_timestamp) + self.assertDictEqual(schedule.get(), {"send_at": unix_timestamp}) def test_timestamp_to_dt(self): td = datetime(2018, 12, 1, 8, 23) - datetime(1970, 1, 1) From 97cbe7fa9662d60146a393ec2a341ccb9df5beba Mon Sep 17 00:00:00 2001 From: "KYLEPC\\Kyle" Date: Sun, 7 Oct 2018 20:12:53 -0600 Subject: [PATCH 15/16] Switched to setattr instead of directly referencing private variables --- sendgrid/helpers/campaigns/schedule.py | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/sendgrid/helpers/campaigns/schedule.py b/sendgrid/helpers/campaigns/schedule.py index b4594e1c9..d94efb363 100644 --- a/sendgrid/helpers/campaigns/schedule.py +++ b/sendgrid/helpers/campaigns/schedule.py @@ -40,15 +40,15 @@ def __init__(self, send_at=None, year=None, month=None, if send_at is None: if year is not None: - self._year = year + self.year = year if month is not None: - self._month = month + self.month = month if day is not None: - self._day = day + self.day = day if hour is not None: - self._hour = hour + self.hour = hour if minute is not None: - self._minute = minute + self.minute = minute self._timestamp = self.convert_datetime( datetime(self.year, self.month, self.day, self.hour, self.minute) ) @@ -75,11 +75,11 @@ def parse_timestamp(self): :rtype: datetime.datetime """ dt = datetime(1970, 1, 1) + timedelta(seconds=self._timestamp) - self._year = dt.year - self._month = dt.month - self._day = dt.day - self._hour = dt.hour - self._minute = dt.minute + self.year = dt.year + self.month = dt.month + self.day = dt.day + self.hour = dt.hour + self.minute = dt.minute return dt @property From 65e52e78fd9ac8b0d08f9c60fce3edff7c520478 Mon Sep 17 00:00:00 2001 From: "KYLEPC\\Kyle" Date: Sun, 7 Oct 2018 20:47:53 -0600 Subject: [PATCH 16/16] Reference timestamp property --- sendgrid/helpers/campaigns/schedule.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sendgrid/helpers/campaigns/schedule.py b/sendgrid/helpers/campaigns/schedule.py index d94efb363..084299a2b 100644 --- a/sendgrid/helpers/campaigns/schedule.py +++ b/sendgrid/helpers/campaigns/schedule.py @@ -33,9 +33,9 @@ def __init__(self, send_at=None, year=None, month=None, if send_at is not None: if isinstance(send_at, datetime): - self._timestamp = self.convert_datetime(send_at) + self.timestamp = self.convert_datetime(send_at) else: - self._timestamp = int(send_at) + self.timestamp = int(send_at) self.parse_timestamp() if send_at is None: