From db2a9db7213b830c0f4f120698153f7545faa4bc Mon Sep 17 00:00:00 2001 From: Vincent Jacques Date: Fri, 17 May 2013 14:27:17 +0200 Subject: [PATCH] Add two new types of exceptions (issue #152) --- github/GithubException.py | 14 ++++- github/Requester.py | 14 +++-- github/__init__.py | 2 +- github/tests/Exceptions.py | 12 ++++ .../SpecificExceptions.testBadUserAgent.txt | 11 ++++ ...ecificExceptions.testRateLimitExceeded.txt | 55 +++++++++++++++++++ 6 files changed, 102 insertions(+), 6 deletions(-) create mode 100644 github/tests/ReplayData/SpecificExceptions.testBadUserAgent.txt create mode 100644 github/tests/ReplayData/SpecificExceptions.testRateLimitExceeded.txt diff --git a/github/GithubException.py b/github/GithubException.py index d244564dec..f821d82627 100644 --- a/github/GithubException.py +++ b/github/GithubException.py @@ -53,5 +53,17 @@ class BadCredentialsException(GithubException): class UnknownObjectException(GithubException): """ - Exception raised a non-existing object is requested (when Github API replies with a 404 HTML status) + Exception raised when a non-existing object is requested (when Github API replies with a 404 HTML status) + """ + + +class BadUserAgentException(GithubException): + """ + Exception raised when request is sent with a bad user agent header (when Github API replies with a 403 bad user agent HTML status) + """ + + +class RateLimitExceededException(GithubException): + """ + Exception raised when the rate limit is exceeded (when Github API replies with a 403 rate limit exceeded HTML status) """ diff --git a/github/Requester.py b/github/Requester.py index 8753e145b7..730c416ac4 100644 --- a/github/Requester.py +++ b/github/Requester.py @@ -105,10 +105,16 @@ def __check(self, status, responseHeaders, output): def __createException(self, status, output): if status == 401 and output["message"] == "Bad credentials": - return GithubException.BadCredentialsException(status, output) - if status == 404 and output["message"] == "Not Found": - return GithubException.UnknownObjectException(status, output) - return GithubException.GithubException(status, output) + cls = GithubException.BadCredentialsException + elif status == 403 and output["message"].startswith("Missing or invalid User Agent string"): + cls = GithubException.BadUserAgentException + elif status == 403 and output["message"].startswith("API Rate Limit Exceeded"): + cls = GithubException.RateLimitExceededException + elif status == 404 and output["message"] == "Not Found": + cls = GithubException.UnknownObjectException + else: + cls = GithubException.GithubException + return cls(status, output) def __structuredFromJson(self, data): if len(data) == 0: diff --git a/github/__init__.py b/github/__init__.py index fc068df020..029d096a45 100644 --- a/github/__init__.py +++ b/github/__init__.py @@ -25,7 +25,7 @@ import logging from MainClass import Github -from GithubException import GithubException, BadCredentialsException, UnknownObjectException +from GithubException import GithubException, BadCredentialsException, UnknownObjectException, BadUserAgentException, RateLimitExceededException from InputFileContent import InputFileContent from InputGitAuthor import InputGitAuthor from InputGitTreeElement import InputGitTreeElement diff --git a/github/tests/Exceptions.py b/github/tests/Exceptions.py index d14ae24137..5f52372064 100644 --- a/github/tests/Exceptions.py +++ b/github/tests/Exceptions.py @@ -96,3 +96,15 @@ def testBadCredentials(self): def testUnknownObject(self): self.assertRaises(github.UnknownObjectException, lambda: self.g.get_user().get_repo("Xxx")) + + def testBadUserAgent(self): + self.assertRaises(github.BadUserAgentException, lambda: github.Github(self.login, self.password, user_agent="").get_user().name) + + def testRateLimitExceeded(self): + g = github.Github() + + def exceed(): + for i in range(100): + g.get_user("jacquev6") + + self.assertRaises(github.RateLimitExceededException, exceed) diff --git a/github/tests/ReplayData/SpecificExceptions.testBadUserAgent.txt b/github/tests/ReplayData/SpecificExceptions.testBadUserAgent.txt new file mode 100644 index 0000000000..05c2beb511 --- /dev/null +++ b/github/tests/ReplayData/SpecificExceptions.testBadUserAgent.txt @@ -0,0 +1,11 @@ +https +GET +api.github.com +None +/user +{'Authorization': 'Basic login_and_password_removed', 'User-Agent': ''} +null +403 +[('date', 'Fri, 17 May 2013 12:16:34 GMT'), ('content-length', '107'), ('content-type', 'application/octet-stream'), ('connection', 'keep-alive'), ('server', 'GitHub.com')] +{"message":"Missing or invalid User Agent string. See http://developer.github.com/v3/#user-agent-required"} + diff --git a/github/tests/ReplayData/SpecificExceptions.testRateLimitExceeded.txt b/github/tests/ReplayData/SpecificExceptions.testRateLimitExceeded.txt new file mode 100644 index 0000000000..2c7ff783a6 --- /dev/null +++ b/github/tests/ReplayData/SpecificExceptions.testRateLimitExceeded.txt @@ -0,0 +1,55 @@ +https +GET +api.github.com +None +/users/jacquev6 +{'User-Agent': 'PyGithub/Python'} +null +200 +[('status', '200 OK'), ('x-ratelimit-remaining', '2'), ('x-github-media-type', 'github.beta; format=json'), ('x-content-type-options', 'nosniff'), ('access-control-expose-headers', 'Link, X-RateLimit-Limit, X-RateLimit-Remaining, X-OAuth-Scopes, X-Accepted-OAuth-Scopes'), ('access-control-allow-credentials', 'true'), ('vary', 'Accept, Accept-Encoding'), ('content-length', '1299'), ('server', 'GitHub.com'), ('last-modified', 'Fri, 17 May 2013 12:09:51 GMT'), ('connection', 'keep-alive'), ('x-ratelimit-limit', '60'), ('etag', '"de9347ae9c0c83b44d6c81d05aba4877"'), ('cache-control', 'public, max-age=60, s-maxage=60'), ('date', 'Fri, 17 May 2013 12:23:52 GMT'), ('access-control-allow-origin', '*'), ('content-type', 'application/json; charset=utf-8')] +{"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":"Criteo","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-05-17T12:09:51Z","public_gists":3} + +https +GET +api.github.com +None +/users/jacquev6 +{'User-Agent': 'PyGithub/Python'} +null +200 +[('status', '200 OK'), ('x-ratelimit-remaining', '1'), ('x-github-media-type', 'github.beta; format=json'), ('x-content-type-options', 'nosniff'), ('access-control-expose-headers', 'Link, X-RateLimit-Limit, X-RateLimit-Remaining, X-OAuth-Scopes, X-Accepted-OAuth-Scopes'), ('access-control-allow-credentials', 'true'), ('vary', 'Accept, Accept-Encoding'), ('content-length', '1299'), ('server', 'GitHub.com'), ('last-modified', 'Fri, 17 May 2013 12:09:51 GMT'), ('connection', 'keep-alive'), ('x-ratelimit-limit', '60'), ('etag', '"de9347ae9c0c83b44d6c81d05aba4877"'), ('cache-control', 'public, max-age=60, s-maxage=60'), ('date', 'Fri, 17 May 2013 12:23:53 GMT'), ('access-control-allow-origin', '*'), ('content-type', 'application/json; charset=utf-8')] +{"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":"Criteo","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-05-17T12:09:51Z","public_gists":3} + +https +GET +api.github.com +None +/users/jacquev6 +{'User-Agent': 'PyGithub/Python'} +null +200 +[('status', '200 OK'), ('x-ratelimit-remaining', '0'), ('x-github-media-type', 'github.beta; format=json'), ('x-content-type-options', 'nosniff'), ('access-control-expose-headers', 'Link, X-RateLimit-Limit, X-RateLimit-Remaining, X-OAuth-Scopes, X-Accepted-OAuth-Scopes'), ('access-control-allow-credentials', 'true'), ('vary', 'Accept, Accept-Encoding'), ('content-length', '1299'), ('server', 'GitHub.com'), ('last-modified', 'Fri, 17 May 2013 12:09:51 GMT'), ('connection', 'keep-alive'), ('x-ratelimit-limit', '60'), ('etag', '"de9347ae9c0c83b44d6c81d05aba4877"'), ('cache-control', 'public, max-age=60, s-maxage=60'), ('date', 'Fri, 17 May 2013 12:23:54 GMT'), ('access-control-allow-origin', '*'), ('content-type', 'application/json; charset=utf-8')] +{"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":"Criteo","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-05-17T12:09:51Z","public_gists":3} + +https +GET +api.github.com +None +/users/jacquev6 +{'User-Agent': 'PyGithub/Python'} +null +200 +[('status', '200 OK'), ('x-ratelimit-remaining', '0'), ('x-github-media-type', 'github.beta; format=json'), ('x-content-type-options', 'nosniff'), ('access-control-expose-headers', 'Link, X-RateLimit-Limit, X-RateLimit-Remaining, X-OAuth-Scopes, X-Accepted-OAuth-Scopes'), ('access-control-allow-credentials', 'true'), ('vary', 'Accept, Accept-Encoding'), ('content-length', '1299'), ('server', 'GitHub.com'), ('last-modified', 'Fri, 17 May 2013 12:09:51 GMT'), ('connection', 'keep-alive'), ('x-ratelimit-limit', '60'), ('etag', '"de9347ae9c0c83b44d6c81d05aba4877"'), ('cache-control', 'public, max-age=60, s-maxage=60'), ('date', 'Fri, 17 May 2013 12:23:55 GMT'), ('access-control-allow-origin', '*'), ('content-type', 'application/json; charset=utf-8')] +{"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":"Criteo","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-05-17T12:09:51Z","public_gists":3} + +https +GET +api.github.com +None +/users/jacquev6 +{'User-Agent': 'PyGithub/Python'} +null +403 +[('status', '403 Forbidden'), ('x-ratelimit-remaining', '0'), ('x-github-media-type', 'github.beta; format=json'), ('x-content-type-options', 'nosniff'), ('access-control-expose-headers', 'Link, X-RateLimit-Limit, X-RateLimit-Remaining, X-OAuth-Scopes, X-Accepted-OAuth-Scopes'), ('content-length', '56'), ('server', 'GitHub.com'), ('connection', 'keep-alive'), ('x-ratelimit-limit', '60'), ('access-control-allow-credentials', 'true'), ('date', 'Fri, 17 May 2013 12:23:56 GMT'), ('access-control-allow-origin', '*'), ('content-type', 'application/json; charset=utf-8')] +{"message":"API Rate Limit Exceeded for 92.104.200.119"} +