From 6746a8f779d7f210ea8327aed393c2e336803a2d Mon Sep 17 00:00:00 2001
From: smaeda-ks <irt_m.jrsyo@ntworkers.com>
Date: Sun, 4 Aug 2019 06:54:13 +0900
Subject: [PATCH 1/5] handle rate limit and implement retry logic

---
 tests/test_rate_limit.py  | 106 +++++++++++++++++++++++++++++++++
 tests/test_retry_count.py | 119 ++++++++++++++++++++++++++++++++++++++
 twitter_ads/client.py     |   7 ++-
 twitter_ads/error.py      |  13 +++--
 twitter_ads/http.py       |  43 +++++++++++++-
 5 files changed, 279 insertions(+), 9 deletions(-)
 create mode 100644 tests/test_retry_count.py

diff --git a/tests/test_rate_limit.py b/tests/test_rate_limit.py
index 6a992d9..14c2395 100644
--- a/tests/test_rate_limit.py
+++ b/tests/test_rate_limit.py
@@ -1,5 +1,6 @@
 import responses
 import unittest
+import time
 
 from tests.support import with_resource, with_fixture, characters
 
@@ -10,6 +11,111 @@
 from twitter_ads.http import Request
 from twitter_ads.resource import Resource
 from twitter_ads import API_VERSION
+from twitter_ads.error import RateLimit
+
+
+@responses.activate
+def test_rate_limit_retry():
+    responses.add(responses.GET,
+                  with_resource('/' + API_VERSION + '/accounts/2iqph'),
+                  body=with_fixture('accounts_load'),
+                  content_type='application/json')
+
+    responses.add(responses.GET,
+                  with_resource('/' + API_VERSION + '/accounts/2iqph/campaigns'),
+                  status=429,
+                  body=with_fixture('campaigns_all'),
+                  content_type='application/json',
+                  headers={
+                      'x-account-rate-limit-limit': '10000',
+                      'x-account-rate-limit-remaining': '0',
+                      'x-account-rate-limit-reset': str(int(time.time()) + 5)
+                  })
+
+    responses.add(responses.GET,
+                  with_resource('/' + API_VERSION + '/accounts/2iqph/campaigns'),
+                  status=200,
+                  body=with_fixture('campaigns_all'),
+                  content_type='application/json',
+                  headers={
+                      'x-account-rate-limit-limit': '10000',
+                      'x-account-rate-limit-remaining': '9999',
+                      'x-account-rate-limit-reset': '1546300800'
+                  })
+
+    client = Client(
+        characters(40),
+        characters(40),
+        characters(40),
+        characters(40),
+        options={
+            'handle_rate_limit': True,
+            'limit_test': True
+        }
+    )
+
+    account = Account.load(client, '2iqph')
+
+    cursor = Campaign.all(account)
+    assert len(responses.calls) == 3
+    assert cursor is not None
+    assert isinstance(cursor, Cursor)
+    assert cursor.rate_limit is None
+    assert cursor.account_rate_limit == '10000'
+    assert cursor.account_rate_limit_remaining == '9999'
+    assert cursor.account_rate_limit_reset == '1546300800'
+
+
+@responses.activate
+def test_rate_limit_retry_error():
+    responses.add(responses.GET,
+                  with_resource('/' + API_VERSION + '/accounts/2iqph'),
+                  body=with_fixture('accounts_load'),
+                  content_type='application/json')
+
+    responses.add(responses.GET,
+                  with_resource('/' + API_VERSION + '/accounts/2iqph/campaigns'),
+                  status=429,
+                  body=with_fixture('campaigns_all'),
+                  content_type='application/json',
+                  headers={
+                      'x-account-rate-limit-limit': '10000',
+                      'x-account-rate-limit-remaining': '0',
+                      'x-account-rate-limit-reset': str(int(time.time()) + 5)
+                  })
+
+    responses.add(responses.GET,
+                  with_resource('/' + API_VERSION + '/accounts/2iqph/campaigns'),
+                  status=429,
+                  body=with_fixture('campaigns_all'),
+                  content_type='application/json',
+                  headers={
+                      'x-account-rate-limit-limit': '10000',
+                      'x-account-rate-limit-remaining': '0',
+                      'x-account-rate-limit-reset': '1546300800'
+                  })
+
+    client = Client(
+        characters(40),
+        characters(40),
+        characters(40),
+        characters(40),
+        options={
+            'handle_rate_limit': True,
+            'limit_test': True
+        }
+    )
+
+    account = Account.load(client, '2iqph')
+
+    try:
+        cursor = Campaign.all(account)
+    except Exception as e:
+        error = e
+        print(error)
+    assert len(responses.calls) == 3
+    assert isinstance(error, RateLimit)
+    assert error.reset_at == '1546300800'
 
 
 @responses.activate
