ghapi icon indicating copy to clipboard operation
ghapi copied to clipboard

expose content of response when a 403 or other http error occurs

Open npdoty opened this issue 2 years ago • 1 comments

I spent a long time debugging this evening when I ran a loop that made 10 search queries. I got this error from ghapi:

HTTP403ForbiddenError: HTTP Error 403: Forbidden

But I couldn't figure out how to get the actual HTTP response object itself so that I could see the text of the response. GitHub actually provides very informative responses, like the following:

{"message":"API rate limit exceeded for [ip address redacted]. (But here's the good news: Authenticated requests get a higher rate limit. Check out the documentation for more details.)","documentation_url":"https://docs.github.com/rest/overview/resources-in-the-rest-api#rate-limiting"}

Documentation reveals that unauthenticated requests to search endpoints are rate-limited to 10 per minute. If ghapi can provide the full response when hitting these errors, it will help with debugging this and other issues, where GitHub rate limits, or detects some abuse, or some other issue.

npdoty avatar Jul 20 '21 01:07 npdoty

+1. I ran into this trying to get file creation working, but what I expected to work was failing with error 422 and no other information. After reading the docs but failing to achieve enlightenment, I applied the interactive debugger and explored the area of the code that was failing, which revealed Github's error message telling me I needed to base64-encode the file contents. Would be great to surface this with the error somehow.

I discovered that in my case, I could catch the exception thrown by GhApi and look for a .fp member on which I could call .read() to get Github's response body. I already had a wrapper to log queries and responses, so I added this exception handler as well. I'm not sure that this works in every case, but here it is in case it helps anyone else:

class MyGithubClass(...):

    def __init__(self, ...):
        self.api = GhApi(...)

    ...

    def _logged_api(self, *args, **kwargs):
        """Call self.api, logging parameters and results"""
        try:
            resp = self.api(*args, **kwargs)
            current_app.logger.debug(
                f"_logged_api called with *args {args} and **kwargs {kwargs}, returned {resp}"
            )
            return resp
        except BaseException as exc:
            current_app.logger.debug(
                f"_logged_api called with *args {args} and **kwargs {kwargs} threw exception {exc}"
            )
            try:
                result_body = json.loads(exc.fp.read())
                current_app.logger.debug(f"Result body from github: {result_body}")
            except BaseException as inner_exc:
                current_app.logger.debug(
                    f"Tried to get result body from Github, but was unsuccessful"
                )
            raise exc

mrled avatar Nov 16 '21 00:11 mrled