user2023861
user2023861

Reputation: 8208

How do I get the body of an HTTP 403 response using Python+aiohttp?

I'm using Python 3.6 and the aiohttp library to make an API Post request to a server. If I use the wrong username when making the request, I get an HTTP 403 error as I expect. When I make this request in Postman, the body of the response shows:

{"error_message": "No entitlements for User123"}

When I make the request using aiohttp however, I don't see this response body anywhere. The message just says "Forbidden". How can I get the error message above in my Python code?

Edit: Here's my aiohttp code, although it's pretty straightforward:

try:
    async with self.client_session.post(url, json=my_data, headers=my_headers) as response:
        return await response.json()
except ClientResponseError as e:
    print(e.message)  # I want to access the response body here
    raise e

Edit 2: I found a workaround. When I'm creating the client_session, I set the raise_for_status value to False. Then when I get a response from the API call, I check if the status is >= 400. If so, I handle the error myself, which includes the body of the response.

Edit 3: Here's the code for my workaround:

self.client_session = ClientSession(loop=asyncio.get_event_loop(), raise_for_status=False)
####################### turn off the default exception handling ---^

try:
    async with self.client_session.post(url, json=my_data, headers=my_headers) as response:
    body = await response.text()

    # handle the error myself so that I have access to the response text
    if response.status >= 400:
        print('Error is %s' % body)
        self.handle_error(response)

Upvotes: 5

Views: 5989

Answers (1)

Zaar Hai
Zaar Hai

Reputation: 9879

Yes, it may be indeed confusing if you are coming from requests package that has its exception objects having .request.response (or the other way around) attribute.

You've already figured that out obviously, but here is a proper answer for the older versions of aiohttp.

async with session.post(...) as response:
    try:
        response.raise_for_status()
    except ClientResponseError as err:
        logger.error("Error: %s, Error body: %s", err, (await response.text()))

    return await response.json()

The newer versions unfortunately recycle the connection once raise_for_status() is called hence you can't fetch the error body later. Here is what work for me nowadays (from http-noah package):

        logger = structlog.get_logger(__name__)

        async with session.post(url, **req_kwargs) as res:
            # Fetching text as error just in case - raise_for_status() will
            # release the connection so the error body will be lost already.
            # The text will be cached in the response internally for the use later on
            # so no waste here.
            err_body = await res.text()
            try:
                res.raise_for_status()
            except aiohttp.ClientResponseError as err:
                logger.error("Request failed", err=err, err_body=err_body)
                raise

Upvotes: 4

Related Questions