Skip to content

Commit

Permalink
Adding support for search/graphql rate limit, fixes PyGithub#553
Browse files Browse the repository at this point in the history
  • Loading branch information
sfdye authored and candrikos committed Sep 25, 2020
1 parent 21a8737 commit 55359b2
Show file tree
Hide file tree
Showing 9 changed files with 56 additions and 29 deletions.
8 changes: 5 additions & 3 deletions github/MainClass.py
Expand Up @@ -144,6 +144,7 @@ def __set_per_page(self, value):
def rate_limiting(self):
"""
First value is requests remaining, second value is request limit.
:type: (int, int)
"""
remaining, limit = self.__requester.rate_limiting
Expand All @@ -155,6 +156,7 @@ def rate_limiting(self):
def rate_limiting_resettime(self):
"""
Unix timestamp indicating when rate limiting will reset.
:type: int
"""
if self.__requester.rate_limiting_resettime == 0:
Expand All @@ -163,16 +165,16 @@ def rate_limiting_resettime(self):

def get_rate_limit(self):
"""
Don't forget you can access the rate limit returned in headers of last Github API v3 response, by :attr:`github.MainClass.Github.rate_limiting` and :attr:`github.MainClass.Github.rate_limiting_resettime`.
Rate limit status for different resources (core/search/graphql).
:calls: `GET /rate_limit <http://developer.github.com/v3/rate_limit>`_
:rtype: :class:`github.RateLimit.RateLimit`
"""
headers, attributes = self.__requester.requestJsonAndCheck(
headers, data = self.__requester.requestJsonAndCheck(
'GET',
'/rate_limit'
)
return RateLimit.RateLimit(self.__requester, headers, attributes, True)
return RateLimit.RateLimit(self.__requester, headers, data["resources"], True)

@property
def oauth_scopes(self):
Expand Down
38 changes: 32 additions & 6 deletions github/RateLimit.py
Expand Up @@ -37,18 +37,44 @@ class RateLimit(github.GithubObject.NonCompletableGithubObject):
"""

def __repr__(self):
return self.get__repr__({"rate": self._rate.value})
return self.get__repr__({"core": self._core.value})

@property
def rate(self):
def core(self):
"""
Rate limit for rest of the API.
:type: class:`github.Rate.Rate`
"""
return self._core.value

@property
def search(self):
"""
Rate limit for Search API.
:type: class:`github.Rate.Rate`
"""
return self._search.value

@property
def graphql(self):
"""
Experimental rate limit for GraphQL, use with caution.
:type: class:`github.Rate.Rate`
"""
return self._rate.value
return self._graphql.value

def _initAttributes(self):
self._rate = github.GithubObject.NotSet
self._core = github.GithubObject.NotSet
self._search = github.GithubObject.NotSet
self._graphql = github.GithubObject.NotSet

def _useAttributes(self, attributes):
if "rate" in attributes: # pragma no branch
self._rate = self._makeClassAttribute(github.Rate.Rate, attributes["rate"])
if "core" in attributes: # pragma no branch
self._core = self._makeClassAttribute(github.Rate.Rate, attributes["core"])
if "search" in attributes: # pragma no branch
self._search = self._makeClassAttribute(github.Rate.Rate, attributes["search"])
if "graphql" in attributes: # pragma no branch
self._graphql = self._makeClassAttribute(github.Rate.Rate, attributes["graphql"])
2 changes: 0 additions & 2 deletions github/tests/ExposeAllAttributes.py
Expand Up @@ -66,7 +66,6 @@ def testAllClasses(self):
status = self.g.get_api_status()
statusMessage = self.g.get_last_api_status_message()
rateLimit = self.g.get_rate_limit()
rate = rateLimit.rate
hook = repository.get_hooks()[0]
hookResponse = hook.last_response
hookDescription = self.g.get_hooks()[0]
Expand Down Expand Up @@ -123,7 +122,6 @@ def testAllClasses(self):
pullRequestComment,
# pullRequestMergeStatus, # Only obtained when merging a pull request through the API
pullRequestPart,
rate,
rateLimit,
repository,
# repositoryKey, # Security issue if put as-is in ReplayData
Expand Down
2 changes: 1 addition & 1 deletion github/tests/Issue142.py
Expand Up @@ -33,4 +33,4 @@ class Issue142(unittest.TestCase): # https://github.com/jacquev6/PyGithub/issue
def testDecodeJson(self):
# This test has to hit GitHub for real, because the record-replay framework looses types
# and python3 does not behave like python2 for strings and bytes
self.assertEqual(github.Github().get_rate_limit().rate.limit, 60)
self.assertEqual(github.Github().get_rate_limit().core.limit, 60)
14 changes: 7 additions & 7 deletions github/tests/RateLimiting.py
Expand Up @@ -35,16 +35,16 @@

class RateLimiting(Framework.TestCase):
def testRateLimiting(self):
self.assertEqual(self.g.rate_limiting, (5000, 5000))
self.assertEqual(self.g.rate_limiting, (4929, 5000))
self.g.get_user("jacquev6")
self.assertEqual(self.g.rate_limiting, (4999, 5000))
self.assertEqual(self.g.rate_limiting_resettime, 1375802816)
self.assertEqual(self.g.rate_limiting, (4928, 5000))
self.assertEqual(self.g.rate_limiting_resettime, 1536123356)

def testResetTime(self):
self.assertEqual(self.g.rate_limiting_resettime, 1375802816)
self.assertEqual(self.g.rate_limiting_resettime, 1536123356)

def testGetRateLimit(self):
rateLimit = self.g.get_rate_limit()
self.assertEqual(rateLimit.rate.limit, 5000)
self.assertEqual(rateLimit.rate.remaining, 5000)
self.assertEqual(rateLimit.rate.reset, datetime.datetime(2013, 9, 6, 10, 29, 57))
self.assertEqual(rateLimit.core.limit, 5000)
self.assertEqual(rateLimit.core.remaining, 4929)
self.assertEqual(rateLimit.core.reset, datetime.datetime(2018, 9, 5, 4, 55, 56))
Expand Up @@ -292,8 +292,8 @@ None
{'Authorization': 'Basic login_and_password_removed', 'User-Agent': 'PyGithub/Python'}
None
200
[('status', '200 OK'), ('x-ratelimit-remaining', '4873'), ('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'), ('x-github-request-id', '7d8f5ceb-ff02-47d5-a6c5-bdc47a1b1306'), ('access-control-allow-credentials', 'true'), ('vary', 'Accept-Encoding'), ('content-length', '59'), ('server', 'GitHub.com'), ('x-ratelimit-limit', '5000'), ('cache-control', 'no-cache'), ('date', 'Fri, 06 Sep 2013 15:05:42 GMT'), ('access-control-allow-origin', '*'), ('content-type', 'application/json; charset=utf-8'), ('x-ratelimit-reset', '1378482241')]
{"rate":{"limit":5000,"remaining":4873,"reset":1378482241}}
[('Date', 'Wed, 05 Sep 2018 03:59:43 GMT'), ('Content-Type', 'application/json; charset=utf-8'), ('Transfer-Encoding', 'chunked'), ('Server', 'GitHub.com'), ('Status', '200 OK'), ('X-RateLimit-Limit', '5000'), ('X-RateLimit-Remaining', '4929'), ('X-RateLimit-Reset', '1536123356'), ('Cache-Control', 'no-cache'), ('X-OAuth-Scopes', 'admin:gpg_key, admin:org, admin:org_hook, admin:public_key, admin:repo_hook, delete_repo, gist, notifications, repo, user, write:discussion'), ('X-Accepted-OAuth-Scopes', ''), ('X-GitHub-Media-Type', 'github.v3; format=json'), ('Access-Control-Expose-Headers', 'ETag, Link, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval'), ('Access-Control-Allow-Origin', '*'), ('Strict-Transport-Security', 'max-age=31536000; includeSubdomains; preload'), ('X-Frame-Options', 'deny'), ('X-Content-Type-Options', 'nosniff'), ('X-XSS-Protection', '1; mode=block'), ('Referrer-Policy', 'origin-when-cross-origin, strict-origin-when-cross-origin'), ('Content-Security-Policy', "default-src 'none'"), ('X-Runtime-rack', '0.021146'), ('Content-Encoding', 'gzip'), ('Vary', 'Accept-Encoding'), ('X-GitHub-Request-Id', 'C8ED:54D0:B4DAB8:EF7B5E:5B8F54AE')]
{"resources":{"core":{"limit":5000,"remaining":4929,"reset":1536123356},"search":{"limit":30,"remaining":30,"reset":1536120043},"graphql":{"limit":5000,"remaining":5000,"reset":1536123583}},"rate":{"limit":5000,"remaining":4929,"reset":1536123356}}

https
GET
Expand Down
4 changes: 2 additions & 2 deletions github/tests/ReplayData/RateLimiting.testGetRateLimit.txt
Expand Up @@ -6,6 +6,6 @@ None
{'Authorization': 'Basic login_and_password_removed', 'User-Agent': 'PyGithub/Python'}
None
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'), ('x-github-request-id', '1a3fa558-6663-4055-91e3-b6824f5f850e'), ('access-control-allow-credentials', 'true'), ('vary', 'Accept-Encoding'), ('content-length', '59'), ('server', 'GitHub.com'), ('x-ratelimit-limit', '5000'), ('cache-control', 'no-cache'), ('date', 'Fri, 06 Sep 2013 09:29:57 GMT'), ('access-control-allow-origin', '*'), ('content-type', 'application/json; charset=utf-8'), ('x-ratelimit-reset', '1378463397')]
{"rate":{"limit":5000,"remaining":5000,"reset":1378463397}}
[('Date', 'Wed, 05 Sep 2018 03:59:43 GMT'), ('Content-Type', 'application/json; charset=utf-8'), ('Transfer-Encoding', 'chunked'), ('Server', 'GitHub.com'), ('Status', '200 OK'), ('X-RateLimit-Limit', '5000'), ('X-RateLimit-Remaining', '4929'), ('X-RateLimit-Reset', '1536123356'), ('Cache-Control', 'no-cache'), ('X-OAuth-Scopes', 'admin:gpg_key, admin:org, admin:org_hook, admin:public_key, admin:repo_hook, delete_repo, gist, notifications, repo, user, write:discussion'), ('X-Accepted-OAuth-Scopes', ''), ('X-GitHub-Media-Type', 'github.v3; format=json'), ('Access-Control-Expose-Headers', 'ETag, Link, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval'), ('Access-Control-Allow-Origin', '*'), ('Strict-Transport-Security', 'max-age=31536000; includeSubdomains; preload'), ('X-Frame-Options', 'deny'), ('X-Content-Type-Options', 'nosniff'), ('X-XSS-Protection', '1; mode=block'), ('Referrer-Policy', 'origin-when-cross-origin, strict-origin-when-cross-origin'), ('Content-Security-Policy', "default-src 'none'"), ('X-Runtime-rack', '0.021146'), ('Content-Encoding', 'gzip'), ('Vary', 'Accept-Encoding'), ('X-GitHub-Request-Id', 'C8ED:54D0:B4DAB8:EF7B5E:5B8F54AE')]
{"resources":{"core":{"limit":5000,"remaining":4929,"reset":1536123356},"search":{"limit":30,"remaining":30,"reset":1536120043},"graphql":{"limit":5000,"remaining":5000,"reset":1536123583}},"rate":{"limit":5000,"remaining":4929,"reset":1536123356}}

8 changes: 4 additions & 4 deletions github/tests/ReplayData/RateLimiting.testRateLimiting.txt
Expand Up @@ -6,8 +6,8 @@ None
{'Authorization': 'Basic login_and_password_removed', 'User-Agent': 'PyGithub/Python'}
None
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}}
[('Date', 'Wed, 05 Sep 2018 04:03:25 GMT'), ('Content-Type', 'application/json; charset=utf-8'), ('Transfer-Encoding', 'chunked'), ('Server', 'GitHub.com'), ('Status', '200 OK'), ('X-RateLimit-Limit', '5000'), ('X-RateLimit-Remaining', '4929'), ('X-RateLimit-Reset', '1536123356'), ('Cache-Control', 'no-cache'), ('X-OAuth-Scopes', 'admin:gpg_key, admin:org, admin:org_hook, admin:public_key, admin:repo_hook, delete_repo, gist, notifications, repo, user, write:discussion'), ('X-Accepted-OAuth-Scopes', ''), ('X-GitHub-Media-Type', 'github.v3; format=json'), ('Access-Control-Expose-Headers', 'ETag, Link, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval'), ('Access-Control-Allow-Origin', '*'), ('Strict-Transport-Security', 'max-age=31536000; includeSubdomains; preload'), ('X-Frame-Options', 'deny'), ('X-Content-Type-Options', 'nosniff'), ('X-XSS-Protection', '1; mode=block'), ('Referrer-Policy', 'origin-when-cross-origin, strict-origin-when-cross-origin'), ('Content-Security-Policy', "default-src 'none'"), ('X-Runtime-rack', '0.020832'), ('Content-Encoding', 'gzip'), ('Vary', 'Accept-Encoding'), ('X-GitHub-Request-Id', 'C91A:07E1:B39E09:EEBCC4:5B8F558C')]
{"resources":{"core":{"limit":5000,"remaining":4929,"reset":1536123356},"search":{"limit":30,"remaining":30,"reset":1536120264},"graphql":{"limit":5000,"remaining":5000,"reset":1536123804}},"rate":{"limit":5000,"remaining":4929,"reset":1536123356}}

https
GET
Expand All @@ -17,6 +17,6 @@ None
{'Authorization': 'Basic login_and_password_removed', 'User-Agent': 'PyGithub/Python'}
None
200
[('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}
[('Date', 'Wed, 05 Sep 2018 04:03:25 GMT'), ('Content-Type', 'application/json; charset=utf-8'), ('Transfer-Encoding', 'chunked'), ('Server', 'GitHub.com'), ('Status', '200 OK'), ('X-RateLimit-Limit', '5000'), ('X-RateLimit-Remaining', '4928'), ('X-RateLimit-Reset', '1536123356'), ('Cache-Control', 'no-cache'), ('X-OAuth-Scopes', 'admin:gpg_key, admin:org, admin:org_hook, admin:public_key, admin:repo_hook, delete_repo, gist, notifications, repo, user, write:discussion'), ('X-Accepted-OAuth-Scopes', ''), ('X-GitHub-Media-Type', 'github.v3; format=json'), ('Access-Control-Expose-Headers', 'ETag, Link, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval'), ('Access-Control-Allow-Origin', '*'), ('Strict-Transport-Security', 'max-age=31536000; includeSubdomains; preload'), ('X-Frame-Options', 'deny'), ('X-Content-Type-Options', 'nosniff'), ('X-XSS-Protection', '1; mode=block'), ('Referrer-Policy', 'origin-when-cross-origin, strict-origin-when-cross-origin'), ('Content-Security-Policy', "default-src 'none'"), ('X-Runtime-rack', '0.020832'), ('Content-Encoding', 'gzip'), ('Vary', 'Accept-Encoding'), ('X-GitHub-Request-Id', 'C91A:07E1:B39E09:EEBCC4:5B8F558C')]
{"resources":{"core":{"limit":5000,"remaining":4928,"reset":1536123356},"search":{"limit":30,"remaining":30,"reset":1536120264},"graphql":{"limit":5000,"remaining":5000,"reset":1536123804}},"rate":{"limit":5000,"remaining":4928,"reset":1536123356}}

5 changes: 3 additions & 2 deletions github/tests/ReplayData/RateLimiting.testResetTime.txt
Expand Up @@ -6,5 +6,6 @@ None
{'Authorization': 'Basic login_and_password_removed', 'User-Agent': 'PyGithub/Python'}
None
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}}
[('Date', 'Wed, 05 Sep 2018 04:07:10 GMT'), ('Content-Type', 'application/json; charset=utf-8'), ('Transfer-Encoding', 'chunked'), ('Server', 'GitHub.com'), ('Status', '200 OK'), ('X-RateLimit-Limit', '5000'), ('X-RateLimit-Remaining', '4928'), ('X-RateLimit-Reset', '1536123356'), ('Cache-Control', 'no-cache'), ('X-OAuth-Scopes', 'admin:gpg_key, admin:org, admin:org_hook, admin:public_key, admin:repo_hook, delete_repo, gist, notifications, repo, user, write:discussion'), ('X-Accepted-OAuth-Scopes', ''), ('X-GitHub-Media-Type', 'github.v3; format=json'), ('Access-Control-Expose-Headers', 'ETag, Link, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval'), ('Access-Control-Allow-Origin', '*'), ('Strict-Transport-Security', 'max-age=31536000; includeSubdomains; preload'), ('X-Frame-Options', 'deny'), ('X-Content-Type-Options', 'nosniff'), ('X-XSS-Protection', '1; mode=block'), ('Referrer-Policy', 'origin-when-cross-origin, strict-origin-when-cross-origin'), ('Content-Security-Policy', "default-src 'none'"), ('X-Runtime-rack', '0.021308'), ('Content-Encoding', 'gzip'), ('Vary', 'Accept-Encoding'), ('X-GitHub-Request-Id', 'C949:54D0:B51EC6:EFD5CD:5B8F566D')]
{"resources":{"core":{"limit":5000,"remaining":4928,"reset":1536123356},"search":{"limit":30,"remaining":30,"reset":1536120490},"graphql":{"limit":5000,"remaining":5000,"reset":1536124030}},"rate":{"limit":5000,"remaining":4928,"reset":1536123356}}

0 comments on commit 55359b2

Please sign in to comment.