GraphQL and Python Series (Expressive Introduction)

GraphQL and Python Series (Expressive Introduction)

This is part 1 of an ongoing series to introduce you to using GraphQL and Python. Hopefully, at the end of this series, you will be able to build a GraphQL Backend with Django and Graphene.

GraphQL Introductions

GraphQL is a strongly typed language and is an alternative to REpresentational Transfer(REST).

Missing explanation: Try and explain why GraphQL was created and why it's an alternative to REST. PayPal Engineering Medium article can help. An example of a GraphQL query us as follows:

{
    allFilms {
        films {
            title
    }
  }
}

GraphQL Type Schema

We'd mostly be using two types in GraphQL, the Object Type and the Scalar Type. Object types can contain their own set of subfields, generally speaking, they have to be an object.

Scalar types are built-in GraphQL data types, and they are five in number: integers, floats, strings, booleans and ids(these are unique strings). Now, these are the standard five, but they can be extended and still be Scalar Types as we will in this article.

CRUD Operations in GraphQL

Create Read Update Delete(CRUD) functionalities are done in GraphQL using two operations: Query and Mutations. Just like we've done above, reading data(R) is done normally using queries. But to write or change data, we need a GraphQL operation called Mutation. In summary, CUD - is covered by mutations while R - is covered by queries. The codes below are examples of using mutation to create, update and delete objects respectively

mutation {
    createBaby(bodyInfo: {
        name:"Edidiong",
        votes: 1
  })  {
      name
      votes
  }
}

mutation {
    upvote(name: "Edidiong") {
        name
        votes
    }
}

mutation {
    remove(name: "Edidiong") {
        name
        votes
    }
}

You notice when reading data, you don't need to use query keyword but in creating, updating or deleting data, you use the mutation keyword so the engine knows you're doing a mutation.

Intro to Graphene/GraphQL In Python

By now you have seen how to do queries and mutations in Vanilla GraphQL(borrowing the word Vanilla from JavaScript), let's see how to perform GraphQL in Python.

Query Operation

To create a query, you, first of all, need a root query(this point will be explained in the code session below). Also, GraphQL uses the OOP structure to work so if you're not familiar with OOP, you may find it difficult to follow.

Code

Create a new file called schema.py and rewrite these codes here into the file.

import uuid
import json
from datetime import datetime
import graphene

class User(graphene.ObjectType):
    id = graphene.ID(default_value=str(uuid.uuid4()))
    username = graphene.String()
    created_at = graphene.DateTime(default_value=datetime.now())


class Query(graphene.ObjectType):
    users = graphene.List(User, limit=graphene.Int())
    hello = graphene.String()

    def resolve_hello(self, info):
        return "world"

    def resolve_users(self, info, limit=None):
        return [
            User(id="1", username="Khalid", created_at=datetime.now()),
            User(id="2", username="Ann", created_at=datetime.now()),
            User(id="3", username="fred", created_at=datetime.now()),
            ][:limit]


# Query of users
schema = graphene.Schema(query=Query, auto_camelcase=True)
result = schema.execute(
    '''
    {
        users {
            id
            username
            createdAt
        }
    }
    '''
)
dict_result = dict(result.data.items())
print(dict_result)

Let's break down the code above to grasp what was done.

User

A User class was created that inherited from the graphene.ObjectType. Almost all of the GraphQL types you define will be object types(only a few percentages will be Scalar Types). Object types have a name, but most importantly describe their fields.

So the User class contains the id, username and date the account was created all. You will notice id and username are default scalar types(the .ID() and .String()), we are adding .DateTime() as a scalar type(Dive deep into the Graphene source code to see all available Scalar types).

Query

Earlier, we learnt that to create a query in graphene, we first need a root query. The class Query is that root query. We have two class attributes defined in the query: hello and users. More about the hello query later. The users attribute takes in the User class and a limit argument, these two are what we would use in our actual GraphQL query. The users' details will be returned in a List structure.

The methods we have created in this Query class are called resolvers. Resolvers are functions that resolves a value for a type or field in a schema. Simply put, resolvers are what return objects or scalar from our query. To name a resolver, it has to have the word resolve_ in its name(just like how Test Driven Development in Python) are. We also set a default value for the limit parameter.

Schema

