Skip to content

Commit 00a9d3b

Browse files
committed
reworks rate limiting, adds tests for same, inline docs
1 parent 139f251 commit 00a9d3b

5 files changed

Lines changed: 438 additions & 23 deletions

File tree

doc/twitter.rst

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,11 @@ twitter.api
3535
:undoc-members:
3636
:show-inheritance:
3737

38+
.. automodule:: twitter.ratelimit
39+
:members:
40+
:undoc-members:
41+
:show-inheritance:
42+
3843
.. automodule:: twitter.status
3944
:members:
4045
:undoc-members:

testdata/ratelimit.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
{"resources": {"help": {"/help/privacy": {"limit": 15,"remaining": 15,"reset": 1452254278},"/help/configuration": {"limit": 15,"remaining": 15,"reset": 1452254278},"/help/settings": {"limit": 15,"remaining": 15,"reset": 1452254278},"/help/tos": {"limit": 15,"remaining": 15,"reset": 1452254278},"/help/languages": {"limit": 15,"remaining": 15,"reset": 1452254278}},"favorites": {"/favorites/list": {"limit": 15,"remaining": 15,"reset": 1452254278}},"geo": {"/geo/similar_places": {"limit": 15,"remaining": 15,"reset": 1452254278},"/geo/id/:place_id": {"limit": 15,"remaining": 15,"reset": 1452254278},"/geo/reverse_geocode": {"limit": 15,"remaining": 15,"reset": 1452254278},"/geo/search": {"limit": 15,"remaining": 15,"reset": 1452254278}},"users": {"/users/suggestions/:slug/members": {"limit": 15,"remaining": 15,"reset": 1452254278},"/users/profile_banner": {"limit": 180,"remaining": 180,"reset": 1452254278},"/users/suggestions/:slug": {"limit": 15,"remaining": 15,"reset": 1452254278},"/users/derived_info": {"limit": 15,"remaining": 15,"reset": 1452254278},"/users/show/:id": {"limit": 181,"remaining": 181,"reset": 1452254278},"/users/lookup": {"limit": 180,"remaining": 180,"reset": 1452254278},"/users/search": {"limit": 180,"remaining": 180,"reset": 1452254278},"/users/suggestions": {"limit": 15,"remaining": 15,"reset": 1452254278},"/users/report_spam": {"limit": 15,"remaining": 15,"reset": 1452254278}},"application": {"/application/rate_limit_status": {"limit": 180,"remaining": 177,"reset": 1452253438}},"moments": {"/moments/permissions": {"limit": 300,"remaining": 300,"reset": 1452254278}},"media": {"/media/upload": {"limit": 500,"remaining": 500,"reset": 1452254278}},"followers": {"/followers/ids": {"limit": 15,"remaining": 15,"reset": 1452254278},"/followers/list": {"limit": 15,"remaining": 15,"reset": 1452254278}},"direct_messages": {"/direct_messages/sent_and_received": {"limit": 15,"remaining": 15,"reset": 1452254278},"/direct_messages": {"limit": 15,"remaining": 15,"reset": 1452254278},"/direct_messages/sent": {"limit": 15,"remaining": 15,"reset": 1452254278},"/direct_messages/show": {"limit": 15,"remaining": 15,"reset": 1452254278}},"account": {"/account/login_verification_enrollment": {"limit": 15,"remaining": 15,"reset": 1452254278},"/account/verify_credentials": {"limit": 15,"remaining": 15,"reset": 1452254278},"/account/update_profile": {"limit": 15,"remaining": 15,"reset": 1452254278},"/account/settings": {"limit": 15,"remaining": 15,"reset": 1452254278}},"lists": {"/lists/subscriptions": {"limit": 15,"remaining": 15,"reset": 1452254278},"/lists/members": {"limit": 180,"remaining": 180,"reset": 1452254278},"/lists/memberships": {"limit": 15,"remaining": 15,"reset": 1452254278},"/lists/list": {"limit": 15,"remaining": 15,"reset": 1452254278},"/lists/members/show": {"limit": 15,"remaining": 15,"reset": 1452254278},"/lists/subscribers": {"limit": 180,"remaining": 180,"reset": 1452254278},"/lists/ownerships": {"limit": 15,"remaining": 15,"reset": 1452254278},"/lists/show": {"limit": 15,"remaining": 15,"reset": 1452254278},"/lists/statuses": {"limit": 180,"remaining": 180,"reset": 1452254278},"/lists/subscribers/show": {"limit": 15,"remaining": 15,"reset": 1452254278}},"friendships": {"/friendships/no_retweets/ids": {"limit": 15,"remaining": 15,"reset": 1452254278},"/friendships/incoming": {"limit": 15,"remaining": 15,"reset": 1452254278},"/friendships/outgoing": {"limit": 15,"remaining": 15,"reset": 1452254278},"/friendships/lookup": {"limit": 15,"remaining": 15,"reset": 1452254278},"/friendships/show": {"limit": 180,"remaining": 180,"reset": 1452254278}},"saved_searches": {"/saved_searches/list": {"limit": 15,"remaining": 15,"reset": 1452254278},"/saved_searches/destroy/:id": {"limit": 15,"remaining": 15,"reset": 1452254278},"/saved_searches/show/:id": {"limit": 15,"remaining": 15,"reset": 1452254278}},"contacts": {"/contacts/addressbook": {"limit": 300,"remaining": 300,"reset": 1452254278},"/contacts/users_and_uploaded_by": {"limit": 300,"remaining": 300,"reset": 1452254278},"/contacts/delete/status": {"limit": 300,"remaining": 300,"reset": 1452254278},"/contacts/users": {"limit": 300,"remaining": 300,"reset": 1452254278},"/contacts/uploaded_by": {"limit": 300,"remaining": 300,"reset": 1452254278}},"statuses": {"/statuses/retweets_of_me": {"limit": 15,"remaining": 15,"reset": 1452254278},"/statuses/lookup": {"limit": 180,"remaining": 180,"reset": 1452254278},"/statuses/friends": {"limit": 15,"remaining": 15,"reset": 1452254278},"/statuses/retweets/:id": {"limit": 60,"remaining": 60,"reset": 1452254278},"/statuses/mentions_timeline": {"limit": 15,"remaining": 15,"reset": 1452254278},"/statuses/show/:id": {"limit": 180,"remaining": 180,"reset": 1452254278},"/statuses/home_timeline": {"limit": 15,"remaining": 15,"reset": 1452254278},"/statuses/retweeters/ids": {"limit": 15,"remaining": 15,"reset": 1452254278},"/statuses/oembed": {"limit": 180,"remaining": 180,"reset": 1452254278},"/statuses/user_timeline": {"limit": 180,"remaining": 180,"reset": 1452254278}},"mutes": {"/mutes/users/ids": {"limit": 15,"remaining": 15,"reset": 1452254278},"/mutes/users/list": {"limit": 15,"remaining": 15,"reset": 1452254278}},"friends": {"/friends/ids": {"limit": 15,"remaining": 11,"reset": 1452253438},"/friends/following/ids": {"limit": 15,"remaining": 15,"reset": 1452254278},"/friends/following/list": {"limit": 15,"remaining": 15,"reset": 1452254278},"/friends/list": {"limit": 15,"remaining": 15,"reset": 1452254278}},"blocks": {"/blocks/list": {"limit": 15,"remaining": 15,"reset": 1452254278},"/blocks/ids": {"limit": 15,"remaining": 15,"reset": 1452254278}},"search": {"/search/tweets": {"limit": 180,"remaining": 180,"reset": 1452254278}},"trends": {"/trends/place": {"limit": 15,"remaining": 15,"reset": 1452254278},"/trends/available": {"limit": 15,"remaining": 15,"reset": 1452254278},"/trends/closest": {"limit": 15,"remaining": 15,"reset": 1452254278}},"device": {"/device/token": {"limit": 15,"remaining": 15,"reset": 1452254278}},"collections": {"/collections/list": {"limit": 1000,"remaining": 1000,"reset": 1452254278},"/collections/show": {"limit": 1000,"remaining": 1000,"reset": 1452254278},"/collections/entries": {"limit": 1000,"remaining": 1000,"reset": 1452254278}}},"rate_limit_context": {"access_token": "4012966701-SVdwWUOaArQBOsy6UI6ejAIszrsR56uG2wDWDnN"}}
1+
{"resources": {"help": {"/help/privacy": {"limit": 15,"remaining": 15,"reset": 1452254278},"/help/configuration": {"limit": 15,"remaining": 15,"reset": 1452254278},"/help/settings": {"limit": 15,"remaining": 15,"reset": 1452254278},"/help/tos": {"limit": 15,"remaining": 15,"reset": 1452254278},"/help/languages": {"limit": 15,"remaining": 15,"reset": 1452254278}},"favorites": {"/favorites/list": {"limit": 15,"remaining": 15,"reset": 1452254278}},"geo": {"/geo/similar_places": {"limit": 15,"remaining": 15,"reset": 1452254278},"/geo/id/:place_id": {"limit": 47,"remaining": 47,"reset": 1452254278},"/geo/reverse_geocode": {"limit": 15,"remaining": 15,"reset": 1452254278},"/geo/search": {"limit": 15,"remaining": 15,"reset": 1452254278}},"users": {"/users/suggestions/:slug/members": {"limit": 15,"remaining": 15,"reset": 1452254278},"/users/profile_banner": {"limit": 180,"remaining": 180,"reset": 1452254278},"/users/suggestions/:slug": {"limit": 15,"remaining": 15,"reset": 1452254278},"/users/derived_info": {"limit": 15,"remaining": 15,"reset": 1452254278},"/users/show/:id": {"limit": 181,"remaining": 181,"reset": 1452254278},"/users/lookup": {"limit": 180,"remaining": 180,"reset": 1452254278},"/users/search": {"limit": 180,"remaining": 180,"reset": 1452254278},"/users/suggestions": {"limit": 15,"remaining": 15,"reset": 1452254278},"/users/report_spam": {"limit": 15,"remaining": 15,"reset": 1452254278}},"application": {"/application/rate_limit_status": {"limit": 180,"remaining": 177,"reset": 1452253438}},"moments": {"/moments/permissions": {"limit": 300,"remaining": 300,"reset": 1452254278}},"media": {"/media/upload": {"limit": 500,"remaining": 500,"reset": 1452254278}},"followers": {"/followers/ids": {"limit": 15,"remaining": 15,"reset": 1452254278},"/followers/list": {"limit": 15,"remaining": 15,"reset": 1452254278}},"direct_messages": {"/direct_messages/sent_and_received": {"limit": 15,"remaining": 15,"reset": 1452254278},"/direct_messages": {"limit": 15,"remaining": 15,"reset": 1452254278},"/direct_messages/sent": {"limit": 15,"remaining": 15,"reset": 1452254278},"/direct_messages/show": {"limit": 15,"remaining": 15,"reset": 1452254278}},"account": {"/account/login_verification_enrollment": {"limit": 15,"remaining": 15,"reset": 1452254278},"/account/verify_credentials": {"limit": 15,"remaining": 15,"reset": 1452254278},"/account/update_profile": {"limit": 15,"remaining": 15,"reset": 1452254278},"/account/settings": {"limit": 15,"remaining": 15,"reset": 1452254278}},"lists": {"/lists/subscriptions": {"limit": 15,"remaining": 15,"reset": 1452254278},"/lists/members": {"limit": 180,"remaining": 180,"reset": 1452254278},"/lists/memberships": {"limit": 15,"remaining": 15,"reset": 1452254278},"/lists/list": {"limit": 15,"remaining": 15,"reset": 1452254278},"/lists/members/show": {"limit": 15,"remaining": 15,"reset": 1452254278},"/lists/subscribers": {"limit": 180,"remaining": 180,"reset": 1452254278},"/lists/ownerships": {"limit": 15,"remaining": 15,"reset": 1452254278},"/lists/show": {"limit": 15,"remaining": 15,"reset": 1452254278},"/lists/statuses": {"limit": 180,"remaining": 180,"reset": 1452254278},"/lists/subscribers/show": {"limit": 15,"remaining": 15,"reset": 1452254278}},"friendships": {"/friendships/no_retweets/ids": {"limit": 15,"remaining": 15,"reset": 1452254278},"/friendships/incoming": {"limit": 15,"remaining": 15,"reset": 1452254278},"/friendships/outgoing": {"limit": 15,"remaining": 15,"reset": 1452254278},"/friendships/lookup": {"limit": 15,"remaining": 15,"reset": 1452254278},"/friendships/show": {"limit": 180,"remaining": 180,"reset": 1452254278}},"saved_searches": {"/saved_searches/list": {"limit": 15,"remaining": 15,"reset": 1452254278},"/saved_searches/destroy/:id": {"limit": 15,"remaining": 15,"reset": 1452254278},"/saved_searches/show/:id": {"limit": 15,"remaining": 15,"reset": 1452254278}},"contacts": {"/contacts/addressbook": {"limit": 300,"remaining": 300,"reset": 1452254278},"/contacts/users_and_uploaded_by": {"limit": 300,"remaining": 300,"reset": 1452254278},"/contacts/delete/status": {"limit": 300,"remaining": 300,"reset": 1452254278},"/contacts/users": {"limit": 300,"remaining": 300,"reset": 1452254278},"/contacts/uploaded_by": {"limit": 300,"remaining": 300,"reset": 1452254278}},"statuses": {"/statuses/retweets_of_me": {"limit": 15,"remaining": 15,"reset": 1452254278},"/statuses/lookup": {"limit": 180,"remaining": 180,"reset": 1452254278},"/statuses/friends": {"limit": 15,"remaining": 15,"reset": 1452254278},"/statuses/retweets/:id": {"limit": 23,"remaining": 23,"reset": 1452254278},"/statuses/mentions_timeline": {"limit": 15,"remaining": 15,"reset": 1452254278},"/statuses/show/:id": {"limit": 180,"remaining": 180,"reset": 1452254278},"/statuses/home_timeline": {"limit": 15,"remaining": 15,"reset": 1452254278},"/statuses/retweeters/ids": {"limit": 15,"remaining": 15,"reset": 1452254278},"/statuses/oembed": {"limit": 180,"remaining": 180,"reset": 1452254278},"/statuses/user_timeline": {"limit": 180,"remaining": 180,"reset": 1452254278}},"mutes": {"/mutes/users/ids": {"limit": 15,"remaining": 15,"reset": 1452254278},"/mutes/users/list": {"limit": 15,"remaining": 15,"reset": 1452254278}},"friends": {"/friends/ids": {"limit": 15,"remaining": 11,"reset": 1452253438},"/friends/following/ids": {"limit": 15,"remaining": 15,"reset": 1452254278},"/friends/following/list": {"limit": 15,"remaining": 15,"reset": 1452254278},"/friends/list": {"limit": 15,"remaining": 15,"reset": 1452254278}},"blocks": {"/blocks/list": {"limit": 15,"remaining": 15,"reset": 1452254278},"/blocks/ids": {"limit": 15,"remaining": 15,"reset": 1452254278}},"search": {"/search/tweets": {"limit": 180,"remaining": 180,"reset": 1452254278}},"trends": {"/trends/place": {"limit": 15,"remaining": 15,"reset": 1452254278},"/trends/available": {"limit": 15,"remaining": 15,"reset": 1452254278},"/trends/closest": {"limit": 15,"remaining": 15,"reset": 1452254278}},"device": {"/device/token": {"limit": 15,"remaining": 15,"reset": 1452254278}},"collections": {"/collections/list": {"limit": 1000,"remaining": 1000,"reset": 1452254278},"/collections/show": {"limit": 1000,"remaining": 1000,"reset": 1452254278},"/collections/entries": {"limit": 1000,"remaining": 1000,"reset": 1452254278}}},"rate_limit_context": {"access_token": "4012966701-SVdwWUOaArQBOsy6UI6ejAIszrsR56uG2wDWDnN"}}

