Skip to content

Commit 3e3f6da

Browse files
committed
Adding auto-throttle support.
1 parent abcc272 commit 3e3f6da

File tree

4 files changed

+95
-6
lines changed

4 files changed

+95
-6
lines changed

intercom/client.py

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,19 @@
11
# -*- coding: utf-8 -*-
22

33
import requests
4+
import time
5+
from datetime import datetime
6+
from pytz import utc
47

58

69
class Client(object):
710

8-
def __init__(self, personal_access_token='my_personal_access_token'):
11+
def __init__(self, personal_access_token='my_personal_access_token', throttle=False):
912
self.personal_access_token = personal_access_token
1013
self.base_url = 'https://api.intercom.io'
1114
self.rate_limit_details = {}
1215
self.http_session = requests.Session()
16+
self.throttle = throttle
1317

1418
@property
1519
def _auth(self):
@@ -81,6 +85,16 @@ def jobs(self):
8185
return job.Job(self)
8286

8387
def _execute_request(self, request, params):
88+
# throttle
89+
if self.throttle and self.rate_limit_details.get('remaining', None) == 0:
90+
# wait until the we've reached the reset time
91+
utcnow = datetime.utcnow().replace(tzinfo=utc)
92+
if utcnow < self.rate_limit_details['reset_at']:
93+
# the delta between the times
94+
delta = self.rate_limit_details['reset_at'] - utcnow
95+
# sleep until one second after the reset
96+
time.sleep(delta.seconds + 1)
97+
8498
result = request.execute(self.base_url, self._auth, params)
8599
self.rate_limit_details = request.rate_limit_details
86100
return result

intercom/request.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,10 @@ def __init__(self, http_method, path, http_session=None):
3333
self.http_session = http_session
3434

3535
def execute(self, base_url, auth, params):
36-
return self.send_request_to_path(base_url, auth, params)
36+
resp = self.send_request_to_path(base_url, auth, params)
37+
parsed_body = self.parse_body(resp)
38+
self.set_rate_limit_details(resp)
39+
return parsed_body
3740

3841
def send_request_to_path(self, base_url, auth, params=None):
3942
""" Construct an API request, send it to the API, and parse the
@@ -81,10 +84,8 @@ def send_request_to_path(self, base_url, auth, params=None):
8184
resp.encoding, resp.status_code)
8285
logger.debug(" content:\n%s", resp.content)
8386

84-
parsed_body = self.parse_body(resp)
8587
self.raise_errors_on_failure(resp)
86-
self.set_rate_limit_details(resp)
87-
return parsed_body
88+
return resp
8889

8990
def parse_body(self, resp):
9091
if resp.content and resp.content.strip():

tests/unit/test_client.py

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
# -*- coding: utf-8 -*- # noqa
2+
3+
import time
4+
import unittest
5+
6+
from datetime import datetime
7+
from datetime import timedelta
8+
from intercom.client import Client
9+
from intercom.request import Request
10+
from mock import Mock
11+
from mock import patch
12+
from nose.tools import istest
13+
from pytz import utc
14+
15+
16+
class ClientTest(unittest.TestCase): # noqa
17+
18+
def setUp(self): # noqa
19+
self.client = Client()
20+
now = datetime.utcnow().replace(tzinfo=utc)
21+
reset_at = now + timedelta(seconds=2)
22+
# set the reset to be in 2 seconds
23+
self.rate_limit_details = {
24+
'remaining': 0,
25+
'limit': 20,
26+
'reset_at': reset_at
27+
}
28+
headers = {
29+
'x-ratelimit-limit': 20,
30+
'x-ratelimit-remaining': 0,
31+
'x-ratelimit-reset': time.mktime(reset_at.timetuple())
32+
}
33+
payload = """{
34+
"user_id": "1224242",
35+
"update_last_request_at": true,
36+
"custom_attributes": {}
37+
}"""
38+
39+
if not hasattr(payload, 'decode'):
40+
# python 3
41+
payload = payload.encode('utf-8')
42+
43+
self.response = Mock(
44+
content=payload,
45+
encoding='utf-8',
46+
headers=headers)
47+
self.started_at = now
48+
49+
@istest
50+
def it_can_control_throttle_on_creation(self): # noqa
51+
client = Client()
52+
self.assertFalse(client.throttle)
53+
client = Client(throttle=True)
54+
self.assertTrue(client.throttle)
55+
56+
@istest
57+
def it_will_sleep_if_throttle_is_on(self): # noqa
58+
client = Client(throttle=True)
59+
client.rate_limit_details = self.rate_limit_details
60+
with patch.object(Request, 'send_request_to_path', return_value=self.response):
61+
# this call should take approximately 2 seconds
62+
client.users.find(email="john@example.com")
63+
finished_at = datetime.utcnow().replace(tzinfo=utc)
64+
self.assertEqual(2, int((finished_at - self.started_at).seconds))
65+
66+
@istest
67+
def it_wont_sleep_if_throttle_is_off(self): # noqa
68+
client = Client()
69+
client.rate_limit_details = self.rate_limit_details
70+
with patch.object(Request, 'send_request_to_path', return_value=self.response):
71+
# this call should take approximately 2 seconds
72+
client.users.find(email="john@example.com")
73+
finished_at = datetime.utcnow().replace(tzinfo=utc)
74+
self.assertEqual(0, int((finished_at - self.started_at).seconds))

tests/unit/test_request.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -327,7 +327,7 @@ def it_needs_encoding_or_apparent_encoding(self):
327327
mock_method.return_value = resp
328328
with assert_raises(TypeError):
329329
request = Request('GET', 'events')
330-
request.send_request_to_path('', ('x', 'y'), resp)
330+
request.execute('', ('x', 'y'), resp)
331331

332332
@istest
333333
def it_allows_the_timeout_to_be_changed(self):

0 commit comments

Comments
 (0)