Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix intx tests #57

Draft
wants to merge 45 commits into
base: TDL-20256-dict-to-class-based-implementation
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
7f0019f
Added custom exception handling with unit tests
savan-chovatiya Aug 25, 2022
ec5bf8c
Added parameterized in a config.yml
savan-chovatiya Aug 25, 2022
882c6d4
Merge branch 'TDL-20256-dict-to-class-based-implementation' of https:…
savan-chovatiya Aug 26, 2022
20e90c1
made chunk_size configurable
Sep 6, 2022
5a65396
updated config.yml file
Sep 6, 2022
e9fb98b
updated config.yml file
Sep 6, 2022
8f81d06
Removed nunecessary comma
savan-chovatiya Sep 9, 2022
6c53d5e
Revert "made chunk_size configurable"
Sep 12, 2022
99c892a
updated the code to make chunk_size configurable
Sep 12, 2022
0c433ed
Added decorator for backoff to keep consistency across functions
savan-chovatiya Sep 12, 2022
0c80fb9
Resolved pylint error
savan-chovatiya Sep 12, 2022
ab90bc3
Resolved 30 times backoff issue for TImeout error
savan-chovatiya Sep 14, 2022
741ea72
updated Primary Key for reports email activities
Sep 21, 2022
9bc0e1c
updated unittests
Sep 21, 2022
3b3d12b
updated tap-tester test
Sep 21, 2022
5113dad
updated the code and unittests
Sep 22, 2022
797844d
Merge branch 'TDL-7361-make-chunk-size-configurable' of https://githu…
Sep 22, 2022
b521890
updated the unittest
Sep 22, 2022
7f50ec7
updated README file
Sep 22, 2022
aa3e45b
resolved merge conflicts
Sep 22, 2022
c0cde34
updated unittest
Sep 22, 2022
ba97463
added docstring
Sep 22, 2022
ae5665a
resolved merge conflicts
Sep 22, 2022
4d82ec4
resolved merge conflicts
Sep 22, 2022
195d9ae
resolve unittest failure
Sep 22, 2022
f0af474
updated hash creation mechanism
Sep 28, 2022
2f0f40a
resolved merge conflicts
Sep 30, 2022
00d03e0
updated the code of raising quthentication error during discovery mode
Oct 6, 2022
82bc777
Merge branch 'TDL-20256-dict-to-class-based-implementation' into TDL-…
somethingmorerelevant Dec 5, 2022
fb37553
updated retry count for timeout test
somethingmorerelevant Dec 5, 2022
83afe1e
updated test_case assert message
somethingmorerelevant Dec 5, 2022
5fb18a9
Merge branch 'TDL-20256-dict-to-class-based-implementation' into TDL-…
somethingmorerelevant Dec 5, 2022
8af177c
Merge branch 'TDL-20256-dict-to-class-based-implementation' into TDL-…
somethingmorerelevant Dec 8, 2022
bd6daf3
Merge branch 'TDL-20256-dict-to-class-based-implementation' into TDL-…
somethingmorerelevant Dec 8, 2022
dea5cb1
Merge branch
somethingmorerelevant Dec 12, 2022
9545c5c
update mapping key
somethingmorerelevant Dec 13, 2022
23a7173
fixed broken test cases
somethingmorerelevant Dec 14, 2022
9c8484a
Merge remote-tracking branch 'origin/TDL-20258-add-custom-exception-h…
somethingmorerelevant Dec 20, 2022
6dbc857
Merge remote-tracking branch 'origin/TDL-7361-make-chunk-size-configu…
somethingmorerelevant Dec 20, 2022
4190791
Merge remote-tracking branch
somethingmorerelevant Dec 21, 2022
72a6602
fixed review comments and linting issues
somethingmorerelevant Dec 22, 2022
69a6c5e
fixed assert condition for all fields
somethingmorerelevant Dec 22, 2022
02c8fa2
fixes for review comments
somethingmorerelevant Jan 4, 2023
0428be3
updated assert for test_bookmarking
somethingmorerelevant Jan 4, 2023
c2e0f7d
review messages+revert pre-commit changes
somethingmorerelevant Jan 9, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ workflows:
commit:
jobs:
- build:
context:
context:
- circleci-user
- tap-tester-user
build_daily:
Expand All @@ -59,6 +59,6 @@ workflows:
- master
jobs:
- build:
context:
context:
- circleci-user
- tap-tester-user
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ Config properties:
| `start_date` | Y | "2010-01-01T00:00:00Z" | The default start date to use for date modified replication, when available. |
| `user_agent` | N | "Vandelay Industries ETL Runner" | The user agent to send on every request. |
| `request_timeout` | N | 300 | Time for which request should wait to get response. |
| `chunk_size` | N | 100 | The length of a batch for 'reports_email_activity'. |

## Usage

Expand Down
5 changes: 3 additions & 2 deletions tap_mailchimp/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,9 @@ def do_discover(client):
LOGGER.info('Testing authentication')
try:
client.get('/lists', params={'count': 1})
except:
raise Exception('Error testing Mailchimp authentication')
except Exception as e:
msg = 'Error testing Mailchimp authentication. Error: {}: {}'.format(e.__class__.__name__, str(e))
raise Exception(msg) from None

LOGGER.info('Starting discover')
catalog = discover()
Expand Down
137 changes: 122 additions & 15 deletions tap_mailchimp/client.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import functools
import backoff
import requests
import singer
Expand All @@ -7,12 +8,129 @@
LOGGER = singer.get_logger()

REQUEST_TIMEOUT = 300