To execute the code we've written, we will use the Graphene Schema. The graphene schema can execute operations(query, mutation and subscription) against the defined types. Looking up type definitions and answering questions about the types through introspection are advance use cases of the schema. The schema has a query parameter which we will pass our class Query into as an argument. This query argument is the entry point for fields to read data in our Schema.

Execute

To provide the result, we use the execute method in the Schema class. In this method, we will provide the query in a GraphQL format but since we're using Python, it will be wrapped in a Block comment. In GraphQL, it is standard to use camelCase names. So our Fieldnames are transformed in Schema's TypeMap from snake_case to camelCase and this is made possible because of the auto_camelcase argument and it set to True by default. The effect is seen in our query as created_at is changed to createdAt following the camelCase convention.

Result

You should have a response like the image below

Include The Result Image.

To better format the result, we use the json.dumps method so as to use the indent parameter in this method. Alter the print statement above to the one below and run it to see the newly formatted result.

print(json.dumps(dict_result, indent=2))

Include The Result Image. If you have a result similar to the image above, congratulations.

The limit parameter is by default set to None so the output shows all the users in the resolver. If we set a limit to 1 as we do in the code below, we see it returns just a single query.

result = schema.execute(
    '''
    {
        users(limit: 1) {
            id
            username
            createdAt
        }
    }
    '''
)

Note: The (limit: 1) is used because of the users attribute in the class Query. We had defined limit as a scalar type inside the users: which is what made (limit: 1) work.

Also, we created a hello resolver. We will say this is our hello world in Graphene. To run the hello query, replace the previous query with the one below:

{
    hello
}

Include The Result Image. The image above is a result of the query.

We have been able to understand everything there is about queries. Now let's move to mutations.

Mutation Operations

Remember when we said to create queries we need a root query, in mutations, it's sorta different. The Mutation is a convenience type that helps us build a Field which takes Arguments and returns a mutation Output ObjectType. This means to perform CUD operations, we need to pass arguments to a mutation. To pass in arguments to a mutation we need to pass in an inner Arguments class.

Code(mutation)

Add these new classes into the schema.py file but arrange the classes to be on top of another.

class CreateUser(graphene.Mutation):
    user = graphene.Field(User)

    class Arguments:
        username = graphene.String()


    def mutate(self, info, username):
        user = User(id="4", username=username, created_at=datetime.now())
        return CreateUser(user=user)


class Mutation(graphene.ObjectType):
    create_user = CreateUser.Field()

Also, comment out the previous schema and append this to the end of the file.

# Mutation
schema = graphene.Schema(query=Query, mutation=Mutation)
result = schema.execute(
    '''
    mutation {
        createUser(username: "Edidiong") {
            user {
                id
                username
                createdAt
            }
        }
    }
    '''
)

Let's break down the code above to grasp what was done.

CreateUser

The CreateUser class inherits from graphene's Mutation class and creates a Field that is available on an ObjectType. The ObjectType here is our User class.

Arguments

Arguments inner class passes the attribute username to the CreateUser class and this username is gotten from the resolver. This attribute is a Scalar Type(String) and it's the arguments we use for the mutation Field.

The mutate method is a resolver method for the Mutation class. It performs data change and returns the output which in this case the username, id and created_at are the data change and a new user is returned.

Mutation

The Mutation class takes in the CreateUser class and mounts it as a Field. Like we know, almost all GraphQL types you define will be ObjectType. Refer to the Mutation Operation header to understand what happens behind the engine of the Mutation class.

Schema(mutation)

We already know about the Schema class and query parameter. The mutation parameter describes the entry point for fields to create, update or delete data in our API. We pass in the Mutation class as an argument to the mutation argument so as to perform CUD operations with it(in our case it's only Create we've done).

Execute(mutation)

The execute in mutation also does the same thing as execute in query: to read the query from a GraphQL format into a Python format so as a result can be gotten. The only difference is the way to write the call. The mutation keyword comes in just like we were told earlier in the article. From the previous Execute header, we understand how (username: "Edidiong") comes about.

Result

We should have a result similar to the image below.

Include The Result Image.

Subscription Operations

Next Steps

Next, we will talk about Building GraphQL Backends with Django and Graphene. If you liked the article upvote and either drop a comment below or send me a mail