diff --git a/tests/test_retry_count.py b/tests/test_retry_count.py
new file mode 100644
index 0000000..b38a2b9
--- /dev/null
+++ b/tests/test_retry_count.py
@@ -0,0 +1,119 @@
+import responses
+import unittest
+import time
+
+from tests.support import with_resource, with_fixture, characters
+
+from twitter_ads.account import Account
+from twitter_ads.campaign import Campaign
+from twitter_ads.client import Client
+from twitter_ads.cursor import Cursor
+from twitter_ads import API_VERSION
+from twitter_ads.error import NotFound
+
+
+@responses.activate
+def test_retry_count_success():
+    responses.add(responses.GET,
+                  with_resource('/' + API_VERSION + '/accounts/2iqph'),
+                  body=with_fixture('accounts_load'),
+                  content_type='application/json')
+
+    responses.add(responses.GET,
+                  with_resource('/' + API_VERSION + '/accounts/2iqph/campaigns'),
+                  status=404,
+                  body=with_fixture('campaigns_all'),
+                  content_type='application/json',
+                  headers={
+                      'x-account-rate-limit-limit': '10000',
+                      'x-account-rate-limit-remaining': '0',
+                      'x-account-rate-limit-reset': str(int(time.time()) + 5)
+                  })
+
+    responses.add(responses.GET,
+                  with_resource('/' + API_VERSION + '/accounts/2iqph/campaigns'),
+                  status=200,
+                  body=with_fixture('campaigns_all'),
+                  content_type='application/json',
+                  headers={
+                      'x-account-rate-limit-limit': '10000',
+                      'x-account-rate-limit-remaining': '9999',
+                      'x-account-rate-limit-reset': '1546300800'
+                  })
+
+    client = Client(
+        characters(40),
+        characters(40),
+        characters(40),
+        characters(40),
+        options={
+            'retry_max': 1,
+            'retry_delay': 3000,
+            'retry_on_status': [404, 500, 503],
+            'limit_test': True
+        }
+    )
+
+    account = Account.load(client, '2iqph')
+
+    cursor = Campaign.all(account)
+    assert len(responses.calls) == 3
+    assert cursor is not None
+    assert isinstance(cursor, Cursor)
+    assert cursor.rate_limit is None
+    assert cursor.account_rate_limit == '10000'
+    assert cursor.account_rate_limit_remaining == '9999'
+    assert cursor.account_rate_limit_reset == '1546300800'
+
+
+@responses.activate
+def test_retry_count_error():
+    responses.add(responses.GET,
+                  with_resource('/' + API_VERSION + '/accounts/2iqph'),
+                  body=with_fixture('accounts_load'),
+                  content_type='application/json')
+
+    responses.add(responses.GET,
+                  with_resource('/' + API_VERSION + '/accounts/2iqph/campaigns'),
+                  status=404,
+                  body=with_fixture('campaigns_all'),
+                  content_type='application/json',
+                  headers={
+                      'x-account-rate-limit-limit': '10000',
+                      'x-account-rate-limit-remaining': '0',
+                      'x-account-rate-limit-reset': str(int(time.time()) + 5)
+                  })
+
+    responses.add(responses.GET,
+                  with_resource('/' + API_VERSION + '/accounts/2iqph/campaigns'),
+                  status=404,
+                  body=with_fixture('campaigns_all'),
+                  content_type='application/json',
+                  headers={
+                      'x-account-rate-limit-limit': '10000',
+                      'x-account-rate-limit-remaining': '9999',
+                      'x-account-rate-limit-reset': '1546300800'
+                  })
+
+    client = Client(
+        characters(40),
+        characters(40),
+        characters(40),
+        characters(40),
+        options={
+            'retry_max': 1,
+            'retry_delay': 3000,
+            'retry_on_status': [404, 500, 503],
+            'limit_test': True
+        }
+    )
+
+    account = Account.load(client, '2iqph')
+
+    try:
+        cursor = Campaign.all(account)
+    except Exception as e:
+        error = e
+        print(error)
+    assert len(responses.calls) == 3
+    assert isinstance(error, NotFound)
diff --git a/twitter_ads/client.py b/twitter_ads/client.py
index 433d1f7..6b91a5c 100644
--- a/twitter_ads/client.py
+++ b/twitter_ads/client.py
@@ -28,7 +28,7 @@ def __init__(self,
         self._consumer_secret = consumer_secret
         self._access_token = access_token
         self._access_token_secret = access_token_secret
-        self._options = kwargs
+        self._options = kwargs.get('options', {})
 
     def __repr__(self):
         return '<{name} object at {mem} consumer_key={key}>'.format(
@@ -37,6 +37,11 @@ def __repr__(self):
             key=getattr(self, 'consumer_key')
         )
 
+    @property
+    def options(self):
+        """Returns the options value."""
+        return self._options
+
     @property
     def consumer_key(self):
         """Returns the consumer_key value."""
diff --git a/twitter_ads/error.py b/twitter_ads/error.py
index 8d84205..82a9256 100644
--- a/twitter_ads/error.py
+++ b/twitter_ads/error.py
@@ -72,17 +72,18 @@ class RateLimit(ClientError):
 
     def __init__(self, response, **kwargs):
         super(RateLimit, self).__init__(response, **kwargs)
-        self._retry_after = response.headers.get('retry-after', None)
-        self._reset_at = response.headers.get('rate_limit_reset', None)
+        account_rate_limit_reset = response.headers.get('x-account-rate-limit-reset')
+        rate_limit_reset = response.headers.get('x-rate-limit-reset')
+
+        if account_rate_limit_reset is not None:
+            self._reset_at = account_rate_limit_reset
+        else:
+            self._reset_at = rate_limit_reset
 
     @property
     def reset_at(self):
         return self._reset_at
 
-    @property
-    def retry_after(self):
-        return self._retry_after
-
 
 class ServerError(Error):
     """Server Error (500)."""
diff --git a/twitter_ads/http.py b/twitter_ads/http.py
index 23d6db9..026b2b3 100644
--- a/twitter_ads/http.py
+++ b/twitter_ads/http.py
@@ -5,8 +5,10 @@
 import sys
 import platform
 import logging
+logger = logging.getLogger(__name__)
 import json
 import zlib
+import time
 
 if sys.version_info[0] != 3:
     import httplib
@@ -77,6 +79,14 @@ def __oauth_request(self):
         files = self.options.get('files', None)
         stream = self.options.get('stream', False)
 
+        handle_rate_limit = self._client.options.get('handle_rate_limit', False)
+        limit_test = self._client.options.get('limit_test', False)  # internal-only
+        retry_max = self._client.options.get('retry_max', 0)
+        retry_delay = self._client.options.get('retry_delay', 1500)
+        retry_on_status = self._client.options.get('retry_on_status', [500, 503])
+        retry_count = 0
+        retry_after = None
+
         consumer = OAuth1Session(
             self._client.consumer_key,
             client_secret=self._client.consumer_secret,
@@ -86,8 +96,37 @@ def __oauth_request(self):
         url = self.__domain() + self._resource
         method = getattr(consumer, self._method)
 
-        response = method(url, headers=headers, data=data, params=params,
-                          files=files, stream=stream)
+        while (retry_count <= retry_max):
+            response = method(url, headers=headers, data=data, params=params,
+                              files=files, stream=stream)
+            # do not retry on 2XX status code
+            if 200 <= response.status_code < 300:
+                break
+
+            if handle_rate_limit and retry_after is None:
+                account_rate_limit_reset = response.headers.get('x-account-rate-limit-reset')
+                rate_limit_reset = response.headers.get('x-rate-limit-reset')
+
+                if account_rate_limit_reset is not None:
+                    rate_limit_reset = int(account_rate_limit_reset)
+                else:
+                    rate_limit_reset = int(rate_limit_reset)
+
+                if response.status_code == 429:
+                    retry_after = rate_limit_reset - int(time.time())
+                    logger.warning("Request reached Rate Limit: resume in %d seconds"
+                                   % retry_after)
+                    if not limit_test:
+                        time.sleep(retry_after + 5)
+                    continue
+
+            if retry_max > 0:
+                if response.status_code not in retry_on_status:
+                    break
+                if not limit_test:
+                    time.sleep(int(retry_delay) / 1000)
+
+            retry_count += 1
 
         raw_response_body = response.raw.read() if stream else response.text
 

From b50c715a705429c94af1c6efbe164e17d0d66e6e Mon Sep 17 00:00:00 2001
From: smaeda-ks <irt_m.jrsyo@ntworkers.com>
Date: Sun, 4 Aug 2019 23:22:10 +0900
Subject: [PATCH 2/5] Add test for client options

---
 tests/test_client.py | 34 +++++++++++++++++++++++++++-------
 1 file changed, 27 insertions(+), 7 deletions(-)

diff --git a/tests/test_client.py b/tests/test_client.py
index ccfc1df..70fbbf3 100644
--- a/tests/test_client.py
+++ b/tests/test_client.py
@@ -1,7 +1,4 @@
 # Copyright (C) 2015 Twitter, Inc.
-
-import pytest
-import requests_oauthlib
 import responses
 
 from tests.support import with_resource, with_fixture, characters
@@ -11,11 +8,12 @@
 from twitter_ads.cursor import Cursor
 from twitter_ads import API_VERSION
 
+
 @responses.activate
 def test_accounts_with_no_id():
     responses.add(responses.GET, with_resource('/' + API_VERSION + '/accounts'),
-                                 body=with_fixture('accounts_all'),
-                                 content_type='application/json')
+                  body=with_fixture('accounts_all'),
+                  content_type='application/json')
 
     client = Client(
         characters(40),
@@ -29,11 +27,12 @@ def test_accounts_with_no_id():
     assert isinstance(cursor, Cursor)
     assert cursor.count == 5
 
+
 @responses.activate
 def test_accounts_with_id():
     responses.add(responses.GET, with_resource('/' + API_VERSION + '/accounts/2iqph'),
-                                 body=with_fixture('accounts_load'),
-                                 content_type='application/json')
+                  body=with_fixture('accounts_load'),
+                  content_type='application/json')
 
     client = Client(
         characters(40),
@@ -46,3 +45,24 @@ def test_accounts_with_id():
     assert account is not None
     assert isinstance(account, Account)
     assert account.id == '2iqph'
+
+
+@responses.activate
+def test_accounts_with_options():
+
+    client = Client(
+        characters(40),
+        characters(40),
+        characters(40),
+        characters(40),
+        options={
+            'handle_rate_limit': True,
+            'retry_max': 1,
+            'retry_delay': 3000,
+            'retry_on_status': [404, 500, 503]
+        }
+    )
+
+    assert client is not None
+    assert isinstance(client, Client)
+    assert len(client.options) == 4

From 812e41618aa6ecdd477946386c6fc05538fc8a06 Mon Sep 17 00:00:00 2001
From: smaeda-ks <irt_m.jrsyo@ntworkers.com>
Date: Sun, 4 Aug 2019 23:35:21 +0900
Subject: [PATCH 3/5] Add more test cases for rate limit & retry options

---
 tests/test_rate_limit.py | 140 ++++++++++++++++++++++++++++++++++++++-
 1 file changed, 138 insertions(+), 2 deletions(-)

diff --git a/tests/test_rate_limit.py b/tests/test_rate_limit.py
index 14c2395..f09dd4c 100644
--- a/tests/test_rate_limit.py
+++ b/tests/test_rate_limit.py
@@ -15,7 +15,143 @@
 
 
 @responses.activate
-def test_rate_limit_retry():
+def test_rate_limit_handle_with_retry_success_1():
+    # scenario:
+    #  - 500 (retry) -> 429 (handle rate limit) -> 200 (end)
+    responses.add(responses.GET,
+                  with_resource('/' + API_VERSION + '/accounts/2iqph'),
+                  body=with_fixture('accounts_load'),
+                  content_type='application/json')
+
+    responses.add(responses.GET,
+                  with_resource('/' + API_VERSION + '/accounts/2iqph/campaigns'),
+                  status=500,
+                  body=with_fixture('campaigns_all'),
+                  content_type='application/json',
+                  headers={
+                      'x-account-rate-limit-limit': '10000',
+                      'x-account-rate-limit-remaining': '0',
+                      'x-account-rate-limit-reset': '1546300800'
+                  })
+
+    responses.add(responses.GET,
+                  with_resource('/' + API_VERSION + '/accounts/2iqph/campaigns'),
+                  status=429,
+                  body=with_fixture('campaigns_all'),
+                  content_type='application/json',
+                  headers={
+                      'x-account-rate-limit-limit': '10000',
+                      'x-account-rate-limit-remaining': '0',
+                      'x-account-rate-limit-reset': str(int(time.time()) + 5)
+                  })
+
+    responses.add(responses.GET,
+                  with_resource('/' + API_VERSION + '/accounts/2iqph/campaigns'),
+                  status=200,
+                  body=with_fixture('campaigns_all'),
+                  content_type='application/json',
+                  headers={
+                      'x-account-rate-limit-limit': '10000',
+                      'x-account-rate-limit-remaining': '9999',
+                      'x-account-rate-limit-reset': '1546300800'
+                  })
+
+    client = Client(
+        characters(40),
+        characters(40),
+        characters(40),
+        characters(40),
+        options={
+            'handle_rate_limit': True,
+            'retry_max': 1,
+            'retry_delay': 3000,
+            'retry_on_status': [500],
+            'limit_test': True
+        }
+    )
+
+    account = Account.load(client, '2iqph')
+
+    cursor = Campaign.all(account)
+    assert len(responses.calls) == 4
+    assert cursor is not None
+    assert isinstance(cursor, Cursor)
+    assert cursor.rate_limit is None
+    assert cursor.account_rate_limit == '10000'
+    assert cursor.account_rate_limit_remaining == '9999'
+    assert cursor.account_rate_limit_reset == '1546300800'
+
+
+@responses.activate
+def test_rate_limit_handle_with_retry_success_2():
+    # scenario:
+    #  - 429 (handle rate limit) -> 500 (retry) -> 200 (end)
+    responses.add(responses.GET,
+                  with_resource('/' + API_VERSION + '/accounts/2iqph'),
+                  body=with_fixture('accounts_load'),
+                  content_type='application/json')
+
+    responses.add(responses.GET,
+                  with_resource('/' + API_VERSION + '/accounts/2iqph/campaigns'),
+                  status=429,
+                  body=with_fixture('campaigns_all'),
+                  content_type='application/json',
+                  headers={
+                      'x-account-rate-limit-limit': '10000',
+                      'x-account-rate-limit-remaining': '0',
+                      'x-account-rate-limit-reset': '1546300800'
+                  })
+
+    responses.add(responses.GET,
+                  with_resource('/' + API_VERSION + '/accounts/2iqph/campaigns'),
+                  status=500,
+                  body=with_fixture('campaigns_all'),
+                  content_type='application/json',
+                  headers={
+                      'x-account-rate-limit-limit': '10000',
+                      'x-account-rate-limit-remaining': '0',
+                      'x-account-rate-limit-reset': str(int(time.time()) + 5)
+                  })
+
+    responses.add(responses.GET,
+                  with_resource('/' + API_VERSION + '/accounts/2iqph/campaigns'),
+                  status=200,
+                  body=with_fixture('campaigns_all'),
+                  content_type='application/json',
+                  headers={
+                      'x-account-rate-limit-limit': '10000',
+                      'x-account-rate-limit-remaining': '9999',
+                      'x-account-rate-limit-reset': '1546300800'
+                  })
+
+    client = Client(
+        characters(40),
+        characters(40),
+        characters(40),
+        characters(40),
+        options={
+            'handle_rate_limit': True,
+            'retry_max': 1,
+            'retry_delay': 3000,
+            'retry_on_status': [500],
+            'limit_test': True
+        }
+    )
+
+    account = Account.load(client, '2iqph')
+
+    cursor = Campaign.all(account)
+    assert len(responses.calls) == 4
+    assert cursor is not None
+    assert isinstance(cursor, Cursor)
+    assert cursor.rate_limit is None
+    assert cursor.account_rate_limit == '10000'
+    assert cursor.account_rate_limit_remaining == '9999'
+    assert cursor.account_rate_limit_reset == '1546300800'
+
+
+@responses.activate
+def test_rate_limit_handle_success():
     responses.add(responses.GET,
                   with_resource('/' + API_VERSION + '/accounts/2iqph'),
                   body=with_fixture('accounts_load'),
@@ -67,7 +203,7 @@ def test_rate_limit_retry():
 
 
 @responses.activate
-def test_rate_limit_retry_error():
+def test_rate_limit_handle_error():
     responses.add(responses.GET,
                   with_resource('/' + API_VERSION + '/accounts/2iqph'),
                   body=with_fixture('accounts_load'),

From ed17e5343608359aed7a36dc8acc65288e9bdea1 Mon Sep 17 00:00:00 2001
From: smaeda-ks <irt_m.jrsyo@ntworkers.com>
Date: Thu, 8 Aug 2019 14:45:46 +0900
Subject: [PATCH 4/5] Use monkeypatch to stub sleep

---
 tests/test_rate_limit.py  | 28 ++++++++++++++++------------
 tests/test_retry_count.py | 14 ++++++++------
 twitter_ads/http.py       |  7 ++-----
 3 files changed, 26 insertions(+), 23 deletions(-)

diff --git a/tests/test_rate_limit.py b/tests/test_rate_limit.py
index f09dd4c..3e89e05 100644
--- a/tests/test_rate_limit.py
+++ b/tests/test_rate_limit.py
@@ -15,9 +15,11 @@
 
 
 @responses.activate
-def test_rate_limit_handle_with_retry_success_1():
+def test_rate_limit_handle_with_retry_success_1(monkeypatch):
     # scenario:
     #  - 500 (retry) -> 429 (handle rate limit) -> 200 (end)
+    monkeypatch.setattr(time, 'sleep', lambda s: None)
+
     responses.add(responses.GET,
                   with_resource('/' + API_VERSION + '/accounts/2iqph'),
                   body=with_fixture('accounts_load'),
@@ -65,8 +67,7 @@ def test_rate_limit_handle_with_retry_success_1():
             'handle_rate_limit': True,
             'retry_max': 1,
             'retry_delay': 3000,
-            'retry_on_status': [500],
-            'limit_test': True
+            'retry_on_status': [500]
         }
     )
 
@@ -83,9 +84,11 @@ def test_rate_limit_handle_with_retry_success_1():
 
 
 @responses.activate
-def test_rate_limit_handle_with_retry_success_2():
+def test_rate_limit_handle_with_retry_success_2(monkeypatch):
     # scenario:
     #  - 429 (handle rate limit) -> 500 (retry) -> 200 (end)
+    monkeypatch.setattr(time, 'sleep', lambda s: None)
+
     responses.add(responses.GET,
                   with_resource('/' + API_VERSION + '/accounts/2iqph'),
                   body=with_fixture('accounts_load'),
@@ -133,8 +136,7 @@ def test_rate_limit_handle_with_retry_success_2():
             'handle_rate_limit': True,
             'retry_max': 1,
             'retry_delay': 3000,
-            'retry_on_status': [500],
-            'limit_test': True
+            'retry_on_status': [500]
         }
     )
 
@@ -151,7 +153,9 @@ def test_rate_limit_handle_with_retry_success_2():
 
 
 @responses.activate
-def test_rate_limit_handle_success():
+def test_rate_limit_handle_success(monkeypatch):
+    monkeypatch.setattr(time, 'sleep', lambda s: None)
+
     responses.add(responses.GET,
                   with_resource('/' + API_VERSION + '/accounts/2iqph'),
                   body=with_fixture('accounts_load'),
@@ -185,8 +189,7 @@ def test_rate_limit_handle_success():
         characters(40),
         characters(40),
         options={
-            'handle_rate_limit': True,
-            'limit_test': True
+            'handle_rate_limit': True
         }
     )
 
@@ -203,7 +206,9 @@ def test_rate_limit_handle_success():
 
 
 @responses.activate
-def test_rate_limit_handle_error():
+def test_rate_limit_handle_error(monkeypatch):
+    monkeypatch.setattr(time, 'sleep', lambda s: None)
+
     responses.add(responses.GET,
                   with_resource('/' + API_VERSION + '/accounts/2iqph'),
                   body=with_fixture('accounts_load'),
@@ -237,8 +242,7 @@ def test_rate_limit_handle_error():
         characters(40),
         characters(40),
         options={
-            'handle_rate_limit': True,
-            'limit_test': True
+            'handle_rate_limit': True
         }
     )
 
diff --git a/tests/test_retry_count.py b/tests/test_retry_count.py
index b38a2b9..270126d 100644
--- a/tests/test_retry_count.py
+++ b/tests/test_retry_count.py
@@ -13,7 +13,9 @@
 
 
 @responses.activate
-def test_retry_count_success():
+def test_retry_count_success(monkeypatch):
+    monkeypatch.setattr(time, 'sleep', lambda s: None)
+
     responses.add(responses.GET,
                   with_resource('/' + API_VERSION + '/accounts/2iqph'),
                   body=with_fixture('accounts_load'),
@@ -49,8 +51,7 @@ def test_retry_count_success():
         options={
             'retry_max': 1,
             'retry_delay': 3000,
-            'retry_on_status': [404, 500, 503],
-            'limit_test': True
+            'retry_on_status': [404, 500, 503]
         }
     )
 
@@ -67,7 +68,9 @@ def test_retry_count_success():
 
 
 @responses.activate
-def test_retry_count_error():
+def test_retry_count_error(monkeypatch):
+    monkeypatch.setattr(time, 'sleep', lambda s: None)
+
     responses.add(responses.GET,
                   with_resource('/' + API_VERSION + '/accounts/2iqph'),
                   body=with_fixture('accounts_load'),
@@ -103,8 +106,7 @@ def test_retry_count_error():
         options={
             'retry_max': 1,
             'retry_delay': 3000,
-            'retry_on_status': [404, 500, 503],
-            'limit_test': True
+            'retry_on_status': [404, 500, 503]
         }
     )
 
diff --git a/twitter_ads/http.py b/twitter_ads/http.py
index 026b2b3..0a198ba 100644
--- a/twitter_ads/http.py
+++ b/twitter_ads/http.py
@@ -80,7 +80,6 @@ def __oauth_request(self):
         stream = self.options.get('stream', False)
 
         handle_rate_limit = self._client.options.get('handle_rate_limit', False)
-        limit_test = self._client.options.get('limit_test', False)  # internal-only
         retry_max = self._client.options.get('retry_max', 0)
         retry_delay = self._client.options.get('retry_delay', 1500)
         retry_on_status = self._client.options.get('retry_on_status', [500, 503])
@@ -116,15 +115,13 @@ def __oauth_request(self):
                     retry_after = rate_limit_reset - int(time.time())
                     logger.warning("Request reached Rate Limit: resume in %d seconds"
                                    % retry_after)
-                    if not limit_test:
-                        time.sleep(retry_after + 5)
+                    time.sleep(retry_after + 5)
                     continue
 
             if retry_max > 0:
                 if response.status_code not in retry_on_status:
                     break
-                if not limit_test:
-                    time.sleep(int(retry_delay) / 1000)
+                time.sleep(int(retry_delay) / 1000)
 
             retry_count += 1
 

From b5843022fe61490b59bffe5a2e3f25b20e978e24 Mon Sep 17 00:00:00 2001
From: smaeda-ks <irt_m.jrsyo@ntworkers.com>
Date: Fri, 9 Aug 2019 13:20:11 +0900
Subject: [PATCH 5/5] Do not use unnecessary temp variables

---
 twitter_ads/error.py |  9 ++-------
 twitter_ads/http.py  | 11 +++--------
 2 files changed, 5 insertions(+), 15 deletions(-)

diff --git a/twitter_ads/error.py b/twitter_ads/error.py
index 82a9256..65df9f6 100644
--- a/twitter_ads/error.py
+++ b/twitter_ads/error.py
@@ -72,13 +72,8 @@ class RateLimit(ClientError):
 
     def __init__(self, response, **kwargs):
         super(RateLimit, self).__init__(response, **kwargs)
-        account_rate_limit_reset = response.headers.get('x-account-rate-limit-reset')
-        rate_limit_reset = response.headers.get('x-rate-limit-reset')
-
-        if account_rate_limit_reset is not None:
-            self._reset_at = account_rate_limit_reset
-        else:
-            self._reset_at = rate_limit_reset
+        self._reset_at = response.headers.get('x-account-rate-limit-reset')\
+            or response.headers.get('x-rate-limit-reset')
 
     @property
     def reset_at(self):
diff --git a/twitter_ads/http.py b/twitter_ads/http.py
index 0a198ba..c49a434 100644
--- a/twitter_ads/http.py
+++ b/twitter_ads/http.py
@@ -103,16 +103,11 @@ def __oauth_request(self):
                 break
 
             if handle_rate_limit and retry_after is None:
-                account_rate_limit_reset = response.headers.get('x-account-rate-limit-reset')
-                rate_limit_reset = response.headers.get('x-rate-limit-reset')
-
-                if account_rate_limit_reset is not None:
-                    rate_limit_reset = int(account_rate_limit_reset)
-                else:
-                    rate_limit_reset = int(rate_limit_reset)
+                rate_limit_reset = response.headers.get('x-account-rate-limit-reset') \
+                    or response.headers.get('x-rate-limit-reset')
 
                 if response.status_code == 429:
-                    retry_after = rate_limit_reset - int(time.time())
+                    retry_after = int(rate_limit_reset) - int(time.time())
                     logger.warning("Request reached Rate Limit: resume in %d seconds"
                                    % retry_after)
                     time.sleep(retry_after + 5)
