From f5d8e221d116b74a200d87afca32247f01204ba1 Mon Sep 17 00:00:00 2001 From: Vincent Jacques Date: Thu, 28 Mar 2013 20:55:48 +0100 Subject: [PATCH 1/5] Add (a bit of) documentation about error handling and exceptions --- doc/utilities.rst | 5 +++++ github/GithubException.py | 20 +++++++++++++++++--- 2 files changed, 22 insertions(+), 3 deletions(-) diff --git a/doc/utilities.rst b/doc/utilities.rst index a871496f..afd99384 100644 --- a/doc/utilities.rst +++ b/doc/utilities.rst @@ -6,6 +6,11 @@ Logging .. autofunction:: github.enable_console_debug_logging +Error Handling +-------------- + +.. autoclass:: github.GithubException.GithubException() + Default argument ---------------- diff --git a/github/GithubException.py b/github/GithubException.py index b93616c7..b8235071 100644 --- a/github/GithubException.py +++ b/github/GithubException.py @@ -16,13 +16,27 @@ class GithubException(Exception): """ - This class represents GithubExceptions as returned for example by http://developer.github.com/v3/todo + Error handling in PyGithub is done with exceptions. This class is the base of all exceptions raised by PyGithub. """ def __init__(self, status, data): Exception.__init__(self) - self.status = status - self.data = data + self.__status = status + self.__data = data + + @property + def status(self): + """ + The status returned by the Github API + """ + return self.__status + + @property + def data(self): + """ + The (decoded) data returned by the Github API + """ + return self.__data def __str__(self): return str(self.status) + " " + str(self.data) From 7b3e4c15ed6182963d66ffa9f0522acd0765275c Mon Sep 17 00:00:00 2001 From: Vincent Jacques Date: Fri, 29 Mar 2013 13:01:06 +0100 Subject: [PATCH 2/5] Raise a specific exception for bad credentials (issue #152) --- github/GithubException.py | 6 ++++++ github/Requester.py | 7 ++++++- github/__init__.py | 2 +- github/tests/Exceptions.py | 10 ++++++++++ .../SpecificExceptions.testBadCredentials.txt | 11 +++++++++++ 5 files changed, 34 insertions(+), 2 deletions(-) create mode 100644 github/tests/ReplayData/SpecificExceptions.testBadCredentials.txt diff --git a/github/GithubException.py b/github/GithubException.py index b8235071..7e040da8 100644 --- a/github/GithubException.py +++ b/github/GithubException.py @@ -40,3 +40,9 @@ class GithubException(Exception): def __str__(self): return str(self.status) + " " + str(self.data) + + +class BadCredentialsException(GithubException): + """ + Exception raised in case of bad credentials (when Github API replies with a 401 or 403 HTML status) + """ diff --git a/github/Requester.py b/github/Requester.py index 7598e3f2..3cb83d8a 100644 --- a/github/Requester.py +++ b/github/Requester.py @@ -90,9 +90,14 @@ class Requester: def __check(self, status, responseHeaders, output): output = self.__structuredFromJson(output) if status >= 400: - raise GithubException.GithubException(status, output) + raise self.__createException(status, output) return responseHeaders, output + def __createException(self, status, output): + if status == 401 and output["message"] == "Bad credentials": + return GithubException.BadCredentialsException(status, output) + return GithubException.GithubException(status, output) + def __structuredFromJson(self, data): if len(data) == 0: return None diff --git a/github/__init__.py b/github/__init__.py index 3f20d650..f2383b24 100644 --- a/github/__init__.py +++ b/github/__init__.py @@ -24,7 +24,7 @@ All classes inherit from :class:`github.GithubObject.GithubObject`. import logging from MainClass import Github -from GithubException import GithubException +from GithubException import GithubException, BadCredentialsException 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 c068122f..c61851f1 100644 --- a/github/tests/Exceptions.py +++ b/github/tests/Exceptions.py @@ -87,3 +87,13 @@ class Exceptions(Framework.TestCase): # To stay compatible with Python 2.6, we else: self.assertEqual(str(exception), "401 {'message': 'Bad credentials'}") # pragma no cover self.assertTrue(raised) + + +class SpecificExceptions(Framework.TestCase): + def testBadCredentials(self): + raised = False + try: + github.Github("BadUser", "BadPassword").get_user().login + except github.BadCredentialsException, exception: + raised = True + self.assertTrue(raised) diff --git a/github/tests/ReplayData/SpecificExceptions.testBadCredentials.txt b/github/tests/ReplayData/SpecificExceptions.testBadCredentials.txt new file mode 100644 index 00000000..f772eabe --- /dev/null +++ b/github/tests/ReplayData/SpecificExceptions.testBadCredentials.txt @@ -0,0 +1,11 @@ +https +GET +api.github.com +None +/user +{'Authorization': 'Basic login_and_password_removed'} +null +401 +[('status', '401 Unauthorized'), ('content-length', '29'), ('server', 'nginx/1.0.13'), ('connection', 'keep-alive'), ('etag', '"ca6a3702f840b6bff0bb1bca6be0337c"'), ('date', 'Sat, 02 Jun 2012 12:12:32 GMT'), ('content-type', 'application/json; charset=utf-8')] +{"message":"Bad credentials"} + From 0bc368973acfb50a531329b6c196ba92e0a81890 Mon Sep 17 00:00:00 2001 From: Vincent Jacques Date: Fri, 29 Mar 2013 13:10:59 +0100 Subject: [PATCH 3/5] Raise a specific exception for non-existing objects (issue #152) --- github/GithubException.py | 6 +++++ github/Requester.py | 2 ++ github/__init__.py | 2 +- github/tests/Exceptions.py | 8 +++++++ .../SpecificExceptions.testUnknownObject.txt | 22 +++++++++++++++++++ 5 files changed, 39 insertions(+), 1 deletion(-) create mode 100644 github/tests/ReplayData/SpecificExceptions.testUnknownObject.txt diff --git a/github/GithubException.py b/github/GithubException.py index 7e040da8..b8e222df 100644 --- a/github/GithubException.py +++ b/github/GithubException.py @@ -46,3 +46,9 @@ class BadCredentialsException(GithubException): """ Exception raised in case of bad credentials (when Github API replies with a 401 or 403 HTML status) """ + + +class UnknownObjectException(GithubException): + """ + Exception raised a non-existing object is requested (when Github API replies with a 404 HTML status) + """ diff --git a/github/Requester.py b/github/Requester.py index 3cb83d8a..d0424a25 100644 --- a/github/Requester.py +++ b/github/Requester.py @@ -96,6 +96,8 @@ class Requester: 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) def __structuredFromJson(self, data): diff --git a/github/__init__.py b/github/__init__.py index f2383b24..2d6254db 100644 --- a/github/__init__.py +++ b/github/__init__.py @@ -24,7 +24,7 @@ All classes inherit from :class:`github.GithubObject.GithubObject`. import logging from MainClass import Github -from GithubException import GithubException, BadCredentialsException +from GithubException import GithubException, BadCredentialsException, UnknownObjectException 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 c61851f1..df397d46 100644 --- a/github/tests/Exceptions.py +++ b/github/tests/Exceptions.py @@ -97,3 +97,11 @@ class SpecificExceptions(Framework.TestCase): except github.BadCredentialsException, exception: raised = True self.assertTrue(raised) + + def testUnknownObject(self): + raised = False + try: + self.g.get_user().get_repo("Xxx") + except github.UnknownObjectException: + raised = True + self.assertTrue(raised) diff --git a/github/tests/ReplayData/SpecificExceptions.testUnknownObject.txt b/github/tests/ReplayData/SpecificExceptions.testUnknownObject.txt new file mode 100644 index 00000000..5298158c --- /dev/null +++ b/github/tests/ReplayData/SpecificExceptions.testUnknownObject.txt @@ -0,0 +1,22 @@ +https +GET +api.github.com +None +/user +{'Authorization': 'Basic login_and_password_removed'} +null +200 +[('status', '200 OK'), ('x-ratelimit-remaining', '4971'), ('content-length', '801'), ('server', 'nginx/1.0.13'), ('connection', 'keep-alive'), ('x-ratelimit-limit', '5000'), ('etag', '"0e6c8f1cbb0c4f0eae96d8a76de9a43f"'), ('date', 'Sat, 02 Jun 2012 12:11:46 GMT'), ('content-type', 'application/json; charset=utf-8')] +{"type":"User","total_private_repos":5,"company":"Criteo","gravatar_id":"b68de5ae38616c296fa345d2b9df2225","login":"jacquev6","public_gists":3,"email":"vincent@vincent-jacques.net","owned_private_repos":5,"avatar_url":"https://secure.gravatar.com/avatar/b68de5ae38616c296fa345d2b9df2225?d=https://a248.e.akamai.net/assets.github.com%2Fimages%2Fgravatars%2Fgravatar-140.png","private_gists":5,"collaborators":0,"created_at":"2010-07-09T06:10:06Z","blog":"http://vincent-jacques.net","location":"Paris, France","url":"https://api.github.com/users/jacquev6","following":24,"disk_usage":16988,"public_repos":10,"name":"Vincent Jacques","hireable":false,"followers":13,"html_url":"https://github.com/jacquev6","id":327146,"plan":{"private_repos":5,"collaborators":1,"space":614400,"name":"micro"},"bio":""} + +https +GET +api.github.com +None +/repos/jacquev6/Xxx +{'Authorization': 'Basic login_and_password_removed'} +null +404 +[('status', '404 Not Found'), ('x-ratelimit-remaining', '4970'), ('content-length', '23'), ('server', 'nginx/1.0.13'), ('connection', 'keep-alive'), ('x-ratelimit-limit', '5000'), ('etag', '"e66a7a6c91e2c26803f3f49feb7a883f"'), ('date', 'Sat, 02 Jun 2012 12:11:47 GMT'), ('content-type', 'application/json; charset=utf-8')] +{"message":"Not Found"} + From c1d747a9133a1c6cae1f0e11105a5f490f65fda6 Mon Sep 17 00:00:00 2001 From: Vincent Jacques Date: Fri, 29 Mar 2013 13:13:44 +0100 Subject: [PATCH 4/5] Refactor tests We don't need to inspect the exception, so we can use unittest.TestCase.assertRaises --- github/tests/Exceptions.py | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/github/tests/Exceptions.py b/github/tests/Exceptions.py index df397d46..c2666a76 100644 --- a/github/tests/Exceptions.py +++ b/github/tests/Exceptions.py @@ -91,17 +91,7 @@ class Exceptions(Framework.TestCase): # To stay compatible with Python 2.6, we class SpecificExceptions(Framework.TestCase): def testBadCredentials(self): - raised = False - try: - github.Github("BadUser", "BadPassword").get_user().login - except github.BadCredentialsException, exception: - raised = True - self.assertTrue(raised) + self.assertRaises(github.BadCredentialsException, lambda: github.Github("BadUser", "BadPassword").get_user().login) def testUnknownObject(self): - raised = False - try: - self.g.get_user().get_repo("Xxx") - except github.UnknownObjectException: - raised = True - self.assertTrue(raised) + self.assertRaises(github.UnknownObjectException, lambda: self.g.get_user().get_repo("Xxx")) From dff094650011398fd8f0a57bf2668a066fb2cbcb Mon Sep 17 00:00:00 2001 From: Vincent Jacques Date: Fri, 29 Mar 2013 13:16:57 +0100 Subject: [PATCH 5/5] Add a documentation note about exceptions --- github/GithubException.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/github/GithubException.py b/github/GithubException.py index b8e222df..aaf7b2eb 100644 --- a/github/GithubException.py +++ b/github/GithubException.py @@ -17,6 +17,8 @@ class GithubException(Exception): """ Error handling in PyGithub is done with exceptions. This class is the base of all exceptions raised by PyGithub. + + Some other types of exceptions might be raised by underlying libraries, for example for network-related issues. """ def __init__(self, status, data):