Reading responses and checking status

aiohttp · Essentials · 9 min

Handling the response correctly, including error statuses and content types, is where most client bugs live.

A ClientResponse gives you:

  • resp.status (an int) and resp.reason (text), resp.ok (status below 400),
  • resp.headers, a case-insensitive multidict (a header may appear several times; use getall()),
  • resp.url (a yarl.URL), resp.history for followed redirects (aiohttp follows them by default),
  • body methods, all coroutines: await resp.read() for bytes, await resp.text() for decoded text, await resp.json() for JSON.

resp.json() checks the Content-Type and raises aiohttp.ContentTypeError if it isn't JSON, a common surprise with APIs that send text/plain. Pass content_type=None to skip the check.

Error statuses do not raise by default. Either call resp.raise_for_status(), which raises aiohttp.ClientResponseError (with .status, .message and .request_info) for 4xx/5xx, or create the session with ClientSession(raise_for_status=True) to make every request raise automatically.

Read the body inside the async with block. After the block exits the connection is released, and reading afterwards fails or returns what was already buffered. Once read, the body is cached, so calling text() and then json() is fine.

For a base URL, ClientSession(base_url="https://api.example.com") lets you call session.get("/users").

Example

import asyncio
import aiohttp

async def main():
    async with aiohttp.ClientSession(base_url="https://httpbin.org") as session:
        async with session.get("/response-headers", params={"X-Tag": "a"}) as resp:
            print(resp.status, resp.ok, resp.headers["Content-Type"])  # 200 True application/json
            print(type(await resp.read()).__name__)                    # bytes

        async with session.get("/html") as resp:
            try:
                await resp.json()
            except aiohttp.ContentTypeError:
                print("not JSON:", resp.content_type)                 # not JSON: text/html

        async with session.get("/status/404") as resp:
            try:
                resp.raise_for_status()
            except aiohttp.ClientResponseError as e:
                print(e.status, e.message)                            # 404 NOT FOUND

asyncio.run(main())

Remember

  • Body methods (read, text, json) are coroutines; read inside the async with block
  • json() checks Content-Type; pass content_type=None to skip
  • raise_for_status() or ClientSession(raise_for_status=True) for error statuses

Watch out for

  • Returning resp from a function and reading the body after the async with block has closed it.

Check yourself

  1. A server returns JSON with Content-Type: text/plain. What does await resp.json() do?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: docs.aiohttp.org/en/stable/client_quickstart.html#json-response-content