From 211e4633ee928cb213cbbb9799b6cf231644fd0d Mon Sep 17 00:00:00 2001 From: Ed Jackson Date: Tue, 6 Aug 2013 10:42:57 -0500 Subject: [PATCH] Minor enhancements to rate limiting information If rate limiting information hasn't been retrieved already, it will be checked rather than just assuming 5000 requests remaining of 5000. Add an additional attribute `rate_limiting_resettime` to reflect github's new X-RateLimit-Reset header. This integer value is a unix timestamp. RateLimiting unit test updated accordingly. --- github/MainClass.py | 24 +++++++++++++++++++ github/Requester.py | 5 +++- github/tests/RateLimiting.py | 1 + .../RateLimiting.testRateLimiting.txt | 15 ++++++++++-- 4 files changed, 42 insertions(+), 3 deletions(-) diff --git a/github/MainClass.py b/github/MainClass.py index ff180ef4..a5d31524 100644 --- a/github/MainClass.py +++ b/github/MainClass.py @@ -95,10 +95,34 @@ class Github(object): @property def rate_limiting(self): """ + First value is requests remaining, second value is request limit. :type: (int, int) """ + remaining, limit = self.__requester.rate_limiting + if limit < 0: + self.__requester.requestJsonAndCheck( + 'GET', + '/rate_limit', + None, + None + ) return self.__requester.rate_limiting + @property + def rate_limiting_resettime(self): + """ + Unix timestamp indicating when rate limiting will reset. + :type: int + """ + if self.__requester.rate_limiting_resettime == 0: + self.__requester.requestJsonAndCheck( + 'GET', + '/rate_limit', + None, + None + ) + return self.__requester.rate_limiting_resettime + @property def oauth_scopes(self): """ diff --git a/github/Requester.py b/github/Requester.py index ca263e0a..26d5d6c8 100644 --- a/github/Requester.py +++ b/github/Requester.py @@ -87,7 +87,8 @@ class Requester: self.__connectionClass = self.__httpConnectionClass else: assert False, "Unknown URL scheme" - self.rate_limiting = (5000, 5000) + self.rate_limiting = (-1, -1) + self.rate_limiting_resettime = 0 self.FIX_REPO_GET_GIT_REF = True self.per_page = per_page @@ -175,6 +176,8 @@ class Requester: if "x-ratelimit-remaining" in responseHeaders and "x-ratelimit-limit" in responseHeaders: self.rate_limiting = (int(responseHeaders["x-ratelimit-remaining"]), int(responseHeaders["x-ratelimit-limit"])) + if "x-ratelimit-reset" in responseHeaders: + self.rate_limiting_resettime = int(responseHeaders["x-ratelimit-reset"]) if "x-oauth-scopes" in responseHeaders: self.oauth_scopes = responseHeaders["x-oauth-scopes"].split(", ") diff --git a/github/tests/RateLimiting.py b/github/tests/RateLimiting.py index 602dfc5f..b45ffbf6 100644 --- a/github/tests/RateLimiting.py +++ b/github/tests/RateLimiting.py @@ -31,3 +31,4 @@ class RateLimiting(Framework.TestCase): self.assertEqual(self.g.rate_limiting, (5000, 5000)) self.g.get_user("jacquev6") self.assertEqual(self.g.rate_limiting, (4999, 5000)) + self.assertEqual(self.g.rate_limiting_resettime, 1375802816) diff --git a/github/tests/ReplayData/RateLimiting.testRateLimiting.txt b/github/tests/ReplayData/RateLimiting.testRateLimiting.txt index 2fad6735..208c1716 100644 --- a/github/tests/ReplayData/RateLimiting.testRateLimiting.txt +++ b/github/tests/ReplayData/RateLimiting.testRateLimiting.txt @@ -1,3 +1,14 @@ +https +GET +api.github.com +None +/rate_limit +{'Authorization': 'Basic login_and_password_removed', 'User-Agent': 'PyGithub/Python'} +null +200 +[('status', '200 OK'), ('x-ratelimit-remaining', '5000'), ('x-github-media-type', 'github.beta; format=json'), ('x-content-type-options', 'nosniff'), ('access-control-expose-headers', 'ETag, Link, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes'), ('cache-control', 'max-age=0, private, must-revalidate'), ('vary', 'Accept-Encoding'), ('content-length', '59'), ('server', 'GitHub.com'), ('x-ratelimit-limit', '5000'), ('etag', '"47ba6b48c8b2986ec54f249b51b0a9ec"'), ('access-control-allow-credentials', 'true'), ('date', 'Tue, 06 Aug 2013 14:52:12 GMT'), ('x-oauth-scopes', 'user, public_repo, repo, gist'), ('content-type', 'application/json; charset=utf-8'), ('access-control-allow-origin', '*'), ('x-ratelimit-reset', '1375802816')] +{"rate":{"limit":5000,"remaining":5000,"reset":1375802816}} + https GET api.github.com @@ -6,6 +17,6 @@ None {'Authorization': 'Basic login_and_password_removed', 'User-Agent': 'PyGithub/Python'} null 200 -[('status', '200 OK'), ('x-ratelimit-remaining', '4999'), ('content-length', '801'), ('server', 'nginx/1.0.13'), ('connection', 'keep-alive'), ('x-ratelimit-limit', '5000'), ('etag', '"8cd01ddcd0adee5501abc607e6a640b0"'), ('date', 'Mon, 21 May 2012 11:08:21 GMT'), ('content-type', 'application/json; charset=utf-8')] -{"total_private_repos":5,"public_gists":1,"type":"User","owned_private_repos":5,"private_gists":5,"company":"Criteo","url":"https://api.github.com/users/jacquev6","gravatar_id":"b68de5ae38616c296fa345d2b9df2225","avatar_url":"https://secure.gravatar.com/avatar/b68de5ae38616c296fa345d2b9df2225?d=https://a248.e.akamai.net/assets.github.com%2Fimages%2Fgravatars%2Fgravatar-140.png","plan":{"private_repos":5,"collaborators":1,"name":"micro","space":614400},"followers":13,"blog":"http://vincent-jacques.net","collaborators":0,"login":"jacquev6","email":"vincent@vincent-jacques.net","disk_usage":16812,"public_repos":11,"html_url":"https://github.com/jacquev6","name":"Vincent Jacques","hireable":false,"created_at":"2010-07-09T06:10:06Z","location":"Paris, France","id":327146,"following":24,"bio":""} +[('status', '200 OK'), ('x-ratelimit-remaining', '4999'), ('x-github-media-type', 'github.beta; format=json'), ('x-content-type-options', 'nosniff'), ('access-control-expose-headers', 'ETag, Link, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes'), ('access-control-allow-credentials', 'true'), ('vary', 'Accept, Authorization, Cookie, Accept-Encoding'), ('content-length', '1293'), ('server', 'GitHub.com'), ('access-control-allow-origin', '*'), ('last-modified', 'Mon, 05 Aug 2013 07:28:42 GMT'), ('x-ratelimit-limit', '5000'), ('etag', '"7d9b8600b27332ec98f57ee9e18639e9"'), ('cache-control', 'private, max-age=60, s-maxage=60'), ('date', 'Tue, 06 Aug 2013 14:52:12 GMT'), ('x-oauth-scopes', 'user, public_repo, repo, gist'), ('content-type', 'application/json; charset=utf-8'), ('x-accepted-oauth-scopes', 'user, user:email, user:follow, site_admin'), ('x-ratelimit-reset', '1375802816')] +{"login":"jacquev6","id":327146,"avatar_url":"https://secure.gravatar.com/avatar/b68de5ae38616c296fa345d2b9df2225?d=https://a248.e.akamai.net/assets.github.com%2Fimages%2Fgravatars%2Fgravatar-user-420.png","gravatar_id":"b68de5ae38616c296fa345d2b9df2225","url":"https://api.github.com/users/jacquev6","html_url":"https://github.com/jacquev6","followers_url":"https://api.github.com/users/jacquev6/followers","following_url":"https://api.github.com/users/jacquev6/following{/other_user}","gists_url":"https://api.github.com/users/jacquev6/gists{/gist_id}","starred_url":"https://api.github.com/users/jacquev6/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/jacquev6/subscriptions","organizations_url":"https://api.github.com/users/jacquev6/orgs","repos_url":"https://api.github.com/users/jacquev6/repos","events_url":"https://api.github.com/users/jacquev6/events{/privacy}","received_events_url":"https://api.github.com/users/jacquev6/received_events","type":"User","name":"Vincent Jacques","company":"","blog":"http://vincent-jacques.net","location":"Paris, France","email":"vincent@vincent-jacques.net","hireable":false,"bio":"","public_repos":16,"followers":27,"following":39,"created_at":"2010-07-09T06:10:06Z","updated_at":"2013-08-05T07:28:42Z","public_gists":3}