tests/test_rate_limit.py

Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
# encoding: utf-8
2+
3+
import sys
4+
import unittest
5+
6+
import twitter
7+
8+
import warnings
9+
10+
warnings.filterwarnings('ignore', category=DeprecationWarning)
11+
12+
import responses
13+
14+
15+
class ErrNull(object):
16+
""" Suppress output of tests while writing to stdout or stderr. This just
17+
takes in data and does nothing with it.
18+
"""
19+
20+
def write(self, data):
21+
pass
22+
23+
24+
class RateLimitTests(unittest.TestCase):
25+
""" Tests for RateLimit object """
26+
27+
def setUp(self):
28+
self.api = twitter.Api(
29+
consumer_key='test',
30+
consumer_secret='test',
31+
access_token_key='test',
32+
access_token_secret='test',
33+
sleep_on_rate_limit=False)
34+
self.base_url = 'https://api.twitter.com/1.1'
35+
self._stderr = sys.stderr
36+
sys.stderr = ErrNull()
37+
38+
def tearDown(self):
39+
sys.stderr = self._stderr
40+
pass
41+
42+
@responses.activate
43+
def testInitializeRateLimit(self):
44+
with open('testdata/ratelimit.json') as f:
45+
resp_data = f.read()
46+
47+
url = '%s/application/rate_limit_status.json' % self.api.base_url
48+
responses.add(
49+
responses.GET,
50+
url,
51+
body=resp_data,
52+
match_querystring=True,
53+
status=200)
54+
self.api.InitializeRateLimit()
55+
self.assertTrue(self.api.rate_limit)
56+
57+
58+
class RateLimitMethodsTests(unittest.TestCase):
59+
""" Tests for RateLimit object """
60+
61+
@responses.activate
62+
def setUp(self):
63+
self.api = twitter.Api(
64+
consumer_key='test',
65+
consumer_secret='test',
66+
access_token_key='test',
67+
access_token_secret='test',
68+
sleep_on_rate_limit=False)
69+
self.base_url = 'https://api.twitter.com/1.1'
70+
self._stderr = sys.stderr
71+
sys.stderr = ErrNull()
72+
73+
with open('testdata/ratelimit.json') as f:
74+
resp_data = f.read()
75+
76+
url = '%s/application/rate_limit_status.json' % self.api.base_url
77+
responses.add(
78+
responses.GET,
79+
url,
80+
body=resp_data,
81+
match_querystring=True,
82+
status=200)
83+
self.api.InitializeRateLimit()
84+
self.assertTrue(self.api.rate_limit)
85+
86+
def tearDown(self):
87+
sys.stderr = self._stderr
88+
pass
89+
90+
def testGetRateLimit(self):
91+
lim = self.api.rate_limit.get_limit('/lists/members')
92+
self.assertEqual(lim.limit, 180)
93+
self.assertEqual(lim.remaining, 180)
94+
self.assertEqual(lim.reset, 1452254278)
95+
96+
def testNonStandardEndpointRateLimit(self):
97+
lim = self.api.rate_limit.get_limit('https://api.twitter.com/1.1/geo/id/312.json?skip_status=True')
98+
self.assertEqual(lim.limit, 47)
99+
100+
lim = self.api.rate_limit.get_limit('https://api.twitter.com/1.1/saved_searches/destroy/312.json')
101+
self.assertEqual(lim.limit, 15)
102+
lim = self.api.rate_limit.get_limit('https://api.twitter.com/1.1/statuses/retweets/312.json?skip_status=True')
103+
self.assertEqual(lim.limit, 23)
104+
105+
def testSetRateLimit(self):
106+
previous_limit = self.api.rate_limit.get_limit('/lists/members')
107+
self.api.rate_limit.set_limit(
108+
url='https://api.twitter.com/1.1/lists/members.json?skip_status=True',
109+
limit=previous_limit.limit,
110+
remaining=previous_limit.remaining - 1,
111+
reset=previous_limit.reset)
112+
new_limit = self.api.rate_limit.get_limit('/lists/members')
113+
self.assertEqual(new_limit.remaining, previous_limit.remaining - 1)
114+
115+
def testFamilyNotFound(self):
116+
limit = self.api.rate_limit.get_limit('/tests/test')
117+
self.assertEqual(limit.limit, 15)
118+
self.assertEqual(limit.remaining, 15)
119+
self.assertEqual(limit.reset, 0)
120+
121+
def testSetUnknownRateLimit(self):
122+
self.api.rate_limit.set_limit(
123+
url='https://api.twitter.com/1.1/not/a/real/endpoint.json',
124+
limit=15,
125+
remaining=14,
126+
reset=100)
127+
limit = self.api.rate_limit.get_limit(
128+
url='https://api.twitter.com/1.1/not/a/real/endpoint.json')
129+
self.assertEqual(limit.remaining, 14)

0 commit comments

Comments
 (0)