⟵back

Making requests to a GraphQL API with Python

For a while, I've been wanting to create some kind of app involving my Goodreads data. I take Goodreads — and reading in general — very seriously. Thanks to obsessively tracking my reading habits since 2012, I know that on average, I finish 50.45 books a year.

The problem is that Goodreads stopped issuing API keys just in time for me becoming a dev. There are other reasons why several alternatives to Goodreads have sprouted up over the past few years. One of them is Literal, and I was excited to see it has a developer API!

Its API runs on GraphQL, which was totally new to me. What makes GraphQL different to the much more common REST APIs? I won't go into it too much here, but one thing you'll notice is that all requests made by the client are POST requests. The individual queries defined for a particular API will determine what kind of HTTP method it is under the hood.

Supposedly, the idea of GraphQL is that you don't over-query, and this is more performant. So if you have a list of restaurant objects, each other with the attributes district, cuisine, and price_range, you could make a query so that you only get the cuisine attribute from each one.

Since I had associated GraphQL with React (after all, both are created by Meta), I was surprised to find that you don't actually need to download any special GraphQL libraries to use it with Python. You just need to install the Python requests module.

Instructions on getting set up with a token and so on are in the documentation. In this query, I want to get my customised shelves by their slugs, each one with three books as an example.

import os
import json
import requests

LITERAL_API_TOKEN = os.getenv("LITERAL_API_TOKEN")
LITERAL_PROFILE_ID = os.getenv("LITERAL_PROFILE_ID")

# Note that the query itself is a multi-line string.
# The schema is very different to a REST request!

body = """
    query getShelvesByProfileId(
        $profileId: String!
        $limit: Int! = 100
        $offset: Int! = 0
    ) {
        getShelvesByProfileId(
            profileId: $profileId,
            limit: $limit,
            offset: $offset
        ) {
            slug
            name
            books(take: 3) {
                title
                 cover
            }
        }
    }
"""

r = requests.post(
    data=json.dumps(
        {
            "query": body,
            "variables": {"profileId": LITERAL_PROFILE_ID}
        }
    ),
    headers={
        "Authorization": "Bearer " + LITERAL_API_TOKEN,
        "Content-Type": "application/json"
    },
    url="https://literal.club/graphql/",
)

if r.status_code == 200:
    response = json.loads(r.content)
    print(json.dumps(response, indent=1))
else:
    r.raise_for_status()

An abridged version of the response from the Literal API:

{
    "data": {
        "getShelvesByProfileId": [
            {
                "slug": "writing-creativity-c0fg8wi",
                "books": [
                    {
                        "title": "A Poetry Handbook",
                        "cover": "https://assets.literal.club/2/ckrl5c2ia533380183s3g08sv02.jpg"
                    },
                    {
                        "title": "This Little Art",
                        "cover": "https://assets.literal.club/2/ckppybazl147121ie3onjas7m5.jpg"
                    },
                    {
                        "title": "Exposure",
                        "cover": "https://assets.literal.club/2/ckrmdf8271024714qyfgny42l1.jpg"
                    }
                ]
            },

            ...
        ]
    }
}