Repository: PyGithub/PyGithub

Issue:
Getting AttributeError: 'NoneType' object has no attribute 'startswith' when trying to get the repo_name
The application works fine but suddenly, the error pops up.

File "/Users/sriharichandramouli/Documents/Git_Auto_comment/git_update.py", line 19, in job
    repo = g.get_repo(repo_name[i])
  File "/usr/local/lib/python3.7/site-packages/github/MainClass.py", line 330, in get_repo
    "GET", "%s%s" % (url_base, full_name_or_id)
  File "/usr/local/lib/python3.7/site-packages/github/Requester.py", line 322, in requestJsonAndCheck
    verb, url, parameters, headers, input, self.__customConnection(url)
  File "/usr/local/lib/python3.7/site-packages/github/Requester.py", line 345, in __check
    raise self.__createException(status, responseHeaders, output)
  File "/usr/local/lib/python3.7/site-packages/github/Requester.py", line 379, in __createException
    elif status == 403 and output.get("message").startswith(
AttributeError: 'NoneType' object has no attribute 'startswith'


Discussion:
Why was this closed? We're hitting the same thing, pygithub 1.55:

```
  File "/home/travis/virtualenv/python3.8.1/lib/python3.8/site-packages/github/MainClass.py", line 256, in get_user
    headers, data = self.__requester.requestJsonAndCheck(
  File "/home/travis/virtualenv/python3.8.1/lib/python3.8/site-packages/github/Requester.py", line 353, in requestJsonAndCheck
    return self.__check(
  File "/home/travis/virtualenv/python3.8.1/lib/python3.8/site-packages/github/Requester.py", line 378, in __check
    raise self.__createException(status, responseHeaders, output)
  File "/home/travis/virtualenv/python3.8.1/lib/python3.8/site-packages/github/Requester.py", line 418, in __createException
    elif status == 403 and output.get("message").startswith(
AttributeError: 'NoneType' object has no attribute 'startswith'
```

Resolving patch:
diff --git a/github/Requester.py b/github/Requester.py
--- a/github/Requester.py
+++ b/github/Requester.py
@@ -422,7 +422,9 @@ def __customConnection(self, url):
         return cnx
 
     def __createException(self, status, headers, output):
-        if status == 401 and output.get("message") == "Bad credentials":
+        message = output.get("message", "").lower() if output is not None else ""
+
+        if status == 401 and message == "bad credentials":
             cls = GithubException.BadCredentialsException
         elif (
             status == 401
@@ -430,18 +432,16 @@ def __createException(self, status, headers, output):
             and re.match(r".*required.*", headers[Consts.headerOTP])
         ):
             cls = GithubException.TwoFactorException
-        elif status == 403 and output.get("message").startswith(
-            "Missing or invalid User Agent string"
+        elif status == 403 and message.startswith(
+            "missing or invalid user agent string"
         ):
             cls = GithubException.BadUserAgentException
         elif status == 403 and (
-            output.get("message").lower().startswith("api rate limit exceeded")
-            or output.get("message")
-            .lower()
-            .endswith("please wait a few minutes before you try again.")
+            message.startswith("api rate limit exceeded")
+            or message.endswith("please wait a few minutes before you try again.")
         ):
             cls = GithubException.RateLimitExceededException
-        elif status == 404 and output.get("message") == "Not Found":
+        elif status == 404 and message == "not found":
             cls = GithubException.UnknownObjectException
         else:
             cls = GithubException.GithubException


Test patch:
diff --git a/tests/Requester.py b/tests/Requester.py
--- a/tests/Requester.py
+++ b/tests/Requester.py
@@ -1,6 +1,6 @@
 ############################ Copyrights and license ############################
 #                                                                              #
-# Copyright 2023 Enrico Minack <github@enrico.minack.dev>                      #
+# Copyright 2022 Enrico Minack <github@enrico.minack.dev>                      #
 #                                                                              #
 # This file is part of PyGithub.                                               #
 # http://pygithub.readthedocs.io/                                              #
@@ -91,3 +91,125 @@ def testBaseUrlPrefixRedirection(self):
         self.logger.info.assert_called_once_with(
             "Following Github server redirection from /api/v3/repos/PyGithub/PyGithub to /repos/PyGithub/PyGithub"
         )
+
+    def assertException(self, exception, exception_type, status, data, headers, string):
+        self.assertIsInstance(exception, exception_type)
+        self.assertEqual(exception.status, status)
+        if data is None:
+            self.assertIsNone(exception.data)
+        else:
+            self.assertEqual(exception.data, data)
+        self.assertEqual(exception.headers, headers)
+        self.assertEqual(str(exception), string)
+
+    def testShouldCreateBadCredentialsException(self):
+        exc = self.g._Github__requester.__createException(
+            401, {"header": "value"}, {"message": "Bad credentials"}
+        )
+        self.assertException(
+            exc,
+            github.BadCredentialsException,
+            401,
+            {"message": "Bad credentials"},
+            {"header": "value"},
+            '401 {"message": "Bad credentials"}',
+        )
+
+    def testShouldCreateTwoFactorException(self):
+        exc = self.g._Github__requester.__createException(
+            401,
+            {"x-github-otp": "required; app"},
+            {
+                "message": "Must specify two-factor authentication OTP code.",
+                "documentation_url": "https://developer.github.com/v3/auth#working-with-two-factor-authentication",
+            },
+        )
+        self.assertException(
+            exc,
+            github.TwoFactorException,
+            401,
+            {
+                "message": "Must specify two-factor authentication OTP code.",
+                "documentation_url": "https://developer.github.com/v3/auth#working-with-two-factor-authentication",
+            },
+            {"x-github-otp": "required; app"},
+            '401 {"message": "Must specify two-factor authentication OTP code.", "documentation_url": "https://developer.github.com/v3/auth#working-with-two-factor-authentication"}',
+        )
+
+    def testShouldCreateBadUserAgentException(self):
+        exc = self.g._Github__requester.__createException(
+            403,
+            {"header": "value"},
+            {"message": "Missing or invalid User Agent string"},
+        )
+        self.assertException(
+            exc,
+            github.BadUserAgentException,
+            403,
+            {"message": "Missing or invalid User Agent string"},
+            {"header": "value"},
+            '403 {"message": "Missing or invalid User Agent string"}',
+        )
+
+    def testShouldCreateRateLimitExceededException(self):
+        for message in [
+            "API Rate Limit Exceeded for 92.104.200.119",
+            "You have triggered an abuse detection mechanism. Please wait a few minutes before you try again.",
+            "You have exceeded a secondary rate limit. Please wait a few minutes before you try again.",
+        ]:
+            with self.subTest(message=message):
+                exc = self.g._Github__requester.__createException(
+                    403, {"header": "value"}, {"message": message}
+                )
+                self.assertException(
+                    exc,
+                    github.RateLimitExceededException,
+                    403,
+                    {"message": message},
+                    {"header": "value"},
+                    f'403 {{"message": "{message}"}}',
+                )
+
+    def testShouldCreateUnknownObjectException(self):
+        exc = self.g._Github__requester.__createException(
+            404, {"header": "value"}, {"message": "Not Found"}
+        )
+        self.assertException(
+            exc,
+            github.UnknownObjectException,
+            404,
+            {"message": "Not Found"},
+            {"header": "value"},
+            '404 {"message": "Not Found"}',
+        )
+
+    def testShouldCreateGithubException(self):
+        for status in range(400, 600):
+            with self.subTest(status=status):
+                exc = self.g._Github__requester.__createException(
+                    status, {"header": "value"}, {"message": "Something unknown"}
+                )
+                self.assertException(
+                    exc,
+                    github.GithubException,
+                    status,
+                    {"message": "Something unknown"},
+                    {"header": "value"},
+                    f'{status} {{"message": "Something unknown"}}',
+                )
+
+    def testShouldCreateExceptionWithoutMessage(self):
+        for status in range(400, 600):
+            with self.subTest(status=status):
+                exc = self.g._Github__requester.__createException(status, {}, {})
+                self.assertException(
+                    exc, github.GithubException, status, {}, {}, f"{status} {{}}"
+                )
+
+    def testShouldCreateExceptionWithoutOutput(self):
+        for status in range(400, 600):
+            with self.subTest(status=status):
+                exc = self.g._Github__requester.__createException(status, {}, None)
+                self.assertException(
+                    exc, github.GithubException, status, None, {}, f"{status} null"
+                )