From 2003cbabea069f1b2fe1cd507ecddea932552e89 Mon Sep 17 00:00:00 2001 From: Soren Spicknall Date: Wed, 6 May 2020 15:07:51 -0500 Subject: [PATCH 01/27] Create Parsons Mailchimp connector --- parsons/mailchimp/__init__.py | 5 + parsons/mailchimp/mailchimp.py | 175 +++++++++++++++++++++++++++++++++ 2 files changed, 180 insertions(+) create mode 100644 parsons/mailchimp/__init__.py create mode 100644 parsons/mailchimp/mailchimp.py diff --git a/parsons/mailchimp/__init__.py b/parsons/mailchimp/__init__.py new file mode 100644 index 0000000000..b974203267 --- /dev/null +++ b/parsons/mailchimp/__init__.py @@ -0,0 +1,5 @@ +from parsons.mailchimp.mailchimp import Mailchimp + +__all__ = [ + 'Mailchimp' +] \ No newline at end of file diff --git a/parsons/mailchimp/mailchimp.py b/parsons/mailchimp/mailchimp.py new file mode 100644 index 0000000000..7f625ca0ba --- /dev/null +++ b/parsons/mailchimp/mailchimp.py @@ -0,0 +1,175 @@ +import logging +import re +from parsons.etl import Table +from parsons.utilities.api_connector import APIConnector + +logger = logging.getLogger(__name__) + + +class Mailchimp(): + """ + Instantiate Mailchimp Class + + `Args:` + api_key: + The Mailchimp-provided application key. Not required if + ``MAILCHIMP_API_KEY`` env variable set. + `Returns:` + Mailchimp Class + """ + + def __init__(self, api_key): + self.api_key = api_key + self.domain = re.findall("(?<=-).+$", self.api_key)[0] + self.uri = f'https://{self.domain}.api.mailchimp.com/3.0/' + self.client = APIConnector(self.uri, auth=('x', self.api_key)) + + def get_request(self, endpoint, params=None, **kwargs): + # Internal method to make a get request. + + r = self.client.request(self.uri + endpoint, 'GET', params=params) + self.client.validate_response(r) + data = r.json() + + return data + + def transform_table(self, tbl, fields_to_expand=None): + # Internal method to transform a table prior to returning + + tbl.sort() + if fields_to_expand: + [tbl.unpack_dict(x, prepend=False) for x in fields_to_expand] + + return tbl + + def get_lists(self, fields=None, exclude_fields=None, + count=None, offset=None, before_date_created=None, + since_date_created=None, before_campaign_last_sent=None, + since_campaign_last_sent=None, email=None, sort_field=None, + sort_dir=None): + + params = {'fields': fields, + 'exclude_fields': exclude_fields, + 'count': count, + 'offset': offset, + 'before_date_created': before_date_created, + 'since_date_created': since_date_created, + 'before_campaign_last_sent': before_campaign_last_sent, + 'since_campaign_last_sent': since_campaign_last_sent, + 'email': email, + 'sort_field': sort_field, + 'sort_dir': sort_dir} + + response = self.get_request('lists', params=params) + tbl = Table(response['lists']) + logger.info(f'Found {tbl.num_rows} lists.') + if tbl.num_rows > 0: + return tbl + else: + return None + + def get_campaigns(self, fields=None, exclude_fields=None, + count=None, offset=None, type=None, status=None, + before_send_time=None, since_send_time=None, + before_create_time=None, since_create_time=None, + list_id=None, folder_id=None, member_id=None, + sort_field=None, sort_dir=None): + + params = {'fields': fields, + 'exclude_fields': exclude_fields, + 'count': count, + 'offset': offset, + 'type': type, + 'status': status, + 'before_send_time': before_send_time, + 'since_send_time': since_send_time, + 'before_create_time': before_create_time, + 'since_create_time': since_create_time, + 'list_id': list_id, + 'folder_id': folder_id, + 'member_id': member_id, + 'sort_field': sort_field, + 'sort_dir': sort_dir} + + response = self.get_request('campaigns', params=params) + tbl = Table(response['campaigns']) + logger.info(f'Found {tbl.num_rows} campaigns.') + if tbl.num_rows > 0: + return tbl + else: + return None + + def get_members(self, list_id, fields=None, + exclude_fields=None, count=None, offset=None, + email_type=None, status=None, since_timestamp_opt=None, + before_timestamp_opt=None, since_last_changed=None, + before_last_changed=None, unique_email_id=None, + vip_only=False, interest_category_id=None, + interest_ids=None, interest_match=None, sort_field=None, + sort_dir=None, since_last_campaign=None, + unsubscribed_since=None): + + params = {'fields': fields, + 'exclude_fields': exclude_fields, + 'count': count, + 'offset': offset, + 'email_type': email_type, + 'status': status, + 'since_timestamp_opt': since_timestamp_opt, + 'before_timestamp_opt': before_timestamp_opt, + 'since_last_changed': since_last_changed, + 'before_last_changed': before_last_changed, + 'unqiue_email_id': unique_email_id, + 'vip_only': vip_only, + 'interest_category_id': interest_category_id, + 'interest_ids': interest_ids, + 'interest_match': interest_match, + 'sort_field': sort_field, + 'sort_dir': sort_dir, + 'since_last_campaign': since_last_campaign, + 'unsubscribed_since': unsubscribed_since} + + response = self.get_request('lists/' + list_id + '/members', + params=params) + tbl = Table(response['members']) + logger.info(f'Found {tbl.num_rows} members.') + if tbl.num_rows > 0: + return tbl + else: + return None + + def get_campaign_emails(self, campaign_id, fields=None, + exclude_fields=None, count=None, offset=None, + since=None): + + params = {'fields': fields, + 'exclude_fields': exclude_fields, + 'count': count, + 'offset': offset, + 'since': since} + + response = self.get_request('reports/' + campaign_id + + '/email-activity', + params=params) + tbl = Table(response['emails']) + if tbl.num_rows > 0: + return tbl + else: + return None + + def get_unsubscribes(self, campaign_id, fields=None, + exclude_fields=None, count=None, offset=None): + + params = {'fields': fields, + 'exclude_fields': exclude_fields, + 'count': count, + 'offset': offset} + + response = self.get_request('reports/' + campaign_id + '/unsubscribed', + params=params) + tbl = Table(response['unsubscribes']) + logger.info(f'Found {tbl.num_rows} unsubscribes for {campaign_id}.') + if tbl.num_rows > 0: + return tbl + else: + return None \ No newline at end of file From db20a8f46db66fe112ad481b981f6e0a61d97d23 Mon Sep 17 00:00:00 2001 From: Soren Spicknall Date: Wed, 6 May 2020 15:09:30 -0500 Subject: [PATCH 02/27] pep8 compliance --- parsons/mailchimp/mailchimp.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/parsons/mailchimp/mailchimp.py b/parsons/mailchimp/mailchimp.py index 7f625ca0ba..425e1f93dc 100644 --- a/parsons/mailchimp/mailchimp.py +++ b/parsons/mailchimp/mailchimp.py @@ -172,4 +172,4 @@ def get_unsubscribes(self, campaign_id, fields=None, if tbl.num_rows > 0: return tbl else: - return None \ No newline at end of file + return None From 7fda770570a2241b66e9cf56391c7eadc575652d Mon Sep 17 00:00:00 2001 From: Soren Spicknall Date: Wed, 6 May 2020 15:10:58 -0500 Subject: [PATCH 03/27] pep8 compliance --- parsons/mailchimp/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/parsons/mailchimp/__init__.py b/parsons/mailchimp/__init__.py index b974203267..03bd942385 100644 --- a/parsons/mailchimp/__init__.py +++ b/parsons/mailchimp/__init__.py @@ -2,4 +2,4 @@ __all__ = [ 'Mailchimp' -] \ No newline at end of file +] From 4b1a3e05e1391bd2a38b90c3a6d8519458af5dd2 Mon Sep 17 00:00:00 2001 From: Soren Spicknall Date: Thu, 7 May 2020 16:08:52 -0500 Subject: [PATCH 04/27] Added RST documentation for Mailchimp class --- docs/index.rst | 1 + docs/mailchimp.rst | 32 ++++++++++++++++++++++++++++++++ 2 files changed, 33 insertions(+) create mode 100644 docs/mailchimp.rst diff --git a/docs/index.rst b/docs/index.rst index 35c1fd61f8..ae84a7749b 100755 --- a/docs/index.rst +++ b/docs/index.rst @@ -179,6 +179,7 @@ Indices and tables google_sheets google_cloud hustle + mailchimp mobilize_america newmode ngpvan diff --git a/docs/mailchimp.rst b/docs/mailchimp.rst new file mode 100644 index 0000000000..4a7823fdd4 --- /dev/null +++ b/docs/mailchimp.rst @@ -0,0 +1,32 @@ +Mailchimp +========= + +`Mailchimp `_ is a platform used for creating and sending mass emails. +`The Mailchimp API `_ allows users to interact with data from existing +email campaigns under their account and to configure further campaigns; this Parsons integration focuses on accessing +information about previous email campaigns and the recipients of those campaigns. + +*********** +Quick Start +*********** + +.. code-block:: python + + from parsons import Mailchimp + + mc = Mailchimp() + + # Get all recipient lists under a Mailchimp account + lists = mc.get_lists() + + # Get campaigns sent since the beginning of 2020 + recent_campaigns = mc.get_campaigns(since_send_time='2020-01-01T00:00:00Z') + + # Get all unsubscribes from a campaign + unsubscribes = mc.get_unsubscribes('dd693a3e74') + +*** +API +*** +.. autoclass :: parsons.mailchimp.Mailchimp + :inherited-members: \ No newline at end of file From 70c6309e8b538d0710600f32b97d2ba36dcfe39c Mon Sep 17 00:00:00 2001 From: Soren Spicknall Date: Thu, 7 May 2020 16:11:22 -0500 Subject: [PATCH 05/27] Used Parsons check_env for API key in __init__ --- parsons/mailchimp/mailchimp.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/parsons/mailchimp/mailchimp.py b/parsons/mailchimp/mailchimp.py index 425e1f93dc..50d8903fe1 100644 --- a/parsons/mailchimp/mailchimp.py +++ b/parsons/mailchimp/mailchimp.py @@ -1,6 +1,7 @@ import logging import re from parsons.etl import Table +from parsons.utilities import check_env from parsons.utilities.api_connector import APIConnector logger = logging.getLogger(__name__) @@ -18,8 +19,8 @@ class Mailchimp(): Mailchimp Class """ - def __init__(self, api_key): - self.api_key = api_key + def __init__(self, api_key=None): + self.api_key = check_env.check('MAILCHIMP_API_KEY', api_key) self.domain = re.findall("(?<=-).+$", self.api_key)[0] self.uri = f'https://{self.domain}.api.mailchimp.com/3.0/' self.client = APIConnector(self.uri, auth=('x', self.api_key)) From 835df402538cf8a6f43c7c3b638cd995108f736f Mon Sep 17 00:00:00 2001 From: Soren Spicknall Date: Thu, 7 May 2020 16:18:13 -0500 Subject: [PATCH 06/27] Substituted f-strings where appropriate --- parsons/mailchimp/mailchimp.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/parsons/mailchimp/mailchimp.py b/parsons/mailchimp/mailchimp.py index 50d8903fe1..5d8ede6fbe 100644 --- a/parsons/mailchimp/mailchimp.py +++ b/parsons/mailchimp/mailchimp.py @@ -130,8 +130,7 @@ def get_members(self, list_id, fields=None, 'since_last_campaign': since_last_campaign, 'unsubscribed_since': unsubscribed_since} - response = self.get_request('lists/' + list_id + '/members', - params=params) + response = self.get_request(f'lists/{list_id}/members', params=params) tbl = Table(response['members']) logger.info(f'Found {tbl.num_rows} members.') if tbl.num_rows > 0: @@ -149,8 +148,7 @@ def get_campaign_emails(self, campaign_id, fields=None, 'offset': offset, 'since': since} - response = self.get_request('reports/' + campaign_id + - '/email-activity', + response = self.get_request(f'reports/{campaign_id}/email-activity', params=params) tbl = Table(response['emails']) if tbl.num_rows > 0: @@ -166,7 +164,7 @@ def get_unsubscribes(self, campaign_id, fields=None, 'count': count, 'offset': offset} - response = self.get_request('reports/' + campaign_id + '/unsubscribed', + response = self.get_request(f'reports/{campaign_id}/unsubscribed', params=params) tbl = Table(response['unsubscribes']) logger.info(f'Found {tbl.num_rows} unsubscribes for {campaign_id}.') From 462ebf06eb489352c976c89ff452905bb0c4c8a2 Mon Sep 17 00:00:00 2001 From: Soren Spicknall Date: Thu, 7 May 2020 16:20:22 -0500 Subject: [PATCH 07/27] pep8 compliance --- parsons/mailchimp/mailchimp.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/parsons/mailchimp/mailchimp.py b/parsons/mailchimp/mailchimp.py index 5d8ede6fbe..6f699c695f 100644 --- a/parsons/mailchimp/mailchimp.py +++ b/parsons/mailchimp/mailchimp.py @@ -20,7 +20,7 @@ class Mailchimp(): """ def __init__(self, api_key=None): - self.api_key = check_env.check('MAILCHIMP_API_KEY', api_key) + self.api_key = check_env.check('MAILCHIMP_API_KEY', api_key) self.domain = re.findall("(?<=-).+$", self.api_key)[0] self.uri = f'https://{self.domain}.api.mailchimp.com/3.0/' self.client = APIConnector(self.uri, auth=('x', self.api_key)) From b1d33cde7ee6979c91d8d7814164c0f361b01144 Mon Sep 17 00:00:00 2001 From: Soren Spicknall Date: Thu, 7 May 2020 17:06:41 -0500 Subject: [PATCH 08/27] Completed docstrings for three functions --- parsons/mailchimp/mailchimp.py | 107 ++++++++++++++++++++++++++++++++- 1 file changed, 106 insertions(+), 1 deletion(-) diff --git a/parsons/mailchimp/mailchimp.py b/parsons/mailchimp/mailchimp.py index 6f699c695f..7be7ec46ec 100644 --- a/parsons/mailchimp/mailchimp.py +++ b/parsons/mailchimp/mailchimp.py @@ -35,8 +35,18 @@ def get_request(self, endpoint, params=None, **kwargs): return data def transform_table(self, tbl, fields_to_expand=None): - # Internal method to transform a table prior to returning + """ + Unpacks selected dictionaries within a Parsons table into separate + columns, returning a version of the table with those new columns added. + `Args:` + fields_to_expand: list of column names as strings + A list of columns within the table containing dictionaries + that the user wishes to expand. + + `Returns:` + Table Class + """ tbl.sort() if fields_to_expand: [tbl.unpack_dict(x, prepend=False) for x in fields_to_expand] @@ -48,7 +58,50 @@ def get_lists(self, fields=None, exclude_fields=None, since_date_created=None, before_campaign_last_sent=None, since_campaign_last_sent=None, email=None, sort_field=None, sort_dir=None): + """ + Get a table of lists under the account based on query parameters. Note + that argument descriptions here are sourced from Mailchimp's official + API documentation. + + `Args:` + fields: list of fields as strings + A comma-separated list of fields to return. Reference + parameters of sub-objects with dot notation. + exclude_fields: list of fields as strings + A comma-separated list of fields to exclude. Reference + parameters of sub-objects with dot notation. + count: int + The number of records to return. Default value is 10. Maximum + value is 1000. + offset: int + The number of records from a collection to skip. Iterating over + large collections with this parameter can be slow. Default + value is 0. + before_date_created: string + Restrict response to lists created before the set date. We + recommend ISO 8601 time format: 2015-10-21T15:41:36+00:00. + since_date_created: string + Restrict results to lists created after the set date. We + recommend ISO 8601 time format: 2015-10-21T15:41:36+00:00. + before_campaign_last_sent: string + Restrict results to lists created before the last campaign send + date. We recommend ISO 8601 time format: + 2015-10-21T15:41:36+00:00. + since_campaign_last_sent: string + Restrict results to lists created after the last campaign send + date. We recommend ISO 8601 time format: + 2015-10-21T15:41:36+00:00. + email: string + Restrict results to lists that include a specific subscriber's + email address. + sort_field: string, can only be 'date_created' or None + Returns files sorted by the specified field. + sort_dir: string, can only be 'ASC', 'DESC', or None + Determines the order direction for sorted results. + `Returns:` + Table Class + """ params = {'fields': fields, 'exclude_fields': exclude_fields, 'count': count, @@ -75,7 +128,59 @@ def get_campaigns(self, fields=None, exclude_fields=None, before_create_time=None, since_create_time=None, list_id=None, folder_id=None, member_id=None, sort_field=None, sort_dir=None): + """ + Get a table of campaigns under the account based on query parameters. + Note that argument descriptions here are sourced from Mailchimp's + official API documentation. + + `Args:` + fields: list of fields as strings + A comma-separated list of fields to return. Reference + parameters of sub-objects with dot notation. + exclude_fields: list of fields as strings + A comma-separated list of fields to exclude. Reference + parameters of sub-objects with dot notation. + count: int + The number of records to return. Default value is 10. Maximum + value is 1000. + offset: int + The number of records from a collection to skip. Iterating over + large collections with this parameter can be slow. Default + value is 0. + type: string, can only be 'regular', 'plaintext', 'absplit', 'rss', + 'variate', or None + The campaign type. + status: string, can only be 'save', 'paused', 'schedule', + 'sending', 'sent', or None + The status of the campaign. + before_send_time: string + Restrict the response to campaigns sent before the set time. We + recommend ISO 8601 time format: 2015-10-21T15:41:36+00:00. + since_send_time: string + Restrict the response to campaigns sent after the set time. We + recommend ISO 8601 time format: 2015-10-21T15:41:36+00:00. + before_create_time: string + Restrict the response to campaigns created before the set time. + We recommend ISO 8601 time format: 2015-10-21T15:41:36+00:00. + since_create_time: string + Restrict the response to campaigns created after the set time. + We recommend ISO 8601 time format: 2015-10-21T15:41:36+00:00. + list_id: string + The unique id for the list. + folder_id: string + The unique folder id. + member_id: string + Retrieve campaigns sent to a particular list member. Member ID + is The MD5 hash of the lowercase version of the list member’s + email address. + sort_field: string, can only be 'create_time', 'send_time', or None + Returns files sorted by the specified field. + sort_dir: string, can only be 'ASC', 'DESC', or None + Determines the order direction for sorted results. + `Returns:` + Table Class + """ params = {'fields': fields, 'exclude_fields': exclude_fields, 'count': count, From 41330c091df18b2862de70623c4059f41f1fcc80 Mon Sep 17 00:00:00 2001 From: Soren Spicknall Date: Fri, 8 May 2020 09:21:53 -0500 Subject: [PATCH 09/27] Completed docstrings --- parsons/mailchimp/mailchimp.py | 139 ++++++++++++++++++++++++++++++++- 1 file changed, 135 insertions(+), 4 deletions(-) diff --git a/parsons/mailchimp/mailchimp.py b/parsons/mailchimp/mailchimp.py index 7be7ec46ec..cf49b918b9 100644 --- a/parsons/mailchimp/mailchimp.py +++ b/parsons/mailchimp/mailchimp.py @@ -64,10 +64,10 @@ def get_lists(self, fields=None, exclude_fields=None, API documentation. `Args:` - fields: list of fields as strings + fields: list of strings A comma-separated list of fields to return. Reference parameters of sub-objects with dot notation. - exclude_fields: list of fields as strings + exclude_fields: list of strings A comma-separated list of fields to exclude. Reference parameters of sub-objects with dot notation. count: int @@ -134,10 +134,10 @@ def get_campaigns(self, fields=None, exclude_fields=None, official API documentation. `Args:` - fields: list of fields as strings + fields: list of strings A comma-separated list of fields to return. Reference parameters of sub-objects with dot notation. - exclude_fields: list of fields as strings + exclude_fields: list of strings A comma-separated list of fields to exclude. Reference parameters of sub-objects with dot notation. count: int @@ -214,7 +214,86 @@ def get_members(self, list_id, fields=None, interest_ids=None, interest_match=None, sort_field=None, sort_dir=None, since_last_campaign=None, unsubscribed_since=None): + """ + Get a table of members in a list based on query parameters. Note that + argument descriptions here are sourced from Mailchimp's official API + documentation. + + `Args:` + list_id: string + The unique ID of the list to fetch members from. + fields: list of strings + A comma-separated list of fields to return. Reference + parameters of sub-objects with dot notation. + exclude_fields: list of fields as strings + A comma-separated list of fields to exclude. Reference + parameters of sub-objects with dot notation. + count: int + The number of records to return. Default value is 10. Maximum + value is 1000. + offset: int + The number of records from a collection to skip. Iterating over + large collections with this parameter can be slow. Default + value is 0. + email_type: string + The email type. + status: string, can only be 'subscribed', 'unsubscribed', + 'cleaned', 'pending', 'transactional', 'archived', or None + The subscriber's status. + since_timestamp_opt: string + Restrict results to subscribers who opted-in after the set + timeframe. We recommend ISO 8601 time format: + 2015-10-21T15:41:36+00:00. + before_timestamp_opt: string + Restrict results to subscribers who opted-in before the set + timeframe. We recommend ISO 8601 time format: + 2015-10-21T15:41:36+00:00. + since_last_changed: string + Restrict results to subscribers whose information changed after + the set timeframe. We recommend ISO 8601 time format: + 2015-10-21T15:41:36+00:00. + before_last_changed: string + Restrict results to subscribers whose information changed + before the set timeframe. We recommend ISO 8601 time format: + 2015-10-21T15:41:36+00:00. + unique_email_id: string + A unique identifier for the email address across all Mailchimp + lists. This parameter can be found in any links with Ecommerce + Tracking enabled. + vip_only: boolean + A filter to return only the list's VIP members. Passing true + will restrict results to VIP list members, passing false will + return all list members. + interest_category_id: string + The unique id for the interest category. + interest_ids: list of strings + Used to filter list members by interests. Must be accompanied + by interest_category_id and interest_match. The value must be a + comma separated list of interest ids present for any supplied + interest categories. + interest_match: string, can only be 'any', 'all', 'none', or None + Used to filter list members by interests. Must be accompanied + by interest_category_id and interest_ids. "any" will match a + member with any of the interest supplied, "all" will only match + members with every interest supplied, and "none" will match + members without any of the interest supplied. + sort_field: string, can only be 'timestamp_opt', + 'timestamp_signup', 'last_changed', or None + Returns files sorted by the specified field. + sort_dir: string, can only be 'ASC', 'DESC', or None + Determines the order direction for sorted results. + since_last_campaign: string + Filter subscribers by those + subscribed/unsubscribed/pending/cleaned since last email + campaign send. Member status is required to use this filter. + unsubscribed_since: string + Filter subscribers by those unsubscribed since a specific date. + Using any status other than unsubscribed with this filter will + result in an error. + `Returns:` + Table Class + """ params = {'fields': fields, 'exclude_fields': exclude_fields, 'count': count, @@ -246,7 +325,35 @@ def get_members(self, list_id, fields=None, def get_campaign_emails(self, campaign_id, fields=None, exclude_fields=None, count=None, offset=None, since=None): + """ + Get a table of individual emails from a campaign based on query + parameters. Note that argument descriptions here are sourced from + Mailchimp's official API documentation. + `Args:` + campaign_id: string + The unique ID of the campaign to fetch emails from. + fields: list of strings + A comma-separated list of fields to return. Reference + parameters of sub-objects with dot notation. + exclude_fields: list of strings + A comma-separated list of fields to exclude. Reference + parameters of sub-objects with dot notation. + count: int + The number of records to return. Default value is 10. Maximum + value is 1000. + offset: int + The number of records from a collection to skip. Iterating over + large collections with this parameter can be slow. Default + value is 0. + since: string + Restrict results to email activity events that occur after a + specific time. We recommend ISO 8601 time format: + 2015-10-21T15:41:36+00:00. + + `Returns:` + Table Class + """ params = {'fields': fields, 'exclude_fields': exclude_fields, 'count': count, @@ -263,7 +370,31 @@ def get_campaign_emails(self, campaign_id, fields=None, def get_unsubscribes(self, campaign_id, fields=None, exclude_fields=None, count=None, offset=None): + """ + Get a table of unsubscribes associated with a campaign based on query + parameters. Note that argument descriptions here are sourced from + Mailchimp's official API documentation. + `Args:` + campaign_id: string + The unique ID of the campaign to fetch unsubscribes from. + fields: list of strings + A comma-separated list of fields to return. Reference + parameters of sub-objects with dot notation. + exclude_fields: list of strings + A comma-separated list of fields to exclude. Reference + parameters of sub-objects with dot notation. + count: int + The number of records to return. Default value is 10. Maximum + value is 1000. + offset: int + The number of records from a collection to skip. Iterating over + large collections with this parameter can be slow. Default + value is 0. + + `Returns:` + Table Class + """ params = {'fields': fields, 'exclude_fields': exclude_fields, 'count': count, From 1388cea2c9c74e8f84d97852c6177cbb6840aaf4 Mon Sep 17 00:00:00 2001 From: Soren Spicknall Date: Fri, 8 May 2020 09:24:50 -0500 Subject: [PATCH 10/27] pep8 compliance --- parsons/mailchimp/mailchimp.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/parsons/mailchimp/mailchimp.py b/parsons/mailchimp/mailchimp.py index cf49b918b9..b19af9c6c9 100644 --- a/parsons/mailchimp/mailchimp.py +++ b/parsons/mailchimp/mailchimp.py @@ -245,7 +245,7 @@ def get_members(self, list_id, fields=None, timeframe. We recommend ISO 8601 time format: 2015-10-21T15:41:36+00:00. before_timestamp_opt: string - Restrict results to subscribers who opted-in before the set + Restrict results to subscribers who opted-in before the set timeframe. We recommend ISO 8601 time format: 2015-10-21T15:41:36+00:00. since_last_changed: string From 94382123934707508093396afdf86e4b73b23d63 Mon Sep 17 00:00:00 2001 From: Soren Spicknall Date: Mon, 11 May 2020 10:48:03 -0500 Subject: [PATCH 11/27] Removed transform_table function (not directly in use within Parsons) --- parsons/mailchimp/mailchimp.py | 19 ------------------- 1 file changed, 19 deletions(-) diff --git a/parsons/mailchimp/mailchimp.py b/parsons/mailchimp/mailchimp.py index b19af9c6c9..26f12b204e 100644 --- a/parsons/mailchimp/mailchimp.py +++ b/parsons/mailchimp/mailchimp.py @@ -34,25 +34,6 @@ def get_request(self, endpoint, params=None, **kwargs): return data - def transform_table(self, tbl, fields_to_expand=None): - """ - Unpacks selected dictionaries within a Parsons table into separate - columns, returning a version of the table with those new columns added. - - `Args:` - fields_to_expand: list of column names as strings - A list of columns within the table containing dictionaries - that the user wishes to expand. - - `Returns:` - Table Class - """ - tbl.sort() - if fields_to_expand: - [tbl.unpack_dict(x, prepend=False) for x in fields_to_expand] - - return tbl - def get_lists(self, fields=None, exclude_fields=None, count=None, offset=None, before_date_created=None, since_date_created=None, before_campaign_last_sent=None, From 3963880d78d00d19ce693280fea61735f352525e Mon Sep 17 00:00:00 2001 From: Soren Spicknall Date: Tue, 12 May 2020 16:00:19 -0500 Subject: [PATCH 12/27] Initial testing structure (functionality incomplete) --- test/test_mailchimp/__init__.py | 0 test/test_mailchimp/expected_json.py | 225 ++++++++++++++++++++++++++ test/test_mailchimp/test_mailchimp.py | 41 +++++ 3 files changed, 266 insertions(+) create mode 100644 test/test_mailchimp/__init__.py create mode 100644 test/test_mailchimp/expected_json.py create mode 100644 test/test_mailchimp/test_mailchimp.py diff --git a/test/test_mailchimp/__init__.py b/test/test_mailchimp/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/test/test_mailchimp/expected_json.py b/test/test_mailchimp/expected_json.py new file mode 100644 index 0000000000..87dfd55f16 --- /dev/null +++ b/test/test_mailchimp/expected_json.py @@ -0,0 +1,225 @@ +test_campaigns = { + "campaigns": [ + { + "id": "abc", + "web_id": 123, + "type": "regular", + "create_time": "2019-10-18T02:35:05+00:00", + "archive_url": "http://example.com/sample-campaign-1", + "long_archive_url": "https://mailchi.mp/abc/sample-campaign-1", + "status": "sent", + "emails_sent": 145, + "send_time": "2019-10-18T14:15:00+00:00", + "content_type": "template", + "needs_block_refresh": false, + "resendable": true, + "recipients": { + "list_id": "zyx", + "list_is_active": true, + "list_name": "Support Our Candidate List 1", + "segment_text": "", + "recipient_count": 145 + }, + "settings": { + "subject_line": "Sample Campaign 1", + "preview_text": "This is a sample campaign.", + "title": "Sample Campaign Donation Ask", + "from_name": "Our Candidate", + "reply_to": "our_candidate@example.com", + "use_conversation": false, + "to_name": "*|FNAME|* *|LNAME|*", + "folder_id": "", + "authenticate": true, + "auto_footer": false, + "inline_css": false, + "auto_tweet": false, + "fb_comments": true, + "timewarp": false, + "template_id": 12345, + "drag_and_drop": true + }, + "tracking": { + "opens": true, + "html_clicks": true, + "text_clicks": false, + "goal_tracking": false, + "ecomm360": false, + "google_analytics": "", + "clicktale": "" + }, + "report_summary": { + "opens": 48, + "unique_opens": 34, + "open_rate": 0.23776223776223776, + "clicks": 1, + "subscriber_clicks": 1, + "click_rate": 0.006993006993006993, + "ecommerce": { + "total_orders": 0, + "total_spent": 0, + "total_revenue": 0 + } + }, + "delivery_status": { + "enabled": false + }, + "_links": [] + }, + { + "id": "def", + "web_id": 456, + "type": "regular", + "create_time": "2019-05-29T11:46:41+00:00", + "archive_url": "http://example.com/sample-campaign-2", + "long_archive_url": "https://mailchi.mp/abc/sample-campaign-2", + "status": "sent", + "emails_sent": 87, + "send_time": "2019-05-29T21:18:15+00:00", + "content_type": "template", + "needs_block_refresh": false, + "resendable": true, + "recipients": { + "list_id": "wvu", + "list_is_active": true, + "list_name": "Support Our Candidate List 2", + "segment_text": "", + "recipient_count": 87 + }, + "settings": { + "subject_line": "Sample Campaign 2", + "preview_text": "This is another sample campaign.", + "title": "Sample Campaign 2 Donation Ask", + "from_name": "Our Candidate", + "reply_to": "our_candidate@example.com", + "use_conversation": false, + "to_name": "*|FNAME|* *|LNAME|*", + "folder_id": "", + "authenticate": true, + "auto_footer": false, + "inline_css": false, + "auto_tweet": false, + "fb_comments": true, + "timewarp": false, + "template_id": 67890, + "drag_and_drop": true + }, + "tracking": { + "opens": true, + "html_clicks": true, + "text_clicks": false, + "goal_tracking": false, + "ecomm360": false, + "google_analytics": "", + "clicktale": "" + }, + "report_summary": { + "opens": 108, + "unique_opens": 48, + "open_rate": 0.5647058823529412, + "clicks": 25, + "subscriber_clicks": 14, + "click_rate": 0.16470588235294117, + "ecommerce": { + "total_orders": 0, + "total_spent": 0, + "total_revenue": 0 + } + }, + "delivery_status": { + "enabled": false + }, + "_links": [] + }} + +test_agent = [{ + 'available': False, + 'occasional': True, + 'id': 47020956237, + 'signature': '