class ClientRateLimitError(Exception):
pass

class Server5xxError(Exception):
pass

class MailchimpError(Exception):
pass

class MailchimpBadRequestError(MailchimpError):
pass

class MailchimpUnAuthorizedError(MailchimpError):
pass

class MailchimpForbiddenError(MailchimpError):
pass

class MailchimpNotFoundError(MailchimpError):
pass

class MailchimpMethodNotAllowedError(MailchimpError):
pass

class MailchimpNestingError(MailchimpError):
pass

class MailchimpInvalidMethodError(MailchimpError):
pass

class MailchimpUpgradeError(MailchimpError):
pass

class MailchimpRateLimitError(ClientRateLimitError):
pass

class MailchimpInternalServerError(Server5xxError):
pass

# Error glossary: https://mailchimp.com/developer/marketing/docs/errors/
ERROR_CODE_EXCEPTION_MAPPING = {
400: {
"raise_exception": MailchimpBadRequestError,
"message": "Mailchimp Client faced a bad request."
},
401: {
"raise_exception": MailchimpUnAuthorizedError,
"message": "The API key is either invalid or disabled."
},
403: {
"raise_exception": MailchimpForbiddenError,
"message": "User does not have access to the requested operation."
},
404: {
"raise_exception": MailchimpNotFoundError,
"message": "The requested resource could not be found."
},
405: {
"raise_exception": MailchimpMethodNotAllowedError,
"message": "The resource does not accept the HTTP method."
},
414: {
"raise_exception": MailchimpNestingError,
"message": "The sub-resource requested is nested too deeply."
},
422: {
"raise_exception": MailchimpInvalidMethodError,
"message": "You can only use the X-HTTP-Method-Override header with the POST method."
},
426: {
"raise_exception": MailchimpUpgradeError,
"message": "Your request was made with the HTTP protocol. Please make your request via HTTPS rather than HTTP."
},
429: {
"raise_exception": MailchimpRateLimitError,
"message": "You have exceeded the limit of 10 simultaneous connections."
},
500: {
"raise_exception": MailchimpInternalServerError,
"message": "A deep internal error has occurred during the processing of your request."
}}

def get_exception_for_error_code(status_code):
"""Function to retrieve exceptions based on status_code"""

exception = ERROR_CODE_EXCEPTION_MAPPING.get(status_code, {}).get('raise_exception')
if not exception:
exception = Server5xxError if status_code > 500 else MailchimpError
return exception

def raise_for_error(response):
"""Function to raise an error by extracting the message from the error response"""
try:
json_response = response.json()

except Exception:
json_response = {}

status_code = response.status_code
msg = json_response.get(
"detail",
ERROR_CODE_EXCEPTION_MAPPING.get(status_code, {}).get(
"message", "Unknown Error"
)
)

message = "HTTP-error-code: {}, Error: {}".format(status_code, msg)

exc = get_exception_for_error_code(status_code)
raise exc(message) from None

def retry_pattern(fnc):
"""Function for backoff"""
@backoff.on_exception(backoff.expo,
(Server5xxError, MailchimpRateLimitError, ConnectionError, Timeout),
max_tries=6,
factor=3)
@functools.wraps(fnc)
def wrapper(*args, **kwargs):
return fnc(*args, **kwargs)
return wrapper

class MailchimpClient:
def __init__(self, config):
self.__user_agent = config.get('user_agent')
Expand Down Expand Up @@ -45,14 +163,6 @@ def get_base_url(self):
endpoint='base_url')
self.__base_url = data['api_endpoint']

@backoff.on_exception(backoff.expo,
Timeout, # Backoff for request timeout
max_tries=5,
factor=2)
@backoff.on_exception(backoff.expo,
(Server5xxError, ClientRateLimitError, ConnectionError),
max_tries=6,
factor=3)
def request(self, method, path=None, url=None, s3=False, **kwargs):
if url is None and self.__base_url is None:
self.get_base_url()
Expand Down Expand Up @@ -87,21 +197,18 @@ def request(self, method, path=None, url=None, s3=False, **kwargs):
response = self.__session.request(method, url, timeout=self.__request_timeout, **kwargs) # Pass request timeout
timer.tags[metrics.Tag.http_status_code] = response.status_code

if response.status_code >= 500:
raise Server5xxError()

if response.status_code == 429:
raise ClientRateLimitError()

response.raise_for_status()
if response.status_code != 200:
raise_for_error(response)

if s3:
return response

return response.json()

@retry_pattern
def get(self, path, **kwargs):
return self.request('GET', path=path, **kwargs)

@retry_pattern
def post(self, path, **kwargs):
return self.request('POST', path=path, **kwargs)
5 changes: 5 additions & 0 deletions tap_mailchimp/schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,11 @@ def get_schemas():
for replication_key in stream_obj.replication_keys:
mdata_map[('properties', replication_key)]['inclusion'] = 'automatic'

# Update inclusion for extra fields which we need to replicate data
if stream_obj.extra_automatic_fields:
for field in stream_obj.extra_automatic_fields:
mdata_map[('properties', field)]['inclusion'] = 'automatic'

metadata_list = metadata.to_list(mdata_map)
field_metadata[stream_name] = metadata_list

Expand Down
6 changes: 6 additions & 0 deletions tap_mailchimp/schemas/reports_email_activity.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,12 @@
"type": "object",
"additionalProperties": false,
"properties": {
"_sdc_record_hash": {
"type": [
"null",
"string"
]
},
"campaign_id": {
"type": [
"string"
Expand Down
Loading