\n
', + 'ticket_scope': 1, + 'created_at': '2020-01-17T15:07:01Z', + 'updated_at': '2020-02-05T00:58:37Z', + 'available_since': None, + 'type': 'support_agent', + 'contact': { + 'active': True, + 'email': 'person@email.org', + 'job_title': None, + 'language': 'en', + 'last_login_at': '2020-01-24T22:49:52Z', + 'mobile': None, + 'name': 'Alissa', + 'phone': None, + 'time_zone': 'Bogota', + 'created_at': '2020-01-17T15:07:00Z', + 'updated_at': '2020-01-24T22:49:52Z' + } +}] + +test_ticket = [{ + 'cc_emails': ['person@email.org', 'person2@email.org'], + 'fwd_emails': [], + 'reply_cc_emails': ['person@email.org', 'person2@email.org'], + 'ticket_cc_emails': ['person@email.org', 'person2@email.org'], + 'fr_escalated': False, + 'spam': False, + 'email_config_id': None, + 'group_id': 47000643034, + 'priority': 1, + 'requester_id': 47021937449, + 'responder_id': 47017224681, + 'source': 3, + 'company_id': 47000491688, + 'status': 5, + 'subject': 'My thing is broken.', + 'association_type': None, + 'to_emails': None, + 'product_id': None, + 'id': 84, + 'type': 'Support Request 1', + 'due_by': '2020-02-19T22:00:00Z', + 'fr_due_by': '2020-02-06T17:00:00Z', + 'is_escalated': False, + 'custom_fields': {}, + 'created_at': '2020-02-05T22:17:41Z', + 'updated_at': '2020-02-06T02:07:37Z', + 'associated_tickets_count': None, + 'tags': [] +}] + +test_company = [{ + 'id': 47000491701, + 'name': 'Big Org', + 'description': None, + 'note': None, + 'domains': [], + 'created_at': '2020-01-09T20:43:09Z', + 'updated_at': '2020-01-09T20:43:09Z', + 'custom_fields': {}, + 'health_score': None, + 'account_tier': 'Tier 2', + 'renewal_date': '2020-12-31T00:00:00Z', + 'industry': None +}] + +test_contact = [{ + 'active': False, + 'address': None, + 'company_id': 47000491686, + 'description': None, + 'email': 'person@email.org', + 'id': 47021299020, + 'job_title': None, + 'language': 'en', + 'mobile': None, + 'name': 'Percy Person', + 'phone': 'N/A', + 'time_zone': 'Bogota', + 'twitter_id': None, + 'custom_fields': {}, + 'facebook_id': None, + 'created_at': '2020-01-27T16:44:34Z', + 'updated_at': '2020-01-27T16:44:34Z', + 'unique_external_id': None +}] + diff --git a/test/test_mailchimp/test_mailchimp.py b/test/test_mailchimp/test_mailchimp.py new file mode 100644 index 0000000000..6717810859 --- /dev/null +++ b/test/test_mailchimp/test_mailchimp.py @@ -0,0 +1,41 @@ +from parsons.mailchimp.mailchimp import Mailchimp +import unittest +import requests_mock +from test.utils import assert_matching_tables +from test.test_mailchimp import expected_json + +API_KEY = 'mykey' + +class TestMailchimp(unittest.TestCase): + + def setUp(self): + + self.mc = Mailchimp(API_KEY) + + @requests_mock.Mocker() + def test_get_campaigns(self, m): + + # Test that agents are returned correctly. + m.get(self.mc.uri + 'campaigns', json=expected_json.test_campaigns) + tbl = self.mc.get_campaigns() + + @requests_mock.Mocker() + def test_get_lists(self, m): + + # Test that tickets are returned correctly. + m.get(self.mc.uri + 'lists', json=expected_json.test_ticket) + tbl = self.mc.get_lists() + + @requests_mock.Mocker() + def test_get_members(self, m): + + # Test that tickets are returned correctly. + m.get(self.mc.uri + 'companies', json=expected_json.test_company) + tbl = self.mc.get_members() + + @requests_mock.Mocker() + def test_get_unsubscribes(self, m): + + # Test that tickets are returned correctly. + m.get(self.mc.uri + 'contacts', json=expected_json.test_contact) + tbl = self.mc.get_unsubscribes() From 3379b9e540d30217684e3a83dfb8d29944b35276 Mon Sep 17 00:00:00 2001 From: Soren Spicknall Date: Tue, 12 May 2020 16:05:44 -0500 Subject: [PATCH 13/27] Fixed syntax and boolean references --- test/test_mailchimp/expected_json.py | 70 ++++++++++++++-------------- 1 file changed, 35 insertions(+), 35 deletions(-) diff --git a/test/test_mailchimp/expected_json.py b/test/test_mailchimp/expected_json.py index 87dfd55f16..47350819f0 100644 --- a/test/test_mailchimp/expected_json.py +++ b/test/test_mailchimp/expected_json.py @@ -11,11 +11,11 @@ "emails_sent": 145, "send_time": "2019-10-18T14:15:00+00:00", "content_type": "template", - "needs_block_refresh": false, - "resendable": true, + "needs_block_refresh": False, + "resendable": True, "recipients": { "list_id": "zyx", - "list_is_active": true, + "list_is_active": True, "list_name": "Support Our Candidate List 1", "segment_text": "", "recipient_count": 145 @@ -26,24 +26,24 @@ "title": "Sample Campaign Donation Ask", "from_name": "Our Candidate", "reply_to": "our_candidate@example.com", - "use_conversation": false, + "use_conversation": False, "to_name": "*|FNAME|* *|LNAME|*", "folder_id": "", - "authenticate": true, - "auto_footer": false, - "inline_css": false, - "auto_tweet": false, - "fb_comments": true, - "timewarp": false, + "authenticate": True, + "auto_footer": False, + "inline_css": False, + "auto_tweet": False, + "fb_comments": True, + "timewarp": False, "template_id": 12345, - "drag_and_drop": true + "drag_and_drop": True }, "tracking": { - "opens": true, - "html_clicks": true, - "text_clicks": false, - "goal_tracking": false, - "ecomm360": false, + "opens": True, + "html_clicks": True, + "text_clicks": False, + "goal_tracking": False, + "ecomm360": False, "google_analytics": "", "clicktale": "" }, @@ -61,7 +61,7 @@ } }, "delivery_status": { - "enabled": false + "enabled": False }, "_links": [] }, @@ -76,11 +76,11 @@ "emails_sent": 87, "send_time": "2019-05-29T21:18:15+00:00", "content_type": "template", - "needs_block_refresh": false, - "resendable": true, + "needs_block_refresh": False, + "resendable": True, "recipients": { "list_id": "wvu", - "list_is_active": true, + "list_is_active": True, "list_name": "Support Our Candidate List 2", "segment_text": "", "recipient_count": 87 @@ -91,24 +91,24 @@ "title": "Sample Campaign 2 Donation Ask", "from_name": "Our Candidate", "reply_to": "our_candidate@example.com", - "use_conversation": false, + "use_conversation": False, "to_name": "*|FNAME|* *|LNAME|*", "folder_id": "", - "authenticate": true, - "auto_footer": false, - "inline_css": false, - "auto_tweet": false, - "fb_comments": true, - "timewarp": false, + "authenticate": True, + "auto_footer": False, + "inline_css": False, + "auto_tweet": False, + "fb_comments": True, + "timewarp": False, "template_id": 67890, - "drag_and_drop": true + "drag_and_drop": True }, "tracking": { - "opens": true, - "html_clicks": true, - "text_clicks": false, - "goal_tracking": false, - "ecomm360": false, + "opens": True, + "html_clicks": True, + "text_clicks": False, + "goal_tracking": False, + "ecomm360": False, "google_analytics": "", "clicktale": "" }, @@ -126,10 +126,10 @@ } }, "delivery_status": { - "enabled": false + "enabled": False }, "_links": [] - }} + }]} test_agent = [{ 'available': False, From 4f9d368508c176a89edfffd15eef6d69445d2397 Mon Sep 17 00:00:00 2001 From: Soren Spicknall Date: Tue, 12 May 2020 16:08:13 -0500 Subject: [PATCH 14/27] Fixed API key structure --- test/test_mailchimp/test_mailchimp.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/test_mailchimp/test_mailchimp.py b/test/test_mailchimp/test_mailchimp.py index 6717810859..21f83b0105 100644 --- a/test/test_mailchimp/test_mailchimp.py +++ b/test/test_mailchimp/test_mailchimp.py @@ -4,7 +4,7 @@ from test.utils import assert_matching_tables from test.test_mailchimp import expected_json -API_KEY = 'mykey' +API_KEY = 'mykey-us00' class TestMailchimp(unittest.TestCase): From e9815085e5d06778f6b7dbbc9faf2c58161fedad Mon Sep 17 00:00:00 2001 From: Soren Spicknall Date: Tue, 12 May 2020 16:30:11 -0500 Subject: [PATCH 15/27] Continued work on test JSON --- test/test_mailchimp/expected_json.py | 213 +++++++++++++++----------- test/test_mailchimp/test_mailchimp.py | 4 +- 2 files changed, 125 insertions(+), 92 deletions(-) diff --git a/test/test_mailchimp/expected_json.py b/test/test_mailchimp/expected_json.py index 47350819f0..27d795a45e 100644 --- a/test/test_mailchimp/expected_json.py +++ b/test/test_mailchimp/expected_json.py @@ -131,95 +131,128 @@ "_links": [] }]} -test_agent = [{ - 'available': False, - 'occasional': True, - 'id': 47020956237, - 'signature': '


\n
', - 'ticket_scope': 1, - 'created_at': '2020-01-17T15:07:01Z', - 'updated_at': '2020-02-05T00:58:37Z', - 'available_since': None, - 'type': 'support_agent', - 'contact': { - 'active': True, - 'email': 'person@email.org', - 'job_title': None, - 'language': 'en', - 'last_login_at': '2020-01-24T22:49:52Z', - 'mobile': None, - 'name': 'Alissa', - 'phone': None, - 'time_zone': 'Bogota', - 'created_at': '2020-01-17T15:07:00Z', - 'updated_at': '2020-01-24T22:49:52Z' - } -}] - -test_ticket = [{ - 'cc_emails': ['person@email.org', 'person2@email.org'], - 'fwd_emails': [], - 'reply_cc_emails': ['person@email.org', 'person2@email.org'], - 'ticket_cc_emails': ['person@email.org', 'person2@email.org'], - 'fr_escalated': False, - 'spam': False, - 'email_config_id': None, - 'group_id': 47000643034, - 'priority': 1, - 'requester_id': 47021937449, - 'responder_id': 47017224681, - 'source': 3, - 'company_id': 47000491688, - 'status': 5, - 'subject': 'My thing is broken.', - 'association_type': None, - 'to_emails': None, - 'product_id': None, - 'id': 84, - 'type': 'Support Request 1', - 'due_by': '2020-02-19T22:00:00Z', - 'fr_due_by': '2020-02-06T17:00:00Z', - 'is_escalated': False, - 'custom_fields': {}, - 'created_at': '2020-02-05T22:17:41Z', - 'updated_at': '2020-02-06T02:07:37Z', - 'associated_tickets_count': None, - 'tags': [] -}] - -test_company = [{ - 'id': 47000491701, - 'name': 'Big Org', - 'description': None, - 'note': None, - 'domains': [], - 'created_at': '2020-01-09T20:43:09Z', - 'updated_at': '2020-01-09T20:43:09Z', - 'custom_fields': {}, - 'health_score': None, - 'account_tier': 'Tier 2', - 'renewal_date': '2020-12-31T00:00:00Z', - 'industry': None -}] +test_lists = { + "lists": [ + { + "id": "zyx", + "web_id": 98765, + "name": "Support Our Candidate List 1", + "contact": { + "company": "Support Our Candidate", + "address1": "123 Main Street", + "address2": "", + "city": "Townsville", + "state": "OH", + "zip": "43358", + "country": "US", + "phone": "" + }, + "permission_reminder": "You are receiving this email because you signed up at an event, while being canvassed, or on our website.", + "use_archive_bar": true, + "campaign_defaults": { + "from_name": "Our Candidate", + "from_email": "our_candidate@example.com", + "subject": "", + "language": "en" + }, + "notify_on_subscribe": "", + "notify_on_unsubscribe": "", + "date_created": "2019-03-25T22:55:44+00:00", + "list_rating": 3, + "email_type_option": False, + "subscribe_url_short": "http://example.com/sample-subscribe_url_2", + "subscribe_url_long": "https://mailchi.mp/zyx/sample-subscribe-url-2", + "beamer_address": "us00-sample-2@inbound.mailchimp.com", + "visibility": "pub", + "double_optin": False, + "has_welcome": True, + "marketing_permissions": False, + "modules": [], + "stats": { + "member_count": 140, + "unsubscribe_count": 8, + "cleaned_count": 16, + "member_count_since_send": 0, + "unsubscribe_count_since_send": 1, + "cleaned_count_since_send": 0, + "campaign_count": 21, + "campaign_last_sent": "2020-01-06T01:54:32+00:00", + "merge_field_count": 5, + "avg_sub_rate": 0, + "avg_unsub_rate": 1, + "target_sub_rate": 3, + "open_rate": 38.40236686390532, + "click_rate": 4.016786570743405, + "last_sub_date": "2019-09-24T01:07:56+00:00", + "last_unsub_date": "2020-01-06T01:55:02+00:00" + }, + "_links": [] + }, + { + "id": "xvu", + "web_id": 43210, + "name": "Support Our Candidate List 2", + "contact": { + "company": "Support Our Candidate", + "address1": "123 Main Street", + "address2": "", + "city": "Townsville", + "state": "OH", + "zip": "43358", + "country": "US", + "phone": "" + }, + "permission_reminder": "You are receiving this email because you signed up at an event, while being canvassed, or on our website.", + "use_archive_bar": true, + "campaign_defaults": { + "from_name": "Our Candidate", + "from_email": "our_candidate@example.com", + "subject": "", + "language": "en" + }, + "notify_on_subscribe": "", + "notify_on_unsubscribe": "", + "date_created": "2018-09-15T22:15:21+00:00", + "list_rating": 3, + "email_type_option": False, + "subscribe_url_short": "http://example.com/sample-subscribe_url_1", + "subscribe_url_long": "https://mailchi.mp/zyx/sample-subscribe-url-1", + "beamer_address": "us00-sample-1@inbound.mailchimp.com", + "visibility": "pub", + "double_optin": False, + "has_welcome": True, + "marketing_permissions": False, + "modules": [], + "stats": { + "member_count": 73, + "unsubscribe_count": 3, + "cleaned_count": 7, + "member_count_since_send": 1, + "unsubscribe_count_since_send": 1, + "cleaned_count_since_send": 0, + "campaign_count": 13, + "campaign_last_sent": "2020-01-03T14:38:11+00:00", + "merge_field_count": 5, + "avg_sub_rate": 0, + "avg_unsub_rate": 1, + "target_sub_rate": 3, + "open_rate": 64.19236186394533, + "click_rate": 3.746759370417411, + "last_sub_date": "2020-01-01T00:19:46+00:00", + "last_unsub_date": "2019-12-23T11:44:31+00:00" + }, + "_links": [] + }, + ], + "total_items": 1, + "constraints": { + "may_create": False, + "max_instances": 1, + "current_total_instances": 1 + }, + "_links": [] +} -test_contact = [{ - 'active': False, - 'address': None, - 'company_id': 47000491686, - 'description': None, - 'email': 'person@email.org', - 'id': 47021299020, - 'job_title': None, - 'language': 'en', - 'mobile': None, - 'name': 'Percy Person', - 'phone': 'N/A', - 'time_zone': 'Bogota', - 'twitter_id': None, - 'custom_fields': {}, - 'facebook_id': None, - 'created_at': '2020-01-27T16:44:34Z', - 'updated_at': '2020-01-27T16:44:34Z', - 'unique_external_id': None -}] +test_members = {} +test_unsubscribes = {} \ No newline at end of file diff --git a/test/test_mailchimp/test_mailchimp.py b/test/test_mailchimp/test_mailchimp.py index 21f83b0105..07c1aae110 100644 --- a/test/test_mailchimp/test_mailchimp.py +++ b/test/test_mailchimp/test_mailchimp.py @@ -30,12 +30,12 @@ def test_get_lists(self, m): def test_get_members(self, m): # Test that tickets are returned correctly. - m.get(self.mc.uri + 'companies', json=expected_json.test_company) + m.get(self.mc.uri + 'list/zyx/members', json=expected_json.test_members) tbl = self.mc.get_members() @requests_mock.Mocker() def test_get_unsubscribes(self, m): # Test that tickets are returned correctly. - m.get(self.mc.uri + 'contacts', json=expected_json.test_contact) + m.get(self.mc.uri + 'reports/abc/unsubscribes', json=expected_json.test_unsubscribes) tbl = self.mc.get_unsubscribes() From 6c9dcd1a6caa3f843bef5ea36259a2ea404ee117 Mon Sep 17 00:00:00 2001 From: Soren Spicknall Date: Tue, 12 May 2020 16:31:21 -0500 Subject: [PATCH 16/27] Fixed incorrectly formatted booleans --- test/test_mailchimp/expected_json.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/test_mailchimp/expected_json.py b/test/test_mailchimp/expected_json.py index 27d795a45e..6a831ab8b6 100644 --- a/test/test_mailchimp/expected_json.py +++ b/test/test_mailchimp/expected_json.py @@ -148,7 +148,7 @@ "phone": "" }, "permission_reminder": "You are receiving this email because you signed up at an event, while being canvassed, or on our website.", - "use_archive_bar": true, + "use_archive_bar": True, "campaign_defaults": { "from_name": "Our Candidate", "from_email": "our_candidate@example.com", @@ -203,7 +203,7 @@ "phone": "" }, "permission_reminder": "You are receiving this email because you signed up at an event, while being canvassed, or on our website.", - "use_archive_bar": true, + "use_archive_bar": True, "campaign_defaults": { "from_name": "Our Candidate", "from_email": "our_candidate@example.com", From e285ced4787baa4cce6ae9011c559572b35847b5 Mon Sep 17 00:00:00 2001 From: Soren Spicknall Date: Tue, 12 May 2020 16:39:06 -0500 Subject: [PATCH 17/27] Added args for function calls --- test/test_mailchimp/test_mailchimp.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/test_mailchimp/test_mailchimp.py b/test/test_mailchimp/test_mailchimp.py index 07c1aae110..0a4906f6ee 100644 --- a/test/test_mailchimp/test_mailchimp.py +++ b/test/test_mailchimp/test_mailchimp.py @@ -31,11 +31,11 @@ def test_get_members(self, m): # Test that tickets are returned correctly. m.get(self.mc.uri + 'list/zyx/members', json=expected_json.test_members) - tbl = self.mc.get_members() + tbl = self.mc.get_members(list_id='zyx') @requests_mock.Mocker() def test_get_unsubscribes(self, m): # Test that tickets are returned correctly. m.get(self.mc.uri + 'reports/abc/unsubscribes', json=expected_json.test_unsubscribes) - tbl = self.mc.get_unsubscribes() + tbl = self.mc.get_unsubscribes(campaign_id='abc') From e0915ba3c455a36528136f72c44ec24a3652bebf Mon Sep 17 00:00:00 2001 From: Soren Spicknall Date: Tue, 12 May 2020 16:46:27 -0500 Subject: [PATCH 18/27] Fixed references to copy/pasted functions --- test/test_mailchimp/test_mailchimp.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/test/test_mailchimp/test_mailchimp.py b/test/test_mailchimp/test_mailchimp.py index 0a4906f6ee..e22afcf665 100644 --- a/test/test_mailchimp/test_mailchimp.py +++ b/test/test_mailchimp/test_mailchimp.py @@ -15,27 +15,27 @@ def setUp(self): @requests_mock.Mocker() def test_get_campaigns(self, m): - # Test that agents are returned correctly. + # Test that campaigns are returned correctly. m.get(self.mc.uri + 'campaigns', json=expected_json.test_campaigns) tbl = self.mc.get_campaigns() @requests_mock.Mocker() def test_get_lists(self, m): - # Test that tickets are returned correctly. - m.get(self.mc.uri + 'lists', json=expected_json.test_ticket) + # Test that lists are returned correctly. + m.get(self.mc.uri + 'lists', json=expected_json.test_lists) tbl = self.mc.get_lists() @requests_mock.Mocker() def test_get_members(self, m): - # Test that tickets are returned correctly. + # Test that list members are returned correctly. m.get(self.mc.uri + 'list/zyx/members', json=expected_json.test_members) tbl = self.mc.get_members(list_id='zyx') @requests_mock.Mocker() def test_get_unsubscribes(self, m): - # Test that tickets are returned correctly. + # Test that campaign unsubscribes are returned correctly. m.get(self.mc.uri + 'reports/abc/unsubscribes', json=expected_json.test_unsubscribes) tbl = self.mc.get_unsubscribes(campaign_id='abc') From 8a3e794e8e6e424a474e0ce68c8cb311302d22f3 Mon Sep 17 00:00:00 2001 From: Soren Spicknall Date: Wed, 13 May 2020 12:18:11 -0500 Subject: [PATCH 19/27] Testing URI fixes --- test/test_mailchimp/test_mailchimp.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/test_mailchimp/test_mailchimp.py b/test/test_mailchimp/test_mailchimp.py index e22afcf665..aad1e9ba2d 100644 --- a/test/test_mailchimp/test_mailchimp.py +++ b/test/test_mailchimp/test_mailchimp.py @@ -30,12 +30,12 @@ def test_get_lists(self, m): def test_get_members(self, m): # Test that list members are returned correctly. - m.get(self.mc.uri + 'list/zyx/members', json=expected_json.test_members) + m.get(self.mc.uri + 'lists/zyx/members', json=expected_json.test_members) tbl = self.mc.get_members(list_id='zyx') @requests_mock.Mocker() def test_get_unsubscribes(self, m): # Test that campaign unsubscribes are returned correctly. - m.get(self.mc.uri + 'reports/abc/unsubscribes', json=expected_json.test_unsubscribes) + m.get(self.mc.uri + 'reports/abc/unsubscribed', json=expected_json.test_unsubscribes) tbl = self.mc.get_unsubscribes(campaign_id='abc') From 92e27df31bf93e1fc8e675a510f69e7de00aa6b6 Mon Sep 17 00:00:00 2001 From: Soren Spicknall Date: Wed, 13 May 2020 12:31:39 -0500 Subject: [PATCH 20/27] Added members JSON to tests --- test/test_mailchimp/expected_json.py | 101 ++++++++++++++++++++++++++- 1 file changed, 100 insertions(+), 1 deletion(-) diff --git a/test/test_mailchimp/expected_json.py b/test/test_mailchimp/expected_json.py index 6a831ab8b6..d1411c9d68 100644 --- a/test/test_mailchimp/expected_json.py +++ b/test/test_mailchimp/expected_json.py @@ -253,6 +253,105 @@ "_links": [] } -test_members = {} +test_members = { + "members": [ + { + "id": "9eb69db8d0371811aa18803a1ae21584", + "email_address": "member_1@example.com", + "unique_email_id": "c82a25d939", + "web_id": 24816326, + "email_type": "html", + "status": "subscribed", + "merge_fields": { + "FNAME": "Member", + "LNAME": "One", + "ADDRESS": { + "addr1": "", + "addr2": "", + "city": "", + "state": "", + "zip": "", + "country": "US" + }, + "PHONE": "", + "BIRTHDAY": "" + }, + "stats": { + "avg_open_rate": 0.3571, + "avg_click_rate": 0 + }, + "ip_signup": "", + "timestamp_signup": "", + "ip_opt": "174.59.50.35", + "timestamp_opt": "2019-03-25T22:55:44+00:00", + "member_rating": 4, + "last_changed": "2019-03-25T22:55:44+00:00", + "language": "en", + "vip": False, + "email_client": "Gmail", + "location": { + "latitude": 40.0293, + "longitude": -76.2656, + "gmtoff": 0, + "dstoff": 0, + "country_code": "US", + "timezone": "717/223" + }, + "source": "Unknown", + "tags_count": 0, + "tags": [], + "list_id": "67fdf4b1f4", + "_links": [] + }, + { + "id": "4f315641dbad7b74acc0f4a5d3741ac6", + "email_address": "member_2@example.com", + "unique_email_id": "8d308d69d3", + "web_id": 12233445, + "email_type": "html", + "status": "subscribed", + "merge_fields": { + "FNAME": "Member", + "LNAME": "Two", + "ADDRESS": "", + "PHONE": "", + "BIRTHDAY": "" + }, + "stats": { + "avg_open_rate": 0.5, + "avg_click_rate": 0 + }, + "ip_signup": "", + "timestamp_signup": "", + "ip_opt": "174.59.50.35", + "timestamp_opt": "2019-03-25T23:04:46+00:00", + "member_rating": 4, + "last_changed": "2019-03-25T23:04:46+00:00", + "language": "", + "vip": False, + "email_client": "iPhone", + "location": { + "latitude": 40.0459, + "longitude": -76.3542, + "gmtoff": 0, + "dstoff": 0, + "country_code": "US", + "timezone": "717/223" + }, + "source": "Import", + "tags_count": 2, + "tags": [ + { + "id": 17493, + "name": "canvass" + }, + { + "id": 17497, + "name": "canvass-03-17-2019" + } + ], + "list_id": "67fdf4b1f4", + "_links": [] + }]} test_unsubscribes = {} \ No newline at end of file From 489bf2951dc7f27191f99785d123ddba0c4638c9 Mon Sep 17 00:00:00 2001 From: Soren Spicknall Date: Wed, 13 May 2020 12:38:51 -0500 Subject: [PATCH 21/27] Added unsubscribe data to expected JSON --- test/test_mailchimp/expected_json.py | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/test/test_mailchimp/expected_json.py b/test/test_mailchimp/expected_json.py index d1411c9d68..fafc729697 100644 --- a/test/test_mailchimp/expected_json.py +++ b/test/test_mailchimp/expected_json.py @@ -354,4 +354,28 @@ "_links": [] }]} -test_unsubscribes = {} \ No newline at end of file +test_unsubscribes = { + "unsubscribes": [ + { + "email_id": "e542e5cd7b414e5ff8409ff57cf154be", + "email_address": "unsubscribe_1@exmaple.com", + "merge_fields": { + "FNAME": "Unsubscriber", + "LNAME": "One", + "ADDRESS": "", + "PHONE": "5558754307", + "BIRTHDAY": "" + }, + "vip": False, + "timestamp": "2019-12-09T21:18:06+00:00", + "reason": "None given", + "campaign_id": "abc", + "list_id": "zyx", + "list_is_active": True, + "_links": [] + } + ], + "campaign_id": "abc"", + "total_items": 1, + "_links": [] +} \ No newline at end of file From 1fc3958b146e01696acd135ef51538da729d11db Mon Sep 17 00:00:00 2001 From: Soren Spicknall Date: Wed, 13 May 2020 12:44:19 -0500 Subject: [PATCH 22/27] Fixed stray punctuation --- test/test_mailchimp/expected_json.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/test_mailchimp/expected_json.py b/test/test_mailchimp/expected_json.py index fafc729697..30953bc267 100644 --- a/test/test_mailchimp/expected_json.py +++ b/test/test_mailchimp/expected_json.py @@ -375,7 +375,7 @@ "_links": [] } ], - "campaign_id": "abc"", + "campaign_id": "abc, "total_items": 1, "_links": [] } \ No newline at end of file From 278d8f487571fa022818ea2c11941ac84c0c3dd7 Mon Sep 17 00:00:00 2001 From: Soren Spicknall Date: Wed, 13 May 2020 13:11:17 -0500 Subject: [PATCH 23/27] Fixed punctuation --- test/test_mailchimp/expected_json.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/test_mailchimp/expected_json.py b/test/test_mailchimp/expected_json.py index 30953bc267..d8d14fb152 100644 --- a/test/test_mailchimp/expected_json.py +++ b/test/test_mailchimp/expected_json.py @@ -375,7 +375,7 @@ "_links": [] } ], - "campaign_id": "abc, + "campaign_id": "abc", "total_items": 1, "_links": [] } \ No newline at end of file From c4012a461ebb9a2a7b4167094cd20f69ca206dee Mon Sep 17 00:00:00 2001 From: Soren Spicknall Date: Wed, 13 May 2020 13:25:53 -0500 Subject: [PATCH 24/27] Added some basic asserts --- test/test_mailchimp/test_mailchimp.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/test/test_mailchimp/test_mailchimp.py b/test/test_mailchimp/test_mailchimp.py index aad1e9ba2d..6b32e89d64 100644 --- a/test/test_mailchimp/test_mailchimp.py +++ b/test/test_mailchimp/test_mailchimp.py @@ -19,12 +19,16 @@ def test_get_campaigns(self, m): m.get(self.mc.uri + 'campaigns', json=expected_json.test_campaigns) tbl = self.mc.get_campaigns() + self.assertEqual(tbl.num_rows, 2) + @requests_mock.Mocker() def test_get_lists(self, m): # Test that lists are returned correctly. m.get(self.mc.uri + 'lists', json=expected_json.test_lists) - tbl = self.mc.get_lists() + tbl = self.mc.get_lists() + + self.assertEqual(tbl.num_rows, 2) @requests_mock.Mocker() def test_get_members(self, m): @@ -33,9 +37,13 @@ def test_get_members(self, m): m.get(self.mc.uri + 'lists/zyx/members', json=expected_json.test_members) tbl = self.mc.get_members(list_id='zyx') + self.assertEqual(tbl.num_rows, 2) + @requests_mock.Mocker() def test_get_unsubscribes(self, m): # Test that campaign unsubscribes are returned correctly. m.get(self.mc.uri + 'reports/abc/unsubscribed', json=expected_json.test_unsubscribes) tbl = self.mc.get_unsubscribes(campaign_id='abc') + + self.assertEqual(tbl.num_rows, 1) From 16645b31f463d5025a3f2db880f1b4b58c296efb Mon Sep 17 00:00:00 2001 From: Soren Spicknall Date: Wed, 13 May 2020 13:38:05 -0500 Subject: [PATCH 25/27] Added Mailchimp to __init__.py --- parsons/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/parsons/__init__.py b/parsons/__init__.py index c48efe408c..ead8de6d45 100644 --- a/parsons/__init__.py +++ b/parsons/__init__.py @@ -41,6 +41,7 @@ from parsons.newmode.newmode import Newmode from parsons.databases.mysql.mysql import MySQL from parsons.rockthevote.rtv import RockTheVote + from parsons.mailchimp.mailchimp import Mailchimp __all__ = [ 'VAN', From d9e1550ac160852820e42b0b0e6672b7c1266eeb Mon Sep 17 00:00:00 2001 From: Soren Spicknall Date: Wed, 13 May 2020 13:42:49 -0500 Subject: [PATCH 26/27] Return empty tables instead of None --- parsons/mailchimp/mailchimp.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/parsons/mailchimp/mailchimp.py b/parsons/mailchimp/mailchimp.py index 26f12b204e..e994944084 100644 --- a/parsons/mailchimp/mailchimp.py +++ b/parsons/mailchimp/mailchimp.py @@ -101,7 +101,7 @@ def get_lists(self, fields=None, exclude_fields=None, if tbl.num_rows > 0: return tbl else: - return None + return Table() def get_campaigns(self, fields=None, exclude_fields=None, count=None, offset=None, type=None, status=None, @@ -184,7 +184,7 @@ def get_campaigns(self, fields=None, exclude_fields=None, if tbl.num_rows > 0: return tbl else: - return None + return Table() def get_members(self, list_id, fields=None, exclude_fields=None, count=None, offset=None, @@ -301,7 +301,7 @@ def get_members(self, list_id, fields=None, if tbl.num_rows > 0: return tbl else: - return None + return Table() def get_campaign_emails(self, campaign_id, fields=None, exclude_fields=None, count=None, offset=None, @@ -347,7 +347,7 @@ def get_campaign_emails(self, campaign_id, fields=None, if tbl.num_rows > 0: return tbl else: - return None + return Table() def get_unsubscribes(self, campaign_id, fields=None, exclude_fields=None, count=None, offset=None): @@ -388,4 +388,4 @@ def get_unsubscribes(self, campaign_id, fields=None, if tbl.num_rows > 0: return tbl else: - return None + return Table() From 72ba0c2f6bd51a8d36e3335346dc7700432ba65b Mon Sep 17 00:00:00 2001 From: Soren Spicknall Date: Wed, 13 May 2020 13:44:19 -0500 Subject: [PATCH 27/27] Fixed Mailchimp references in __init__.py --- parsons/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/parsons/__init__.py b/parsons/__init__.py index ead8de6d45..a602606bfc 100644 --- a/parsons/__init__.py +++ b/parsons/__init__.py @@ -77,6 +77,7 @@ 'Newmode', 'MySQL', 'RockTheVote', + 'Mailchimp', ] # Define the default logging config for Parsons and its submodules. For now the